先看效果图:

想要实现这种效果,首先要了解下Xfermode图像混合模式中的PorterDuff.Mode.CLEAR,它可以用来清除原图像的部分绘制内容,可以理解为它是一块橡皮,可以擦去图像上的任意一块地方。

其次,canvas中的也有着图层的概念。图层是什么,简单来说就是一层一层的图片叠加在同一个地方,比如有一幢摩天大楼,它有一层,两层,三层......十八层等等,我们如果从大楼正上方俯瞰大楼,因为它的下面几层都被最上层压住了,所以我们只能看到它的最上层。图层的概念也一样,我们正常情况只能看到最上层的图层,其它层都被覆盖住了。

所以,大家应该已经猜到了这个动画效果的实现原理,没错,我们一共需要两层,第一层保留着主题更改前的效果图,第二层就是主题更改后的效果图,然后我们用PorterDuff.Mode.CLEAR这个模式,通过drawCircle画圆的方式来擦去旧样式。旧样式被擦去以后,我们就能看见被压在下面的新样式,也就是主题被更改以后的样式。

大致原理如下图:(注意:旧样式在最上层,是后面新添加进来的图层)

下面来说说代码实现。

1、我们需要获取到更改前界面的样式,并用bitmap保存下来,同时得到我们需要用来擦除旧图层的圆的半径:

//将View当前的样式截图保存在bg中private void createbg(){    rootView=(ViewGroup)((Activity)getContext()).getWindow().getDecorView();    totalRadius=rootView.getMeasuredWidth()>rootView.getMeasuredHeight()?rootView.getMeasuredWidth():rootView.getMeasuredHeight();    rootView.setDrawingCacheEnabled(true);    bg=Bitmap.createBitmap(rootView.getDrawingCache(), 0, 0, rootView.getMeasuredWidth(), rootView.getMeasuredHeight());    rootView.setDrawingCacheEnabled(false);    attachToRootView();}

rootView:原界面Activity的View

totalRadius:总共需要绘制的圆的半径

bg:用来保存原界面样式的Bitmap

attachToRootView():将该View添加到rootView中的方法(即动态添加该布局控件)

 2、通过saveLayer方法添加新图层来保存旧样式,并以动画形式擦除旧样式(注意,添加后的操作都是在新图层上进行的)

@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    //是否开始绘制    if(!start)return;    //如果已绘制的半径超过需要绘制的半径,则绘制完毕    if(radius>totalRadius){        animateFinish();        return;    }    //在新图层上进行绘制    int layer = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);    canvas.drawBitmap(bg, 0, 0, null);    canvas.drawCircle(0, 0, radius+perRadius, paint);    radius+=perRadius;    canvas.restoreToCount(layer);    postInvalidateDelayed(5);}//绘制完毕,动画结束private void animateFinish(){    start=false;    bg.recycle();    radius=0;}

3、在Activity中完成点击按钮的监听和新样式的更新

private void initView(){    animatorThemeView=new MyAnimatorThemeView(this);    textView=findViewById(R.id.tv_name);    findViewById(R.id.btn_change).setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {            if(!animatorThemeView.hasStart()){                clickNum++;                //开始绘制                animatorThemeView.start();                //更新textView的样式                textView.setTextColor(getResources().getColor(tvColors[clickNum%2]));                textView.setBackground(getDrawable(bgColors[clickNum%2]));            }        }    });}

下面是完整的自定义View代码:

import android.app.Activity;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.view.View;import android.view.ViewGroup;public class MyAnimatorThemeView extends View {    private Paint paint;    private Bitmap bg;    private ViewGroup rootView;    private boolean start=false;    private int radius=0,totalRadius=3000,perRadius=55;   //当前已绘制的圆的半径;需要绘制出的圆的半径;每次绘制的半径    public MyAnimatorThemeView(Context context){        super(context);        init();    }    private void init(){        paint=new Paint();        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //是否开始绘制        if(!start)return;        //如果已绘制的半径超过需要绘制的半径,则绘制完毕        if(radius>totalRadius){            animateFinish();            return;        }        //在新的图层上面绘制        int layer = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);        canvas.drawBitmap(bg, 0, 0, null);        canvas.drawCircle(0, 0, radius+perRadius, paint);        radius+=perRadius;        canvas.restoreToCount(layer);        postInvalidateDelayed(5);    }    //开启动画    public void start(){        if(!start){            createbg();            start=true;            invalidate();        }    }    //将View当前的样式截图保存在bg中    private void createbg(){        rootView=(ViewGroup)((Activity)getContext()).getWindow().getDecorView();        totalRadius=rootView.getMeasuredWidth()>rootView.getMeasuredHeight()?rootView.getMeasuredWidth():rootView.getMeasuredHeight();        rootView.setDrawingCacheEnabled(true);        bg=Bitmap.createBitmap(rootView.getDrawingCache(), 0, 0, rootView.getMeasuredWidth(), rootView.getMeasuredHeight());        rootView.setDrawingCacheEnabled(false);        attachToRootView();    }    //当前View添加到布局中    private void attachToRootView() {        if(this.getParent()==null){            this.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));            rootView.addView(this);        }    }    //绘制完毕,动画结束    private void animateFinish(){        start=false;        bg.recycle();        radius=0;    }    //向外提供检查是否开始的方法    public boolean hasStart(){        return start;    }}

Activity类的代码:

public class MyAnimatorThemeAct extends AppCompatActivity {    private MyAnimatorThemeView animatorThemeView;    private TextView textView;    private int clickNum=0;    private int[] tvColors={R.color.white,R.color.black};    private int[] bgColors={R.color.light_black,R.color.blue};    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.act_animator_theme);        initView();    }    private void initView(){        animatorThemeView=new MyAnimatorThemeView(this);        textView=findViewById(R.id.tv_name);        findViewById(R.id.btn_change).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if(!animatorThemeView.hasStart()){                    clickNum++;                    animatorThemeView.start();                    textView.setTextColor(getResources().getColor(tvColors[clickNum%2]));                    textView.setBackground(getDrawable(bgColors[clickNum%2]));                }            }        });    }}

xml布局文件如下:

<?xml version="1.0" encoding="utf-8"?>    

 

更多相关文章

  1. Android(安卓)4.0的图形硬件加速及绘制技巧(1)
  2. 卡拉OK歌词原理和实现高仿Android网易云音乐
  3. Android(安卓)UI开发专题(五) Bitmap和Canvas实例
  4. view绘制流程些许心得
  5. Android中View的绘制过程 onMeasure方法简述
  6. Android面试题(28)-android的view加载和绘制流程
  7. Android入门——画布Canvas的简单应用
  8. Android自定义一个属于自己的时间钟表
  9. Android(安卓)studio中.9图片的含义及制作教程

随机推荐

  1. android权限设置
  2. Android地图添加标记和文字【代码片段】
  3. ubuntu系统下,搭建Android开发环境!!
  4. Android编译源码时出现的错误: “_FORTIFY
  5. 【Android(安卓)UI】状态栏和toolbar颜色
  6. Android面试总结(持续更新)
  7. 面试题5:椭圆里面有个内切圆,内切圆中有文
  8. Android(安卓)点击back键两次退出程序
  9. Android中ps命令各字段的含义
  10. TextView内容太长怎么办?