美文网首页
LoadingDrawable

LoadingDrawable

作者: 常强儿 | 来源:发表于2016-06-28 00:33 被阅读251次

    LoadingDrawable

    LoadingDrawable是github上挺火的一个项目, 通过自定义Drawable来实现各式各样的Loading动画. 现已经有多种有意思的动画效果, 可以直接用在自己项目中, 或者仿照他的做法实现自己的loading动画.

    LoadingDrawable使用

    凡是好用的东西一般都使用非常简单, 当然这个也不例外, 只要有一个ImageView, 在代码中创建一个需要的LoadingDrawable, 再将drawable设给ImageView就可以了.

    
    mIvMaterial= (ImageView) findViewById(R.id.material_view);
    
    //使用自己需要的LoadingRenderer
    
    mMaterialDrawable=newLoadingDrawable(newMaterialLoadingRenderer(this));
    
    mIvMaterial.setImageDrawable(mMaterialDrawable);
    
    

    LoadingDrawable分析

    概述

    LoadingDrawable通过自定义一个Drawable将不同的动画画出来, 其中的对于不同的动画对应不同的LoadingRender, 他们都是LoadingRender的子类, 分别重写了不同的计算和绘制的方法以实现不同的效果. 下面先看看他所涉及的类:

    LoadingDrawable包结构
    真的是很简捷清晰, 高亮是Drawable的子类, render包下是对不同动画的渲染器, 其中的LoadingRender是基类, 实现了基本的逻辑和定义绘制计算接口. 上面的几个包就是具体的动画实现.

    LoadingDrawable

    直接来看LoadingDrawable的实现:

    //LoadingDrawable继承自Drawable, 可以自定义不用交互的可见控件
    //实现Animatable接口, 他就成为一个动画, 可以在合适的时机显示或者停止
    public class LoadingDrawable extends Drawable implements Animatable {
      private LoadingRenderer mLoadingRender;
      //定义一个Callback, 并将其传递给Render, 负责更新当前视图, 将其传递给Render可以避免Render去持有过多的引用
      private final Callback mCallback = new Callback() {
        @Override
        public void invalidateDrawable(Drawable d) {
          invalidateSelf();
        }
        @Override
        public void scheduleDrawable(Drawable d, Runnable what, long when) {
          scheduleSelf(what, when);
        }
        @Override
        public void unscheduleDrawable(Drawable d, Runnable what) {
          unscheduleSelf(what);
        }
      };
    
      //构造方法, 将Render保存在Drawable中, 方便后面将所有的计算绘制任务交给他
      public LoadingDrawable(LoadingRenderer loadingRender) {
        this.mLoadingRender = loadingRender;
        this.mLoadingRender.setCallback(mCallback);
      }
    
      //直接将绘制的任务交给Render去做, 后面的好多方法也是类似的, 直接给Render去处理
      @Override
      public void draw(Canvas canvas) {
        mLoadingRender.draw(canvas, getBounds());
      }我是我
      /*...省略部分代码...*/
    }
    

    代码量不大, 主要是将任务交给Render处理, 其中使用Callback的思想要学习一下.

    LoadingRenderer

    最核心的部分, 连接LoadingDrawable与各个具体动画, 规范各种动画接口的类, 就是LoadingRenderer, 下面我们看看他都做了什么.

    public abstract class LoadingRenderer {
    
      public LoadingRenderer(Context context) {
        //设置大小
        setupDefaultParams(context);
        //设置动画更新相关
        setupAnimators();
      }
    
      //其中对不同动画的抽象都在这里定义, 在子类中实现这些方法以实现对应动画
      public abstract void draw(Canvas canvas, Rect bounds);
      public abstract void computeRender(float renderProgress);
      public abstract void setAlpha(int alpha);
      public abstract void setColorFilter(ColorFilter cf);
      public abstract void reset();
    
      //这里的start其实就是start渲染动画
      public void start() {
        reset();
        setDuration(mDuration);
        mRenderAnimator.start();
      }
    
      public void stop() {
        mRenderAnimator.cancel();
      }
    
      public boolean isRunning() {
        return mRenderAnimator.isRunning();
      }
    
      public void setCallback(Drawable.Callback callback) {
        this.mCallback = callback;
      }
    
      protected void invalidateSelf() {
        mCallback.invalidateDrawable(null);
      }
    
      private void setupDefaultParams(Context context) {
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        final float screenDensity = metrics.density;
    
        mWidth = DEFAULT_SIZE * screenDensity;
        mHeight = DEFAULT_SIZE * screenDensity;
        mStrokeWidth = DEFAULT_STROKE_WIDTH * screenDensity;
        mCenterRadius = DEFAULT_CENTER_RADIUS * screenDensity;
        mDuration = ANIMATION_DURATION;
      }
    
      private void setupAnimators() {
        mRenderAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        mRenderAnimator.setRepeatCount(Animation.INFINITE);
        mRenderAnimator.setRepeatMode(Animation.RESTART);
        //fuck you! the default interpolator is AccelerateDecelerateInterpolator
        mRenderAnimator.setInterpolator(new LinearInterpolator());
        mRenderAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
          @Override
          public void onAnimationUpdate(ValueAnimator animation) {
            //就是从这里把计算的任务与绘制的任务分开, 动画这部分只负责计算
            //真正的画出来是在draw里面
            computeRender((float) animation.getAnimatedValue());
            //通过CallBack通知Drable更新
            invalidateSelf();
          }
        });
      }
      
      /*....省略部分代码.....*/
    
      public void setDuration(long duration) {
        this.mDuration = duration;
        mRenderAnimator.setDuration(mDuration);
      }
    }
    

    LoadingRender对各种不同的动画进行了抽象, 将动画的计算和绘制拆分出来, 十分有利于后面不同动画的实现, 另外还对一些默认参数进行设置. 真正的计算和绘制都在下面的子类中, 我们分析一个MaterialLoadingRenderer.

    MaterialLoadingRenderer

    MaterialLoadingRenderer是右上方的效果, 三种颜色交替过渡出现, 圆先变大半圆再变小半圆, 然后整体还在转动,,初分析感觉好难, 下面细细看其代码, 不得不说以前自己写的动画都是什么玩意儿啊..下面看源码

    public void computeRender(float renderProgress) {
        updateRingColor(renderProgress);
    
        // Moving the start trim only occurs in the first 50% of a
        // single ring animation
        if (renderProgress <= START_TRIM_DURATION_OFFSET) {
            //除定前半程比例, 这里相当于把一个动画分成了两个动画, 前半段是只移动头, 后半段只移动尾巴
            //这里把原来一共的比例换算到前半段的时间上来
            float startTrimProgress = renderProgress / START_TRIM_DURATION_OFFSET;
            //要开始的角度. 原始角度加已经过了的角度
            //向前伸出去的那个头,加原始角度(在一次动画中他是不变的, 等于上一次动画结束的地方)
            //再加最大的多半圈乘扫过的比例
            mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress);
        }
    
        // Moving the end trim starts after 50% of a single ring
        // animation completes
        if (renderProgress > START_TRIM_DURATION_OFFSET) {
            //超过一半的比例/后半程比例, 同上
            float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET);
            //尾巴所在的角度
            mEndDegrees = mOriginEndDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress);
        }
    
        //要显示的角度
        if (Math.abs(mEndDegrees - mStartDegrees) > MIN_SWIPE_DEGREE) {
            mSwipeDegrees = mEndDegrees - mStartDegrees;
        }
    
        //整个过程中画布一一直慢慢的转动
        mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS));
        //不知道干啥的....
        mRotationIncrement = mOriginRotationIncrement + (MAX_ROTATION_INCREMENT * renderProgress);
    }
    

    过程中设了画笔的颜色, 颜色是动画前80%使用一个颜色, 后20%的时候使用

    return ((startA + (int) (fraction * (endA - startA))) << 24)
            | ((startR + (int) (fraction * (endR - startR))) << 16)
            | ((startG + (int) (fraction * (endG - startG))) << 8)
            | ((startB + (int) (fraction * (endB - startB))));
    

    计算两个颜色的过渡色, 就会产生过渡的颜色变化.
    后面的计算基本如注释描述. 直接看draw方法.

    public void draw(Canvas canvas, Rect bounds) {
        int saveCount = canvas.save();
        //对Canvas进行转动, 产生画的过程中首尾都在转动的效果
        canvas.rotate(mGroupRotation, bounds.exactCenterX(), bounds.exactCenterY());
    
        RectF arcBounds = mTempBounds;
        arcBounds.set(bounds);
        arcBounds.inset(mStrokeInset, mStrokeInset);
    
        mPaint.setColor(mCurrentColor);
        //绘制弧线, 就是Loading的主体
        canvas.drawArc(arcBounds, mStartDegrees, mSwipeDegrees, false, mPaint);
    
        canvas.restoreToCount(saveCount);
    }
    

    基本上是拿一到计算的数据进行绘制就可以了, 记得每次都要将canvas进行restore. 别的方法就是与动画相关的: 开始, 停止之类, 不再分析.
    学习一个简单的Loading图就是这样, 后面会再分析一个使用图片的Loading图, 就可以根据自己的需求进行自定义各种动画了.

    学习这个开源代码最大的收获就是感觉结果清晰, 每一个类, 每一个方法的责任都十分的明确, 十分值得我们学习.

    相关文章

      网友评论

          本文标题:LoadingDrawable

          本文链接:https://www.haomeiwen.com/subject/grlzdttx.html