实现一个类似容器里面的水慢慢上涨或者充电电量上涨的动画效果的曲折历程

题外话:文章需要配动态图gif来展示动画效果,在ubuntu下制作gif可参考这篇文章,简单方便,我精简了下步骤,如下:

制作gif动画图片:
https://www.cnblogs.com/bozhicheng/p/5933984.html
首先用录屏工具Kazam录制一段视频,其次再用ffmpeg工具转换成gif
安装ffmpeg工具:
$ sudo add-apt-repository ppa:kirillshkrogalev/ffmpeg-next 
$ sudo apt-get update
$ sudo apt-get install ffmpeg
简单转换指令:
ffmpeg -i test.mp4 out.gif //把整个MP4输出成gif,会很大
精确转换:
ffmpeg -ss 2 -t 12 -i test.mp4 -s 649x320 -r 15 output1.gif  //其中,  -ss 2 to 12 表示从从视频的第2秒开始转换, 转换时间长度为12秒后停止. -s用于设定分辨率, -r 用于设定帧数.  通常Gif有15帧左右就比较流程了
ffmpeg -t <时长> -ss <hh:mm:ss开始制作GIF的时间点> -i <视频文件> out_name.gif

 正式开写~~

首先上一张动图,需求给到的动画效果,很不明显,注意看这个小房子图标的变化:

效果相当简单,就是房子内部一个褐色块不断的匀速往上移动,直至填充满整个房子。咋一看非常简单吧,但就是这么个小动画,前后花费了我一天多的时间,哎,这方面技术真是渣啊。

首先,脑子里会想到在Android中执行动画有三种方式:

1,帧动画,通过多张图片叠加幻灯片方式实现,这样就需要提供多张切图,图片多了必然导致apk包增大,还需要UI去另外切图,为了这么个小动画做搞这么多,明显得不偿失啊。这条路不想走,放弃。

2,视图动画,这个动画也就支持缩放scaleAnimation,平移translateAnimation,旋转rotateAnimation,透明度alphaAnimation四中类型,仔细想想这里的没有一种是可以运用在该效果上的。

3,属性动画,那么最后只有这一种动画可以选择了。这里最重要的两个动画就是ObjectAnimator和ValueAnimator了,其中OjectAnimator继承自ValueAnimator。ObjectAnimator可以改变对象的属性值从而实现动画效果,这个小房子动画没什么特别属性可以让他改变的,那么还是选用ValueAnimator,这个更自由,印象中ValueAnimator可以设置线性插值器,这样褐色块的匀速移动不就好执行了么。

不管选哪种动画,最终还是要解析这个动画是怎么执行的。

首先拆解下这个房子的图像,拿到原图后发现是一个只有边框是褐色,内部和四周都是透明的。

除了我们自己画一个一模一样的图形,好像没什么方法能把原图形的内部填充成我们想要的颜色了吧。

于是就想到了自定义view,然后在onDraw方法里面,根据这个图像的形状模拟出一个多边形来,然后在这个多边形里面再动态的从底部不断的画褐色矩形块向上填充,但是到了顶部三角区域该怎么处理呢,不能在使用矩形块了吧,或者可以继续使用,但是矩形块的高度要变得更小,长度也要变小,不然就超出了房子内部。显然这样处理没法控制匀速的填充,计算矩形块的高度和宽度也是比较不容易的。

后来想到使用图像的混合模式,也就是paint的Xfermode,类似于ps的图层的概念,Xfermode有18种模式,两个图像使用不同的混合模式会得到不同的效果。具体的使用可参考这位大神的文章:

自定义控件三部曲之绘图篇(十)——Paint之setXfermode(一)

一定要搞懂了Xfermode模式,这样才能理解后面我所经历的实验。

再附上一篇模式的讲解,可从文章的中部开始看:

Android Paint之 setXfermode PorterDuffXfermode 讲解

经过琢磨,我选择了

PorterDuff.Mode.SRC_IN

这个模式。大致的意思是把源图像的颜色值作用到目标图像上,也就是两个图像相交的地方,使用源图像的颜色。这里关键的就是如何选择源图像和目标图像。我们可以把目标图像规定为想要对其改变的图像,源图像就是要作用的颜色的来源,都是相对而言的,也可以想想ps的图层,目标图层相当于是最底部的图层,源图层相当于最上部的图层,把最上面的图层的内容作用到最下面的图层上。仔细想一想,我是想改变上面那个四周和内部都是透明的只有边框的小房子的内部的颜色,那么姑且把这个小房子看做是目标图像吧,目标图像有了,那么源图像呢?不如直接弄一个实心的矩形吧,为了效果展示得更直观,这里姑且把实心矩形的颜色设置为红色。

第一组实验:

首先需要自定义一个view,暂时继承自ImageView吧,需要重写onDraw方法,并在view的构造方法中初始化一些必要的变量,包括需要设置混合模式的画笔,目标图像和源图像:

private void initGroup1(){
    mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
    mPaint = new Paint();// 这个是用来绘制图层的画笔,包括设置Xfermode
    BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.mz_bottom_ic_home_nor_light);
    width = drawable.getBitmap().getWidth();
    height = drawable.getBitmap().getHeight();
    setLayoutParams(new ViewGroup.LayoutParams(width,height));//设置当前view的宽高就是图像的宽高
    dst = drawable.getBitmap();//目标图像来源于资源文件

    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个宽高一致的图像作为源图像,此时的图像还是一个没有任何颜色值的
    Canvas canvas = new Canvas(bitmap);//在图像上着色画图形,需要使用画布来操作
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//作画需要画笔嘛
    paint.setColor(Color.RED);// 设置画笔的颜色
    canvas.drawRect(0,0,width,height,paint);//在该画布上画一个和view的宽高一样的红色矩形块,这个矩形块填满了bitmap
    src = bitmap;//把这个红色矩形图像当做源图像,源图像的内容要作用到目标图像dst上
}

然后在onDraw方法中进行图层的合并混合:

private void drawGroup1(Canvas canvas){
    int layerID = canvas.saveLayer(0,0,width,height,mPaint,Canvas.ALL_SAVE_FLAG); // 图像混合的标准步骤1

    canvas.drawBitmap(dst, 0, 0, mPaint); // 将目标图像设置到view的画布图层中

    mPaint.setXfermode(mXfermode); // 图像混合的标准步骤2:设置模式

    canvas.drawBitmap(src, 0, 0, mPaint); // 将源图像加到view的画布图层中,并根据模式进行运算

    mPaint.setXfermode(null);//图像混合的标准步骤3:清除模式,恢复图层
    canvas.restoreToCount(layerID);
}

非常期待的运行下看看结果:

咦~明显不对嘛,理想情况是想改变小房子内部的颜色,怎么现在貌似只是改变了房子周边的颜色(原来房子边框是灰色的),内部怎么没有变呢?

原来的效果:

为了能够动态的展示这个混合的效果,我这里让混合效果动起来。

首先在initGroup1方法中增加当前view的高度为目标图像高度的两倍,以便能够展示源图像:

setLayoutParams(new ViewGroup.LayoutParams(width,height*2));//设置当前view的高度是图像的两倍

用srcTop变量存储初始源图像在view中画布的初始位置,也就是在当前小房子的下面绘制红色矩形:

srcTop = height;
startAnim();

startAnim用来开启一个属性动画:

public void startAnim(){
    final ValueAnimator animator = ValueAnimator.ofInt(height,0);//动画执行的值从目标图像的高度开始,一直变化到0
    animator.setDuration(3000);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            srcTop = (int)animation.getAnimatedValue();//不断的取得源图像在view中的canvas的位置
            postInvalidate();
        }
    });
    postDelayed(new Runnable() {
        @Override
        public void run() {
            isStartMix = true; // 标记开始混合图像
            animator.start();
        }
    },3000);

}

这里延迟3s开始执行属性动画,为了能看到初始状态下,目标图像和源图像所在整个view画布中的初始位置,以及没有执行混合模式下的状态。

接着在drawGroup1方法中,将画布图层的高度扩大两倍,这样才能显示出目标图像和源图像,因为源图像初始是放在目标图像下方的。

int layerID = canvas.saveLayer(0,0,width,height*2,mPaint,Canvas.ALL_SAVE_FLAG); // 图像混合的标准步骤1
还没进行动画的时候,不要执行混合模式,否则将看不到源图像的初始状态
if(isStartMix)
mPaint.setXfermode(mXfermode); // 图像混合的标准步骤2:设置模式
在view的画布中动态的改变源图像在画布中的高度
canvas.drawBitmap(src, 0, srcTop, mPaint);

改造后的完整方法如下:

private void initGroup1(){
    mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
    mPaint = new Paint();// 这个是用来绘制图层的画笔,包括设置Xfermode
    BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.mz_bottom_ic_home_nor_light);
    width = drawable.getBitmap().getWidth();
    height = drawable.getBitmap().getHeight();
    setLayoutParams(new ViewGroup.LayoutParams(width,height*2));//设置当前view的高度是图像的两倍
    dst = drawable.getBitmap();//目标图像来源于资源文件

    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个宽高一致的图像作为源图像,此时的图像还是一个没有任何颜色值的
    Canvas canvas = new Canvas(bitmap);//在图像上着色画图形,需要使用画布来操作
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//作画需要画笔嘛
    paint.setColor(Color.RED);// 设置画笔的颜色
    canvas.drawRect(0,0,width,height,paint);//在该画布上画一个和view的宽高一样的红色矩形块,这个矩形块填满了bitmap
    src = bitmap;//把这个红色矩形图像当做源图像,源图像的内容要作用到目标图像dst上

    srcTop = height;
    startAnim();
}

private void drawGroup1(Canvas canvas){
    int layerID = canvas.saveLayer(0,0,width,height*2,mPaint,Canvas.ALL_SAVE_FLAG); // 图像混合的标准步骤1

    canvas.drawBitmap(dst, 0, 0, mPaint); // 将目标图像设置到view的画布图层中

    if(isStartMix)
    mPaint.setXfermode(mXfermode); // 图像混合的标准步骤2:设置模式

    canvas.drawBitmap(src, 0, srcTop, mPaint); // 将源图像加到view的画布图层中,并根据模式进行运算

    mPaint.setXfermode(null);//图像混合的标准步骤3:清除模式,恢复图层
    canvas.restoreToCount(layerID);
}

看下执行效果:

从动画中可以看到,红色方块在3s后消失了。这个是因为此时进行了图像的混合模式,目标图像就是这里的小房子,源图像就是红色方块,两个图像进行混合的时候,由于目标图像除了房子的轮廓是有色值的,其他地方都是透明的,所以在源图像作用到目标图像的时候,透明区域是无法作用的,只能作用到房子的轮廓上,表现出来就是房子轮廓从下往上慢慢变成红色(源图像色值)。

看这个效果,明显和我们开篇所要达到的效果是不一致的,我们是希望小房子的内部能有一个颜色慢慢上升填充,而不是仅仅改变房子的轮廓。

通过上面的实验可以知道,想要实现慢慢上升的效果,就是两个图层不断的混合,目标图层可以保持位置不变,只需要改变源图层在目标图层上的位置就能实现。既然源图层的内容要作用到有色值的图层上才能产生效果,那么我们就把小房子内部全部图上颜色,这样在混合的时候不就能把源图层的颜色作用到房子上了么。

这里还有个问题就是怎么给房子内部填充颜色,想来想去也只能依赖UI再给一张实心房子的切图了。

,这是一张ui给切的房子内部的实心图,可以看到他把四周的透明都去掉了,图片实际宽高不是原来房子的宽高了。在AS中打开看下:

我们先用这张图来进行混合看看效果。

第二组实验:

这里只简单的改变下目标图像的来源,源图像不变。所以整个代码如下:

private void initGroup2(){
    mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
    mPaint = new Paint();// 这个是用来绘制图层的画笔,包括设置Xfermode
    BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.home_solid1);//这里变成了实心房子
    width = drawable.getBitmap().getWidth();
    height = drawable.getBitmap().getHeight();
    setLayoutParams(new ViewGroup.LayoutParams(width,height*2));//设置当前view的高度是图像的两倍
    dst = drawable.getBitmap();//目标图像来源于资源文件

    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个宽高一致的图像作为源图像,此时的图像还是一个没有任何颜色值的
    Canvas canvas = new Canvas(bitmap);//在图像上着色画图形,需要使用画布来操作
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//作画需要画笔嘛
    paint.setColor(Color.RED);// 设置画笔的颜色
    canvas.drawRect(0,0,width,height,paint);//在该画布上画一个和view的宽高一样的红色矩形块,这个矩形块填满了bitmap
    src = bitmap;//把这个红色矩形图像当做源图像,源图像的内容要作用到目标图像dst上

    srcTop = height;
    startAnim();
}

合成的代码还是不变,复用drawGroup1方法。

从效果来看,和我们预期的效果又接近了一步,确实可以实现慢慢上升的效果了。但是我们动画的初始状态应该是一个透明的小房子,然后内部慢慢的被填充颜色。房子边框和外部的透明空间应该是要保留的。这里ui给我切的图是房子内部,去掉了外部透明的区域和边框区域,如果我将小房子图作为背景,那么就需要精确的调整实心房子的坐标才能把实心房子刚好放在透明房子的内部,在多分辨率机子上计算可能会出现偏差,导致透明边框房子和实心房子没有正常叠加,于是我自己又在原图的基础上做了一张实心的小房子,抹掉了边框的像素,保留了外部所有透明区域,如下:

内部是个实心白色房子,然后把原图设置为该自定义view的背景,在onDraw方法中,使用该内部实心房子作为目标图层,与红色矩形图层作用。到这里我们不再在原图上做手脚,而是将该实心房子作为了目标图像,原来的透明房子只作为一个背景。

private void initGroup3(){
    BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(R.drawable.mz_bottom_ic_home_nor_light);
    setImageDrawable(bitmapDrawable);//把最原始的透明房子设置给自己
    mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
    mPaint = new Paint();// 这个是用来绘制图层的画笔,包括设置Xfermode
    BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.home_solid);//取出实心小房子,宽高和原来的是一样的
    width = drawable.getBitmap().getWidth();
    height = drawable.getBitmap().getHeight();
    setLayoutParams(new ViewGroup.LayoutParams(width,height));//设置当前view的高度是图像的高度
    dst = drawable.getBitmap();//目标图像来源于资源文件

    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个宽高一致的图像作为源图像,此时的图像还是一个没有任何颜色值的
    Canvas canvas = new Canvas(bitmap);//在图像上着色画图形,需要使用画布来操作
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//作画需要画笔嘛
    paint.setColor(Color.RED);// 设置画笔的颜色
    canvas.drawRect(0,0,width,height,paint);//在该画布上画一个和view的宽高一样的红色矩形块,这个矩形块填满了bitmap
    src = bitmap;//把这个红色矩形图像当做源图像,源图像的内容要作用到目标图像dst上

    srcTop = height;
    startAnim();
}

在这组实验中,我将view的宽高设置成了原始图片的宽高,所以在小房子下部分绘制出来的红色矩形图层在开始的时候没有出现在视图中。仔细看下,其实四周的图层叠加的效果不是很好,主要还是切图不准确,细节的东西慢慢再调了。

上面这组实验其实还是有问题的,内部实心图层始终是显示的白色的,现在我的父视图的白色背景没什么问题,一旦我的背景变成了其他颜色,看起来就很怪了。比如我父布局背景设置成灰色。

在动画执行的过程中,房子内部没有混合的区域始终都是白色的,这和实际需求不符,实际需求内部应该是透明的,用上升的颜色来填充满整个房子。

接下来第四组实验:

private Canvas srcCanvas;
private Paint srcPaint;
/**
 * 第四组实验,也是在源bitmap上绘制一个矩形图案,但是这个矩形图案的高度初始值为bitmap的height值,也就是在该bitmap的最底部。
 * 也就是说现在的bitmap上方是一个透明的区域,最下方是一个矩形图案。当透明的区域和目标图像进行合成的时候,就会将目标图像擦除,也变成了透明区域。
 * 接下来,在onDraw方法中,通过改变矩形图像的top值来动态移动矩形图像在源bitmap中的位置,也就是矩形图像慢慢的填充满整个源bitmap。
 * 而此次合并的过程中目标的bitmap和源bitmap是完全重合的。透明合成的区域被擦除,有矩形块的区域的颜色值就作用到目标图像上了。
 */
private void initGroup4(){
    BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(R.drawable.mz_bottom_ic_home_nor_light);
    setImageDrawable(bitmapDrawable);//把最原始的透明房子设置给自己
    mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
    mPaint = new Paint();// 这个是用来绘制图层的画笔,包括设置Xfermode
    BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.home_solid);//取出实心小房子,宽高和原来的是一样的
    width = drawable.getBitmap().getWidth();
    height = drawable.getBitmap().getHeight();
    setLayoutParams(new ViewGroup.LayoutParams(width,height));//设置当前view的高度是图像的高度
    dst = drawable.getBitmap();//目标图像来源于资源文件

    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个宽高一致的图像作为源图像,此时的图像还是一个没有任何颜色值的
    //画布和画笔都定义成全局变量,因为在动画的过程中,需要用到这两个变量对矩形块进行绘制
    srcCanvas = new Canvas(bitmap);//在图像上着色画图形,需要使用画布来操作
    srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//作画需要画笔嘛
    srcPaint.setColor(Color.RED);// 设置画笔的颜色

    //在这组实验中,主要是改变源图像的矩形块的高度
    srcTop = height;
    mRect = new Rect(0,srcTop, width, height); // 创建一个红色矩形块,这个矩形块顶部的距离初始就是目标图像的高度(即在底部)
    srcCanvas.drawRect(mRect,srcPaint);//把这个矩形块画在bitmap上,初始效果是在bitmap的最底部
    src = bitmap;//把这个红色矩形图像当做源图像,源图像的内容要作用到目标图像dst上

    startAnim();
}

目标图层还是由实心小房子来担当,源图层有点变化,源图层初始状态是一个宽高和目标图层一致的透明图层,在最底部绘制一个矩形块,onDraw方法中,通过不断改变这个矩形块的位置来实现慢慢上升的效果。

private void drawGroup2(Canvas canvas){
    int layerID = canvas.saveLayer(0,0,width,height*2,mPaint,Canvas.ALL_SAVE_FLAG); // 图像混合的标准步骤1

    canvas.drawBitmap(dst, 0, 0, mPaint); // 将目标图像设置到view的画布图层中

    mPaint.setXfermode(mXfermode); // 图像混合的标准步骤2:设置模式

    mRect.top = srcTop; // 动态改变源图像中的矩形块的top值
    srcCanvas.drawRect(mRect, srcPaint);//在源画布上根据新的top距离绘制矩形块,绘制的矩形块会在src上表现出来
    canvas.drawBitmap(src, 0, 0, mPaint); // 将源图像加到view的画布图层中,并根据模式进行运算,(源图像的左边和顶部局域始终和目标图像重合)

    mPaint.setXfermode(null);//图像混合的标准步骤3:清除模式,恢复图层
    canvas.restoreToCount(layerID);
}

在这组实验中,我们需要理解的就是,假如目标图层是有色值的,源图层是一个透明的图层,那么两个相互作用的时候,源图层就会把目标图层的颜色擦除,只有当源图层有颜色值的时候,才会将该颜色值作用到目标图层的有颜色值的区域中。

最终,这一组实验达到了我们想要的效果:透明边框小房子,内部慢慢充满红色。

这个动画最终用到的就是两个图像的混合模式,理解了混合模式就能很好的实现各种各样的效果了。

最后,附上完整的自定义view代码:

public class HomeAnimView extends ImageView {
    private Paint mPaint;
    private Path mPath;
    private Rect mRect;
    private Bitmap src,dst;
    private int srcTop;
    private int width,height;
    private Xfermode mXfermode;
    private boolean isStartMix;
    public HomeAnimView(Context context) {
        super(context);
//        initGroup1();
//        initGroup2();
//        initGroup3();
        //以上三组实验都开启的时候,应该打开drawGroup1方法

        initGroup4();//开启这个实验的时候,onDraw中应该执行drawGroup2
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        drawGroup1(canvas);
        drawGroup2(canvas);
    }

    private void initGroup1(){
        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        mPaint = new Paint();// 这个是用来绘制图层的画笔,包括设置Xfermode
        BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.mz_bottom_ic_home_nor_light);
        width = drawable.getBitmap().getWidth();
        height = drawable.getBitmap().getHeight();
        setLayoutParams(new ViewGroup.LayoutParams(width,height*2));//设置当前view的高度是图像的两倍
        dst = drawable.getBitmap();//目标图像来源于资源文件

        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个宽高一致的图像作为源图像,此时的图像还是一个没有任何颜色值的
        Canvas canvas = new Canvas(bitmap);//在图像上着色画图形,需要使用画布来操作
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//作画需要画笔嘛
        paint.setColor(Color.RED);// 设置画笔的颜色
        canvas.drawRect(0,0,width,height,paint);//在该画布上画一个和view的宽高一样的红色矩形块,这个矩形块填满了bitmap
        src = bitmap;//把这个红色矩形图像当做源图像,源图像的内容要作用到目标图像dst上

        srcTop = height;
        startAnim();
    }

    private void drawGroup1(Canvas canvas){
        int layerID = canvas.saveLayer(0,0,width,height*2,mPaint,Canvas.ALL_SAVE_FLAG); // 图像混合的标准步骤1

        canvas.drawBitmap(dst, 0, 0, mPaint); // 将目标图像设置到view的画布图层中

        if(isStartMix)
        mPaint.setXfermode(mXfermode); // 图像混合的标准步骤2:设置模式

        canvas.drawBitmap(src, 0, srcTop, mPaint); // 将源图像加到view的画布图层中,并根据模式进行运算

        mPaint.setXfermode(null);//图像混合的标准步骤3:清除模式,恢复图层
        canvas.restoreToCount(layerID);
    }



    private void initGroup2(){
        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        mPaint = new Paint();// 这个是用来绘制图层的画笔,包括设置Xfermode
        BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.home_solid1);
        width = drawable.getBitmap().getWidth();
        height = drawable.getBitmap().getHeight();
        setLayoutParams(new ViewGroup.LayoutParams(width,height*2));//设置当前view的高度是图像的两倍
        dst = drawable.getBitmap();//目标图像来源于资源文件

        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个宽高一致的图像作为源图像,此时的图像还是一个没有任何颜色值的
        Canvas canvas = new Canvas(bitmap);//在图像上着色画图形,需要使用画布来操作
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//作画需要画笔嘛
        paint.setColor(Color.RED);// 设置画笔的颜色
        canvas.drawRect(0,0,width,height,paint);//在该画布上画一个和view的宽高一样的红色矩形块,这个矩形块填满了bitmap
        src = bitmap;//把这个红色矩形图像当做源图像,源图像的内容要作用到目标图像dst上

        srcTop = height;
        startAnim();
    }

    private void initGroup3(){
        BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(R.drawable.mz_bottom_ic_home_nor_light);
        setImageDrawable(bitmapDrawable);//把最原始的透明房子设置给自己
        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        mPaint = new Paint();// 这个是用来绘制图层的画笔,包括设置Xfermode
        BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.home_solid);//取出实心小房子,宽高和原来的是一样的
        width = drawable.getBitmap().getWidth();
        height = drawable.getBitmap().getHeight();
        setLayoutParams(new ViewGroup.LayoutParams(width,height));//设置当前view的高度是图像的高度
        dst = drawable.getBitmap();//目标图像来源于资源文件

        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个宽高一致的图像作为源图像,此时的图像还是一个没有任何颜色值的
        Canvas canvas = new Canvas(bitmap);//在图像上着色画图形,需要使用画布来操作
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//作画需要画笔嘛
        paint.setColor(Color.RED);// 设置画笔的颜色
        canvas.drawRect(0,0,width,height,paint);//在该画布上画一个和view的宽高一样的红色矩形块,这个矩形块填满了bitmap
        src = bitmap;//把这个红色矩形图像当做源图像,源图像的内容要作用到目标图像dst上

        srcTop = height;
        startAnim();
    }


    private Canvas srcCanvas;
    private Paint srcPaint;
    /**
     * 第四组实验,也是在源bitmap上绘制一个矩形图案,但是这个矩形图案的高度初始值为bitmap的height值,也就是在该bitmap的最底部。
     * 也就是说现在的bitmap上方是一个透明的区域,最下方是一个矩形图案。当透明的区域和目标图像进行合成的时候,就会将目标图像擦除,也变成了透明区域。
     * 接下来,在onDraw方法中,通过改变矩形图像的top值来动态移动矩形图像在源bitmap中的位置,也就是矩形图像慢慢的填充满整个源bitmap。
     * 而此次合并的过程中目标的bitmap和源bitmap是完全重合的。透明合成的区域被擦除,有矩形块的区域的颜色值就作用到目标图像上了。
     */
    private void initGroup4(){
        BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(R.drawable.mz_bottom_ic_home_nor_light);
        setImageDrawable(bitmapDrawable);//把最原始的透明房子设置给自己
        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        mPaint = new Paint();// 这个是用来绘制图层的画笔,包括设置Xfermode
        BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.home_solid);//取出实心小房子,宽高和原来的是一样的
        width = drawable.getBitmap().getWidth();
        height = drawable.getBitmap().getHeight();
        setLayoutParams(new ViewGroup.LayoutParams(width,height));//设置当前view的高度是图像的高度
        dst = drawable.getBitmap();//目标图像来源于资源文件

        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个宽高一致的图像作为源图像,此时的图像还是一个没有任何颜色值的
        //画布和画笔都定义成全局变量,因为在动画的过程中,需要用到这两个变量对矩形块进行绘制
        srcCanvas = new Canvas(bitmap);//在图像上着色画图形,需要使用画布来操作
        srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//作画需要画笔嘛
        srcPaint.setColor(Color.RED);// 设置画笔的颜色

        //在这组实验中,主要是改变源图像的矩形块的高度
        srcTop = height;
        mRect = new Rect(0,srcTop, width, height); // 创建一个红色矩形块,这个矩形块顶部的距离初始就是目标图像的高度(即在底部)
        srcCanvas.drawRect(mRect,srcPaint);//把这个矩形块画在bitmap上,初始效果是在bitmap的最底部
        src = bitmap;//把这个红色矩形图像当做源图像,源图像的内容要作用到目标图像dst上

        startAnim();
    }

    private void drawGroup2(Canvas canvas){
        int layerID = canvas.saveLayer(0,0,width,height*2,mPaint,Canvas.ALL_SAVE_FLAG); // 图像混合的标准步骤1

        canvas.drawBitmap(dst, 0, 0, mPaint); // 将目标图像设置到view的画布图层中

        mPaint.setXfermode(mXfermode); // 图像混合的标准步骤2:设置模式

        mRect.top = srcTop; // 动态改变源图像中的矩形块的top值
        srcCanvas.drawRect(mRect, srcPaint);//在源画布上根据新的top距离绘制矩形块,绘制的矩形块会在src上表现出来
        canvas.drawBitmap(src, 0, 0, mPaint); // 将源图像加到view的画布图层中,并根据模式进行运算,(源图像的左边和顶部局域始终和目标图像重合)

        mPaint.setXfermode(null);//图像混合的标准步骤3:清除模式,恢复图层
        canvas.restoreToCount(layerID);
    }

    public void startAnim(){
        final ValueAnimator animator = ValueAnimator.ofInt(height,0);//动画执行的值从目标图像的高度开始,一直变化到0
        animator.setDuration(3000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                srcTop = (int)animation.getAnimatedValue();//不断的取得源图像在view中的canvas的位置
                postInvalidate();
            }
        });
        postDelayed(new Runnable() {
            @Override
            public void run() {
                isStartMix = true; // 标记开始混合图像
                animator.start();
            }
        },3000);

    }
}

参考资料:

自定义控件三部曲之绘图篇(十二)——Paint之setXfermode(三)

Android Paint之 setXfermode PorterDuffXfermode 讲解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值