Android自定义动画专题一

Android自定义动画

在目前的移动端产品中,不管是app还是网页一个好看酷炫的页面总是会第一时间吸引人的眼球,那么对于android开发人员来说,要想实现一个好看的页面必然需要掌握自定义控件以及自定义动画这门技术。

1.Android原生动画

Android下已经给我们提供了几种原生动画的表现形式:

①补间动画

平移:TranslateAnimation

旋转:RotateAnimation

缩放:ScaleAnimation

渐变:AlphaAnimation

②属性动画

ObjectAnimatior: translation(x或y),rotation(x或y),scale(x或y)
ValueAnimator:ObjectAnimatior的父类,值动画

③帧动画

AniamteDrawable
  • 注意补间动画和属性动画的最大区别:
    • 补间动画只是改变了View的显示效果,并没有真正改变View的属性
    • 属性动画是真正改变了View的属性,比如平移效果。
    • 属性动画是Android 3.0引入的

2.Android自定义动画-表现形式一

那么什么是自定义动画呢?其实不明觉厉,那就是自己根据需求定义的动画效果。因为在实际开发中,大部分的复杂酷炫的动画效果用我们Android原生提供的动画都是满足不了的,那么就需要我们自己去定义。

那么本篇文章将通过三个案例的形式给大家演示通过自定义动画的第一种表现方式-自定义控件绘制的方式

那么接下来通过一些小demo案例来进行演示如何实现一些原生动画无法实现的自定义效果。

①WIFI效果

首先看下效果图:

1)思路

根据上面的效果图我们可以发现android原生动画是无法实现的,所以我们需要自定义控件动态去绘制这样的效果,那么思路可以分为以下两步:

  1. 先绘制WIFI在满信号的情况下的视图效果,也就是静态视图
  2. 通过handler的postDelayed方法可以死循环不断执行view的invalidate()方法来达到动态绘制的效果(每次绘制需要控制绘制几个信号)

2)具体实现

那么因为我们需要去绘制这个扇形和弧线,所以首先需要去创建一个自定义的view重写它的onDraw()方法,在绘制之前可以在view创建的时候先将画笔初始化出来。

具体的难点在于第二步如何动态去绘制,可以定义一个具体的数值比如 shouldExistSignalSize 来控制每次绘制的时候绘制哪个信号,从最开始的时候只绘制第一个信号(也就是扇形),接着第二次绘制的时候需要绘制第一个和第二个信号,往后就是第一个信号,第二个信号,第三个信号;当四格信号都绘制完毕的时候记得将 shouldExistSignalSize 重置。

代码如下:

/**
 * Created by PeiHuan on 2017/6/24.
 * <p>
 * WIFI控件
 */
public class WIFIView extends View {

    public WIFIView(Context context) {
        this(context, null);
    }

    public WIFIView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WIFIView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private Paint paint;
    /**
     * 初始化准备
     */
    private void init() {
        paint = new Paint();
        //画笔颜色
        paint.setColor(Color.BLACK);
        //画笔粗细
        paint.setStrokeWidth(6);
        //抗锯齿
        paint.setAntiAlias(true);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                invalidate();
                handler.postDelayed(this,500);
            }
        },500);
    }
    private Handler handler = new Handler();

    /**WIFI控件宽高较小一边的长度*/
    private int wifiLength;
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        wifiLength = Math.min(w, h);
    }

    /**
     * 开始角度
     */
    private float startAngle = -135;
    /**
     * 扇形或者弧的旋转角度
     */
    private float sweepAngle = 90;
    /**
     * 信号大小,默认4格
     */
    private int signalSize = 4;

    /**每次应该绘制的信号个数*/
    private float shouldExistSignalSize = 0f;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        shouldExistSignalSize++;
        if(shouldExistSignalSize>4){
            shouldExistSignalSize=1;
        }
        canvas.save();
        //计算最小的扇形信号所在的圆的半径
        float signalRadius = wifiLength/2/signalSize;
        //向下平移画布,保证绘制的图形能够完全显示
        canvas.translate(0,signalRadius);
        for (int i = 0; i < signalSize; i++) {
            if(i>=signalSize-shouldExistSignalSize) {
                //定义每个信号所在圆的半径
                float radius = signalRadius * i;
                RectF rectf = new RectF(radius, radius, wifiLength - radius, wifiLength - radius);
                if (i < signalSize - 1) {
                    paint.setStyle(Paint.Style.STROKE);
                    canvas.drawArc(rectf, startAngle, sweepAngle, false, paint);
                } else {
                    paint.setStyle(Paint.Style.FILL);
                    canvas.drawArc(rectf, startAngle, sweepAngle, true, paint);
                }
            }
        }
        canvas.restore();
    }
}

详细代码请参考GitHub仓库:
https://github.com/zphuanlove/AnimationProject

②MUSIC效果

接下来再来看一个很常见的效果,同样也是通过自定义控件实现ondraw()去动态绘制;这也是很多目前能看到的一些音乐相关的app具有的效果,效果图如下:

1)思路

流程和思路和第一个demo类似,但产生动画的策略略有不同。

  1. 首先绘制静态效果图,剖析出整个图形由哪几部分组成:(两个圆,四段弧线)

  2. 通过不断绘制产生动画(不断更改四段弧线的起始角度)

WIFI demo我们使用的是handler的postDelayed方法造成一个死循环
这次我们可以通过在onDraw方法中调用invalidate来实现动画。

2)具体实现

具体实现同样也是在构造函数初始的时候去初始化画笔,然后在ondraw()方法中去绘制一个大圆一个小圆以及四段弧线,这里四段弧线可以分成两部分,及相对的两部分,每部分由一个大弧和一个小弧组成,两部分之间间隔180度。要绘制弧线就是要确认弧所在圆的外接矩形的左上右下,通过下图的计算可以很方便的计算出大弧所在的矩形的左上右下:

代码如下:

/**
 * Created by PeiHuan on 2017/6/24.
 * <p>
 * Music控件
 */
public class MusicView extends View {
    private Paint paint;
    private int length;

    public MusicView(Context context) {
        this(context, null);
    }

    public MusicView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MusicView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始化操作
     */
    private void init() {
        paint = new Paint();
        //画笔颜色
        paint.setColor(Color.BLACK);
        //画笔粗细
        paint.setStrokeWidth(2);
        //抗锯齿
        paint.setAntiAlias(true);
        //不填充
        paint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        length = Math.min(w, h);
        bigCircleRadius = length / 2;
        bigAngelRadius = length / 3;
        smallAngelRadius = length / 4;
    }

    /**
     * 大圆的半径
     */
    private float bigCircleRadius;
    /**
     * 小圆的半径
     */
    private float smallCircleRadius = 5f;
    /**
     * 两段大弧的半径
     */
    private float bigAngelRadius;
    /**
     * 两段小弧的半径
     */
    private float smallAngelRadius;

    private float startAngle1 = 0;
    private float startAngle2 = 180;
    private float sweepAngle = 60;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制两个圆
        canvas.drawCircle(bigCircleRadius, bigCircleRadius, bigCircleRadius - smallCircleRadius, paint);
        //小圆粗一些
        paint.setStrokeWidth(3);
        canvas.drawCircle(bigCircleRadius, bigCircleRadius, smallCircleRadius, paint);

        //绘制四段弧线
        //两段大弧,弧度相差180度
        RectF rectF = new RectF(bigCircleRadius-bigAngelRadius,bigCircleRadius-bigAngelRadius,bigCircleRadius+bigAngelRadius,bigCircleRadius+bigAngelRadius);
        canvas.drawArc(rectF,startAngle1,sweepAngle,false,paint);
        canvas.drawArc(rectF,startAngle2,sweepAngle,false,paint);
        //两段小弧,弧度相差180度
        RectF rectFSmaller = new RectF(bigCircleRadius-smallAngelRadius,bigCircleRadius-smallAngelRadius,bigCircleRadius+smallAngelRadius,bigCircleRadius+smallAngelRadius);
        canvas.drawArc(rectFSmaller,startAngle1,sweepAngle,false,paint);
        canvas.drawArc(rectFSmaller,startAngle2,sweepAngle,false,paint);

        startAngle1+=5;
        startAngle2+=5;
        if(!isDetached) {
            invalidate();
        }
    }

    /**
     * 自定义控件是否脱离窗体
     */
    private boolean isDetached;

    /**
     * 当自定义控件脱离窗体,即将销毁
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        isDetached = true;
    }
}

详细代码请参考GitHub仓库:
https://github.com/zphuanlove/AnimationProject

③文字水波效果

最后一个效果就要酷炫一些了,不过代码实现起来也很简单,因为这里主要是要用到android中的一个关键技术:着色器实现。

我们先来看看效果:

1)思路

自定义TextView,在TextView的文本上添加遮盖物,并让着色器不断平移和下降,当下降完全显示文字后再移动着色器到文字顶部继续重复执行。

2)着色器Shader

简单铺垫下shader的作用,如果想要深入学习的同学可以下去自行查阅资料,这里不是本篇的重点了。

我们在用Android中的Canvas绘制各种图形时,可以通过Paint.setShader(shader)方法为画笔Paint设置shader,这样就可以绘制出多彩的图形。那么Shader是什么呢?Shader就是着色器的意思。我们可以这样理解,Canvas中的各种drawXXX方法定义了图形的形状,画笔中的Shader则定义了图形的着色、外观,二者结合到一起就决定了最终Canvas绘制的被色彩填充的图形的样子。

类android.graphics.Shader有五个子类:

  • LinearGradient 线性梯度渐变
  • RadialGradient 环形梯度渐变或者灯光渲染
  • SweepGradient 扫描梯度渐变
  • BitmapShader 图片渲染
  • ComposeShader 组合渲染

这里重点介绍下BitmapShader,因为该案例需要使用到它。

BitmapShader,顾名思义,就是用Bitmap对绘制的图形进行渲染着色,其实就是用图片对图形进行贴图。如果亲自装修过或者看过装修的同学应该知道新房在喷漆的时候会将一些不需要喷漆的地方用报纸进行包住来遮挡,那么BitmapShader也是通过的道理,比如我们想要对文字Loading进行喷色,着色,那么只需要将文字其余的地方遮住,只取图片上的颜色来对文字进行着色即可。

从字面上理论的角度理解后,再从它的构造函数上进行理解:

/**
 * Call this to create a new shader that will draw with a bitmap.
 *
 * @param bitmap            The bitmap to use inside the shader
 * @param tileX             The tiling mode for x to draw the bitmap in.
 * @param tileY             The tiling mode for y to draw the bitmap in.
 */
public BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY) 

第一个参数是Bitmap对象,该Bitmap决定了用什么图片对绘制的图形进行贴图,着色。

第二个参数和第三个参数都是Shader.TileMode类型的枚举值,有以下三个取值:CLAMP 、REPEAT 和 MIRROR。

  • CLAMP表示,当所画图形的尺寸大于Bitmap的尺寸的时候,会用Bitmap四边的颜色填充剩余空间。
  • REPEAT表示,当我们绘制的图形尺寸大于Bitmap尺寸时,会用Bitmap重复平铺整个绘制的区域。
  • MIRROR 与REPEAT类似,当绘制的图形尺寸大于Bitmap尺寸时,MIRROR也会用Bitmap重复平铺整个绘图区域,与REPEAT不同的是,两个相邻的Bitmap互为镜像。
3)代码实现

首先创建一个自定义view继承TextView,并且初始化字体,初始化着色器,代码如下:

/**
 * Created by PeiHuan on 2017/6/25.
 *
 * 水面下降效果控件
 */
public class WaterTextView extends android.support.v7.widget.AppCompatTextView {

    public WaterTextView(Context context) {
         this(context,null);
    }

    public WaterTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public WaterTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //让当前的TextView的字体为美术字体
        Typeface typeface = Typeface.createFromAsset(getResources().getAssets(), "Satisfy-Regular.ttf");
        setTypeface(typeface);
        //Matrix:矩阵,可以实现视图的平移旋转等效果
        matrix = new Matrix();
        //创建一个着色器
        createShader();
    }

     private void createShader() {
            Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.wave);
            int waveWidth = originalBitmap.getWidth();
            waveHeight = originalBitmap.getHeight();
            Bitmap bitmap=Bitmap.createBitmap(waveWidth, waveHeight, originalBitmap.getConfig());
            //创建一个画布,为了将wave的图片颜色数据写入到Bitmap中
            Canvas canvas=new Canvas(bitmap);
            //设置画布的颜色从而控制文字的着色颜色
            canvas.drawColor(Color.RED);
            canvas.drawBitmap(originalBitmap,new Matrix(),getPaint());
            //CLAMP:使用原来的那张图片整体
            //REPEAT:将原来的图片复制无数份
            //MIRROR:镜像,将原来的图片镜像后,写入,再镜像...
            shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
            getPaint().setShader(shader);
            shaderX = 0;
            shaderY = -waveHeight/2;
        }

}

接着让波浪动起来,通过ondraw()方法调用invalidate(),控制着色器的Y轴平移,代码如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    repeatShader();
}

private void repeatShader() {
    shaderX +=5;
    shaderY +=0.1;
    if(shaderY >-waveHeight/2+height){
        shaderY = -waveHeight/2;
    }
    matrix.setTranslate(shaderX, shaderY);
    shader.setLocalMatrix(matrix);
    invalidate();
}

详细代码请参考GitHub仓库:
https://github.com/zphuanlove/AnimationProject

总结

该篇文章通过三个案例效果给大家演示了如何实现android下的自定义动画效果,不过这仅仅只是android自定义动画的第一种表现形式,接下来还会在下篇文章中继续讲解第二种形式的展现。

文中的案例在github上均有详细以及扩展代码展示,三个案例汇总到一个项目中。

Thanks!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值