Android-两个小球不停的绕中心旋转的进度条

转载请标明出处:

http://blog.csdn.net/hanhailong726188/article/details/47363911

本文出自:海龙的博客

一、概述

最近做了一个比较清新的进度条,没啥难度的,就是涉及到属性动画和canvas绘制圆形的知识,因为群里有一个问怎么实现的,这里就稍微写了一下原理,先看效果图



二、效果图

Gif录制的帧数有点低,导致稍微有点卡,但是在真实运行的时候一点都不卡



三、实现原理

  1. 自定义view
  2. 自定义属性动画
  3. canvas画圆

四、代码实现

因为代码非常简单,注释也非常详细,看一下代码就明白了,这里就直接贴代码了:

package com.hhl.twoballrotationprogress;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.ColorInt;
import android.support.annotation.Keep;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;

/**
 * 两个颜色的小球循环旋转,
 * 目前只支持代码设置颜色、最大半径、最小半径等属性
 * version 1.0
 * //TODO 2.0 添加xml自定义属性支持
 * Created by HanHailong on 15/8/07.
 */
public class TwoBallRotationProgressBar extends View {

    //默认小球最大半径
    private final static int DEFAULT_MAX_RADIUS = 15;
    //默认小球最小半径
    private final static int DEFAULT_MIN_RADIUS = 5;
    //默认两个小球运行轨迹直径距离
    private final static int DEFAULT_DISTANCE = 20;

    //默认第一个小球颜色
    private final static int DEFAULT_ONE_BALL_COLOR = Color.parseColor("#40df73");
    //默认第二个小球颜色
    private final static int DEFAULT_TWO_BALL_COLOR = Color.parseColor("#ffdf3e");

    //默认动画执行时间
    private final static int DEFAULT_ANIMATOR_DURATION = 1000;

    //画笔
    private Paint mPaint;

    //球的最大半径
    private float maxRadius = DEFAULT_MAX_RADIUS;
    //球的最小半径
    private float minRadius = DEFAULT_MIN_RADIUS;

    //两球旋转的范围距离
    private int distance = DEFAULT_DISTANCE;

    //动画的时间
    private long duration = DEFAULT_ANIMATOR_DURATION;

    private Ball mOneBall;
    private Ball mTwoBall;

    private float mCenterX;
    private float mCenterY;

    private AnimatorSet animatorSet;

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

    public TwoBallRotationProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TwoBallRotationProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {

        mOneBall = new Ball();
        mTwoBall = new Ball();

        mOneBall.setColor(DEFAULT_ONE_BALL_COLOR);
        mTwoBall.setColor(DEFAULT_TWO_BALL_COLOR);

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        configAnimator();

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCenterX = w / 2;
        mCenterY = h / 2;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //画两个小球,半径小的先画,半径大的后画
        if (mOneBall.getRadius() > mTwoBall.getRadius()) {
            mPaint.setColor(mTwoBall.getColor());
            canvas.drawCircle(mTwoBall.getCenterX(), mCenterY, mTwoBall.getRadius(), mPaint);

            mPaint.setColor(mOneBall.getColor());
            canvas.drawCircle(mOneBall.getCenterX(), mCenterY, mOneBall.getRadius(), mPaint);
        } else {
            mPaint.setColor(mOneBall.getColor());
            canvas.drawCircle(mOneBall.getCenterX(), mCenterY, mOneBall.getRadius(), mPaint);

            mPaint.setColor(mTwoBall.getColor());
            canvas.drawCircle(mTwoBall.getCenterX(), mCenterY, mTwoBall.getRadius(), mPaint);
        }
    }

    /**
     * 配置属性动画
     */
    private void configAnimator() {

        //中间半径大小
        float centerRadius = (maxRadius + minRadius) * 0.5f;

        //第一个小球缩放动画,通过改变小球的半径
        //半径变化规律:中间大小->最大->中间大小->最小->中间大小
        ObjectAnimator oneScaleAnimator = ObjectAnimator.ofFloat(mOneBall, "radius",
                centerRadius, maxRadius, centerRadius, minRadius, centerRadius);
        //无限循环
        oneScaleAnimator.setRepeatCount(ValueAnimator.INFINITE);

        //第一个小球位移动画,通过改变小球的圆心
        ValueAnimator oneCenterAnimator = ValueAnimator.ofFloat(-1, 0, 1, 0, -1);
        oneCenterAnimator.setRepeatCount(ValueAnimator.INFINITE);
        oneCenterAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (Float) animation.getAnimatedValue();
                float x = mCenterX + (distance) * value;
                mOneBall.setCenterX(x);
                //不停的刷新view,让view不停的重绘
                invalidate();
            }
        });

        //第二个小球缩放动画
        //变化规律:中间大小->最小->中间大小->最大->中间大小
        ObjectAnimator twoScaleAnimator = ObjectAnimator.ofFloat(mTwoBall, "radius", centerRadius, minRadius,
                centerRadius, maxRadius, centerRadius);
        twoScaleAnimator.setRepeatCount(ValueAnimator.INFINITE);

        //第二个小球位移动画
        ValueAnimator twoCenterAnimator = ValueAnimator.ofFloat(1, 0, -1, 0, 1);
        twoCenterAnimator.setRepeatCount(ValueAnimator.INFINITE);
        twoCenterAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (Float) animation.getAnimatedValue();
                float x = mCenterX + (distance) * value;
                mTwoBall.setCenterX(x);
            }
        });

        //属性动画集合
        animatorSet = new AnimatorSet();
        //四个属性动画一块执行
        animatorSet.playTogether(oneScaleAnimator, oneCenterAnimator, twoScaleAnimator, twoCenterAnimator);
        //动画一次运行时间
        animatorSet.setDuration(DEFAULT_ANIMATOR_DURATION);
        //时间插值器,这里表示动画开始最快,结尾最慢
        animatorSet.setInterpolator(new DecelerateInterpolator());
    }

    /**
     * 小球
     */
    public class Ball {
        @Keep
        private float radius;//半径
        private float centerX;//圆心
        private int color;//颜色

        @Keep
        public float getRadius() {
            return radius;
        }

        @Keep
        public void setRadius(float radius) {
            this.radius = radius;
        }

        public float getCenterX() {
            return centerX;
        }

        public void setCenterX(float centerX) {
            this.centerX = centerX;
        }

        public int getColor() {
            return color;
        }

        public void setColor(int color) {
            this.color = color;
        }
    }

    @Override
    public void setVisibility(int v) {
        if (getVisibility() != v) {
            super.setVisibility(v);
            if (v == GONE || v == INVISIBLE) {
                stopAnimator();
            } else {
                startAnimator();
            }
        }
    }

    @Override
    protected void onVisibilityChanged(View changedView, int v) {
        super.onVisibilityChanged(changedView, v);
        if (v == GONE || v == INVISIBLE) {
            stopAnimator();
        } else {
            startAnimator();
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        startAnimator();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopAnimator();
    }

    /**
     * 设置第一个球的颜色
     *
     * @param color
     */
    public void setOneBallColor(@ColorInt int color) {
        mOneBall.setColor(color);
    }

    /**
     * 设置第二个球的颜色
     *
     * @param color
     */
    public void setmTwoBallColor(@ColorInt int color) {
        mTwoBall.setColor(color);
    }

    /**
     * 设置球的最大半径
     *
     * @param maxRadius
     */
    public void setMaxRadius(float maxRadius) {
        this.maxRadius = maxRadius;
        configAnimator();
    }

    /**
     * 设置球的最小半径
     *
     * @param minRadius
     */
    public void setMinRadius(float minRadius) {
        this.minRadius = minRadius;
        configAnimator();
    }

    /**
     * 设置两个球旋转的最大范围距离
     *
     * @param distance
     */
    public void setDistance(int distance) {
        this.distance = distance;
    }

    public void setDuration(long duration) {
        this.duration = duration;
        if (animatorSet != null) {
            animatorSet.setDuration(duration);
        }
    }

    /**
     * 开始动画
     */
    public void startAnimator() {
        if (getVisibility() != VISIBLE) return;

        if (animatorSet.isRunning()) return;

        if (animatorSet != null) {
            animatorSet.start();
        }
    }

    /**
     * 结束停止动画
     */
    public void stopAnimator() {
        if (animatorSet != null) {
            animatorSet.end();
        }
    }
}

这里需要注意一个地方,因为我们用到了属性动画,在这里:

//第一个小球缩放动画,通过改变小球的半径
        //半径变化规律:中间大小->最大->中间大小->最小->中间大小
        ObjectAnimator oneScaleAnimator = ObjectAnimator.ofFloat(mOneBall, "radius",
                centerRadius, maxRadius, centerRadius, minRadius, centerRadius);

其中radius是我们定义的Ball对象的半径属性,这个在混淆的时候会有问题,我们在这里把有关radius都@Keep掉,不让proguard混淆

 /**
     * 小球
     */
    public class Ball {
        //防止混淆
        @Keep
        private float radius;//半径
        private float centerX;//圆心
        private int color;//颜色

        //防止混淆
        @Keep
        public float getRadius() {
            return radius;
        }

        //防止混淆
        @Keep
        public void setRadius(float radius) {
            this.radius = radius;
        }

        public float getCenterX() {
            return centerX;
        }

        public void setCenterX(float centerX) {
            this.centerX = centerX;
        }

        public int getColor() {
            return color;
        }

        public void setColor(int color) {
            this.color = color;
        }
    }

但是目前Gradle没有启动这个插件,所以需要我们手动开启@Keep

五、开始@Keep防止混淆注解

在app/proguard-rules.pro里面添加


#手动启用support keep注解
#http://tools.android.com/tech-docs/support-annotations
-keep,allowobfuscation @interface android.support.annotation.Keep

-keep @android.support.annotation.Keep class *

-keepclassmembers class * {

    @android.support.annotation.Keep *;
}

这样就实现了一个清新的两球绕中心滚动的进度条了!



最后附上源码下载地址:github-TwoBallRotationProgressBar

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-11 02:11:55

Android-两个小球不停的绕中心旋转的进度条的相关文章

IOS版本-两个小球不停的绕中心旋转的进度条

转载请标明出处: http://blog.csdn.net/hanhailong726188/article/details/47375157 本文出自:海龙的博客 一.概述 昨天实现了一个Android版本的小清新动画,可以当成进度条来用,这里补上IOS版本的 二.效果图 三.实现原理 自定义UIView,然后添加两个CALayer,通过CAShapeLayer和UIBezierPath画出两个小圆球,动起来是通过CAAnimation动画,动画包括小球的位移动画.缩放动画.透明动画组合起来的

Android自定义控件实现一个带文本与数字的圆形进度条

实现的效果图如下所示: 第一步:绘制下方有缺口的空心圆,称为外围大弧吧 anvas.clipRect(0, 0, mWidth, mHeight / 2 + radius - textHeight * 3 / 4); 第二步:计算绘制圆弧进度条时的起始角度,设置为外围大弧的左端点为进度值得起点,扫过的角度所占外围大弧的百分比就是进度值 第三步:绘制数字.文字.百分号 第四步:使用Handler Runnable 和DecelerateInterpolator是进度条和数字动起来 测试代码: fi

Qt QGraphicsItem 绕中心旋转、放缩

最近用到了QGraphicsItem,可以通过QGraphicsItemAnimation使其产生动画效果. QGraphicsItemAnimation自带了setPosAt().setRotationAt().setScaleAt()等方法可以用来移动.旋转.放缩QGraphicsItem,但其默认的OriginPoint是这个Item的左上角,虽然QGraphicsItem自带了setTransformOriginPoint()方法,但是设置以后没有效果,还是绕左上角放缩旋转,只好采取其他

实现Canvas2D绘图 使元素绕中心居中旋转

我之前用canvas写了个头像剪切的demo,但是关于让载入的图片旋转是个问题,虽然通过其它方法实现了,但是感觉并不太好,于是查了些资料,想试着重新做一下canvas的旋转. 在开始之前,先让我们来做一些准备工作: 1 (function () { 2 // 设置画布的宽高 3 var width = 300, 4 heigh = 100, 5 cache = {}; // 存储canvas上下文 6 7 // 获取绘图上下文 8 function getCtx(name, w, h) { 9

【转】Android UI系列-----时间、日期、Toasts和进度条Dialog

原文网址:http://www.cnblogs.com/xiaoluo501395377/p/3421727.html 您可以通过点击 右下角 的按钮 来对文章内容作出评价, 也可以通过左下方的 关注按钮 来关注我的博客的最新动态. 如果文章内容对您有帮助, 不要忘记点击右下角的 推荐按钮 来支持一下哦 如果您对文章内容有任何疑问, 可以通过评论或发邮件的方式联系我: [email protected] / [email protected] 如果需要转载,请注明出处,谢谢!! 本篇随笔将继续学

[Android]组件-进度条1

多式样ProgressBar 普通圆形ProgressBar 该类型进度条也就是一个表示运转的过程,例如发送短信,连接网络等等,表示一个过程正在执行中. 一般只要在XML布局中定义就可以了. <progressBar android:id="@+id/widget43" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layou

Android基础入门教程——2.3.7 ProgressBar(进度条)

Android基础入门教程--2.3.7 ProgressBar(进度条) 标签(空格分隔): Android基础入门教程 本节引言: 本节给大家带来的是Android基本UI控件中的ProgressBar(进度条),ProgressBar的应用场景很多,比如 用户登录时,后台在发请求,以及等待服务器返回信息,这个时候会用到进度条:或者当在进行一些比较 耗时的操作,需要等待一段较长的时间,这个时候如果没有提示,用户可能会以为程序Carsh或者手机死机 了,这样会大大降低用户体验,所以在需要进行耗

Android零基础入门第51节:进度条ProgressBar

不知不觉这已经是第51期了,在前面50期我们学了Android开发中使用频率非常高的一些UI组件,当然这些组件还不足够完成所有APP的开发,还会经常用到一些诸如进度条.拖动条.搜索框.时间和日期选择器等组件,那么后面几期就来一起学习这些高级组件. 一.ProgressBar系列组件 ProgressBar也是一组重要的组件,ProgressBar本身代表了进度条组件,它还派生了两个常用的组件:SeekBar和RatingBar.ProgressBar及其子类在用法上十分相似,只是显示界面有一定的

Android学习笔记(24):进度条组件ProgressBar及其子类

ProgressBar作为进度条组件使用,它还派生了SeekBar(拖动条)和RatingBar(星级评分条). ProgressBar支持的XML属性: Attribute Name Related Method Description style 设置ProgressBar指定风格 android:indeterminate 设置为true时,进度条不显示运行进度 android:indeterminateBehavior indeterminate模式下.当进度条达到最大值时的动画处理行为