Skip to content

Commit e4f10d7

Browse files
authored
添加更多动画效果 (#4803)
1 parent a21a8d5 commit e4f10d7

13 files changed

Lines changed: 132 additions & 183 deletions

File tree

HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java

Lines changed: 13 additions & 0 deletions
Large diffs are not rendered by default.

HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/AnimationUtils.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818
package org.jackhuang.hmcl.ui.animation;
1919

20+
import javafx.scene.Node;
2021
import org.jackhuang.hmcl.setting.ConfigHolder;
2122
import org.jackhuang.hmcl.util.platform.OperatingSystem;
2223

@@ -46,4 +47,12 @@ public static boolean isAnimationEnabled() {
4647
public static boolean playWindowAnimation() {
4748
return PLAY_WINDOW_ANIMATION;
4849
}
50+
51+
public static void reset(Node node, boolean opaque) {
52+
node.setTranslateX(0);
53+
node.setTranslateY(0);
54+
node.setScaleX(1);
55+
node.setScaleY(1);
56+
node.setOpacity(opaque ? 1 : 0);
57+
}
4958
}

HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525

2626
public enum ContainerAnimations implements TransitionPane.AnimationProducer {
2727
NONE {
28+
@Override
29+
public void init(TransitionPane container, Node previousNode, Node nextNode) {
30+
AnimationUtils.reset(previousNode, false);
31+
AnimationUtils.reset(nextNode, true);
32+
}
33+
2834
@Override
2935
public Timeline animate(
3036
Pane container, Node previousNode, Node nextNode,
@@ -46,9 +52,10 @@ public TransitionPane.AnimationProducer opposite() {
4652
public Timeline animate(
4753
Pane container, Node previousNode, Node nextNode,
4854
Duration duration, Interpolator interpolator) {
49-
return new Timeline(new KeyFrame(Duration.ZERO,
50-
new KeyValue(previousNode.opacityProperty(), 1, interpolator),
51-
new KeyValue(nextNode.opacityProperty(), 0, interpolator)),
55+
return new Timeline(
56+
new KeyFrame(Duration.ZERO,
57+
new KeyValue(previousNode.opacityProperty(), 1, interpolator),
58+
new KeyValue(nextNode.opacityProperty(), 0, interpolator)),
5259
new KeyFrame(duration,
5360
new KeyValue(previousNode.opacityProperty(), 0, interpolator),
5461
new KeyValue(nextNode.opacityProperty(), 1, interpolator)));
@@ -66,7 +73,8 @@ public TransitionPane.AnimationProducer opposite() {
6673
SWIPE_LEFT {
6774
@Override
6875
public void init(TransitionPane container, Node previousNode, Node nextNode) {
69-
super.init(container, previousNode, nextNode);
76+
AnimationUtils.reset(previousNode, true);
77+
AnimationUtils.reset(nextNode, true);
7078
nextNode.setTranslateX(container.getWidth());
7179
}
7280

@@ -94,7 +102,8 @@ public TransitionPane.AnimationProducer opposite() {
94102
SWIPE_RIGHT {
95103
@Override
96104
public void init(TransitionPane container, Node previousNode, Node nextNode) {
97-
super.init(container, previousNode, nextNode);
105+
AnimationUtils.reset(previousNode, true);
106+
AnimationUtils.reset(nextNode, true);
98107
nextNode.setTranslateX(-container.getWidth());
99108
}
100109

@@ -199,17 +208,9 @@ public Timeline animate(
199208
},
200209

201210
NAVIGATION {
202-
@Override
203-
public void init(TransitionPane container, Node previousNode, Node nextNode) {
204-
}
205-
206211
@Override
207212
public Animation animate(Pane container, Node previousNode, Node nextNode, Duration duration, Interpolator interpolator) {
208213
Timeline timeline = new Timeline();
209-
if (previousNode instanceof TransitionPane.EmptyPane) {
210-
return timeline;
211-
}
212-
213214
Duration halfDuration = duration.divide(2);
214215

215216
timeline.getKeyFrames().add(new KeyFrame(Duration.ZERO,
@@ -248,20 +249,6 @@ public Animation animate(Pane container, Node previousNode, Node nextNode, Durat
248249

249250
return timeline;
250251
}
251-
}
252+
},
252253
;
253-
254-
protected static void reset(Node node) {
255-
node.setTranslateX(0);
256-
node.setTranslateY(0);
257-
node.setScaleX(1);
258-
node.setScaleY(1);
259-
node.setOpacity(1);
260-
}
261-
262-
@Override
263-
public void init(TransitionPane container, Node previousNode, Node nextNode) {
264-
reset(previousNode);
265-
reset(nextNode);
266-
}
267254
}

HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionPane.java

Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -61,60 +61,39 @@ public final void setContent(Node newView, AnimationProducer transition, Duratio
6161

6262
public void setContent(Node newView, AnimationProducer transition,
6363
Duration duration, Interpolator interpolator) {
64-
Node previousNode;
65-
if (getWidth() > 0 && getHeight() > 0) {
66-
previousNode = currentNode;
67-
if (previousNode == null) {
68-
if (getChildren().isEmpty())
69-
previousNode = EMPTY_PANE;
70-
else
71-
previousNode = getChildren().get(0);
72-
}
73-
} else
74-
previousNode = EMPTY_PANE;
75-
76-
if (previousNode == newView)
77-
previousNode = EMPTY_PANE;
78-
64+
Node previousNode = currentNode != newView && getWidth() > 0 && getHeight() > 0 ? currentNode : null;
7965
currentNode = newView;
8066

81-
getChildren().setAll(previousNode, currentNode);
82-
83-
if (previousNode == EMPTY_PANE) {
67+
if (!AnimationUtils.isAnimationEnabled() || previousNode == null || transition == ContainerAnimations.NONE) {
8468
getChildren().setAll(newView);
8569
return;
8670
}
8771

88-
if (AnimationUtils.isAnimationEnabled() && transition != ContainerAnimations.NONE) {
89-
setMouseTransparent(true);
90-
transition.init(this, previousNode, getCurrentNode());
91-
92-
Node finalPreviousNode = previousNode;
93-
// runLater or "init" will not work
94-
Platform.runLater(() -> {
95-
Animation newAnimation = transition.animate(
96-
this,
97-
finalPreviousNode,
98-
getCurrentNode(),
99-
duration, interpolator);
100-
newAnimation.setOnFinished(e -> {
101-
setMouseTransparent(false);
102-
getChildren().remove(finalPreviousNode);
103-
});
104-
FXUtils.playAnimation(this, "transition_pane", newAnimation);
72+
getChildren().setAll(previousNode, newView);
73+
74+
setMouseTransparent(true);
75+
transition.init(this, previousNode, newView);
76+
77+
// runLater or "init" will not work
78+
Platform.runLater(() -> {
79+
Animation newAnimation = transition.animate(
80+
this,
81+
previousNode,
82+
newView,
83+
duration, interpolator);
84+
newAnimation.setOnFinished(e -> {
85+
setMouseTransparent(false);
86+
getChildren().remove(previousNode);
10587
});
106-
} else {
107-
getChildren().remove(previousNode);
108-
}
109-
}
110-
111-
private final EmptyPane EMPTY_PANE = new EmptyPane();
88+
FXUtils.playAnimation(this, "transition_pane", newAnimation);
89+
});
11290

113-
public static class EmptyPane extends StackPane {
11491
}
11592

11693
public interface AnimationProducer {
11794
default void init(TransitionPane container, Node previousNode, Node nextNode) {
95+
AnimationUtils.reset(previousNode, true);
96+
AnimationUtils.reset(nextNode, false);
11897
}
11998

12099
Animation animate(Pane container, Node previousNode, Node nextNode,

HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@
1818
package org.jackhuang.hmcl.ui.construct;
1919

2020
import javafx.collections.ObservableList;
21+
import javafx.geometry.Insets;
22+
import javafx.geometry.Pos;
2123
import javafx.scene.Node;
2224
import javafx.scene.control.ScrollPane;
2325
import javafx.scene.input.MouseEvent;
2426
import javafx.scene.layout.Pane;
2527
import javafx.scene.layout.StackPane;
2628
import javafx.scene.layout.VBox;
29+
import javafx.scene.paint.Paint;
2730
import org.jackhuang.hmcl.ui.FXUtils;
2831
import org.jackhuang.hmcl.ui.SVG;
32+
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
33+
import org.jackhuang.hmcl.ui.animation.TransitionPane;
2934
import org.jackhuang.hmcl.ui.versions.VersionPage;
3035

3136
import java.util.function.Consumer;
@@ -98,6 +103,28 @@ public AdvancedListBox addNavigationDrawerTab(TabHeader tabHeader, TabControl.Ta
98103
return add(item);
99104
}
100105

106+
public AdvancedListBox addNavigationDrawerTab(TabHeader tabHeader, TabControl.Tab<?> tab, String title,
107+
SVG unselectedGraphic, SVG selectedGraphic) {
108+
AdvancedListItem item = createNavigationDrawerItem(title, null);
109+
item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));
110+
item.setOnAction(e -> tabHeader.select(tab));
111+
112+
Node unselectedIcon = unselectedGraphic.createIcon((Paint) null, 20);
113+
Node selectedIcon = selectedGraphic.createIcon((Paint) null, 20);
114+
115+
TransitionPane leftGraphic = new TransitionPane();
116+
leftGraphic.setAlignment(Pos.CENTER);
117+
FXUtils.setLimitWidth(leftGraphic, 30);
118+
FXUtils.setLimitHeight(leftGraphic, 20);
119+
leftGraphic.setPadding(Insets.EMPTY);
120+
leftGraphic.setContent(item.isActive() ? selectedIcon : unselectedIcon, ContainerAnimations.NONE);
121+
FXUtils.onChange(item.activeProperty(), active ->
122+
leftGraphic.setContent(active ? selectedIcon : unselectedIcon, ContainerAnimations.FADE));
123+
124+
item.setLeftGraphic(leftGraphic);
125+
return add(item);
126+
}
127+
101128
public AdvancedListBox add(int index, Node child) {
102129
if (child instanceof Pane || child instanceof AdvancedListItem)
103130
container.getChildren().add(index, child);

HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,24 @@
1717
*/
1818
package org.jackhuang.hmcl.ui.construct;
1919

20-
import com.jfoenix.controls.JFXButton;
21-
import javafx.animation.Animation;
22-
import javafx.animation.KeyFrame;
23-
import javafx.animation.KeyValue;
24-
import javafx.animation.Timeline;
20+
import javafx.animation.*;
2521
import javafx.application.Platform;
2622
import javafx.beans.property.BooleanProperty;
2723
import javafx.beans.property.SimpleBooleanProperty;
2824
import javafx.geometry.Insets;
2925
import javafx.geometry.Pos;
3026
import javafx.scene.Node;
3127
import javafx.scene.control.Label;
28+
import javafx.scene.input.MouseButton;
29+
import javafx.scene.input.MouseEvent;
3230
import javafx.scene.layout.*;
3331
import javafx.scene.shape.Rectangle;
3432
import javafx.util.Duration;
3533
import org.jackhuang.hmcl.setting.Theme;
3634
import org.jackhuang.hmcl.ui.FXUtils;
3735
import org.jackhuang.hmcl.ui.SVG;
3836
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
37+
import org.jackhuang.hmcl.ui.animation.Motion;
3938

4039
/**
4140
* @author huangyuhui
@@ -71,7 +70,6 @@ protected void layoutChildren() {
7170
}
7271
}
7372

74-
@SuppressWarnings("unchecked")
7573
private void updateLayout() {
7674
if (content instanceof ComponentList list) {
7775
content.getStyleClass().remove("options-list");
@@ -82,9 +80,8 @@ private void updateLayout() {
8280
VBox groupNode = new VBox();
8381

8482
Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(Theme.blackFill(), 20);
85-
JFXButton expandButton = new JFXButton();
86-
expandButton.setGraphic(expandIcon);
87-
expandButton.getStyleClass().add("options-list-item-expand-button");
83+
expandIcon.setMouseTransparent(true);
84+
HBox.setMargin(expandIcon, new Insets(0, 8, 0, 8));
8885

8986
VBox labelVBox = new VBox();
9087
labelVBox.setMouseTransparent(true);
@@ -123,7 +120,7 @@ private void updateLayout() {
123120
if (rightNode != null)
124121
header.getChildren().add(rightNode);
125122
}
126-
header.getChildren().add(expandButton);
123+
header.getChildren().add(expandIcon);
127124

128125
RipplerContainer headerRippler = new RipplerContainer(header);
129126
groupNode.getChildren().add(headerRippler);
@@ -138,7 +135,12 @@ private void updateLayout() {
138135
container.getChildren().setAll(content);
139136
groupNode.getChildren().add(container);
140137

141-
Runnable onExpand = () -> {
138+
headerRippler.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
139+
if (event.getButton() != MouseButton.PRIMARY)
140+
return;
141+
142+
event.consume();
143+
142144
if (expandAnimation != null && expandAnimation.getStatus() == Animation.Status.RUNNING) {
143145
expandAnimation.stop();
144146
}
@@ -155,16 +157,23 @@ private void updateLayout() {
155157
double newAnimatedHeight = (list.prefHeight(list.getWidth()) + (hasPadding ? 8 + 10 : 4)) * (expanded ? 1 : -1);
156158
double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(list.getWidth());
157159
double contentHeight = expanded ? newAnimatedHeight : 0;
160+
double targetRotate = expanded ? -180 : 0;
158161

159162
if (expanded) {
160163
updateClip(newHeight);
161164
}
162165

163166
if (AnimationUtils.isAnimationEnabled()) {
164-
expandAnimation = new Timeline(new KeyFrame(new Duration(320.0),
165-
new KeyValue(container.minHeightProperty(), contentHeight, FXUtils.SINE),
166-
new KeyValue(container.maxHeightProperty(), contentHeight, FXUtils.SINE)
167-
));
167+
double currentRotate = expandIcon.getRotate();
168+
Duration duration = Motion.LONG2.multiply(Math.abs(currentRotate - targetRotate) / 180.0);
169+
Interpolator interpolator = Motion.EASE_IN_OUT_CUBIC_EMPHASIZED;
170+
171+
expandAnimation = new Timeline(
172+
new KeyFrame(duration,
173+
new KeyValue(container.minHeightProperty(), contentHeight, interpolator),
174+
new KeyValue(container.maxHeightProperty(), contentHeight, interpolator),
175+
new KeyValue(expandIcon.rotateProperty(), targetRotate, interpolator))
176+
);
168177

169178
if (!expanded) {
170179
expandAnimation.setOnFinished(e2 -> updateClip(newHeight));
@@ -174,18 +183,14 @@ private void updateLayout() {
174183
} else {
175184
container.setMinHeight(contentHeight);
176185
container.setMaxHeight(contentHeight);
186+
expandIcon.setRotate(targetRotate);
177187

178188
if (!expanded) {
179189
updateClip(newHeight);
180190
}
181191
}
182192
});
183-
};
184-
185-
FXUtils.onClicked(headerRippler, onExpand);
186-
expandButton.setOnAction(e -> onExpand.run());
187-
188-
expandedProperty().addListener((a, b, newValue) -> expandIcon.setRotate(newValue ? 180 : 0));
193+
});
189194

190195
getChildren().setAll(groupNode);
191196
} else {

HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public class Navigator extends TransitionPane {
5050
public void init(Node init) {
5151
stack.push(init);
5252
backable.set(canGoBack());
53-
getChildren().setAll(init);
53+
setContent(init, ContainerAnimations.NONE);
5454

5555
fireEvent(new NavigationEvent(this, init, Navigation.NavigationDirection.START, NavigationEvent.NAVIGATED));
5656
if (init instanceof PageAware) ((PageAware) init).onPageShown();

HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -507,13 +507,7 @@ enum NavBarAnimations implements TransitionPane.AnimationProducer {
507507
NEXT {
508508
@Override
509509
public void init(TransitionPane container, Node previousNode, Node nextNode) {
510-
previousNode.setScaleX(1);
511-
previousNode.setScaleY(1);
512-
previousNode.setOpacity(0);
513-
previousNode.setTranslateX(0);
514-
nextNode.setScaleX(1);
515-
nextNode.setScaleY(1);
516-
nextNode.setOpacity(1);
510+
super.init(container, previousNode, nextNode);
517511
nextNode.setTranslateX(container.getWidth());
518512
}
519513

@@ -544,13 +538,7 @@ public TransitionPane.AnimationProducer opposite() {
544538
PREVIOUS {
545539
@Override
546540
public void init(TransitionPane container, Node previousNode, Node nextNode) {
547-
previousNode.setScaleX(1);
548-
previousNode.setScaleY(1);
549-
previousNode.setOpacity(1);
550-
previousNode.setTranslateX(0);
551-
nextNode.setScaleX(1);
552-
nextNode.setScaleY(1);
553-
nextNode.setOpacity(0);
541+
super.init(container, previousNode, nextNode);
554542
nextNode.setTranslateX(container.getWidth());
555543
}
556544

0 commit comments

Comments
 (0)