Gone'd view 在 LayoutTransition.DISAPPEARING 动画师期间绘制在顶部

Gone'd view draws on top during LayoutTransition.DISAPPEARING animator

当我分别将可见性设置为 VISIBLE 和 GONE 时,我正在使用 LayoutTransition 淡入和淡出具有半透明背景的视图。标准的东西。我在那个过渡视图之上(在 XML 之后)有一个带有纯色背景的视图。我希望用户在整个过渡过程中看到纯色背景的顶视图保持不变,这与覆盖视图出现时运行的动画完全相反。

APPEARING 动画师按预期工作:用户可以在整个动画过程中看到顶视图。 DISAPPEARING 动画师没有按预期工作:叠加视图最终绘制在所有其他视图之上。

可能值得注意的是,即使您不设置自己的 LayoutTransition 而是依赖 XML 中的 android:animateLayoutChanges="true",也会发生这种情况;我添加了自己的以增加持续时间,从而更容易看到过渡。

对如何解决此问题有任何想法吗?我猜这很常见,而且我一定遗漏了一些明显的东西,因为这是默认行为。我已经尝试了一些事情,比如附加一个 AnimatorUpdateListener 来使每一帧的顶视图无效,设置我自己的 DISAPPEARING ObjectAnimator 和一个更新侦听器,使每帧的顶视图无效,并替换具有 TextView 和其他视图类型的叠加视图以防万一 FrameLayout 以某种特殊方式表现。

如果我用常规 ObjectAnimator 替换过渡动画师,我会得到预期的行为,除了视图不是 GONE 并因此接受触摸事件和所有垃圾(这使得 "solution"站不住脚)。因此,我认为问题不仅仅是过渡视图具有关联的动画师。这似乎是 LayoutTransition 代码或调用 said.

的问题

主要活动:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final View overlay = findViewById(R.id.overlay);

        final LayoutTransition lt = new LayoutTransition();

        lt.setDuration(LayoutTransition.APPEARING, 300);
        lt.setStartDelay(LayoutTransition.APPEARING, 0);
        lt.setDuration(LayoutTransition.DISAPPEARING, 1000);
        lt.setStartDelay(LayoutTransition.DISAPPEARING, 0);
        ((ViewGroup) overlay.getParent()).setLayoutTransition(lt);

        final Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if (overlay.getVisibility() == View.VISIBLE) {
                    overlay.setVisibility(View.GONE);
                } else {
                    overlay.setVisibility(View.VISIBLE);
                }

                overlay.postDelayed(this, 1500);
            }
        };

        overlay.post(runnable);
    }
}

activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#ffffff"
                android:paddingBottom="@dimen/activity_vertical_margin"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                tools:context=".MainActivity"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#ff0000"
        android:text="THIS IS BEHIND THE OVERLAY AND THUS SHOULD TINT"
        android:textColor="#ffffff"
        />

    <FrameLayout
        android:id="@+id/overlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#7f00ff00"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:layout_margin="64dp"
        android:background="#ffffff"
        android:gravity="center"
        android:text="THIS VIEW IS IN FRONT OF THE OVERLAY AND THUS SHOULD NOT SUFFER TINTING"
        android:textColor="#000000"
        android:textSize="32sp"
        />

</RelativeLayout>

我的设备运行 API 22,我也将 targetSdkVersion 设置为 22。基本上我创建了一个全新的项目并修改了生成的 MainActivityactivity_main.xml 以几乎完全匹配这些粘贴的文件(为了简洁我只排除了 importpackage 行).

我今天遇到了同样的问题,所以我查看了ViewGroup.java源代码。结果是消失的children总是在其他人身上画画。

这是 API 23 中 ViewGroup.dispatchDraw(Canvas) 的片段,我很确定它在 API 22 中几乎相同。

for (int i = 0; i < childrenCount; i++) {
    while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
        final View transientChild = mTransientViews.get(transientIndex);
        if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                transientChild.getAnimation() != null) {
            more |= drawChild(canvas, transientChild, drawingTime);
        }
        transientIndex++;
        if (transientIndex >= transientCount) {
            transientIndex = -1;
        }
    }
    int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
    final View child = (preorderedList == null)
            ? children[childIndex] : preorderedList.get(childIndex);
    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
        more |= drawChild(canvas, child, drawingTime);
    }
}
while (transientIndex >= 0) {
    // there may be additional transient views after the normal views
    final View transientChild = mTransientViews.get(transientIndex);
    if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
            transientChild.getAnimation() != null) {
        more |= drawChild(canvas, transientChild, drawingTime);
    }
    transientIndex++;
    if (transientIndex >= transientCount) {
        break;
    }
}
if (preorderedList != null) preorderedList.clear();

// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
    final ArrayList<View> disappearingChildren = mDisappearingChildren;
    final int disappearingCount = disappearingChildren.size() - 1;
    // Go backwards -- we may delete as animations finish
    for (int i = disappearingCount; i >= 0; i--) {
        final View child = disappearingChildren.get(i);
        more |= drawChild(canvas, child, drawingTime);
    }
}

浏览量在 mDisappearingChildren

正如源代码所说,普通视图和瞬态视图先绘制,然后消失视图绘制。所以消失children总是吸取别人的教训。应用开发者无法更改顺序。

我的建议是不要用LayoutTransition,自己写动画

编辑:

我发现了一个技巧,可以在其他视图之前绘制消失 children,反映需要。

您需要一个转储视图来使消失的 children 在 ViewGroup.drawChild(Canvas, View, long) 中替换它。

这里有一个例子。

public class TrickLayout extends FrameLayout {

    private Field mDisappearingChildrenField;
    private ArrayList<View> mSuperDisappearingChildren;
    // The dump view to draw disappearing children
    // Maybe you need more than one dump view
    private View mDumpView;
    private boolean mDoTrick;

    public TrickLayout(Context context) {
        super(context);
        init(context);
    }

    public TrickLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public TrickLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        try {
            mDisappearingChildrenField = ViewGroup.class.getDeclaredField("mDisappearingChildren");
            mDisappearingChildrenField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            // Ignore
        }

        if (mDisappearingChildrenField != null) {
            // You can add dump view in xml or somewhere else in code
            mDumpView = new View(context);
            addView(mDumpView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        }
    }


    @SuppressWarnings("unchecked")
    private void getSuperDisappearingChildren() {
        if (mDisappearingChildrenField == null || mSuperDisappearingChildren != null) {
            return;
        }

        try {
            mSuperDisappearingChildren = (ArrayList<View>) mDisappearingChildrenField.get(this);
        } catch (IllegalAccessException e) {
            // Ignore
        }
    }

    private boolean iWantToDoTheTrick() {
        // Do I need do the trick?
        return true;
    }

    private boolean beforeDispatchDraw() {
        getSuperDisappearingChildren();

        if (mSuperDisappearingChildren == null ||
                mSuperDisappearingChildren.size() <= 0 || getChildCount() <= 1) { // dump view included
            return false;
        }

        return iWantToDoTheTrick();
    }

    private void afterDispatchDraw() {
        // Clean up here
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        mDoTrick = beforeDispatchDraw();
        super.dispatchDraw(canvas);
        if (mDoTrick) {
            afterDispatchDraw();
            mDoTrick = false;
        }
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        ArrayList<View> disappearingChildren = mSuperDisappearingChildren;

        if (mDoTrick) {
            if (child == mDumpView) {
                boolean more = false;
                for (int i = disappearingChildren.size() - 1; i >= 0; i--) {
                    more |= super.drawChild(canvas, disappearingChildren.get(i), drawingTime);
                }
                return more;
            } else if (disappearingChildren.contains(child)) {
                // Skip
                return false;
            }
        }

        return super.drawChild(canvas, child, drawingTime);
    }
}