自定义控件-绕着圆形轨迹旋转的小球

http://blog.csdn.net/xingxing_yan/article/details/54730068 转载

http://www.cnblogs.com/jiayongji/p/5560806.html

一. 概述

用过华为手机的人应该知道华为手机系统应用中的加载动画是一个小球绕着圆圈旋转,之前有一个项目用过类似的功能,所以写了一个自定义的控件,记录一下。希望能给需要这个功能的同志们提供一些思路。先上效果图: 
这里写图片描述

二. 思路

我们分析一下,一个小球绕着圆运动,首先需要一个小球,那么自定义控件绘制一个小球。然后需要一个圆环,最后设置小球在圆环上的一个起始位置,执行旋转动画即可。接下来就开始动手吧。

三. 实现

1. 绘制小球

绘制小球很简单,自定义一个控件,重写onDraw方法,绘制一个实心圆就好,这里就不多说了,直接上代码: 
BallView:

public class BallView extends View {
    private int mBallColor = Color.BLACK;
    private float mRadius = 10f;
    private PointF mCenterPoint;
    private Paint mPaint;

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

    public BallView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init(){
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mCenterPoint = new PointF(mRadius, mRadius);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = (int) (getPaddingLeft() + 2*mRadius + getPaddingRight());
        int height = (int) (getPaddingTop() + 2*mRadius + getPaddingBottom());
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mBallColor);
        canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mRadius, mPaint);
    }

    public void setBallColor(int mBallColor) {
        this.mBallColor = mBallColor;
    }

    public void setRadius(float radius) {
        this.mRadius = radius;
        mCenterPoint.set(radius, radius);
    }

    public float getRadius() {
        return mRadius;
    }
}

1

  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

2. 实现圆形轨迹

(1) 写一个控件AroundCircleBall继承RelativeLayout,然后在此控件中绘制一个圆环

//圆形轨迹的中心的(圆的半径+小球的半径)
mCircleCenterPoint = new PointF(mCircleRadius + mBallRadius, mCircleRadius + mBallRadius);
...
@Override
    protected void onDraw(Canvas canvas) {
        //绘制圆
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setColor(mCircleColor);
        canvas.drawCircle(mCircleCenterPoint.x, mCircleCenterPoint.y, mCircleRadius, mPaint);
        super.onDraw(canvas);
    }

因为小球的中心点在圆形轨迹上,所以整个AroundCircleBall的宽度和高度都是圆形轨迹的半径+小球的半径

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //测量控件宽高
        int width = (int) (getPaddingLeft() + mCircleRadius * 2 + mBallRadius * 2 + getPaddingRight());
        int height = (int) (getPaddingTop() + mCircleRadius * 2 + mBallRadius * 2 + getPaddingBottom());
        setMeasuredDimension(width, height);
    }

(2) 添加小球到AroundCircleBall中,计算初始位置,初始位置在圆的最底部。

private void init() {
       ...
        mBall = new BallView(getContext());
        mBall.setRadius(mBallRadius);
        mBall.setBallColor(mBallColor);
        //小球的初始位置在圆环的最底部
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        params.leftMargin = (int) (mCircleRadius);
        params.topMargin = (int) (mCircleRadius * 2);
        mBall.setLayoutParams(params);
        addView(mBall);
       ...
    }

到此,我们圆环和小球的初始化都做完了,就下来就是让小球绕着圆形轨迹旋转。 
(3) 旋转动画: 
小球需要绕着圆环旋转,我们首先需要计算旋转的中心。View中提供两个方法setPivotX()和 setPivotY() 方法来设置要旋转View的中心点。这里注意一下,pivotX和pivotY的坐标点是相对View自身坐标系的,如果使用屏幕坐标系可能会出现位置错乱。 
这里写图片描述

private void initRotateAnim(){
        mRotateAnim = ObjectAnimator.ofFloat(mBall, "rotation", 0f, 360f);
        //计算小球旋转的中心点(此点的左边是在小球自身的坐标系中)
        float pivotX = mBall.getRadius();
        float pivotY = mBall.getRadius() - mCircleRadius;
        mBall.setPivotX(pivotX);
        mBall.setPivotY(pivotY);
        mRotateAnim.setDuration(mDuration);
        mRotateAnim.setInterpolator(getInterpolator());
        mRotateAnim.setRepeatCount(-1);
        mRotateAnim.setStartDelay(500);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

提供四种设置动画的补间器:先加速在减速,匀速,加速,减速

private Interpolator getInterpolator(){
        Interpolator interpolator = null;
        switch (mInterpolator){
            case ACCELERATE_DECELERATE_INTERPOLATOR:    //先加上后减速
                interpolator = new AccelerateDecelerateInterpolator();
                break;
            case LINEAR_INTERPOLATOR:   //匀速
                interpolator = new LinearInterpolator();
                break;
            case ACCELERATE:    //加速
                interpolator = new AccelerateInterpolator();
                break;
            case DECELERATE:    //减速
                interpolator = new DecelerateInterpolator();
                break;
        }
        return interpolator;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

最后就是对动画的操作,提供启动,暂停,取消三个方法:

    /**
     * 启动旋转动画
     */
    public void startRotate(){
        if (mRotateAnim != null){
            mRotateAnim.start();
        }
    }

    /**
     * 暂停旋转动画
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public void pauseRotate(){
        if (mRotateAnim != null && mRotateAnim.isRunning()){
            mRotateAnim.pause();
        }
    }

    /**
     * 取消旋转动画
     */
    public void cancelRotate(){
        if (mRotateAnim != null){
            mRotateAnim.cancel();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

到此,整个自定义控件AroundCircleBall就写完了,接下来就用吧。 
(4) 使用 
布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.yyx.simple.MainActivity">

    <com.yyx.mylibrary.widget.AroundCircleBall
        android:id="@+id/main_acb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:layout_centerInParent="true"
        app:ball_color="@android:color/holo_green_light"
        app:ball_radius="5dp"
        app:circle_color="@android:color/holo_blue_light"
        app:circle_width="1dp"
        app:circle_radius="40dp"
        app:rotate_duration="5"
        app:rotate_interpolator="accelerate_decelerate"/>

</RelativeLayout>

在代码中启动动画:

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mAcb = (AroundCircleBall) findViewById(R.id.main_acb);
        mAcb.startRotate();
    }

真个过程并没有很难的地方,唯一需要注意的就是旋转中心点是要被旋转的View自身坐标系中的点,不是屏幕坐标系的。 
(5) 可以看到上边的布局文件中有一些属性是自定义的,方便我们在布局文件中设置一些参数,自定义属性如下:

<!--AroundCircleBall自定义属性-->
    <declare-styleable name="AroundCircleBall">
        <!--绘制圆形轨迹的线宽-->
        <attr name="circle_width" format="dimension"></attr>
        <!--绘制圆形轨迹的颜色-->
        <attr name="circle_color" format="color"></attr>
        <!--圆形轨迹的半径-->
        <attr name="circle_radius" format="dimension"></attr>
        <!--小球的颜色-->
        <attr name="ball_color" format="color"></attr>
        <!--小球的半径-->
        <attr name="ball_radius" format="dimension"></attr>
        <!--设置旋转一周需要的时间(单位为秒)-->
        <attr name="rotate_duration" format="integer"></attr>
        <!--设置旋转动画的补间器-->
        <attr name="rotate_interpolator" format="enum">
            <enum name="accelerate_decelerate" value="1"/>
            <enum name="linear" value="2"/>
            <enum name="accelerate" value="3"/>
            <enum name="decelerate" value="4"/>
        </attr>
    </declare-styleable>


http://blog.csdn.net/u010800708/article/details/49780085 另外一个例子
原文地址:https://www.cnblogs.com/wcLT/p/7689486.html