打造兼容的Android揭面水波动画库

今天看到了@Anderson大码渣写的文章,对5.0以上默认使用ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius);来实现渐变,

预览
,写得非常好,这一块的封装就跳过了,可以直接看他的原文。

4.0以上兼容

要想在低版本中也能正常使用,我们可以直接自定义一个viewGroup,挂载在decorview上,然后使用canvas.clipPath裁剪一个圆,这样达到覆盖的目的。


public class CircularRevealLayout extends FrameLayout {

    private static final String TAG = "CircularRevealLayout";

    private float revealRadius;
    private int centerX;
    private int centerY;
    private float startRadius;
    private float endRadius;

    private View childRevealView;
    private boolean isRunning = false;//动画是否正在执行
    private Path path;//绘制路径



    public CircularRevealLayout(Context context) {
        this(context, null, 0);
    }

    public CircularRevealLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircularRevealLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();

    }

    private void init() {
        path = new Path();
        setFocusable(false);
    }

    /**
     * 设置要揭示的子view
     * @param childRevealView
     */
    public void setChildRevealView(View childRevealView) {
        this.childRevealView = childRevealView;
    }

    /**
     * 设置要揭示的子view的下标
     * @param index
     */
    public void setChildRevealViewIndex(int index) {
        if(getChildCount()>index){
            this.childRevealView=getChildAt(index);
        }

    }

    /**
     * 设置揭示半径
     * @param revealRadius
     */
    private void setRevealRadius(float revealRadius) {
        this.revealRadius = revealRadius;
        Log.e(TAG, "revealRadius=" + revealRadius);
        invalidate();
    }

    /**
     * 设置揭示的中心点x坐标
     * @param centerX
     */
    public void setCenterX(int centerX) {
        this.centerX = centerX;
    }

    /**
     * 设置揭示的中心点y坐标
     * @param centerY
     */

    public void setCenterY(int centerY) {
        this.centerY = centerY;
    }

    /**
     * 设置揭示的开始半径
     * @param startRadius
     */
    public void setStartRadius(float startRadius) {
        this.startRadius = startRadius;

    }

    /**
     * 设置揭示的结束半径
     * @param endRadius
     */
    public void setEndRadius(float endRadius) {
        this.endRadius = endRadius;
    }


    public Animator getAnimator() {

        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "revealRadius", startRadius, endRadius);
        objectAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                isRunning = true;
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                isRunning = false;
            }

            @Override
            public void onAnimationCancel(Animator animator) {
                isRunning = false;
            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        return objectAnimator;
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        if (isRunning && child == childRevealView) {
            final int state = canvas.save();

            path.reset();
            path.addCircle(centerX, centerY, revealRadius, Path.Direction.CW);//Pat.Direction.CW:顺时针

            canvas.clipPath(path);//裁剪

            boolean isInvalided = super.drawChild(canvas, child, drawingTime);

            canvas.restoreToCount(state);

            return isInvalided;
        }

        return super.drawChild(canvas, child, drawingTime);
    }

}

再实现兼容的ViewAnimationCompatUtils


public class ViewAnimationCompatUtils {
    private static final String TAG = "ViewAnimationCompatUtil";

    public static Animator createCircularReveal(@NonNull View view, int centerX, int centerY, float startRadius, float endRadius) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius);
        }

        //如果父view已经是CircularRevealLayout,则设置参数后直接返回animator
        if (view.getParent() != null && view.getParent() instanceof CircularRevealLayout) {
            Log.e(TAG, "parent is CircularRevealLayout");
            CircularRevealLayout circularRevealLayout = (CircularRevealLayout) view.getParent();
            circularRevealLayout.setCenterX(centerX);
            circularRevealLayout.setCenterY(centerY);
            circularRevealLayout.setStartRadius(startRadius);
            circularRevealLayout.setEndRadius(endRadius);
            circularRevealLayout.setChildRevealView(view);
            return circularRevealLayout.getAnimator();
        }

        Log.e(TAG, "parent is not CircularRevealLayout");

        //如果父view不是CircularRevealLayout,则先为view添加父view CircularRevealLayout
        //之后将CircularRevealLayout替换掉原来的view
        CircularRevealLayout circularRevealLayout = new CircularRevealLayout(view.getContext());

        //开启硬件加速
        circularRevealLayout.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        circularRevealLayout.setCenterX(centerX);
        circularRevealLayout.setCenterY(centerY);
        circularRevealLayout.setStartRadius(startRadius);
        circularRevealLayout.setEndRadius(endRadius);
        circularRevealLayout.setChildRevealView(view);

        ViewGroup.LayoutParams params = view.getLayoutParams();
        ViewGroup parent = (ViewGroup) view.getParent();

        int index = 0;
        if (parent != null) {
            index = parent.indexOfChild(view);//记录view在原先父view的下标
            Log.e(TAG, "index=" + index);
            parent.removeView(view);
            circularRevealLayout.addView(view, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            parent.addView(circularRevealLayout, index, params);
        }
        return circularRevealLayout.getAnimator();
    }
}

写到这儿,已经能正常显示了,不过在@Anderson大码渣](http://www.jianshu.com/p/d4b421795154)的基础上我们最好再继续做一定的修改,上文在AnimationHelper的startActivityForResult中写了个延迟任务,

 view.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Animator anim = ViewAnimationCompatUtils.createCircularReveal(imageview, xCenter, yCenter, finalRadius, 0);
                        anim.setDuration(durationMills);
                        anim.addListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(Animator animation) {
                                super.onAnimationEnd(animation);
                                try {
                                    decorView.removeView(imageview);
                                    imageview.setVisibility(View.GONE);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                        anim.start();
                    }
                }, 1000);

在4.0的系统上退回到前一个activity的时候动画有时候会显示不出来,所以最在在baseactivity的onresume中写一个回调,当回调时调用此动画执行。

BaseActivity...

    @Override
    protected void onResume() {
        super.onResume();
        if (resume != null) {
            resume.setResume();
        }
    }

    public void setResume(IonResume resume) {
        this.resume = resume;
    }

    private IonResume resume;
    
    public interface IonResume {
        void setResume();
    }

...
thisActivity.setResume(new BaseActivity.IonResume() {
                    @Override
                    public void setResume() {
                        if (imageview.getVisibility()==View.VISIBLE){
                            Animator anim = ViewAnimationCompatUtils.createCircularReveal(imageview, xCenter, yCenter, finalRadius, 0);
                            anim.setDuration(800);
                            anim.addListener(new AnimatorListenerAdapter() {
                                @Override
                                public void onAnimationEnd(Animator animation) {
                                    super.onAnimationEnd(animation);
                                    try {
                                        decorView.removeView(imageview);
                                        imageview.setVisibility(View.GONE);
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }
                                }
                            });
                            anim.start();
                        }
                    }
                });

Github代码下载
也欢迎大家关注我的简书CSDN

推荐阅读更多精彩内容