24/01/2019, 15:23

Tất tần tật về Animation trong Android

Trong bài viết này mình muốn chia sẻ với các bạn một cái gì đó mới về Animation trong Android. Google đã công bố với Material Design là Animation không chỉ dành riêng cho IOS. Một phần của khái niệm mới là Material motion . Chuyển động cung cấp một ý nghĩa. Các đối tượng được trình bày cho ...

Trong bài viết này mình muốn chia sẻ với các bạn một cái gì đó mới về Animation trong Android. Google đã công bố với Material Design là Animation không chỉ dành riêng cho IOS. Một phần của khái niệm mới là Material motion.

Chuyển động cung cấp một ý nghĩa. Các đối tượng được trình bày cho người dùng mà không phá vỡ tính liên tục của trải nghiệm ngay cả khi họ biến đổi và sắp xếp lại. Chuyển động trong thế giới thiết kế Material được sử dụng để mô tả các mối quan hệ không gian, chức năng và ý định với vẻ đẹp và sự trôi chảy. Material Design guidelines

Trong thực tế, quá trình tạo hình ảnh động cần có thời gian. Nó chắc chắn sẽ hấp dẫn khi chỉ cần gọi setVisibility (View.VISIBLE) và chuyển sang logic kinh doanh của tính năng mới tuyệt vời của bạn, với điều kiện là nó đã vượt quá mọi thời hạn. Nhưng hãy nhớ rằng: mỗi khi bạn bỏ qua cơ hội để thêm một chuyển động trong thiết kế UI của mình, một nhà thiết kế khác lại làm điều nó ở đâu đó trên thế giới, họ có thể vượt qua bạn. Việc sử dụng các Animation thực ra không cần đòi hỏi nhiều nỗ lực và cố gắng như bạn nghĩ. Bạn đã bao giờ nghe nói về Transitions API? Nó được Google cung cấp sử dụng các Animation giữa các Activity trong ứng dụng, nhưng đáng buồn thay nó chỉ hỗ trợ target version từ Android 5.0. Hãy tưởng tượng rằng những API này được sử dụng một cách hiệu quả trong nhiều trường hợp khác nhau và tuyệt vời hơn khi nó có sẵn trên các phiên bản Android cũ.

Hãy bắt đầu với một số version cũ

Tham số animateLayoutChange mới đã được giới thiệu cho Viewgroup trong Android 4.0. Nhưng ngay cả khi gọi getLayoutTransition () và định cấu hình một số nội dung bên trong nó, nó vẫn không ổn định và không đủ linh hoạt. Vì vậy, bạn không thể làm gì nhiều với nó. Phiên bản 4.4 Kitkat mang đến cho chúng ta ý tưởng về các cảnh và sự chuyển tiếp. Về mặt kỹ thuật, trạng thái của tất cả các khung nhìn trong cảnh gốc (layout container) của chúng tôi. Transition là tập hợp các Animators sẽ được áp dụng cho chế độ xem để thực hiện chuyển đổi suôn sẻ từ cảnh này sang cảnh khác. Hãy tưởng tượng chúng ta có một nút. Sau một cú nhấp chuột, chúng ta muốn có một văn bản xuất hiện bên dưới nút.

<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à trong java chúng ta lắng nghe sự kiện click vào nút

final ViewGroup transitionsContainer = (ViewGroup) view.findViewById(R.id.transitions_container);
final TextView text = (TextView) transitionsContainer.findViewById(R.id.text);
final Button button = (Button) transitionsContainer.findViewById(R.id.button);
 
button.setOnClickListener(new View.OnClickListener() {

    boolean visible;

    @Override
    public void onClick(View v) {
        TransitionManager.beginDelayedTransition(transitionsContainer);
        visible = !visible;
        text.setVisibility(visible ? View.VISIBLE : View.GONE);          
    }

})

Kết quả thu được Không tệ. Ảnh động chỉ với một dòng code. Điều thú vị ở đây là không chỉ khả năng hiển thị văn bản là hoạt hình mà còn có vị trí nút. Khung chuyển đổi tự động tạo hiệu ứng thay đổi bố cục do sự xuất hiện của TextView, do đó, bạn không phải tự mình làm điều đó. Chúng ta có thể chỉ định các loại Transition chính xác sẽ được áp dụng thông qua tham số thứ hai của phương thức BeginDelayedTransition.

Các loại Transtion đơn giản

  1. ChangeBound : Nó là hoạt cảnh thay đổi vị trí và kích thước của một View. Điều này thể hiện qua việc di chuyển nút ở ví dụ trên
  2. Fade : Nó mở rộng lớp Hiển thị và thực hiện hầu hết các hình ảnh động phổ biến - mờ dần và mờ dần. Trong ví dụ, nó được áp dụng cho TextView.
  3. TransitionSet: Nó là một Transtion tập hợp của các transtion khác. Nó có thể bắt đầu cùng nhau hoặc bắt đầu một cách tuần tự, để thay đổi nó gọi là setOrdering.
  4. AutoTransition: Nó là Transitionet có chứa Fade out, ChangeBound và Fade theo thứ tự liên tiếp. Đầu tiên, các view không tồn tại trong cảnh thứ hai bị mờ dần, sau đó thay đổi giới hạn được áp dụng cho các thay đổi về vị trí và kích thước và cuối cùng, các view mới xuất hiện với độ mờ dần. Tự động sử dụng theo mặc định khi bạn không chỉ định bất kỳ chuyển đổi trong đối số thứ hai của BeginDelayedTransition. AutoTransition tự động sử dụng theo chế độ mặc định khi bạn không chỉ định bất kỳ chuyển đổi trong đối số thứ hai của BeginDelayedTransition.

Google đã công bố thư viện hỗ trợ Transitions framework. Vậy nó có thể làm những gì? Trước hết, chúng ta có thể thay đổi thời lượng, bộ nội suy và bắt đầu độ trễ cho các trình hoạt hình bên trong Transition

transition.setDuration(300);
transition.setInterpolator(new FastOutSlowInInterpolator());
transition.setStartDelay(200);

Slide

Giống như chuyển đổi Fade, mở rộng lớp Hiển thị. Nó giúp góc nhìn mới trong cảnh trượt vào từ một trong hai phía. Ví dụ với Slide (Gravity.RIGHT):

Explode và Propagation

Explode rất giống với Slide, nhưng chế độ xem sẽ trượt theo một số hướng được tính toán tùy thuộc vào tâm chấn chuyển tiếp (bạn nên cung cấp cho nó phương thức setEpicenterCallback). TransitionPropagation tính toán độ trễ bắt đầu cho mỗi hoạt cảnh. Ví dụ, Explode sử dụng CircularPropagation theo mặc định. Độ trễ cho hình ảnh động phụ thuộc vào khoảng cách giữa view và tâm chấn. Để áp dụng nó, hãy gọi setPropagation trong Transition. Giờ chúng ta có RecyclerView với GridLayoutManager và muốn xóa tất cả các phần tử sau khi nhấn vào bất kỳ phần tử cụ thể nào. Như thế này:

public void onClick(View clickedView) {
    // save rect of view in screen coordinates
    final Rect viewRect = new Rect();
    clickedView.getGlobalVisibleRect(viewRect);
 
    // create Explode transition with epicenter
    Transition explode = new Explode()
        .setEpicenterCallback(new Transition.EpicenterCallback() {
            @Override
            public Rect onGetEpicenter(Transition transition) {
                return viewRect;
            }
        });
    explode.setDuration(1000);
    TransitionManager.beginDelayedTransition(recyclerView, explode);
 
    // remove all views from Recycler View
    recyclerView.setAdapter(null);
}

ChangeImageTransform

ChangeImageTransform. Sẽ làm thay đổi hoạt hình của ma trận hình ảnh. Nó rất hữu ích cho các tình huống khi chúng ta thay đổi scaleType của ImageView. Trong hầu hết các trường hợp, bạn sẽ muốn sử dụng nó cùng với ChangeBound để thay đổi vị trí, kích thước và scaleType.

TransitionManager.beginDelayedTransition(transitionsContainer, new TransitionSet()
    .addTransition(new ChangeBounds())
    .addTransition(new ChangeImageTransform()));
 
ViewGroup.LayoutParams params = imageView.getLayoutParams();
params.height = expanded ? ViewGroup.LayoutParams.MATCH_PARENT : 
    ViewGroup.LayoutParams.WRAP_CONTENT;
imageView.setLayoutParams(params);
 
imageView.setScaleType(expanded ? ImageView.ScaleType.CENTER_CROP : 
    ImageView.ScaleType.FIT_CENTER);

Path (Curved) motion

Lực lượng trong thế giới thực, giống như lực hấp dẫn, truyền cảm hứng cho một phần tử chuyển động dọc theo một vòng cung chứ không phải theo một đường thẳng. Material Design guidelines Đối với mọi Transition hoạt động với hai tọa độ thứ nguyên (ví dụ: thay đổi vị trí chế độ xem bằng ChangeBound), chúng ta có thể áp dụng chuyển động cong với phương thức setPathMotion.

TransitionManager.beginDelayedTransition(transitionsContainer,
    new ChangeBounds().setPathMotion(new ArcMotion()).setDuration(500));
 
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) button.getLayoutParams();
params.gravity = isReturnAnimation ? (Gravity.LEFT | Gravity.TOP) :
    (Gravity.BOTTOM | Gravity.RIGHT);
button.setLayoutParams(params);

TransitionName

Giả sử chúng ta cần xóa tất cả các view khỏi vùng chứa và thêm view mới. Và một số yếu tố mới thực sự giống như trước khi tái tạo. Làm thế nào chúng ta có thể giúp khung để hiểu mục nào đã bị xóa và phần tử nào đã được chuyển đến vị trí mới? Dễ dàng. Chỉ cần gọi phương thức tĩnh TransitionManager.setTransitionName (Xem v, String transName) để cung cấp bất kỳ tên duy nhất tùy thuộc vào mô hình dữ liệu của bạn cho mỗi view.

Ví dụ: nếu chúng tôi muốn tạo một danh sách các tiêu đề và trộn nó với các view được tạo lại trên mỗi lần nhấp vào nút.

createViews(inflater, layout, titles);
shuffleButton.setOnClickListener(new View.OnClickListener() {
 
    @Override
    public void onClick(View v) {
        TransitionManager.beginDelayedTransition(layout, new ChangeBounds());
        Collections.shuffle(titles);
        createViews(inflater, layout, titles);
    }
 
});
 
// In createViews we should provide transition name for every view.

private static void createViews(LayoutInflater inflater, ViewGroup layout, List<String> titles) {
    layout.removeAllViews();
    for (String title : titles) {
        TextView textView = (TextView) inflater.inflate(R.layout.text_view, layout, false);
        textView.setText(title);
        TransitionManager.setTransitionName(textView, title);
        layout.addView(textView);
    }
}

Create Transition with xml

Transition có thể được tạo trong xml. Xml phải được tạo trong res/anim. Ví dụ:

<?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 inflating:
TransitionInflater.from(getContext()).inflateTransition(R.anim.my_the_best_transition);

Custom Transitions

Transitions có thể được sử dụng cho mọi mục đích, cho mọi View xem. Hãy cùng cố gắng tạo ra một cái gì đó độc đáo - Transitions của chúng ta.

Tất cả những gì chúng ta nên làm là thực hiện ba phương thức: captStartValues, captEndValues và createdAnimator. Hai cái đầu tiên là để chụp trạng thái xem trước và sau khi thay đổi view

Chúng tôi sẽ tạo chuyển đổi để thay đổi tiến trình thuận lợi cho ProgressBar ngang:

private class ProgressTransition extends Transition {
 
    /**
     * Property is like a helper that contain setter and getter in one place
     */
    private static final Property<ProgressBar, Integer> PROGRESS_PROPERTY = 
        new IntProperty<ProgressBar>() {
 
        @Override
        public void setValue(ProgressBar progressBar, int value) {
            progressBar.setProgress(value);
        }
 
        @Override
        public Integer get(ProgressBar progressBar) {
            return progressBar.getProgress();
        }
    };
 
    /**
      * Internal name of property. Like a intent bundles 
      */
    private static final String PROPNAME_PROGRESS = "ProgressTransition:progress";
 
    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        captureValues(transitionValues);
    }
 
    @Override
    public void captureEndValues(TransitionValues transitionValues) {
        captureValues(transitionValues);
    }
 
    private void captureValues(TransitionValues transitionValues) {
        if (transitionValues.view instanceof ProgressBar) {
            // save current progress in the values map
            ProgressBar progressBar = ((ProgressBar) transitionValues.view);
            transitionValues.values.put(PROPNAME_PROGRESS, progressBar.getProgress());
        }
    }
 
    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, 
            TransitionValues endValues) {
        if (startValues != null && endValues != null && endValues.view instanceof ProgressBar) {
            ProgressBar progressBar = (ProgressBar) endValues.view;
            int start = (Integer) startValues.values.get(PROPNAME_PROGRESS);
            int end = (Integer) endValues.values.get(PROPNAME_PROGRESS);
            if (start != end) {
                // first of all we need to apply the start value, because right now
                // the view has end value
                progressBar.setProgress(start);
                // create animator with our progressBar, property and end value
                return ObjectAnimator.ofInt(progressBar, PROGRESS_PROPERTY, end);
            }
         }
         return null;
    }
}

Tổng kết

Việc sử dụng Animation trong ứng dụng sẽ làm tăng trải nghiệm người dùng, làm ứng dụng của bạn hấp dẫn và trôi chảy hơn. Trong bài viết mình đã giới thiệu một số Animation cơ bản qua API Transition. Bài viết còn một số hạn chế, còn gì sai sót mong mọi người góp ý .

0