Mở đầu
Khi quá chú trọng vào chức năng của ứng dụng, ta có thể sẽ đánh giá thấp sự ảnh hưởng của trải nghiệm người dùng. Đa phần người dùng sẽ không thích những ứng dụng không có sự hấp dẫn về mặt trực quan. Để tạo ra sự khác biệt cho ứng dụng, chúng ta cần cung cấp cho các chức năng của ứng dụng một bộ Animations
.
Lấy một ví dụ:
Ta cần một ứng dụng đặt bánh pizza trong đó người dùng có thể đặt bánh pizza với một số lượng tùy chỉnh. Bây giờ bạn sẽ giới thiệu ca sử dụng này như thế nào?
Dưới đây là một ví dụ mà bạn có thể tham khảo:
Điều cần thiết bây giờ là ta cần chuyển đổi ảnh GIF trên thành code để triển khai trong ứng dụng. Công việc nghe có vẻ không hề dễ dàng. Đầu tiên ta cần quan sát kĩ, thậm chí có thể cần phải làm chậm tốc độ của ảnh GIF lại để có thể theo dõi chính xác những gì đang diễn ra. Hãy để ý kĩ và kiểm tra từng màn hình một:
- Màn hình danh sách pizza
- Màn hình tùy biến
- Theo dõi màn hình giao hàng của bạn
Bây giờ ta có một sự hiểu biết tốt hơn về thiết kế. Giờ là thời điểm để chuyển đổi nó thành mã code.
Màn hình Pizza List
Màn hình này đang hiển thị một danh sách các loại pizza và bằng cách nhấp vào nút CUSTOMIZE
, ứng dụng điều hướng đến một màn hình khác với hình ảnh bánh pizza của màn hình hiện tại và đế bánh pizza của màn hình khác.
Bằng cách thêm một vài dòng code chúng ta có thể đạt được điều này:
1 2 3 4 5 6 7 8 9 10 | customiseButton<span class="token punctuation">.</span>setOnClickListener <span class="token punctuation">{</span> val intent <span class="token operator">=</span> <span class="token function">Intent</span><span class="token punctuation">(</span>this<span class="token punctuation">,</span> CustomiseActivity<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">.</span>java<span class="token punctuation">)</span> val options <span class="token operator">=</span> ActivityOptionsCompat <span class="token punctuation">.</span><span class="token function">makeSceneTransitionAnimation</span><span class="token punctuation">(</span> this<span class="token punctuation">,</span> pizzaImageView <span class="token keyword">as</span> View<span class="token punctuation">,</span> <span class="token double-quoted-string string">"pizza"</span><span class="token punctuation">)</span> <span class="token function">startActivity</span><span class="token punctuation">(</span>intent<span class="token punctuation">,</span> options<span class="token punctuation">.</span><span class="token function">toBundle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Đừng quên thêm đoạn code dưới đây vào file styles.xml
của bạn:
1 2 | <span class="token operator"><</span>item name<span class="token operator">=</span><span class="token double-quoted-string string">"android:windowContentTransitions"</span><span class="token operator">></span><span class="token boolean">true</span><span class="token operator"><</span><span class="token operator">/</span>item<span class="token operator">></span> |
Màn hình Customization
Ở đây chúng ta cần rất nhiều animations. Trước khi bắt đầu về màn hình này, hãy tìm hiểu một chút về ObjectAnimator, ValueAnimator và AnimationSet.
ObjectAnimator
: Lớp con này củaValueAnimator
cung cấp hỗ trợ cho hoạt ảnh các thuộc tính trên các đối tượng đích.ValueAnimator
: Lớp này cung cấp một công cụ thời gian đơn giản để chạy các animation, tính toán các giá trị animated và đặt chúng trên các đối tượng đích.AnimationSet
: Đại diện cho một nhóm Animations được biểu diễn cùng nhau. Ta cần hiển thị nhiềuanimation
đồng thời hoặc theo thứ tự liên tục.AnimationSet
là phù hợp nhất cho việc này.
AnimateCheeseHeight
Khi bắt đầu màn hình này, ta nhận thấy có thể điều khiển chiều cao của lớp phô mai tăng lên. Vì vậy, chúng ta cần sử dụng ValueAnimator
. Ta cần tăng chiều cao từ 10dp đến 30dp, và cần cập nhật giá trị với AnimatorUpdateListener
. Từ đó ta nhận được giá trị chiều cao được cập nhật và cập nhật các thông số bố cục của PizzaCheeseImageView
.
1 2 3 4 5 6 7 8 9 10 | val anim <span class="token operator">=</span> ValueAnimator<span class="token punctuation">.</span><span class="token function">ofInt</span><span class="token punctuation">(</span>pizzaCheeseImageView<span class="token punctuation">.</span>measuredHeight<span class="token punctuation">,</span> height<span class="token punctuation">)</span> anim<span class="token punctuation">.</span>addUpdateListener <span class="token punctuation">{</span> valueAnimator <span class="token operator">-</span><span class="token operator">></span> val value <span class="token operator">=</span> valueAnimator<span class="token punctuation">.</span>animatedValue <span class="token keyword">as</span> Int val layoutParams <span class="token operator">=</span> pizzaCheeseImageView<span class="token punctuation">.</span>layoutParams layoutParams<span class="token punctuation">.</span>height <span class="token operator">=</span> value pizzaCheeseImageView<span class="token punctuation">.</span>layoutParams <span class="token operator">=</span> layoutParams <span class="token punctuation">}</span> anim<span class="token punctuation">.</span>duration <span class="token operator">=</span> <span class="token number">300</span> anim<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span> |
AnimateCrustHeight
Với việc chiều cao lớp phô mai tăng lên, ta cũng cần tăng chiều cao của lớp vỏ bánh. Ta cũng sử dụng ValueAnimator
. Ngoài ra khi chiều cao tăng, ta cần thay đổi cả bán kính góc để cung cấp hình dạng đồng nhất cho hình ảnh của đế bánh hoặc lớp phô mai. Vì vậy ta sẽ cập nhật đồng thời cả 2 thông số chiều cao và bán kính góc của PizzaCrustImageView
1 2 3 4 5 6 7 8 9 10 11 | val anim <span class="token operator">=</span> ValueAnimator<span class="token punctuation">.</span><span class="token function">ofInt</span><span class="token punctuation">(</span>pizzaCrustImageView<span class="token punctuation">.</span>measuredHeight<span class="token punctuation">,</span> height<span class="token punctuation">)</span> anim<span class="token punctuation">.</span>addUpdateListener <span class="token punctuation">{</span> valueAnimator <span class="token operator">-</span><span class="token operator">></span> val value <span class="token operator">=</span> valueAnimator<span class="token punctuation">.</span>animatedValue <span class="token keyword">as</span> Int val layoutParams <span class="token operator">=</span> pizzaCrustImageView<span class="token punctuation">.</span>layoutParams layoutParams<span class="token punctuation">.</span>height <span class="token operator">=</span> value pizzaCrustImageView<span class="token punctuation">.</span>layoutParams <span class="token operator">=</span> layoutParams pizzaCrustImageView<span class="token punctuation">.</span>radius <span class="token operator">=</span> value<span class="token punctuation">.</span><span class="token function">div</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toFloat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> anim<span class="token punctuation">.</span>duration <span class="token operator">=</span> <span class="token number">300</span> anim<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span> |
AnimateToppings
Khi người dùng chọn một topping, ta cần hiển thị một hình ảnh động rơi xuống từ đỉnh màn hình đến phía trên phô mai của pizza. Ta đang sử dụng ObjectAnimator
vì nó có một thuộc tính được gọi là translationY
, giúp tạo các lớp phủ để animate từ vị trí intialY
đến vị trí destinationY
. Ngoài ra, ta đang sử dụng AccelerateInterpolator
giúp tăng tốc dần animations
.
1 2 3 4 5 6 | ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>toppingsLayout<span class="token punctuation">,</span><span class="token double-quoted-string string">"translationY"</span><span class="token punctuation">,</span> <span class="token number">0</span>f<span class="token punctuation">,</span> toppingsLayout<span class="token punctuation">.</span>height<span class="token punctuation">.</span><span class="token function">toFloat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">12</span>f<span class="token punctuation">)</span><span class="token punctuation">.</span>apply <span class="token punctuation">{</span> interpolator <span class="token operator">=</span> <span class="token function">AccelerateInterpolator</span><span class="token punctuation">(</span><span class="token punctuation">)</span> duration <span class="token operator">=</span> <span class="token number">600</span> <span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> |
AnimatePizzaLayout
Như chúng ta có thể quan sát rằng nhiều animation
đang diễn ra, vì vậy ta có thể chia chúng thành nhiều phần.
FadeCheeseAnimation
1 2 | val fadeCheese <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>pizzaCheeseImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"alpha"</span><span class="token punctuation">,</span> <span class="token number">1</span>f<span class="token punctuation">,</span> <span class="token number">0</span>f<span class="token punctuation">)</span> |
FadeCrustAnimation
1 2 | val fadeCrust <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>pizzaCrustImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"alpha"</span><span class="token punctuation">,</span> <span class="token number">1</span>f<span class="token punctuation">,</span> <span class="token number">0</span>f<span class="token punctuation">)</span> |
FadeInPizzaAnimation
1 2 | val fadeInPizza <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>pizzaImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"alpha"</span><span class="token punctuation">,</span> <span class="token number">0</span>f<span class="token punctuation">,</span> <span class="token number">1</span>f<span class="token punctuation">)</span> |
ScaleUpPizzaXAnimation
1 2 | val scaleUpPizzaX <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>pizzaImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"scaleX"</span><span class="token punctuation">,</span> <span class="token number">0</span>f<span class="token punctuation">,</span> <span class="token number">0.8</span>f<span class="token punctuation">)</span> |
ScaleUpPizzaYAnimation
1 2 | val scaleUpPizzaY <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>pizzaImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"scaleY"</span><span class="token punctuation">,</span> <span class="token number">0</span>f<span class="token punctuation">,</span> <span class="token number">14</span>f<span class="token punctuation">)</span> |
TranslatePizzaYAnimation
1 2 | val translatePizzaY <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>pizzaImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"translationY"</span><span class="token punctuation">,</span> <span class="token number">0</span>f<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">360</span>f<span class="token punctuation">)</span> |
PlayAnimationsTogether
1 2 3 4 | <span class="token function">AnimatorSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>apply <span class="token punctuation">{</span> <span class="token function">play</span><span class="token punctuation">(</span>fadeCheese<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">with</span><span class="token punctuation">(</span>fadeCrust<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">with</span><span class="token punctuation">(</span>fadeInPizza<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">with</span><span class="token punctuation">(</span>scaleUpPizzaX<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">with</span><span class="token punctuation">(</span>scaleUpPizzaY<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">with</span><span class="token punctuation">(</span>translatePizzaY<span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Theo dõi màn hình Delivery
Trong màn hình này, chúng ta cần làm animation
phần trước của hộp và xoay hộp với giá trị scaling
và translation
.
AnimateFrontCover
Chúng tôi cũng có thể sử dụng ViewPropertyAnimator thay vì ObjectAnimator
cho animation
. Bằng cách sử dụng ViewPropertyAnimator
, chúng ta có thể thực hiện nhiều animation
cùng nhau mà không cần sử dụng AnimatioSet
. Nó cung cấp một cú pháp tốt hơn và một cách tối ưu hóa để làm sinh động một khung nhìn.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | frontCoverImageView <span class="token punctuation">.</span><span class="token function">animate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">translationY</span><span class="token punctuation">(</span><span class="token number">790</span>f<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setDuration</span><span class="token punctuation">(</span><span class="token number">300</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setListener</span><span class="token punctuation">(</span>object <span class="token punctuation">:</span> Animator<span class="token punctuation">.</span>AnimatorListener <span class="token punctuation">{</span> override fun <span class="token function">onAnimationEnd</span><span class="token punctuation">(</span>animation<span class="token punctuation">:</span> Animator<span class="token operator">?</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> backCoverImageView<span class="token punctuation">.</span>visibility <span class="token operator">=</span> <span class="token constant">VISIBLE</span> backCoverImageView<span class="token punctuation">.</span>alpha <span class="token operator">=</span> <span class="token number">1</span>f pizzaImageView<span class="token punctuation">.</span>visibility <span class="token operator">=</span> <span class="token constant">GONE</span> frontCoverImageView<span class="token punctuation">.</span>visibility <span class="token operator">=</span> <span class="token constant">GONE</span> <span class="token function">rotateBoxAnimation</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>startDelay <span class="token operator">=</span> <span class="token number">500</span>L |
RotateBoxAnimation
Một lần nữa chúng ta có thể tách nhỏ các animation
:
ScaleDownXAnimation
1 2 | val scaleDownX <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>backCoverImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"scaleX"</span><span class="token punctuation">,</span> <span class="token number">1</span>f<span class="token punctuation">,</span> <span class="token number">0.6</span>f<span class="token punctuation">)</span> |
ScaleDownYAnimation
1 2 | val scaleDownY <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>backCoverImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"scaleY"</span><span class="token punctuation">,</span> <span class="token number">1</span>f<span class="token punctuation">,</span> <span class="token number">0.6</span>f<span class="token punctuation">)</span> |
RotateBoxAnimation
1 2 | val rotateBox <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>backCoverImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"rotation"</span><span class="token punctuation">,</span> <span class="token number">0</span>f<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">45</span>f<span class="token punctuation">)</span> |
TranslateXAnimation
1 2 | val translateX <span class="token operator">=</span> ObjectAnimator<span class="token punctuation">.</span><span class="token function">ofFloat</span><span class="token punctuation">(</span>backCoverImageView<span class="token punctuation">,</span> <span class="token double-quoted-string string">"translationX"</span><span class="token punctuation">,</span> <span class="token number">0</span>f<span class="token punctuation">,</span> <span class="token number">1000</span>f<span class="token punctuation">)</span> |
PlayAnimationsTogether
1 2 3 4 | <span class="token function">AnimatorSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>apply <span class="token punctuation">{</span> <span class="token function">play</span><span class="token punctuation">(</span>scaleDownX<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">with</span><span class="token punctuation">(</span>scaleDownY<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">with</span><span class="token punctuation">(</span>rotateBox<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">before</span><span class="token punctuation">(</span>translateX<span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Showtime
Chạy code và thưởng thức thành quả thôi nào!