美文网首页Android高级进阶Android开发Android开发经验谈
Android进阶:九、自定义View之手写Loading动效

Android进阶:九、自定义View之手写Loading动效

作者: 小小小小怪兽_666 | 来源:发表于2019-04-23 15:51 被阅读16次

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

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

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

    1.png
    1.0.gif
    大致思路
    我们自定义一个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文件代码如下:

        <declare-styleable name="LoadingView">
            <attr name="lineWidth" format="float" />
            <attr name="viewColor" format="color" />
        </declare-styleable>
    

    相关文章

      网友评论

        本文标题:Android进阶:九、自定义View之手写Loading动效

        本文链接:https://www.haomeiwen.com/subject/jcnvgqtx.html