小球滑动已八卦方式滑进滑出、可以在外圈滑动的BallSpinningView

今天又个下拉刷新的需求,UI需要我们自定义一个比较刷新动画。两个重叠小球先按太极分割线的方式到圆的两端,然后进行旋转,最后又已太极的形式收回来。
说着很简单,但是做起来还是有一点难度的,需要计算蛮多东西的,对于我这种数学不好得人很要命啊。
先说思路吧

  1. 先在控件上绘制重叠的两个圆
  2. 开启滑动之后,两个小圆按八卦的形式滑动出去,此时他们的轨迹是一个半圆形,直径是外层轨迹(小圆将在他的圆线上滑动)的半径,可以先确定两个半圆的圆心,根据公式计算出八卦分割线上每个点的的坐标
  3. 当两个小圆滑动到大圆上时,进行旋转
  4. 回收,思路与2一样
//根据圆心、半径求圆上每一个点的坐标
X坐标=a + Math.sin(2*Math.PI / 360*角度) * r ;
Y坐标=b + Math.cos(2*Math.PI / 360*角度) * r ;

上核心代码

  /**
     * 定义小球的状态
     * 0、初始化化阶段
     * 1、两个小球向沿八卦从圆心滑动到圆上
     * 2、两个小球进行旋转
     * 3、小球沿八卦从圆上滑动到圆心
     * 4、小球交换位置后沿八卦从圆心滑动到圆上
     * 5、两个小球进行旋转
     * 6、小球交换位置小球沿八卦从圆上滑动到圆心
     */
    private int mState = 0;
      //小圆的半径
    private int mRadius;
    //大圆的半径(轨迹)
    private int mMaxRadius = 150;
    //小圆在轨迹上快速滑动的速度
    private int mFast = 8;
    //小圆在轨迹上快速滑动的速度(角度)
    private int mSlow = 6;
    //小圆缩放时的速度(角度)
    private int mZoom = 10;
    //上半部分的圆上的点以及角度(角度)
    private int mTopPointX;
    //小圆滑动的距离(角度)
    private int mDistance = 360;

    private int mTopPointY;
    private int mTopAngle = 0; //默认为0
    //下半部分的圆上的点以及角度
    private int mBottomPointX;
    private int mBottomPointY;
    private int mBottomAngle = 180; //默认为180
    //小圆绕大圆滑动距离
    private int mRotateAngle = 0;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mState == 0) /*初始状态*/ {
            stateFor0(canvas);
        }
        if (mState == 1)/*小球沿八卦从圆心滑动到圆上*/ {
            stateFor1(canvas);
        }
        if (mState == 2)/*小球在圆上滑动*/ {
            stateFor2(canvas);
        }
        if (mState == 3)/*小球沿八卦从圆上滑动到圆心*/ {
            stateFor3(canvas);
        }
        if (mState == 4)/*两个小球交换位置后小球沿八卦从圆心滑动到圆上*/ {
            stateFor4(canvas);
        }
        if (mState == 5)/*小球沿八卦在圆上滑动*/ {
            stateFor5(canvas);
        }
        if (mState == 6)/*两个小球交换位置后小球沿八卦从圆上滑动到圆心*/ {
            stateFor6(canvas);
        }
    }

    private void stateFor0(Canvas canvas) {
        for (Paint paint : mPaints) {
            canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2, mRadius, paint);
            canvas.save();
        }
    }

    private void stateFor1(Canvas canvas) {
        for (int i = 0; i < mPaints.size(); i++) {
            if (i == 0) {
                mTopPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
                mTopPointY = (int) (mMeasuredHeight / 2 - mMaxRadius + Math.cos(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
                canvas.drawCircle(mTopPointX, mTopPointY, mRadius, mPaints.get(i));
            } else {
                mBottomPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
                mBottomPointY = (int) (mMeasuredHeight / 2 + mMaxRadius + Math.cos(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
                canvas.drawCircle(mBottomPointX, mBottomPointY, mRadius, mPaints.get(i));
            }
        }
        if (mBottomAngle == 0) {
            mState = 2;
            //重置数据,为进入mState=3状态做准备
            mBottomAngle = 0;
            mTopAngle = 180;
            invalidate();
            return;
        }
        mTopAngle -= mZoom;
        mBottomAngle -= mZoom;
        invalidate();
    }

    private void stateFor2(Canvas canvas) {
        for (int i = 0; i < mPaints.size(); i++) {
            if (i == 0) {
                canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2 - mMaxRadius * 2, mRadius, mPaints.get(i));
            } else {
                canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2 + mMaxRadius * 2, mRadius, mPaints.get(i));
            }
        }
        if (mRotateAngle % 360 <= 60)/*慢速旋转*/ {
            mRotateAngle += mSlow;
        } else if (mRotateAngle % 360 <= 300)/*快速旋转*/ {
            mRotateAngle += mFast;
        } else if (mRotateAngle % 360 < 360)/*慢速旋转*/ {
            mRotateAngle += mSlow;
            if (mRotateAngle > mDistance) { //必须判断,防止角度出问题
                mRotateAngle = mDistance;
            }
        }
        setRotation(mRotateAngle);
        if (mRotateAngle >= mDistance) {
            mRotateAngle = 0;
            mState = 3;
            invalidate();
            return;
        }
        invalidate();
    }

    private void stateFor3(Canvas canvas) {
        for (int i = 0; i < mPaints.size(); i++) {
            if (i == 0) {
                mTopPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
                mTopPointY = (int) (mMeasuredHeight / 2 - mMaxRadius + Math.cos(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
                canvas.drawCircle(mTopPointX, mTopPointY, mRadius, mPaints.get(i));
            } else {
                mBottomPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
                mBottomPointY = (int) (mMeasuredHeight / 2 + mMaxRadius + Math.cos(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
                canvas.drawCircle(mBottomPointX, mBottomPointY, mRadius, mPaints.get(i));
            }
        }
        if (mTopAngle == 0) {
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    mState = 4;
                    invalidate();
                }
            }, 80);
            //重置数据,为mState = 1状态做准备
            mBottomAngle = 180;
            mTopAngle = 0;
            invalidate();
            return;
        }
        mTopAngle -= mZoom;
        mBottomAngle -= mZoom;
        invalidate();
    }

    private void stateFor4(Canvas canvas) {
        for (int i = 0; i < mPaints.size(); i++) {
            if (i == 1) {
                mTopPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
                mTopPointY = (int) (mMeasuredHeight / 2 - mMaxRadius + Math.cos(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
                canvas.drawCircle(mTopPointX, mTopPointY, mRadius, mPaints.get(i));
            } else {
                mBottomPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
                mBottomPointY = (int) (mMeasuredHeight / 2 + mMaxRadius + Math.cos(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
                canvas.drawCircle(mBottomPointX, mBottomPointY, mRadius, mPaints.get(i));
            }
        }
        if (mBottomAngle == 0) {
            mState = 5;
            //重置数据,为进入mState=3状态做准备
            mBottomAngle = 0;
            mTopAngle = 180;
            invalidate();
            return;
        }
        mTopAngle -= mZoom;
        mBottomAngle -= mZoom;
        invalidate();
    }

    private void stateFor5(Canvas canvas) {
        for (int i = 0; i < mPaints.size(); i++) {
            if (i == 1) {
                canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2 - mMaxRadius * 2, mRadius, mPaints.get(i));
            } else {
                canvas.drawCircle(mMeasuredWidth / 2, mMeasuredHeight / 2 + mMaxRadius * 2, mRadius, mPaints.get(i));
            }
        }
        if (mRotateAngle % 360 <= 60)/*慢速旋转*/ {
            mRotateAngle += mSlow;
        } else if (mRotateAngle % 360 <= 300)/*快速旋转*/ {
            mRotateAngle += mFast;
        } else if (mRotateAngle % 360 < 360)/*慢速旋转*/ {
            mRotateAngle += mSlow;
            if (mRotateAngle > mDistance) { //必须判断,防止角度出问题
                mRotateAngle = mDistance;
            }
        }
        setRotation(mRotateAngle);
        if (mRotateAngle >= mDistance) {
            mRotateAngle = 0;
            mState = 6;
            invalidate();
            return;
        }
        invalidate();
    }

    private void stateFor6(Canvas canvas) {
        for (int i = 0; i < mPaints.size(); i++) {
            if (i == 1) {
                mTopPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
                mTopPointY = (int) (mMeasuredHeight / 2 - mMaxRadius + Math.cos(2 * Math.PI / 360 * mTopAngle) * mMaxRadius);
                canvas.drawCircle(mTopPointX, mTopPointY, mRadius, mPaints.get(i));
            } else {
                mBottomPointX = (int) (mMeasuredWidth / 2 + Math.sin(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
                mBottomPointY = (int) (mMeasuredHeight / 2 + mMaxRadius + Math.cos(2 * Math.PI / 360 * mBottomAngle) * mMaxRadius);
                canvas.drawCircle(mBottomPointX, mBottomPointY, mRadius, mPaints.get(i));
            }
        }
        if (mTopAngle == 0) {
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    mState = 1;
                    invalidate();
                }
            }, 80);
            //重置数据,为mState = 1状态做准备
            mBottomAngle = 180;
            mTopAngle = 0;
            invalidate();
            return;
        }
        mTopAngle -= mZoom;
        mBottomAngle -= mZoom;
        invalidate();
    }

    /**
     * 开始旋转(最后一个调用的方法,开始之后不能改变属性)
     */
    public void startSpinning() {
        mState = 1;
        invalidate();
    }

这样,调用startSpinning()就可以运行了。还可以提供一些设置方法。这个控件就完成了

写在结尾:我感觉这个控件写的有点复杂了,应该有更简单的方式,希望哪位看到的话可以帮我修改下,让我可以学到更好的解决方式

控件项目地址:https://git.oschina.net/qiangshen/commentview.git
这是我开始维护的一个控件地址,这里面的控件都是我自己学习时或工作时写的,可能控件还有各种bug,可能我的做法太复杂或代码规范有问题,我希望大家可以帮我提出并改正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要让小圆自动在折线上滑动,你需要实现以下几个步骤: 1. 确定小圆的起点和终点位置。 2. 计算折线上每个点之间的距离。 3. 根据小圆在折线上的移动速度,计算它每次移动的距离。 4. 在小圆到达终点之前,不断更新小圆的位置。 5. 在更新小圆的位置时,可以使用动画效果,使其在折线上平移动。 以下是一个简单的实现示例: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>自动滑动</title> <style> #line { position: relative; width: 400px; height: 200px; border: 1px solid black; } #ball { position: absolute; top: 0; left: 0; width: 20px; height: 20px; border-radius: 50%; background-color: red; transition: top 0.5s ease-in-out, left 0.5s ease-in-out; } </style> </head> <body> <div id="line"> <div class="point" style="top: 50px; left: 50px;"></div> <div class="point" style="top: 100px; left: 150px;"></div> <div class="point" style="top: 150px; left: 250px;"></div> </div> <script> var ball = document.getElementById('ball'); var line = document.getElementById('line'); var points = line.querySelectorAll('.point'); var speed = 50; // 移动速度,单位:像素/秒 var currentPointIndex = 0; // 当前所在的点的索引 var distanceMap = []; // 保存每个点之间的距离 var totalDistance = 0; // 折线的总长度 var startTime = null; // 动画开始时间 // 初始化距离映射表和折线总长度 for (var i = 1; i < points.length; i++) { var p1 = points[i - 1]; var p2 = points[i]; var distance = Math.sqrt(Math.pow(p2.offsetTop - p1.offsetTop, 2) + Math.pow(p2.offsetLeft - p1.offsetLeft, 2)); distanceMap.push(distance); totalDistance += distance; } function updateBallPosition(timestamp) { if (!startTime) startTime = timestamp; var elapsed = timestamp - startTime; var distance = speed * (elapsed / 1000); // 如果当前点到达了终点,则跳到下一个点 if (distance > distanceMap[currentPointIndex]) { distance -= distanceMap[currentPointIndex]; currentPointIndex++; if (currentPointIndex >= points.length - 1) { currentPointIndex = 0; } } // 计算小在当前点和下一个点之间的位置 var p1 = points[currentPointIndex]; var p2 = points[currentPointIndex + 1]; var ratio = distance / distanceMap[currentPointIndex]; var x = p1.offsetLeft + (p2.offsetLeft - p1.offsetLeft) * ratio; var y = p1.offsetTop + (p2.offsetTop - p1.offsetTop) * ratio; // 更新小位置 ball.style.left = x + 'px'; ball.style.top = y + 'px'; // 如果小还没有到达终点,则继续更新位置 if (currentPointIndex < points.length - 1 || distance < distanceMap[0]) { requestAnimationFrame(updateBallPosition); } else { // 动画结束后重置一些变量 currentPointIndex = 0; startTime = null; setTimeout(function() { requestAnimationFrame(updateBallPosition); }, 1000); // 等待1秒后重新开始动画 } } // 启动动画 requestAnimationFrame(updateBallPosition); </script> </body> </html> ``` 在这个示例中,我们根据折线上的点来确定小圆的起点和终点位置,并且计算每个点之间的距离。然后,我们根据小圆的移动速度来计算它每次移动的距离,并在每次移动时更新小圆的位置。在更新小圆的位置时,我们使用了CSS3的`transition`属性,让小圆在折线上平移动。最后,我们使用`requestAnimationFrame`来不断更新小圆的位置,直到它到达终点。当小圆到达终点时,我们可以让它暂停一段时间,然后重新开始动画。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值