安卓自定义控件-实现IOS版UC浏览器三点加载动画效果
2016-01-24 15:37
656 查看
1.实现分析
废话不多说,看下IOS版UC浏览器的加载效果
简单画个图看下整个过程
1.B圆的圆心移动的坐标为:A圆和B圆的圆心的距离L的中点为圆心O1的下半圆的运动轨迹经过的坐标,就有一个由B位置到A位置圆周运动的轨迹。
2.C圆的圆心移动的坐标为:B圆和C圆的圆心的距离L的中点为圆心02的上半圆的运动轨迹经过的坐标,就有一个由C位置到B位置圆周运动的轨迹。
3.A圆就特别一些,我分为两个过程:一个是起点P0为A圆心,控制点P1为(L/2,L/2),终点P2为B圆心的二阶贝塞尔曲线;一个是起点P0为B圆心,控制点P1为(L*3/2,-L/2),终点P2为C圆心的二阶贝塞尔曲线
4.A圆的透明度为255,B圆为255*0.8,C圆为255*0.6
4.1 A移动到C,透明度变化255->255*0.6
4.2 B移动到A,透明度变化255*0.8->255
4.3 C移动到B,透明度变化255*0.6->255*0.8
2.代码实现
2.1 需要的变量
[code] public class ThreePointLoadingView extends View { // 画笔 private Paint mBallPaint; // 宽度 private int mWidth; // 高度 private int mHeight; // 圆之间的距离 private float mSpace; // 圆的半径 private float mBallRadius; // 三个圆合起来的距离(包括间距) private float mTotalLength; // A圆心的x坐标 private float mABallX; // A圆心的y坐标 private float mABallY; // B圆心的x坐标 private float mBBallX; // B圆心的y坐标 private float mBBallY; // C圆心的x坐标 private float mCBallX; // C圆心的y坐标 private float mCBallY; // 圆心移动的距离 private float mMoveLength; // A圆心做二阶贝塞尔曲线的起点、控制点、终点 private PointF mABallP0; private PointF mABallP1; private PointF mABallP2; // A圆心贝塞尔曲线运动时的坐标 private float mABallazierX; private float mABallazierY; // 值动画 private ValueAnimator mAnimator; // 值动画产生的x方向的偏移量 private float mOffsetX = 0; // 根据mOffsetX算得的y方向的偏移量 private float mOffsetY; // A圆的起始透明度 private int mABallAlpha = 255; // B圆的起始透明度 private int mBBallAlpha = (int) (255 * 0.8); // C圆的起始透明度 private int mCBallAlpha = (int) (255 * 0.6);
2.2 构造时初始化画笔和A圆的三个点
[code] public ThreePointLoadingView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mBallPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mBallPaint.setColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_a200)); mBallPaint.setStyle(Paint.Style.FILL); mABallP0 = new PointF(); mABallP1 = new PointF(); mABallP2 = new PointF(); }
2.3 测量时初始化圆半径、间距等信息
[code] @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 考虑padding值 mWidth = measureSize(widthMeasureSpec, MeasureUtil.dip2px(getContext(), 180)) + getPaddingLeft() + getPaddingRight(); mHeight = measureSize(heightMeasureSpec, MeasureUtil.dip2px(getContext(), 180)) + getPaddingTop() + getPaddingBottom(); setMeasuredDimension(mWidth, mHeight); // 间距为宽度10分之一 mSpace = mWidth * 1.0f / 20; // 半径为宽度50分之一 mBallRadius = mWidth * 1.0f / 50; // 总的长度为三个圆直径加上之间的间距 mTotalLength = mBallRadius * 6 + mSpace * 2; // 两个圆圆心的距离 mMoveLength = mSpace + mBallRadius * 2; // A圆心起始坐标,同时贝塞尔曲线的起始坐标也是这个 mABallazierX = mABallX = (mWidth - mTotalLength) / 2 + mBallRadius; mABallazierY = mABallY = mHeight / 2; // A圆心起始点,控制点,终点 mABallP0.set(mABallX, mABallY); mABallP1.set(mABallX + mMoveLength / 2, mABallY - mMoveLength / 2); mABallP2.set(mBBallX, mBBallY); // B圆心的起始坐标 mBBallX = (mWidth - mTotalLength) / 2 + mBallRadius * 3 + mSpace; mBBallY = mHeight / 2; // C圆心的起始坐标 mCBallX = (mWidth - mTotalLength) / 2 + mBallRadius * 5 + mSpace * 2; mCBallY = mHeight / 2; }
2.4 绘制三个圆并且开启值动画
[code] @Override protected void onDraw(Canvas canvas) { // 根据x方向偏移量求出y方向偏移量 mOffsetY = (float) Math.sqrt(mMoveLength / 2 * mMoveLength / 2 - (mMoveLength / 2 - mOffsetX) * (mMoveLength / 2 - mOffsetX)); // 绘制B圆 mBallPaint.setAlpha(mBBallAlpha); canvas.drawCircle(mBBallX - mOffsetX, (float) (mBBallY + mOffsetY), mBallRadius, mBallPaint); // 绘制C圆 mBallPaint.setAlpha(mCBallAlpha); canvas.drawCircle(mCBallX - mOffsetX, (float) (mCBallY - mOffsetY), mBallRadius, mBallPaint); // 绘制A圆 mBallPaint.setAlpha(mABallAlpha); canvas.drawCircle(mABallazierX, mABallazierY, mBallRadius, mBallPaint); if (mAnimator == null) { // 启动值动画 startLoading(); } }
BC圆的移动依赖于:
mOffsetY = (float) Math.sqrt(mMoveLength / 2 * mMoveLength / 2 - (mMoveLength / 2 - mOffsetX) * (mMoveLength / 2 - mOffsetX))对应的计算,mMoveLength / 2为半径r,mOffsetX为offset,看草图即可理解,第三象限的情况其实跟第四象限一样的,因为(mMoveLength / 2 - mOffsetX)的平方总是为正
A圆的移动则是在值动画中算出坐标点(mABallazierX, mABallazierY),首先看下二阶贝塞尔曲线:
二阶贝塞尔曲线(抛物线):
原理:由 P0 至 P1 的连续点 Q0,描述一条线段。
由 P1 至 P2 的连续点 Q1,描述一条线段。
由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。
2.5 值动画的逻辑处理
[code] // 开启值动画 private void startLoading() { // 范围在0到圆心移动的距离,这个是以B圆到A圆位置为基准的 mAnimator = ValueAnimator.ofFloat(0, mMoveLength); // 设置监听 mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // B圆和C圆对应的X的偏移量 mOffsetX = (float) animation.getAnimatedValue(); float fraction = animation.getAnimatedFraction(); // B移动到A,透明度变化255*0.8->255 mBBallAlpha = (int) (255 * 0.8 + 255 * fraction * 0.2); // C移动到B,透明度变化255*0.6->255*0.8 mCBallAlpha = (int) (255 * 0.6 + 255 * fraction * 0.2); // A移动到C,透明度变化255->255*0.6 mABallAlpha = (int) (255 - 255 * fraction * 0.4); // A圆的分段二阶贝塞尔曲线的处理 if (fraction < 0.5) { // fraction小于0.5时,为A到B过程的情况 // 乘以2是因为贝塞尔公式的t范围在0到1 fraction *= 2; // 设置当前情况的起始点、控制点、终点 mABallP0.set(mABallX, mABallY); mABallP1.set(mABallX + mMoveLength / 2, mABallY - mMoveLength / 2); mABallP2.set(mBBallX, mBBallY); // 代入贝塞尔公式得到贝塞尔曲线过程的x,y坐标 mABallazierX = getBazierValue(fraction, mABallP0.x, mABallP1.x, mABallP2.x); mABallazierY = getBazierValue(fraction, mABallP0.y, mABallP1.y, mABallP2.y); } else { // fraction大于等于0.5时,为A到B过程之后,再从B到C过程的情况 // 减0.5是因为t要从0开始变化 fraction -= 0.5; // 乘以2是因为贝塞尔公式的t范围在0到1 fraction *= 2; // 设置当前情况的起始点、控制点、终点 mABallP0.set(mBBallX, mBBallY); mABallP1.set(mBBallX + mMoveLength / 2, mBBallY + mMoveLength / 2); mABallP2.set(mCBallX, mCBallY); // 代入贝塞尔公式得到贝塞尔曲线过程的x,y坐标 mABallazierX = getBazierValue(fraction, mABallP0.x, mABallP1.x, mABallP2.x); mABallazierY = getBazierValue(fraction, mABallP0.y, mABallP1.y, mABallP2.y); } // 强制刷新 postInvalidate(); } }); // 动画无限模式 mAnimator.setRepeatCount(ValueAnimator.INFINITE); // 时长1秒 mAnimator.setDuration(1000); // 延迟0.5秒执行 mAnimator.setStartDelay(500); // 开启动画 mAnimator.start(); } /** * 二阶贝塞尔公式:B(t)=(1-t)^2*P0+2*t*(1-t)*P1+t^2*P2,(t∈[0,1]) */ private float getBazierValue(float fraction, float p0, float p1, float p2) { return (1 - fraction) * (1 - fraction) * p0 + 2 * fraction * (1 - fraction) * p1 + fraction * fraction * p2; }
2.7 View销毁时的处理
[code] @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); // 销毁view时取消动画,避免内存泄露 mAnimator.cancel(); }
3.实现效果
Demo下载:防IOS-UC浏览器三点加载动画
相关文章推荐
- IOS 小技巧
- iOS开发过程中使用Core Data应避免的十个错误
- iOS开发之运行时编程(Runtime Programming)浅读
- IOS应用生命周期
- IOS高级控件(四)
- IOS高级控件(三)
- IOS高级控件(二)
- IOS高级控件(一)
- IOS基础控件(二)
- 我是如何入门IOS的
- IOS之基础控件(一)
- IOS绘制虚线
- IOS屏幕截图
- iOS 绘图
- ios保持界面流程的技巧
- IOS 百度糯米客户端登录BUG
- IOS开发 深入坐标系frame,bounds,center,transform的作用于之间的联系
- ios中图片不显示的问题
- ios关于retainCount的一些疑问
- nagios安装配置