博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android进阶:九、自定义View之手写Loading动效
阅读量:7197 次
发布时间:2019-06-29

本文共 7431 字,大约阅读时间需要 24 分钟。

这是一个很简单的动画效果,使用属性动画即可实现,希望对读者学习动画能达到抛砖引玉的效果

一.自定义动画效果——Loading效果

如上是我们需要做的一个Loading动画。Loading效果是很常见的一种动画,最简单的实现让设计画个动态图即可,或者画个静态图然后使用帧动画也可以实现。但是今天我们用纯代码实现,不用任何图片资源。

大致思路
我们自定义一个View,继承View类,然后画两个不同半径的弧形,转动不同的角度即可实现。

绘制两个不同半径的弧形

首先初始化外圆和内园的Recf();

private RectF mOuterCircleRectF = new RectF();    private RectF mInnerCircleRectF = new RectF();复制代码

然后在onDraw方法绘制圆弧:

//获取View的中心        float centerX = getWidth() / 2;        float centerY = getHeight() / 2;        if (lineWidth > centerX) {            throw new IllegalArgumentException("lineWidth值太大了");        }        //外圆半径,因为我们的弧形是有宽度的,所以计算半径的时候应该把这部分减去,不然会有切割的效果        float outR = centerX - lineWidth;        //小圆半径        float inR = outR * 0.6f - lineWidth;        //设置弧形的距离上下左右的距离,也就是包围园的矩形。        mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR);        mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR);        //绘制外圆        canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint);        //绘制内圆        canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint);复制代码

代码很简单,就像注释一样:

  • 获取整个loadView的宽高,然后计算loadview的中心
  • 利用中心计算外圆和内园的半径,因为圆弧的弧边有宽度,所以应该减去这部分宽度,不然上下左右会有被切割的效果。
  • 在Recf中设置以圆半径为边长的矩形
  • 在画布中以矩形的数据绘制圆弧即可,这里设置了角度,使圆形有缺角,只要不是360度的圆都是有缺角的。

绘制圆的过程应该放在onDraw方法中,这样我们可以不断的重绘,也可以获取view的真实的宽高

当然,我们还需设置一个画笔来画我们的圆

mStrokePaint = new Paint();        mStrokePaint.setStyle(Paint.Style.STROKE);        mStrokePaint.setStrokeWidth(lineWidth);        mStrokePaint.setColor(color);        mStrokePaint.setAntiAlias(true);        mStrokePaint.setStrokeCap(Paint.Cap.ROUND);        mStrokePaint.setStrokeJoin(Paint.Join.ROUND);复制代码

二.设置属性动画

圆弧画好了,然后利用属性动画即可实现动画效果。这里采用的是ValueAnimator,值属性动画,我们可以设置一个值范围,然后让他在这个范围内变化。

mFloatValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);        mFloatValueAnimator.setRepeatCount(Animation.INFINITE);        mFloatValueAnimator.setDuration(ANIMATION_DURATION);        mFloatValueAnimator.setStartDelay(ANIMATION_START_DELAY);        mFloatValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());复制代码

这个设置很简单,设置值得范围,这是无线循环,设置动画执行的时间,这只动画循环时延迟的时间,设置插值器。

三.弧形动起来

让弧形动起来的原理,就是监听值属性动画的值变化,然后在这个变化的过程中不断的改变弧形的角度,然后让它重绘即可。

我们让我们的loadview实现ValueAnimator.AnimatorUpdateListener接口,然后在onAnimationUpdate监听动画的变化。我们初始化值属性动画的时候设置了值得范围为float型,所以这里可以获取这个变化的值。然后利用这个值可以改变绘制圆的角度大小,再调用重绘方法,即可实现:

@Override    public void onAnimationUpdate(ValueAnimator animation) {        mRotateAngle = 360 * (float)animation.getAnimatedValue();        invalidate();    }复制代码

整个思路大致就是这样。完整代码如下:

public class LoadingView extends View implements Animatable, ValueAnimator.AnimatorUpdateListener {    private static final long ANIMATION_START_DELAY = 200;    private static final long ANIMATION_DURATION = 1000;    private static final int OUTER_CIRCLE_ANGLE = 270;    private static final int INTER_CIRCLE_ANGLE = 90;    private ValueAnimator mFloatValueAnimator;    private Paint mStrokePaint;    private RectF mOuterCircleRectF;    private RectF mInnerCircleRectF;    private float mRotateAngle;    public LoadingView (Context context) {        this(context, null);    }    public LoadingView (Context context, @Nullable AttributeSet attrs) {        this(context, attrs, -1);    }    public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        this(context, attrs, defStyleAttr, -1);    }    public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        initView(context, attrs);    }    float lineWidth;    private void initView(Context context, AttributeSet attrs) {        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomLoadingView);        lineWidth = typedArray.getFloat(R.styleable.MyCustomLoadingView_lineWidth, 10.0f);        int color = typedArray.getColor(R.styleable.MyCustomLoadingView_viewColor, context.getColor(R.color.colorAccent));        typedArray.recycle();        initAnimators();        mOuterCircleRectF = new RectF();        mInnerCircleRectF = new RectF();        //初始化画笔        initPaint(lineWidth, color);        //旋转角度        mRotateAngle = 0;    }    private void initAnimators() {        mFloatValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);        mFloatValueAnimator.setRepeatCount(Animation.INFINITE);        mFloatValueAnimator.setDuration(ANIMATION_DURATION);        mFloatValueAnimator.setStartDelay(ANIMATION_START_DELAY);        mFloatValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());    }    /**     * 初始化画笔     */    private void initPaint(float lineWidth, int color) {        mStrokePaint = new Paint();        mStrokePaint.setStyle(Paint.Style.STROKE);        mStrokePaint.setStrokeWidth(lineWidth);        mStrokePaint.setColor(color);        mStrokePaint.setAntiAlias(true);        mStrokePaint.setStrokeCap(Paint.Cap.ROUND);        mStrokePaint.setStrokeJoin(Paint.Join.ROUND);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        float centerX = getWidth() / 2;        float centerY = getHeight() / 2;        //最大尺寸        if (lineWidth > centerX) {            throw new IllegalArgumentException("lineWidth值太大了");        }        float outR = centerX - lineWidth;        //小圆尺寸        float inR = outR * 0.6f;        mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR);        mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR);        //先保存画板的状态        canvas.save();        //外圆        canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint);        //内圆        canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint);        //恢复画板的状态        canvas.restore();    }    @Override    protected void onAttachedToWindow() {        super.onAttachedToWindow();        startLoading();    }    public void startLoading() {        start();    }    @Override    protected void onDetachedFromWindow() {        super.onDetachedFromWindow();        stopLoading();    }    public void stopLoading() {        stop();    }    @Override    public void onAnimationUpdate(ValueAnimator animation) {        mRotateAngle = 360 * (float)animation.getAnimatedValue();        invalidate();    }    protected void computeUpdateValue(float animatedValue) {        mRotateAngle = (int) (360 * animatedValue);    }    @Override    public void start() {        if (mFloatValueAnimator.isStarted()) {            return;        }        mFloatValueAnimator.addUpdateListener(this);        mFloatValueAnimator.setRepeatCount(Animation.INFINITE);        mFloatValueAnimator.setDuration(ANIMATION_DURATION);        mFloatValueAnimator.start();    }    @Override    public void stop() {        mFloatValueAnimator.removeAllUpdateListeners();        mFloatValueAnimator.removeAllListeners();        mFloatValueAnimator.setRepeatCount(0);        mFloatValueAnimator.setDuration(0);        mFloatValueAnimator.end();    }    @Override    public boolean isRunning() {        return mFloatValueAnimator.isRunning();    }}复制代码

attr文件代码如下:

复制代码

如果喜欢我的文章,想与一群资深开发者一起交流学习的话,欢迎加入我的合作群Android Senior Engineer技术交流群。有flutter—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,后面也有和本篇文章想对应的视频资料分享

群号:925019412

转载地址:http://kzakm.baihongyu.com/

你可能感兴趣的文章
JavaScript-事件周期-点击替换颜色
查看>>
c# 遍历文件夹及其所有文件
查看>>
电商2.0时代
查看>>
关于 Android 程序员最近的状况
查看>>
虚拟化之lxc
查看>>
高层治理、底层做事
查看>>
【歪谈】创业和守业期间老板的“嘴脸”为何会不同?
查看>>
【VMware虚拟化解决方案】配置和部署VMware ESXi5.5
查看>>
配置虚拟交换机vSwitch
查看>>
adb的那点小事——360电视助手实现研究
查看>>
源码浅谈(一):java中的 toString()方法
查看>>
Elastic Search Java Api
查看>>
60行代码爬取知乎神回复
查看>>
为什么typeof null 的结果为 object
查看>>
简单教学 apache 配置 Expire/Cache-Control 头
查看>>
我所知道的JavaScript中判断数据类型
查看>>
JS : Blob() 转换二进制下载文件流实例
查看>>
使用docker-compose实现一个简单应用(基于官方文档)
查看>>
Linux/Mac/Shell常用命令
查看>>
Java编程思想 数据库SQL语句的 优化总结
查看>>