Animate trong Android
Từ phiên bản Android 5.0 Google đã giới thiệu tới đông đảo giới lập trình viên về Material Design. Vậy Material Design là gì? Material Design là 1 chuẩn thiết kế giao diện mới giành cho lập trình viên Android, nó bao gồm bố cục về cách sắp xếp layout, màu sắc, định hướng thao tác người dùng và ...
Từ phiên bản Android 5.0 Google đã giới thiệu tới đông đảo giới lập trình viên về Material Design. Vậy Material Design là gì?
Material Design là 1 chuẩn thiết kế giao diện mới giành cho lập trình viên Android, nó bao gồm bố cục về cách sắp xếp layout, màu sắc, định hướng thao tác người dùng và tất nhiên là cả hiệu ứng làm sao để cho ra đời 1 ứng dụng vừa đẹp mắt về giao diện cũng như là thao tác thuận tiện nhất.
Nói về cách sắp xếp layout, màu sắc hay hành vi người dùng thì giúp cho đội thiết kế để vẽ nên những bản Design đẹp. Còn mình thấy cái thú vị nhất trong thiết kế Material Design mà Google hỗ trợ có ảnh hưởng lớn nhất đến thế giới lập trình chính là Animate. Animate giúp tạo ra các ứng dụng có giao diện, cách chuyển qua chuyển lại giữa các màn hình hay đơn giản là tạo ra các hiệu ứng về chữ, button đẹp mắt. Tất nhiên điều này thì ngôn ngữ lập trình nào cũng có hỗ trợ, nhưng từ các bản Android trước 5.0 thì để làm điều này các lập trình viên thường tốn rất nhiều công sức và có thể gây ra rất nhiều Bug trong quá trình code.
Dưới đây mình sẽ giới thiệu với các bạn 1 vài Animate để tạo ra những hiệu ứng chuyển động bắt mắt.
Đầu tiên các bạn hãy chuẩn bị cho mình 1 layout đơn giản.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/transitions_container" android:layout_awidth="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <Button android:id="@+id/button" android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:text="DO MAGIC"/> <TextView android:id="@+id/text" android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Transitions are awesome!" android:visibility="gone"/> </LinearLayout>
và file Kotlin
var visible = false button.setOnClickListener({ TransitionManager.beginDelayedTransition(transitions_container) visible = !visible text.visibility = if (visible) View.VISIBLE else View.GONE })
và đây là kết quả.
Hiệu ứng cũng không quá tồi đúng không? Tất cả hiệu ứng ẩn hiện TextView đều được thự hiện và quản lý trong TransitionManager. Và tất điều các bạn cần làm là để lại quyền quản lý sự thay đổi của ViewGroup lại cho TransitionManager thông qua 1 câu lệnh.
TransitionManager.beginDelayedTransition(viewGroup)
ngoài ra các bạn có thế Custom các hiệu ứng thông qua tham số thứ 2 của hàm beginDelayedTransition
beginDelayedTransition(final ViewGroup sceneRoot, Transition transition)
Dưới đây là 1 số hiệu ứng mặc định mà thư viện đã hỗ trợ sẵn cho các lập trình viên.
- ChangeBounds: Hiệu ứng này dùng để thay đổi vị trí và kích thước của View
- Fade: View bình thường thì chỉ có 2 trạng thái ẩn và hiện thì hiệu ứng này sẽ bổ xung thêm về trạng thái ẩn hiện của View bằng cách tăng dần độ hiện thị giúp View có trạng thái mờ mờ.
- TransitionSet: Hiệu ứng này cho phép lập trình viên thêm các chuyển tiếp giữa các chuyển động của View trên màn hình.
- AutoTransition: Nó thực chất là TransitionSet nhưng chứa Fade out, ChangeBounds và Fade theo thứ tự tuần tự. AutoTransition được sử dụng là đối số mặc định khi bạn không chỉ định bất kỳ TransitionSet nào trong đối số thứ hai của beginDelayedTransition.
Ngoài những thuộc tính mặc định thì Transition còn cung cấp cho các bạn 1 số thuộc tính để bạn cài thêm cho hiệu ứng. VD như:
val transitionSet = TransitionSet() .apply { duration = 300 // set time startDelay = 200 // set time start animation // ... }
Dưới đây mình sẽ đưa thêm 1 số ví dụ về sử dụng Transition để tạo các hiệu ứng chuyển động cho View.
Slide
var visible = false button.setOnClickListener({ TransitionManager.beginDelayedTransition(transitions_container, Slide(Gravity.END)) visible = !visible text.visibility = if (visible) View.VISIBLE else View.GONE })
hoặc các bạn có thẻ set thêm duration và startDelay
val slide = Slide(Gravity.END).apply { duration = 1000 startDelay = 200 } // ... TransitionManager.beginDelayedTransition(transitions_container, slide)
ChangeImageTransform
Transition này sẽ tác động lên matrix của Image. Nó tạo ra hiệu ứng đẹp khi chúng ta thay đổi scaleType của ImageView, thường thì ChangeImageTransform sẽ đi kèm với ChangeBounds để thay đổi kích thước và vị trí của ảnh
override fun onCreate(savedInstanceState: Bundle?) { // ... val transitionSet = TransitionSet() .apply { addTransition(ChangeBounds()) addTransition(ChangeImageTransform()) } var mExpanded = false image.setOnClickListener({ TransitionManager.beginDelayedTransition(transitions_container, transitionSet) mExpanded = !mExpanded val params : ViewGroup.LayoutParams = image.layoutParams params.height = if (mExpanded) ViewGroup.LayoutParams.MATCH_PARENT else ViewGroup.LayoutParams.WRAP_CONTENT image.scaleType = if (mExpanded) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER }) }
Recolor
var isColorsInverted = false; button.setOnClickListener({ button.setTextColor(resources.getColor( if (!isColorsInverted) R.color.colorAccent else R.color.colorPrimary)) button.setBackgroundColor(resources.getColor( if (!isColorsInverted) R.color.colorPrimary else R.color.colorAccent)) })
Create Transition with xml
<?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:app="http://schemas.android.com/apk/res-auto" app:transitionOrdering="together" app:duration="400"> <changeBounds/> <changeImageTransform/> <fade app:fadingMode="fade_in" app:startDelay="200"> <targets> <target app:targetId="@id/transition_title"/> </targets> </fade> </transitionSet> // And using: TransitionInflater.from(getContext()).inflateTransition(R.anim.my_the_best_transition);
Custom Transitions
Các bạn cũng có thể tự Custom riêng từng hiệu ứng sao cho phù hợp vơi yêu cầu của Project bằng cách kế thừa từ class Transition.
class CustomTransition : Transition() { override fun captureEndValues(transitionValues: TransitionValues?) { TODO("Send End value to createAnimator") } override fun captureStartValues(transitionValues: TransitionValues?) { TODO("Send Start value to createAnimator") } override fun createAnimator(sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues?): Animator { TODO("Custom Animation") return super.createAnimator(sceneRoot, startValues, endValues) } }
ở đây mình sẽ viết 1 class Scale() đơn giản để làm ví dụ cho các bạn dễ hiểu hơn
class Scale : Visibility() { val SCALE_X = "scaleX" val SCALE_Y = "scaleY" var disappearedScale = 0f fun setDisappearedScale(disappearedScale: Float): Scale { if (disappearedScale < 0f) { throw IllegalArgumentException("disappearedScale cannot be negative!") } this.disappearedScale = disappearedScale return this } override fun captureEndValues(transitionValues: TransitionValues?) { TODO("Send End value to createAnimator") } override fun captureStartValues(transitionValues: TransitionValues?) { super.captureStartValues(transitionValues) transitionValues?.values?.put(SCALE_X, transitionValues.view.scaleX) transitionValues?.values?.put(SCALE_Y, transitionValues.view.scaleY) } private fun createAnimation(view: View?, startScale: Float, endScale: Float, values: TransitionValues?): Animator? { val initialScaleX = view?.scaleX val initialScaleY = view?.scaleY var startScaleX = initialScaleX?.times(startScale) val endScaleX = initialScaleX?.times(endScale) var startScaleY = initialScaleY?.times(startScale) val endScaleY = initialScaleY?.times(endScale) if (values != null) { val savedScaleX: Float = values.values[SCALE_X] as Float val savedScaleY: Float = values.values[SCALE_Y] as Float if (savedScaleX != initialScaleX) { startScaleX = savedScaleX } if (savedScaleY != initialScaleY) { startScaleY = savedScaleY } } view?.scaleX = if (startScaleX == null) 0f else startScaleX view?.scaleY = if (startScaleY == null) 0f else startScaleY return mergeAnimators( ObjectAnimator.ofFloat(view, View.SCALE_X, startScaleX!!, endScaleX!!), ObjectAnimator.ofFloat(view, View.SCALE_Y, startScaleY!!, endScaleY!!)) } override fun onAppear(sceneRoot: ViewGroup?, view: View?, startValues: TransitionValues?, endValues: TransitionValues?): Animator? { return createAnimation(view, disappearedScale, 1f, startValues) } override fun onDisappear(sceneRoot: ViewGroup?, view: View?, startValues: TransitionValues?, endValues: TransitionValues?): Animator? { return createAnimation(view, 1f, disappearedScale, startValues) } }
Lời kết: Qua bài viết này hi vọng các bạn hiểu rõ hơn vềAnimationtrong android và nhất là từ phiên bản Android 5.0 trở lên và có thể áp dụng các hiệu ứng chuyển động làm sao cho hợp lý để tạo nên 1 Project đẹp.
Link tham khảo: https://medium.com/@andkulikov/animate-all-the-things-transitions-in-android-914af5477d50