天天看點

揭露動畫效果Circular Reveal的使用及封裝

使用

調用原生方法,正常使用

// 先設定FloatingActionButton的點選事件,然後直接在onClick中進行動畫操作
    @Override
    public void onClick(View v) {
        // 擷取FloatingActionButton的中心點的坐标
        int centerX = (v.getLeft() + v.getRight()) / 2;
        int centerY = (v.getTop() + v.getBottom()) / 2;
        // Math.hypot(x,y):  傳回sqrt(x2 +y2)
        // 擷取擴散的半徑
        float finalRadius = (float) Math.hypot((double) centerX, (double) centerY);
        // 定義揭露動畫
        Animator mCircularReveal = ViewAnimationUtils.createCircularReveal(
                secondView, centerX, centerY, 0, finalRadius);
        // 設定動畫持續時間,并開始動畫
        mCircularReveal.setDuration(4000).start();
    }
           

工具類封裝使用

//以 ivMap 為中心,收縮或伸展 rlMapContainer
CircularAnim.show(rlMapContainer).triggerView(ivMap).go();


//布局
<RelativeLayout 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:id="@+id/run_station_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_white">

    <RelativeLayout
        android:id="@+id/rl_data_container"
        android:background="@drawable/bg_white"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        //....       
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl_map_container"
        android:visibility="invisible"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>
           

工具類

public class CircularAnim {

    public static final long PERFECT_MILLS = 618;
    public static final int MINI_RADIUS = 0;

    public interface OnAnimationEndListener {
        void onAnimationEnd();
    }

    @SuppressLint("NewApi")
    public static class VisibleBuilder {

        private View mAnimView, mTriggerView;

        private Float mStartRadius, mEndRadius;

        private long mDurationMills = PERFECT_MILLS;

        private boolean isShow;

        private OnAnimationEndListener mOnAnimationEndListener;

        public VisibleBuilder(View animView, boolean isShow) {
            mAnimView = animView;
            this.isShow = isShow;

            if (isShow) {
                mStartRadius = MINI_RADIUS + 0F;
            } else {
                mEndRadius = MINI_RADIUS + 0F;
            }
        }

        public VisibleBuilder triggerView(View triggerView) {
            mTriggerView = triggerView;
            return this;
        }

        public VisibleBuilder startRadius(float startRadius) {
            mStartRadius = startRadius;
            return this;
        }

        public VisibleBuilder endRadius(float endRadius) {
            mEndRadius = endRadius;
            return this;
        }

        public VisibleBuilder duration(long durationMills) {
            mDurationMills = durationMills;
            return this;
        }

        @Deprecated //You can use method - go(OnAnimationEndListener onAnimationEndListener).
        public VisibleBuilder onAnimationEndListener(OnAnimationEndListener onAnimationEndListener) {
            mOnAnimationEndListener = onAnimationEndListener;
            return this;
        }

        public void go() {
            go(null);
        }

        public void go(OnAnimationEndListener onAnimationEndListener) {
            mOnAnimationEndListener = onAnimationEndListener;

            // 版本判斷
            if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
                doOnEnd();
                return;
            }

            int rippleCX, rippleCY, maxRadius;
            if (mTriggerView != null) {
                int[] tvLocation = new int[2];
                mTriggerView.getLocationInWindow(tvLocation);
                final int tvCX = tvLocation[0] + mTriggerView.getWidth() / 2;
                final int tvCY = tvLocation[1] + mTriggerView.getHeight() / 2;

                int[] avLocation = new int[2];
                mAnimView.getLocationInWindow(avLocation);
                final int avLX = avLocation[0];
                final int avTY = avLocation[1];

                int triggerX = Math.max(avLX, tvCX);
                triggerX = Math.min(triggerX, avLX + mAnimView.getWidth());

                int triggerY = Math.max(avTY, tvCY);
                triggerY = Math.min(triggerY, avTY + mAnimView.getHeight());

                // 以上全為絕對坐标

                int avW = mAnimView.getWidth();
                int avH = mAnimView.getHeight();

                rippleCX = triggerX - avLX;
                rippleCY = triggerY - avTY;

                // 計算水波中心點至 @mAnimView 邊界的最大距離
                int maxW = Math.max(rippleCX, avW - rippleCX);
                int maxH = Math.max(rippleCY, avH - rippleCY);
                maxRadius = (int) Math.sqrt(maxW * maxW + maxH * maxH) + 1;
            } else {
                rippleCX = (mAnimView.getLeft() + mAnimView.getRight()) / 2;
                rippleCY = (mAnimView.getTop() + mAnimView.getBottom()) / 2;

                int w = mAnimView.getWidth();
                int h = mAnimView.getHeight();

                // 勾股定理 & 進一法
                maxRadius = (int) Math.sqrt(w * w + h * h) + 1;
            }

            if (isShow && mEndRadius == null)
                mEndRadius = maxRadius + 0F;
            else if (!isShow && mStartRadius == null)
                mStartRadius = maxRadius + 0F;

            try {
                Animator anim = ViewAnimationUtils.createCircularReveal(
                        mAnimView, rippleCX, rippleCY, mStartRadius, mEndRadius);


                mAnimView.setVisibility(View.VISIBLE);
                anim.setDuration(mDurationMills);

                anim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        doOnEnd();
                    }
                });

                anim.start();
            } catch (Exception e) {
                e.printStackTrace();
                doOnEnd();
            }
        }

        private void doOnEnd() {
            if (isShow)
                mAnimView.setVisibility(View.VISIBLE);
            else
                mAnimView.setVisibility(View.INVISIBLE);

            if (mOnAnimationEndListener != null)
                mOnAnimationEndListener.onAnimationEnd();
        }

    }

    @SuppressLint("NewApi")
    public static class FullActivityBuilder {
        private Activity mActivity;
        private View mTriggerView;
        private float mStartRadius = MINI_RADIUS;
        private int mColorOrImageRes = android.R.color.white;
        private Long mDurationMills;
        private OnAnimationEndListener mOnAnimationEndListener;
        private int mEnterAnim = android.R.anim.fade_in, mExitAnim = android.R.anim.fade_out;

        public FullActivityBuilder(Activity activity, View triggerView) {
            mActivity = activity;
            mTriggerView = triggerView;
        }

        public FullActivityBuilder startRadius(float startRadius) {
            mStartRadius = startRadius;
            return this;
        }

        public FullActivityBuilder colorOrImageRes(int colorOrImageRes) {
            mColorOrImageRes = colorOrImageRes;
            return this;
        }

        public FullActivityBuilder duration(long durationMills) {
            mDurationMills = durationMills;
            return this;
        }

        public FullActivityBuilder overridePendingTransition(int enterAnim, int exitAnim) {
            mEnterAnim = enterAnim;
            mExitAnim = exitAnim;
            return this;
        }

        public void go(OnAnimationEndListener onAnimationEndListener) {
            mOnAnimationEndListener = onAnimationEndListener;

            // 版本判斷,小于5.0則無動畫.
            if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
                doOnEnd();
                return;
            }

            int[] location = new int[2];
            mTriggerView.getLocationInWindow(location);
            final int cx = location[0] + mTriggerView.getWidth() / 2;
            final int cy = location[1] + mTriggerView.getHeight() / 2;
            final ImageView view = new ImageView(mActivity);
            view.setScaleType(ImageView.ScaleType.CENTER_CROP);
            view.setImageResource(mColorOrImageRes);
            final ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView();
            int w = decorView.getWidth();
            int h = decorView.getHeight();
            decorView.addView(view, w, h);

            // 計算中心點至view邊界的最大距離
            int maxW = Math.max(cx, w - cx);
            int maxH = Math.max(cy, h - cy);
            final int finalRadius = (int) Math.sqrt(maxW * maxW + maxH * maxH) + 1;

            try {
                Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, mStartRadius, finalRadius);

                int maxRadius = (int) Math.sqrt(w * w + h * h) + 1;
                // 若未設定時長,則以PERFECT_MILLS為基準根據水波擴散的距離來計算實際時間
                if (mDurationMills == null) {
                    // 算出實際邊距與最大邊距的比率
                    double rate = 1d * finalRadius / maxRadius;
                    // 為了讓使用者便于感觸到水波,速度應随最大邊距的變小而越慢,擴散時間應随最大邊距的變小而變小,是以比率應在 @rate 與 1 之間。
                    mDurationMills = (long) (PERFECT_MILLS * Math.sqrt(rate));
                }
                final long finalDuration = mDurationMills;
                // 由于thisActivity.startActivity()會有所停頓,是以進入的水波動畫應比退出的水波動畫時間短才能保持視覺上的一緻。
                anim.setDuration((long) (finalDuration * 0.9));
                anim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);

                        doOnEnd();

                        mActivity.overridePendingTransition(mEnterAnim, mExitAnim);

                        // 預設顯示傳回至目前Activity的動畫.
                        mTriggerView.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                if (mActivity.isFinishing()) return;
                                try {
                                    Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy,
                                            finalRadius, mStartRadius);
                                    anim.setDuration(finalDuration);
                                    anim.addListener(new AnimatorListenerAdapter() {
                                        @Override
                                        public void onAnimationEnd(Animator animation) {
                                            super.onAnimationEnd(animation);
                                            try {
                                                decorView.removeView(view);
                                            } catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                        }
                                    });
                                    anim.start();
                                } catch (Exception e) {
                                    e.printStackTrace();
                                    try {
                                        decorView.removeView(view);
                                    } catch (Exception e1) {
                                        e1.printStackTrace();
                                    }
                                }
                            }
                        }, 1000);

                    }
                });
                anim.start();
            } catch (Exception e) {
                e.printStackTrace();
                doOnEnd();
            }
        }

        private void doOnEnd() {
            mOnAnimationEndListener.onAnimationEnd();
        }
    }


    /* 上面為實作邏輯,下面為外部調用方法 */


    /* 伸展并顯示@animView */
    public static VisibleBuilder show(View animView) {
        return new VisibleBuilder(animView, true);
    }

    /* 收縮并隐藏@animView */
    public static VisibleBuilder hide(View animView) {
        return new VisibleBuilder(animView, false);
    }

    /* 以@triggerView 為觸發點鋪滿整個@activity */
    public static FullActivityBuilder fullActivity(Activity activity, View triggerView) {
        return new FullActivityBuilder(activity, triggerView);
    }

}
           

參考文章

使用Circular Reveal為你的應用添加揭露動畫效果

AndroidDemos