MaterialContainerTransform-拆解

动效是一个非常定制化的东西,如果完全使用开源免不了和产品打一架。再被产品鄙视之前现在来继续分析一下MaterialContainerTransform代码是怎么实现专场动效实现的。

MaterialContainerTransform继承自Transition,总共实现了父类的五个方法,现在将逐一拆解这些方法具体负责什么事情。

captureStartValues(TransitionValues)

该方法会构建动画开始时的画面属性,再之后实现动画的时候传递下去实现动画。

在MaterialContainerTransform中会直接将方法转接到captureValues中下面来看一下实现。

/**
* @param transitionValues 通过这个对象返回返回值
* @param viewOverride 需要被选中作为起始帧的view
* @param viewIdOverride 需要被选中作为起始帧的view id
* @param shapeAppearanceModelOverride 已经被显示设置的背景 通过set方法指定
*/
private static void captureValues(
      @NonNull TransitionValues transitionValues,
      @Nullable View viewOverride,
      @IdRes int viewIdOverride,
      @Nullable ShapeAppearanceModel shapeAppearanceModelOverride) {
    // 优先通过id确定需要使用的view
    if (viewIdOverride != View.NO_ID) {
      transitionValues.view = findDescendantOrAncestorById(transitionValues.view, viewIdOverride);
    // id没有设置则检查是否直接设置了view
    } else if (viewOverride != null) {
      transitionValues.view = viewOverride;
    // 也支持通过tag来覆盖传入的view
    } else if (transitionValues.view.getTag(R.id.mtrl_motion_snapshot_view) instanceof View) {
      View snapshotView = (View) transitionValues.view.getTag(R.id.mtrl_motion_snapshot_view);
      // Clear snapshot so that we don't accidentally use it for another transform transition.
      transitionValues.view.setTag(R.id.mtrl_motion_snapshot_view, null);
      // Use snapshot if entering and capturing start values or returning and capturing end values.
      transitionValues.view = snapshotView;
    }
    View view = transitionValues.view;
    // ViewCompat.isLaidOut如果view已经执行过一次onLayout返回true
    if (ViewCompat.isLaidOut(view) || view.getWidth() != 0 || view.getHeight() != 0) {
      // Capture location in screen co-ordinates 计算选择的view大小
      RectF bounds = view.getParent() == null ? getRelativeBounds(view) : getLocationOnScreen(view);
      transitionValues.values.put(PROP_BOUNDS, bounds);
      transitionValues.values.put(
         PROP_SHAPE_APPEARANCE,
         // 关键方法指定选择的view的背景 可以实现边缘和圆角
         // 可以通过set方法指定 view创建tag指定 资源id指定 view继承Shapeable指定 如果都没有指定会使用默认样式
         captureShapeAppearance(view, bounds, shapeAppearanceModelOverride));
    }
  }

通过以上分析我们可以得知captureStartValues完成了 view bounds 背景的指定这些数据都会被动画执行的时候用到。

captureEndValues(TransitionValues)

该方法会构建动画结束是的画面属性。此方法实现与上面的实现没有区别,都是继续执行了captureValues,只是传入的参数为结束画面的id 对象等数据。

createAnimator(ViewGroup sceneRoot,TransitionValues startValues,TransitionValues endValues)

从名字就可以明显看出我们这个方法的重要程度,这个方法就是我们这次整个类最重要的方法了。

// 属性检查代码这里省略 
// 如果startValues和endValues中的view,bounds,ShapeAppearanceModel三者中有为空就不再开始动画直接返回

// 计算边距
RectF drawingViewBounds = getLocationOnScreen(drawingView);
float offsetX = -drawingViewBounds.left;
float offsetY = -drawingViewBounds.top;
RectF drawableBounds = calculateDrawableBounds(drawingView, boundingView, offsetX, offsetY);
startBounds.offset(offsetX, offsetY);
endBounds.offset(offsetX, offsetY);

// 创建最关键的 TransitionDrawable
final TransitionDrawable transitionDrawable =
    new TransitionDrawable(
    getPathMotion(),
    startView,
    startBounds,
    startShapeAppearanceModel,
    getElevationOrDefault(startElevation, startView),
    endView,
    endBounds,
    endShapeAppearanceModel,
    getElevationOrDefault(endElevation, endView),
    containerColor,
    startContainerColor,
    endContainerColor,
    scrimColor,
    entering,
    elevationShadowEnabled,
    FadeModeEvaluators.get(fadeMode, entering),
    FitModeEvaluators.get(fitMode, entering, startBounds, endBounds),
    buildThresholdsGroup(entering),
    drawDebugEnabled);
// 创建属性动画 并将动画进度传递给transitionDrawable进行具体绘制
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.addUpdateListener(
    new AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            transitionDrawable.setProgress(animation.getAnimatedFraction());
        }
    });
// 将属性动画返回
return animator;

setPathMotion(PathMotion pathMotion)

创建移动的路线,这里没有做过多的定制。仅仅是简单标记了一下并不重要

getTransitionProperties()

通过这个方法可以自定义属性,在captureStart/EndValues的时候组织好需要传递的参数。通过transitionValues在createAnimator时接着使用。MaterialContainerTransform定义了以下两个属性。

// 边界
private static final String PROP_BOUNDS = "materialContainerTransition:bounds";
// 背景
private static final String PROP_SHAPE_APPEARANCE = "materialContainerTransition:shapeAppearance";

总结

通过上面的拆解我们可以看到整个过程涉及的方法并不是很多。主要就是一前一后的view对象获取,和动画的创建三个方法。

而最后执行我们的变换还是放在了TransitionDrawable这个类部类中,这个里面就是一些定制化的样式逻辑。后续会简单阅读有必要的话在单独写一篇看看转场动画是怎么欺骗眼睛完成页面弹出特效的。

你可能感兴趣的:(MaterialContainerTransform-拆解)