1. 程式人生 > >使用CircularReveal動畫效果切換頁面

使用CircularReveal動畫效果切換頁面

歡迎Follow我的GitHub, 關注我的CSDN.

Material Design

Book

Android的Material Design設計理念, 帶來很多絢麗的動畫效果. 在頁面切換中, 最常用的就是SharedElementTransition, 通過設定控制元件的變換方式, 在進入時把控制元件變換為頁面, 在退出時, 把頁面變換為控制元件, 同時, 可以設定控制元件移動的軌跡. 這樣的控制元件, 可以應用於訊息通知, 或者廣告顯示, 提供非常好的使用者體驗. 那麼是如何實現的呢?

隨著廠商的版本迭代, 超過三分之一的手機都是5.0以上的作業系統, 隨著更多便宜的低端手機普及5.0+系統(如紅米系列), 給使用者帶來更好的體驗, 會大大增加應用留存率.

Android系統(20160227)

本文主要內容:
(1) 修改分享元素的滑動軌跡, 以90°圓弧方式出現和返回, 即transition動畫.
(2) 生成和銷燬頁面的爆炸和凝聚效果, 即CircularReveal的使用方式.

本文原始碼的GitHub下載地址.

最終動畫效果
CircularReveal

1. 首頁

新建一個含義Fab按鈕的HelloWorld工程. 新增ButterKnife和Lambda庫.

設定Fab按鈕的跳轉事件.

    // Fab的跳轉事件
    public void startOtherActivity(View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            ActivityOptions options =
                    ActivityOptions.makeSceneTransitionAnimation(this
, mFab, mFab.getTransitionName()); startActivity(new Intent(this, OtherActivity.class), options.toBundle()); } else { startActivity(new Intent(this, OtherActivity.class)); } }

確保版本號大於5.0, 支援Material Design的動畫效果.

設定Fab的TransitionName, 即變化名稱.

    <android.support
.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" android:clickable="true" android:onClick="startOtherActivity" android:src="@android:drawable/ic_dialog_email" android:transitionName="@string/other_transition_name" tools:targetApi="lollipop"/>

注意android:transitionName屬性, 表示變換名稱.

2. 跳轉頁

頁面由背景, 變化控制元件, 關閉控制元件, 這三部分組成.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/other_rl_container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/other_fab_circle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:transitionName="@string/other_transition_name"
        app:backgroundTint="@color/colorAccent"
        app:elevation="0dp"
        app:fabSize="normal"
        app:pressedTranslationZ="8dp"
        tools:targetApi="21"/>

    <TextView
        android:id="@+id/other_tv_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="@string/other_text"
        android:textColor="@android:color/white"
        android:textSize="40sp"
        android:textStyle="bold"
        android:visibility="invisible"
        tools:visibility="visible"/>

    <ImageView
        android:id="@+id/other_iv_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:contentDescription="@null"
        android:onClick="backActivity"
        android:src="@drawable/ic_close_white"
        android:visibility="invisible"/>

</RelativeLayout>

注意, 變換控制元件FloatingActionButton與主頁的變換控制元件有相同的transitionName.

顯示邏輯, 設定入場和退場動畫, 爆炸的動畫效果.

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);
        ButterKnife.bind(this);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setupEnterAnimation(); // 入場動畫
            setupExitAnimation(); // 退場動畫
        } else {
            initViews();
        }
    }

退出邏輯, 退出動畫, 凝聚的動畫效果.

    // 退出按鈕
    public void backActivity(View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            onBackPressed();
        } else {
            defaultBackPressed();
        }
    }

下面仔細分析顯示和退場動畫.

3. 顯示動畫

分享元素切換使用弧度規矩, 即arc_motion, 90°旋轉過去和回來. 在動畫結束後, 頁面顯示使用爆炸效果.

    // 入場動畫
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void setupEnterAnimation() {
        Transition transition = TransitionInflater.from(this)
                .inflateTransition(R.transition.arc_motion);
        getWindow().setSharedElementEnterTransition(transition);
        transition.addListener(new Transition.TransitionListener() {
            @Override public void onTransitionStart(Transition transition) {

            }

            @Override public void onTransitionEnd(Transition transition) {
                transition.removeListener(this);
                animateRevealShow();
            }

            @Override public void onTransitionCancel(Transition transition) {

            }

            @Override public void onTransitionPause(Transition transition) {

            }

            @Override public void onTransitionResume(Transition transition) {

            }
        });
    }

    // 動畫展示
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void animateRevealShow() {
        GuiUtils.animateRevealShow(
                this, mRlContainer,
                mFabCircle.getWidth() / 2, R.color.colorAccent,
                new GuiUtils.OnRevealAnimationListener() {
                    @Override public void onRevealHide() {

                    }

                    @Override public void onRevealShow() {
                        initViews();
                    }
                });
    }

arc_motion角度變換

<transitionSet
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:interpolator="@android:interpolator/linear_out_slow_in">

    <changeBounds>
        <!--suppress AndroidElementNotAllowed -->
        <arcMotion
            android:maximumAngle="90"
            android:minimumHorizontalAngle="90"
            android:minimumVerticalAngle="0"/>
    </changeBounds>
</transitionSet>

爆炸的動畫效果

    // 圓圈爆炸效果顯示
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static void animateRevealShow(
            final Context context, final View view,
            final int startRadius, @ColorRes int color,
            OnRevealAnimationListener listener) {
        int cx = (view.getLeft() + view.getRight()) / 2;
        int cy = (view.getTop() + view.getBottom()) / 2;

        float finalRadius = (float) Math.hypot(view.getWidth(), view.getHeight());

        // 設定圓形顯示動畫
        Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, startRadius, finalRadius);
        anim.setDuration(300);
        anim.setInterpolator(new AccelerateDecelerateInterpolator());
        anim.addListener(new AnimatorListenerAdapter() {
            @Override public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                view.setVisibility(View.VISIBLE);
                listener.onRevealShow();
            }

            @Override public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                view.setBackgroundColor(ContextCompat.getColor(context, color));
            }
        });

        anim.start();
    }

使用CircularReveal, 即圓形顯示的動畫效果.
第一個引數是顯示的檢視, 第二個和第三個是變換的中心位置, 第三個和第四個是變換的起始半徑和結束半徑.

4. 退出動畫

退出動畫是凝聚效果, 同樣使用的是CircularReveal.

    // 退出事件
    @Override public void onBackPressed() {
        GuiUtils.animateRevealHide(
                this, mRlContainer,
                mFabCircle.getWidth() / 2, R.color.colorAccent,
                new GuiUtils.OnRevealAnimationListener() {
                    @Override
                    public void onRevealHide() {
                        defaultBackPressed();
                    }

                    @Override
                    public void onRevealShow() {

                    }
                });
    }

退出和顯示的區別就是起始和終止的半徑不同.

    // 圓圈凝聚效果
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static void animateRevealHide(
            final Context context, final View view,
            final int finalRadius, @ColorRes int color,
            OnRevealAnimationListener listener
    ) {
        int cx = (view.getLeft() + view.getRight()) / 2;
        int cy = (view.getTop() + view.getBottom()) / 2;
        int initialRadius = view.getWidth();
        // 與入場動畫的區別就是圓圈起始和終止的半徑相反
        Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, initialRadius, finalRadius);
        anim.setDuration(300);
        anim.setInterpolator(new AccelerateDecelerateInterpolator());
        anim.addListener(new AnimatorListenerAdapter() {
            @Override public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                view.setBackgroundColor(ContextCompat.getColor(context, color));
            }

            @Override public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                listener.onRevealHide();
                view.setVisibility(View.INVISIBLE);
            }
        });
        anim.start();
    }

最後說一些有關使用者體驗的事情, 對於一款應用而言, 好的效能固然重要, 但優秀的設計也非常關鍵, 優異的產品需要優異的展現方式.

OK, that’s all! Enjoy it!