登录 立即注册
安币:

LoadingDrawable之CircleJump系列的两个动画的实现原理

[复制链接]
来自: MrlLee 分类: Android精品源码 上传时间: 2016-8-24 15:52:27
Tag:
立即下载
收藏

项目介绍:

概述LoadingDrawable所实现的动画深受大家的青睐,但是它是如何实现的呢? 这篇博文将带领大家领悟我的内心世界。这篇博文主要讲述CollisionLoadingRenderer和SwapLoadingRenderer这两个渲染器的实现原理。首先预览一下这两个LoadingRenderer的效果图(左上方为CollisionLoadingRenderer,右上方为SwapLoadingRenderer),看到效果图不要太快的往下看,可以先思考一下实现方式。先思考再借鉴最后实践对于一个程序员的迅速提升是必不可少的。




CollisionLoadingRenderer的思路
  • CollisionLoadingRenderer的原理
    (1)首先需要调用Paint的setShader(Shader shader)方法, 通过LinearGradient设置渐变区域。 Note:渐变的距离是从第二个球到倒数第二个球之间的距离。
    (2)绘制第二个至倒数第二个之间的圆球和圆球下面的椭圆。
    (3)左右两边的球运动的曲线是y=ax^2. a > 0 所以第一个球的运动轨迹就是抛物线y=ax^2位于y轴左边的曲线, 最后一个球的运动轨迹就是抛物线y=ax^2位于y轴右边的曲线。 附二次函数
    (4)根据运动曲线绘制运动的球。
  • CollisionLoadingRenderer的实现细节
    LoadingRenderer的动画实现主要通过draw(Canvas canvas)(负责动画的绘制)和computeRender(float)(负责计算绘制需要的参数)。 此动画的主要分为三步
    设置渐变区域 --> 绘制渐变区域的图像和两边的球 --> 动起来。

(1)设置渐变区域
[Java] 查看源文件 复制代码
private void adjustParams() {    
    mBallCenterY = mHeight / 2.0f;    
    //mWidth是drawable的宽度
    //mBallRadius 是球的半径, 乘2表示直径
    //mBallCount - 2是因为渐变区域是从第二个到倒数第二个, 减去两边的两个
    mBallSideOffsets = (mWidth - mBallRadius * 2.0f * (mBallCount - 2)) / 2; 
}
private void setupPaint() {   
    mPaint.setStyle(Paint.Style.FILL);    
    mPaint.setShader(new LinearGradient(mBallSideOffsets, 0, mWidth - mBallSideOffsets, 0,
            mColors, mPositions, Shader.TileMode.CLAMP));
}
其中mBallSideOffsets是线性渐变在x方向上的开始位置, mWidth - mBallSideOffsets是线性渐变在x方向上的结束位置,在y方向上开始和结束值要一样, 因为我们是水平方向渐变。  
(2)绘制渐变区域的图像和两边的球
[Java] 查看源文件 复制代码
@Override
protected void draw(Canvas canvas) {    
    //保存图层
    int saveCount = canvas.save();    
    //绘制第二个到倒数第二个之间的球和球下面的椭圆
    for (int i = 1; i < mBallCount - 1; i++) {      
        //绘制球
        mPaint.setAlpha(MAX_ALPHA);  
        canvas.drawCircle(mBallRadius * (i * 2 - 1) + mBallSideOffsets, 
                          mBallCenterY, mBallRadius, mPaint); 
        //绘制椭圆
        mOvalRect.set(mBallRadius * (i * 2 - 2) + mBallSideOffsets, 
                      mHeight - mOvalVerticalRadius * 2, 
                      mBallRadius * (i * 2) + mBallSideOffsets,
                      mHeight); 
        mPaint.setAlpha(OVAL_ALPHA);
        canvas.drawOval(mOvalRect, mPaint); 
     }    
    //绘制第一个球
    mPaint.setAlpha(MAX_ALPHA);    
    canvas.drawCircle(mBallSideOffsets - mBallRadius - mLeftBallMoveXOffsets,            
                      mBallCenterY - mLeftBallMoveYOffsets, mBallRadius, mPaint);
    //绘制第一个椭圆
    mOvalRect.set(mBallSideOffsets - mBallRadius - mBallRadius * mLeftOvalShapeRate - mLeftBallMoveXOffsets,            
                  mHeight - mOvalVerticalRadius - mOvalVerticalRadius * mLeftOvalShapeRate,  
                  mBallSideOffsets - mBallRadius + mBallRadius * mLeftOvalShapeRate - mLeftBallMoveXOffsets,           
                  mHeight - mOvalVerticalRadius + mOvalVerticalRadius * mLeftOvalShapeRate);    
    mPaint.setAlpha(OVAL_ALPHA);  
    canvas.drawOval(mOvalRect, mPaint);    
    //绘制最后一个球    
    mPaint.setAlpha(MAX_ALPHA);    
    canvas.drawCircle(mBallRadius * (mBallCount * 2 - 3) + mBallSideOffsets + mRightBallMoveXOffsets,            
                      mBallCenterY - mRightBallMoveYOffsets, mBallRadius, mPaint);   
    //绘制最后一个椭圆    
    mOvalRect.set(mBallRadius * (mBallCount * 2 - 3) - mBallRadius * mRightOvalShapeRate + mBallSideOffsets + mRightBallMoveXOffsets,            
                  mHeight - mOvalVerticalRadius - mOvalVerticalRadius * mRightOvalShapeRate,            
                  mBallRadius * (mBallCount * 2 - 3) + mBallRadius * mRightOvalShapeRate + mBallSideOffsets + mRightBallMoveXOffsets,            
                  mHeight - mOvalVerticalRadius + mOvalVerticalRadius * mRightOvalShapeRate);    
    mPaint.setAlpha(OVAL_ALPHA);    
    canvas.drawOval(mOvalRect, mPaint);
    //恢复图层         
    canvas.restoreToCount(saveCount);
}
draw(Canvas canvas)函数的代码的难点主要是计算球心,与椭圆的归一化位置。
[1]首先给出渐变区域的球的球心和椭圆归一化位置的计算方式
第i个球的球心X坐标 = 第1个球的最左边坐标(线性渐变的开始位置mBallSideOffsets) + 第i 个球心距第1个球的最左边的偏移量。【公式Ball】。
第i个椭圆的left = 第i个球的球心坐标 - 球的半径(mBallRadius)【公式Oval_Left】。
第i个椭圆的right = 第i个球的球心坐标 + 球的半径(mBallRadius)【公式Oval_Right】。
第i个椭圆的bottom = Drawable的底部(mHeight)【公式Oval_Bottom】。
第i个椭圆的top = 第i个椭圆的bottom - 椭圆的高度(mOvalVerticalRadius  * 2)【公式Oval_Top】。
[2]然后给出第1个球的球心和椭圆归一化位置的计算方式
第1个球的球心X坐标 = 【公式Ball】i置0 - 当前第一个球的偏移量(mLeftBallMoveXOffsets)。
第1个椭圆的left = 第1个球的球心坐标 - 球的半径(mBallRadius) * 椭圆的缩小比例(mLeftOvalShapeRate)。
第1个椭圆的right = 第1个球的球心坐标 + 球的半径(mBallRadius)* 椭圆的缩小比例(mLeftOvalShapeRate)。
第1个椭圆的bottom = Drawable的底部(mHeight)+ 球的半径(mOvalVerticalRadius)* 椭圆的缩小比例(mLeftOvalShapeRate)。
第1个椭圆的top = 第1个椭圆的bottom - 椭圆垂直方向的半径(mOvalVerticalRadius) * 椭圆的缩小比例(mLeftOvalShapeRate)。
[3]最后给出最后1个球的球心和椭圆归一化位置的计算方式
  同[2]。
(3)动起来
[Java] 查看源文件 复制代码
@Override
protected void computeRender(float renderProgress) {    
    // 在进度的前25%将第一个球移动到最左边 
    if (renderProgress <= START_LEFT_DURATION_OFFSET) {
        float star