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();
}
- 保存目标视图
mView,并修改其mGhostView指针,标明此 GhostView 是它的“镜像”。 - 调用
setTransitionVisibility(4)将原View在过渡动画中设为不可见,避免与 GhostView 重叠显示。 - 调用
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 版本的图形栈实现有关,需要在实际项目中测试。
总结
- 作用: GhostView 主要用于 Android 过渡动画场景(如共享元素动画),在动画期间可以隐藏原视图、显示 GhostView,以确保视觉效果平滑。
- 关键机制: 通过硬件加速的
RecordingCanvas+RenderNode来绘制原视图内容;同时通过切换过渡可见性(setTransitionVisibility)来避免原视图和 GhostView 重叠。 - 多次引用: 使用
mReferences做引用计数管理,多次调用addGhost只会创建一个 GhostView 实例;只有当引用计数完全归零后,才真正移除。
这就是该 GhostView 的工作原理与代码结构详解。