试分析-ShimmerFrameLayout

数据结构

public class ShimmerFrameLayout extends FrameLayout{
    // enum specity(特殊) the shape(形状) of the highlight mask applied to the contained view
    // 内容控件高光蒙版效果的枚举
    public enum MaskShape{
        LINEAR, // 线性效果
        RADIAL  // 辐射效果
    }
    // enum controlling the angle of the highlight mask animation
    // 控制高光蒙版动画的角度
    public enum MaskAngel{
        CW_0, // left to right      水平向右划过
        CW_90, // top to bottom     垂直向下落
        CW_180, // right to left    水平向左划过
        CW_270, // bottom to top    垂直向上冲
    }
    // struct storing(存储) various(各种) mask related(相关) parameters, which are used to construct the mask bitmap
    // 存放各种蒙版参数的类结构,这些参数是用于创建蒙版位图
    private static class Mask{
        public MaskAngle angle; // 动画角度
        public float tilt;      // 动画角度偏移
        public float dropoff;   // 掉落??
        public int fixedWidth;  // 固定宽度
        public int fixedHeight; // 固定高度
        public float intensity; // 强度
        public float relativeWidth; // 相对宽度
        public float relativeHeight; // 相对高度
        public MaskShape shape; // 动画形状
    }
    // struct for storing the mask translation animation values
    // 存放蒙版移动动画的起始点信息
    private staic class MaskTranslation{
        public int fromX;
        public int fromY;
        public int toX;
        public int toY;
    }
}

流程步骤

  1. 在构造方法中初始化参数
  2. 启动动画(自动启动或主动调用)
  3. 在动画中刷新参数,并调用invalidate(),触发逻辑
  4. 在dispatchDraw中,使用一个Bitmap结合子控件内容绘制

构造方法

    setWillNotDraw(false);
    mAlphaPaint = new Paint();
    mMaskPaint = new Paint();
    // 初始化
    useDefaults();

    if (attrs != null) {
        // 读取属性
    }

setWillNotDraw

动画

/**
   * Start the shimmer animation. If the 'auto start' property is set, this method is called automatically when the
   * layout is attached to the current window. Calling this method has no effect if the animation is already playing.
   * 如果设置为自动开启动画(auto start),这个方法当绑定到窗口之后就会自动调用
   */
  public void startShimmerAnimation() {
    if (mAnimationStarted) {
      return;
    }
    Animator animator = getShimmerAnimation();
    animator.start();
    mAnimationStarted = true;
  }

  /**
   * Stop the shimmer animation. Calling this method has no effect if the animation hasn't been started yet.
   */
  public void stopShimmerAnimation() {
    if (mAnimator != null) {
      mAnimator.end();
      mAnimator.removeAllUpdateListeners();
      mAnimator.cancel();
    }
    mAnimator = null;
    mAnimationStarted = false;
  }

  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (mGlobalLayoutListener == null) {
      mGlobalLayoutListener = getLayoutListener();
    }
    // 绑定到窗口后,等子视图都摆放好后,就会尝试启动动画
    getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
  }

  private ViewTreeObserver.OnGlobalLayoutListener getLayoutListener() {
    return new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        boolean animationStarted = mAnimationStarted;
        resetAll();
        if (mAutoStart || animationStarted) {
          startShimmerAnimation();
        }
      }
    };
  }

  @Override
  protected void onDetachedFromWindow() {
    stopShimmerAnimation();
    if (mGlobalLayoutListener != null) {
      getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
      mGlobalLayoutListener = null;
    }
    super.onDetachedFromWindow();
  }

动画和绘制

动画改变数据

  // Get the shimmer Animator object, which is responsible(负责的) for driving the highlight mask animation.
  private Animator getShimmerAnimation() {
    if (mAnimator != null) {
      return mAnimator;
    }
    int width = getWidth();
    int height = getHeight();
    switch (mMask.shape) {
      default:
      case LINEAR:
        switch (mMask.angle) {
          default: case CW_0: mMaskTranslation.set(-width, 0, width, 0); break;
          case CW_90: mMaskTranslation.set(0, -height, 0, height); break;
          case CW_180: mMaskTranslation.set(width, 0, -width, 0); break;
          case CW_270: mMaskTranslation.set(0, height, 0, -height); break;
        }
    }
    mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f + (float) mRepeatDelay / mDuration);
    mAnimator.setDuration(mDuration + mRepeatDelay);
    mAnimator.setRepeatCount(mRepeatCount);
    mAnimator.setRepeatMode(mRepeatMode);
    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        float value = Math.max(0.0f, Math.min(1.0f, (Float) animation.getAnimatedValue()));
        setMaskOffsetX((int) (mMaskTranslation.fromX * (1 - value) + mMaskTranslation.toX * value));
        setMaskOffsetY((int) (mMaskTranslation.fromY * (1 - value) + mMaskTranslation.toY * value));
      }
    });
    return mAnimator;
  }

  /**
   * Translate the mask offset horizontally. Used by the animator.
   *
   * @param maskOffsetX Horizontal translation offset of the mask
   */
  private void setMaskOffsetX(int maskOffsetX) {
    if (mMaskOffsetX == maskOffsetX) {
      return;
    }
    mMaskOffsetX = maskOffsetX;
    invalidate(); // 使其重绘
  }

  /**
   * Translate the mask offset vertically. Used by the animator.
   *
   * @param maskOffsetY Vertical translation offset of the mask
   */
  private void setMaskOffsetY(int maskOffsetY) {
    if (mMaskOffsetY == maskOffsetY) {
      return;
    }
    mMaskOffsetY = maskOffsetY;
    invalidate(); // 使其重绘
  }

在dispatchDraw处理绘制工作

@Override
  protected void dispatchDraw(Canvas canvas) {
    if (!mAnimationStarted || getWidth() <= 0 || getHeight() <= 0) {
      super.dispatchDraw(canvas);
      return;
    }
    dispatchDrawUsingBitmap(canvas);
  }

  /**
   * Draws and masks the children using a Bitmap.
   *
   * @param canvas Canvas that the masked children will end up being drawn to.
   */
  private boolean dispatchDrawUsingBitmap(Canvas canvas) {
    Bitmap unmaskBitmap = tryObtainRenderUnmaskBitmap();
    Bitmap maskBitmap = tryObtainRenderMaskBitmap();
    if (unmaskBitmap == null || maskBitmap == null) {
      return false;
    }
    // First draw a desaturated version
    drawUnmasked(new Canvas(unmaskBitmap));
    canvas.drawBitmap(unmaskBitmap, 0, 0, mAlphaPaint);

    // Then draw the masked version
    drawMasked(new Canvas(maskBitmap));
    canvas.drawBitmap(maskBitmap, 0, 0, null);

    return true;
  }

最主要的是对Bitmap的处理

/**
   * Draws and masks the children using a Bitmap.
   *
   * @param canvas Canvas that the masked children will end up being drawn to.
   */
  private boolean dispatchDrawUsingBitmap(Canvas canvas) {
    Bitmap unmaskBitmap = tryObtainRenderUnmaskBitmap();    // 获取一个Bitmap,
    Bitmap maskBitmap = tryObtainRenderMaskBitmap();        // 获取一个Bitmap,
    if (unmaskBitmap == null || maskBitmap == null) {
      return false;
    }
    // First draw a desaturated version
    drawUnmasked(new Canvas(unmaskBitmap));     // 将子控件绘制到临时的画布上
    canvas.drawBitmap(unmaskBitmap, 0, 0, mAlphaPaint);// 将子控件绘制到屏幕上

    // Then draw the masked version
    drawMasked(new Canvas(maskBitmap));
    canvas.drawBitmap(maskBitmap, 0, 0, null);

    return true;
  }
  // 试图获取一个Bitmap
  private Bitmap tryObtainRenderUnmaskBitmap() {
    if (mRenderUnmaskBitmap == null) {
      mRenderUnmaskBitmap = tryCreateRenderBitmap();
    }
    return mRenderUnmaskBitmap;
  }

  // 试图获取一个Bitmap
  private Bitmap tryObtainRenderMaskBitmap() {
    if (mRenderMaskBitmap == null) {
      mRenderMaskBitmap = tryCreateRenderBitmap();
    }
    return mRenderMaskBitmap;
  }

  // 其实就是创建一个和自己一样大的Bitmap
  private Bitmap tryCreateRenderBitmap() {
    int width = getWidth();
    int height = getHeight();
    try {
      return createBitmapAndGcIfNecessary(width, height);
    } catch (OutOfMemoryError e) {
      // 打印log
      String logMessage = "ShimmerFrameLayout failed to create working bitmap";
      StringBuilder logMessageStringBuilder = new StringBuilder(logMessage);
      logMessageStringBuilder.append(" (width = ");
      logMessageStringBuilder.append(width);
      logMessageStringBuilder.append(", height = ");
      logMessageStringBuilder.append(height);
      logMessageStringBuilder.append(")\n\n");
      for (StackTraceElement stackTraceElement :
          Thread.currentThread().getStackTrace()) {
        logMessageStringBuilder.append(stackTraceElement.toString());
        logMessageStringBuilder.append("\n");
      }
      logMessage = logMessageStringBuilder.toString();
      Log.d(TAG, logMessage);
    }
    return null;
  }

  // Draws the children without any mask.
  // 将子控件绘制到画布上
  private void drawUnmasked(Canvas renderCanvas) {
    renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
    super.dispatchDraw(renderCanvas);
  }

  // Draws the children and masks them on the given Canvas.
  private void drawMasked(Canvas renderCanvas) {
    Bitmap maskBitmap = getMaskBitmap();
    if (maskBitmap == null) {
      return;
    }

    renderCanvas.clipRect(
        mMaskOffsetX,
        mMaskOffsetY,
        mMaskOffsetX + maskBitmap.getWidth(),
        mMaskOffsetY + maskBitmap.getHeight());
    renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
    super.dispatchDraw(renderCanvas);

    renderCanvas.drawBitmap(maskBitmap, mMaskOffsetX, mMaskOffsetY, mMaskPaint);
  }

  // Return the mask bitmap, creating it if necessary.
  private Bitmap getMaskBitmap() {
    if (mMaskBitmap != null) {
      return mMaskBitmap;
    }

    int width = mMask.maskWidth(getWidth());
    int height = mMask.maskHeight(getHeight());

    mMaskBitmap = createBitmapAndGcIfNecessary(width, height);
    Canvas canvas = new Canvas(mMaskBitmap);
    Shader gradient;
    switch (mMask.shape) {
      default:
      case LINEAR: {
        int x1, y1;
        int x2, y2;
        switch (mMask.angle) {
          default:
          case CW_0:
            x1 = 0;
            y1 = 0;
            x2 = width;
            y2 = 0;
            break;
          case CW_90:
            x1 = 0;
            y1 = 0;
            x2 = 0;
            y2 = height;
            break;
          case CW_180:
            x1 = width;
            y1 = 0;
            x2 = 0;
            y2 = 0;
            break;
          case CW_270:
            x1 = 0;
            y1 = height;
            x2 = 0;
            y2 = 0;
            break;
        }
        gradient =
            new LinearGradient(
                x1, y1,
                x2, y2,
                mMask.getGradientColors(),
                mMask.getGradientPositions(),
                Shader.TileMode.REPEAT);
        break;
      }
      case RADIAL: {
        int x = width / 2;
        int y = height / 2;
        gradient =
            new RadialGradient(
                x,
                y,
                (float) (Math.max(width, height) / Math.sqrt(2)),
                mMask.getGradientColors(),
                mMask.getGradientPositions(),
                Shader.TileMode.REPEAT);
        break;
      }
    }
    canvas.rotate(mMask.tilt, width / 2, height / 2);
    Paint paint = new Paint();
    paint.setShader(gradient);
    // We need to increase the rect size to account for the tilt
    int padding = (int) (Math.sqrt(2) * Math.max(width, height)) / 2;
    canvas.drawRect(-padding, -padding, width + padding, height + padding, paint);

    return mMaskBitmap;
  }
打赏