天天看点

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

系列中其他文章:

【Android进阶】如何写一个很屌的动画(1)---先实现一个简易的自定义动画框架

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

【Android进阶】如何写一个很屌的动画(3)---高仿腾讯手机管家火箭动画

文章中充满了很多很大的Gif图,请耐心等待加载或者刷新页面,谢谢~

本文有关源码在最下面的下载地址。

要写好一个动画,需要不少好帮手,利用好这些帮手,写一个屌屌的动画就事半功倍了。

好帮手一:图片素材 

一个酷炫的动画,尤其是原创的动画,必须要有一些好的图片素材,而且这些素材地位举足轻重。当然这些都是UI射鸡师需要做的,所以快去催图吧!

好帮手二:Canvas和Paint 

关于这个帮手比较详细的分析可以看看这个系列的文章自定义控件其实很简单

Canvas就是画布的意思,没有画布肯定画不出任何东西,所以Canvas是绘制动画的基础,而且它提供了许多极其伟大的api,让我们绘制东西十分方便。Paint可以理解是画笔,它不是普通的画笔,而是一个有七十二变的画笔,想它变粗,变细,变红,变绿,怎样都行,可谓是“屌中之王”。

相信大家对于Canvas和Paint有哪些基础的api(如drawPath,drawRect等等)都十分熟悉,所以一些基础的用法就不介绍。我会详细说说一些比较重要的小帮手api。

小帮手1、Shader类 

Shader是着色器的意思,它提供了五个子类的Shader来渲染图像,分别是BitmapShader(图片渲染)、ComposeShader(混合渲染)、LinearGradient(线性渐变渲染)、RadialGradient(环形渐变渲染)以及SweepGradient(梯度渐变渲染);

a、BitmapShader可以用一张图片作为纹理,然后绘制的时候,就会用这张图片填充要绘制的区域:

//加载图像资源
        mBitmap = ((BitmapDrawable) getResources().
                getDrawable(R.drawable.snow)).getBitmap();

        //创建Bitmap渲染对象
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, 
                Shader.TileMode.MIRROR);
public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //绘制Bitmap渲染的椭圆
        mPaint.setShader(mBitmapShader);
        canvas.drawOval(new RectF(, , +mBitmap.getWidth(), 
                +mBitmap.getHeight()), mPaint);
    }
           

b、LinearGradient,RadialGradient,SweepGradient则可以让不同颜色按照不同的规律渐变,例如线性渐变,或者环形渐变等等,

//创建线性渲染对象
        int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE}; 
        mLinearGradient = new LinearGradient(, , , , mColorLinear, null, 
                Shader.TileMode.REPEAT);

//创建环形渲染对象
        int mColorRadial[] = {Color.GREEN, Color.RED, Color.BLUE, Color.WHITE};
        mRadialGradient = new RadialGradient(, , , mColorRadial, null, 
                Shader.TileMode.REPEAT);

        //创建梯形渲染对象
        int mColorSweep[] = {Color.GREEN, Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN};
        mSweepGradient = new SweepGradient(, , mColorSweep, null);   

 //绘制线性渐变的矩形
        mPaint.setShader(mLinearGradient);
        canvas.drawRect(, , , , mPaint);

        //绘制环形渐变的圆
        mPaint.setShader(mRadialGradient);
        canvas.drawCircle(, , , mPaint);

        //绘制梯形渐变的矩形
        mPaint.setShader(mSweepGradient);
        canvas.drawRect(, , , , mPaint);
           

c.ComposeShader混合渲染其作用就是把两个Shader效果按一定规则组合,这点跟下面将要介绍的Xfermode是一样的,请往下看。

绘制出来的结果如下图: 

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

关于Shader更详细的用法可看Android学习笔记12:图像渲染(Shader)

小帮手2、PorterDuff.Mode / Xfermode 

这个也是一个极其凶残的帮手,它的作用就是将不同的图像按照一定要求混合成一个图像。官方的ApiDemos工程也有专门介绍该类的使用方法,下面的ApiDemos的截图:

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

为何说它凶残?因为它能做到的事实在太屌了。请看上图,其中src,dst分别是两张图片,后面的效果就是将这两张图片按照一定规则组合起来的效果。 

注意一点:只有有像素值的地方才会被组合出来,透明处是组合一起就是透明。这个也是该接口最重要的地方。

来看看一个效果: 

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

像上图中部分字被染成红色了,但染的的方法并不是工整的一定字数,这种效果用Xfermode就可以轻易的做到了。请看下图:

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

假设src就是层下黑色的文字,而dst是上面红色的圆,那么按照DST_IN组合在一起之后就可以得到被染成红色部分的字了。 

关于这个可以看看我另一篇:深入理解Xfermode,使用时要注意以及顺便膜拜下saveLayer的强大

如果想详细了解Xfermode,可以看看此文章:Android Xfermode 实战 实现圆形、圆角图片

小帮手3、saveLayer()方法 

saveLayer是Canvas里的一个api,我们接触最多的是save这个接口,两者有什么区别呢? 

我们看看源码对两者的注释:

/**
     * Saves the current matrix and clip onto a private stack.
     * <p>
     * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
     * clipPath will all operate as usual, but when the balancing call to
     * restore() is made, those calls will be forgotten, and the settings that
     * existed before the save() will be reinstated.
     *
     * @return The value to pass to restoreToCount() to balance this save()
     */
    public int save() {
        return native_save(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
    }


    /**
     * This behaves the same as save(), but in addition it allocates and
     * redirects drawing to an offscreen bitmap.
     * <p class="note"><strong>Note:</strong> this method is very expensive,
     * incurring more than double rendering cost for contained content. Avoid
     * using this method, especially if the bounds provided are large, or if
     * the {@link #CLIP_TO_LAYER_SAVE_FLAG} is omitted from the
     * {@code saveFlags} parameter. It is recommended to use a
     * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View
     * to apply an xfermode, color filter, or alpha, as it will perform much
     * better than this method.
     * <p>
     * All drawing calls are directed to a newly allocated offscreen bitmap.
     * Only when the balancing call to restore() is made, is that offscreen
     * buffer drawn back to the current target of the Canvas (either the
     * screen, it's target Bitmap, or the previous layer).
     * <p>
     * Attributes of the Paint - {@link Paint#getAlpha() alpha},
     * {@link Paint#getXfermode() Xfermode}, and
     * {@link Paint#getColorFilter() ColorFilter} are applied when the
     * offscreen bitmap is drawn back when restore() is called.
     *
     * @param bounds May be null. The maximum size the offscreen bitmap
     *               needs to be (in local coordinates)
     * @param paint  This is copied, and is applied to the offscreen when
     *               restore() is called.
     * @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended
     *               for performance reasons.
     * @return       value to pass to restoreToCount() to balance this save()
     */
    public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint, @Saveflags int saveFlags) {
        if (bounds == null) {
            bounds = new RectF(getClipBounds());
        }
        return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags);
    }
           

save方法可以保存当前的matrix and clip,并且在restore把它恢复,一些平移,旋转,缩放等操作都会影响Canvas的matrix,所以save操作一般可以保存这些信息以及clip信息;

而saveLayer则强大很多,它相当于另外起一张干净图层,并在上面进行绘制操作,然后在restoreToCount的时候,把刚才所绘制的重新绘制在原本的Canvas上。当时正如所知的那样,它会绘制两次,所以消耗是十分巨大。

具体分析可以看看我另一篇文章:深入理解Xfermode,使用时要注意以及顺便膜拜下saveLayer的强大

小帮手4、clipXXXX,裁剪区域的api和drawPath 

在Canvas中以“clip”开头的api都是代表裁剪区域,在调用该方法后,canvas可绘制的区域就剩下clip出来的区域,例如:

@Override
        protected void onDraw(Canvas canvas) {
            canvas.clipRect(, , , );
            canvas.drawColor(Color.GREEN);
        }
           

上述代码裁剪了一个坐标为(100,100,300,300)的正方形,并且为该区域绘制绿色,结果如下: 

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

clip开头的还有clipPath和clipRegion,一个用来裁剪Path路径包围的区域,另一个则裁剪Region类定义区域,两者都身材绝技。但是要注意,clipPath方法有兼容性问题,应该说是Android的一个bug,在旧版(貌似是4.3以下)clipPath绘制的效果是有问题的,要关闭硬件加速才能正常。所以,如果条件允许下,我一般使用drawPath作为代替方案,只是drawPath可以实现的效果有限,自行取舍吧。

小帮手5、ColorFilter 

大神ColorFilter终于来了,它有多牛逼?给你看几个图就知道: 

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们
【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们
【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

第一张是原图,下面两张都是设置ColorFilter后画出来的效果。太不可思议了! 

ColorFilter有三个子类,分别是ColorMatrixColorFilter、LightingColorFilter和PorterDuffColorFilter,关于它们具体可以详细看看这篇文章:自定义控件其实很简单1/6,有时间一定要看看。

熟悉使用上面几个小帮手,已经可以足够绘制出十分惊艳的效果了。

好帮手三、数学,高等输血,函数,各种含树等 

虽然很多人都讨厌数学,但是数学真的很重要,数学很重要,数学很重要!事实上,很多动画插值器(Interpolator)就是用各种函数计算插值的。

先来看看一个简单的动画例子:

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

看看这魔性的滑动,看似混乱,但是也好有节奏感。可能一时间会觉得这个动画有点复杂,因为不知道要怎么实现。 

其实实现起来不难,简单分析就知道了。如果把每一条方块单独来看,你会发现其实每一条的运动轨迹是一模一样的,只是开始运动的时间有快有慢而已。所以,我们只要实现其中一条方块的运动,就足够了。 

但是它是按照什么规律运动呢?看着像是普通的匀速地上下滑动而已,是这样吗?我写一个出来就知道,下面是效果图:

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

两者实现上固然有一定差异,但是后者无论怎样实现,出来的效果都无法达到前者的平滑,因为后者是匀速滑动,而前者是用了sin函数计算滑动值。

如果仔细看,前者的效果本身就是一个sin函数图像。这就是数学的魅力,做出来的效果会有一种觉得屌屌的感觉。这只是sin函数,我再换换别的函数: 

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

运动轨迹很诡异吧?这个是我随意写一个函数做成的,虽然诡异,但是效果不错,可以尝试把各种函数带进去试试效果,相信会有很多非常酷炫的效果。

还有一个家伙不得不说,那就是“贝塞尔曲线”。它经常被用来做各种动画的效果,例如水流波动的效果,请看看这篇文章[Android自定义控件实战——水流波动效果的实现WaveView],看完你会爱上这个曲线。

看看效果图:

【Android进阶】如何写一个很屌的动画(2)---动画的好帮手们

下一节

下一节我将用一个实践的例子--高仿手机管家火箭的动画,具体介绍如何一步一步写一个动画出来。

【Android进阶】如何写一个很屌的动画(3)---高仿腾讯手机管家火箭动画

源码下载

http://download.csdn.net/detail/scnuxisan225/9389457