实现一个类似容器里面的水慢慢上涨或者充电电量上涨的动画效果的曲折历程_小程序透明汽车充电中动画-程序员宅基地

技术标签: 水波纹动画  基础知识  慢慢上涨动画  电量上涨动画  

题外话:文章需要配动态图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 讲解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/anhenzhufeng/article/details/88314494

智能推荐

ReactNative常用插件使用_react-native-screens-程序员宅基地

文章浏览阅读4.1k次。项目版本介绍目前使用的项目node版本v14.17.5, npm版本6.14.14, reactNative项目使用的模板结合typescript语法的项目模板架构,安装的方式如下:npx react-native init xxx(项目名) --template react-native-template-typescript完成之后可直接运行yarn android 或 npm run android 将项目启动。1. react-native-config查看reactNative插件的网_react-native-screens

C++:STL中sort()函数的用法总结_stl sort用法-程序员宅基地

文章浏览阅读894次。借鉴:http://blog.csdn.net/appte/article/details/8253930。http://blog.csdn.net/csust_acm/article/details/7326418。在做ACM题的过程中,算法中经常会用到排序的处理,自己写排序有点麻烦,最主要是记不住排序算法的代码。。。 STL中sort()函数的用法,所以就取巧适用STL中的sort函数,这个省心又放心,只需要包含头文件就可以了,这里总结一下用法以作备忘。STL中的sort函数有两种:templ_stl sort用法

springmvc配置文件和注解方式实现_mvc 通过注解和配置文件来实现,-程序员宅基地

文章浏览阅读72次。一、使用配置文件controllerpublic class Hello implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView(); //业务代码 String r_mvc 通过注解和配置文件来实现,

调用微信小程序统一下单接口_小程序下单接口-程序员宅基地

文章浏览阅读3k次,点赞2次,收藏3次。最近刚接触微信小程序项目,踩了几个坑,以此记录一下1、微信小程序需要绑定商户 【mch_id=商户号】2、调用统一接口部分代码public Map&lt;String,String&gt; pay(){ //微信统一下单URL String payUrl="https://api.mch.weixin.qq.com/pay/unifiedorder";..._小程序下单接口

Java并发指南7:JUC的核心类AQS详解-程序员宅基地

文章浏览阅读57次。这位大侠,这是我的公众号:程序员江湖。分享程序员面试与技术的那些事。 干货满满,关注就送。一行一行源码分析清楚AbstractQueuedSynchronizer转自https://www.javadoop.com/post/AbstractQueued...

1-网络协议基础_网络协议词法大全-程序员宅基地

文章浏览阅读4.7k次,点赞10次,收藏10次。转载请注明出处:http://blog.csdn.net/q1007729991/article/details/69101159很高兴你已经完成了 apue 的基础部分(Linux 环境编程学习笔记),如果你对这部分知识不甚了解,建议你先完成它。在 Linux 环境编程中,已经对基本的 socket 编程做了一个简单的介绍,如果你还没读那部分内容,建议你非常有必要再去看一遍,因为博客并不打算..._网络协议词法大全

随便推点

关于腾讯云服务器不能用公网ip访问的解决方案_腾讯云公网ip不能访问-程序员宅基地

文章浏览阅读3.8k次。关于腾讯云服务器不能用公网ip访问的解决方案_腾讯云公网ip不能访问

HP小机命令集详解-程序员宅基地

文章浏览阅读595次。在工作中经常会用到的命令,整理总结于此: 1.model 说明: 机器型号键入:#model显示:ia64 hp server rx8640分析:ia64 : Intel 64位架构I-tanium(安腾)处理器hp server: Hewlett-Packard Development Company,L.P. 惠普的服务器rx8640: 机器型号,属于H..._惠普小机命令

Java线程池-程序员宅基地

文章浏览阅读150次。Java线程池 一、什么是线程池为了避免系统频繁的创建线程,我们可以让创建的线程复用。由线程池统一管理线程的创建和回收以及销毁的过程,当使用需要使用一个线程的时候,就从线程池中取出一个空闲线程,当完成工作后,并不是关闭线程,而是将这个线程退回到线程池,供其他任务使用。创建线程池的几个原因:频繁的创建销毁线程可能会耗尽cpu和内存资源任务的执..._创建线程池的方法

nRF51822 S110 用户指南_s110_nrf5182_1.0.0.alpha6csdn-程序员宅基地

文章浏览阅读1.5k次。1.SoftDevice结构 SoftDevice是nRF公司实现的一个BLE蓝牙协议栈,只提供HEX文件,可以实现从机角色(Peripheral role)或广播者角色(Broadcaster role)1.1使用前阅读S110 nRF51822 SoftDevice Specification nRF51822 Product SpecificationnRF51 Se_s110_nrf5182_1.0.0.alpha6csdn

eclipse打开后显示 Failed to create the part‘s controls 解决方法_eclipse中打开javascript文件显示failed to create the part'-程序员宅基地

文章浏览阅读739次。eclipse打开后显示 Failed to create the part's controls 解决方法_eclipse中打开javascript文件显示failed to create the part's controls

Spring任务调度_spring 调度-程序员宅基地

文章浏览阅读1k次。在做项目过程中,一些耗时长的任务可能需要在后台线程池中运行;典型的如发送邮件等,由于需要调用外部的接口来进行实际的发送操作,如果客户端在提交发送请求后一直等待服务器端发送成功后再返回,就会长时间的占用服务器的一个连接;当这类请求过多时,服务器连接数会不够用,新的连接请求可能无法得到满足,从而导致客户端连接失败。因此这类服务一般需要使用到后台线程池来处理。在这种情况下,我们可以直接使用concurrent包中的线程池来处理,也可以使用其它的方案如Quartz等组件中的线程池来解决;为适配..._spring 调度

推荐文章

热门文章

相关标签