GhostView源码分析

283 阅读3分钟

GhostView 源码分析

以下基于您提供的 android.view.GhostView 源码进行简要分析:


1. 核心字段

  • View mView
    指向被“Ghost化”的目标视图(原始 View)。

    private final View mView;
    
  • int mReferences
    引用计数。同一个 View 如果多次被添加到 GhostView(例如重复执行 addGhost),该值会递增;移除时(调用 removeGhost)会递减,直到为 0 时才真正移除。

    private int mReferences;
    
  • boolean mBeingMoved
    用于在重新添加、移动 GhostView 时做标记,避免触发多余操作。

    private boolean mBeingMoved;
    

2. 构造函数

private GhostView(View view) {
    super(view.getContext());
    this.mView = view;
    this.mView.mGhostView = this;
    ViewGroup parent = (ViewGroup)this.mView.getParent();
    this.mView.setTransitionVisibility(4); // 将原视图的过渡可见性设为 INVSIBLE(=4)
    parent.invalidate();
}
  1. 保存目标视图 mView,并修改其 mGhostView 指针,标明此 GhostView 是它的“镜像”。
  2. 调用 setTransitionVisibility(4) 将原 View 在过渡动画中设为不可见,避免与 GhostView 重叠显示。
  3. 调用 parent.invalidate() 触发重绘。

3. onDraw(Canvas canvas)

protected void onDraw(Canvas canvas) {
    if (canvas instanceof RecordingCanvas) {
        RecordingCanvas dlCanvas = (RecordingCanvas)canvas;
        this.mView.mRecreateDisplayList = true;
        RenderNode renderNode = this.mView.updateDisplayListIfDirty();
        if (renderNode.hasDisplayList()) {
            dlCanvas.enableZ();
            dlCanvas.drawRenderNode(renderNode);
            dlCanvas.disableZ();
        }
    }
}
  • 使用硬件加速渲染方式(RecordingCanvas + RenderNode)把原视图的显示列表绘制到 GhostView 上。
  • mView.updateDisplayListIfDirty() 会生成或更新原视图的 RenderNode,然后 GhostView 对其进行绘制。

4. setMatrix(Matrix matrix)

public void setMatrix(Matrix matrix) {
    this.mRenderNode.setAnimationMatrix(matrix);
}
  • 可以通过 Matrix 实现 GhostView 的平移、缩放、旋转等变换。
  • 因为是硬件加速,所以该变换也只会影响 GhostView 本身,不会影响原视图。

5. setVisibility(int visibility)

public void setVisibility(int visibility) {
    super.setVisibility(visibility);
    if (this.mView.mGhostView == this) {
        int inverseVisibility = visibility == 0 ? 4 : 0;
        this.mView.setTransitionVisibility(inverseVisibility);
    }
}
  • 当 GhostView 变为可见 (visibility == 0, 即 View.VISIBLE) 时,会将原视图的过渡可见性设为 INVISIBLE(4)
  • 当 GhostView 不可见时,则反过来把原视图的过渡可见性恢复为 VISIBLE(0)
  • 这样可以保证在动画中,只看到 GhostView 显示,原视图不会与其冲突。

6. onDetachedFromWindow()

protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (!this.mBeingMoved) {
        this.mView.setTransitionVisibility(0);
        this.mView.mGhostView = null;
        ViewGroup parent = (ViewGroup)this.mView.getParent();
        if (parent != null) {
            parent.invalidate();
        }
    }
}
  • 当 GhostView 从窗口移除时,若不是正在移动 (mBeingMoved),就恢复原视图的可见性(setTransitionVisibility(0)VISIBLE),并将 mGhostView 重置为 null
  • 最后 parent.invalidate() 通知重绘。

7. addGhost / removeGhost

addGhost(View view, ViewGroup viewGroup, Matrix matrix)

  • viewGroup.getOverlay() 中创建相应容器(一个 FrameLayout),然后把一个新的 GhostView 实例添加进来。
  • 如果 view 原本有 GhostView,则会把原先的容器从旧父布局移除,再插入到新父布局。
  • 最后递增 mReferences,表明对该 GhostView 的使用计数。

removeGhost(View view)

public static void removeGhost(View view) {
    GhostView ghostView = view.mGhostView;
    if (ghostView != null) {
        --ghostView.mReferences;
        if (ghostView.mReferences == 0) {
            ViewGroup parent = (ViewGroup)ghostView.getParent();
            ViewGroup grandParent = (ViewGroup)parent.getParent();
            grandParent.removeView(parent);
        }
    }
}
  • 递减引用计数 mReferences;若降到 0,表示不再有任何地方需要此 GhostView,就会把它彻底移除。

8. 关于 SurfaceView 兼容

  • GhostView 利用硬件加速的方式将原视图的渲染内容复制到新的 RenderNode 中;
  • SurfaceView 自身有独立的 Surface 机制,理论上并不会直接被 RenderNode 录制;有时需要额外处理才能让 SurfaceView 在动画中正常显示。
  • 具体效果可能会与不同设备、不同 Android 版本的图形栈实现有关,需要在实际项目中测试。

总结

  1. 作用: GhostView 主要用于 Android 过渡动画场景(如共享元素动画),在动画期间可以隐藏原视图、显示 GhostView,以确保视觉效果平滑。
  2. 关键机制: 通过硬件加速的 RecordingCanvas + RenderNode 来绘制原视图内容;同时通过切换过渡可见性(setTransitionVisibility)来避免原视图和 GhostView 重叠。
  3. 多次引用: 使用 mReferences 做引用计数管理,多次调用 addGhost 只会创建一个 GhostView 实例;只有当引用计数完全归零后,才真正移除。

这就是该 GhostView 的工作原理与代码结构详解。