【问题标题】:Gone'd view draws on top during LayoutTransition.DISAPPEARING animator在 LayoutTransition.DISAPPEARING 动画师期间,已消失的视图绘制在顶部
【发布时间】:2015-09-30 15:05:49
【问题描述】:

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

APPEARING 动画师按预期工作:用户可以在整个动画中看到顶视图。 DISAPPEARING 动画器无法按预期工作:覆盖视图最终绘制在所有其他视图之上。

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

关于如何解决此问题的任何想法?我猜这很常见,而且我必须遗漏一些明显的东西,因为这是默认行为。我尝试了一些方法,例如附加AnimatorUpdateListener 以使每帧的顶视图无效,设置我自己的DISAPPEARING ObjectAnimator 并使用更新侦听器使每帧的顶视图无效,并用@ 替换覆盖视图987654328@ 和其他视图类型以防FrameLayout 以某种特殊方式运行。

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

主活动:

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 行)。

【问题讨论】:

    标签: android animator


    【解决方案1】:

    我今天遇到了同样的问题,所以我查看了 ViewGroup.java 源代码。结果是消失的孩子总是会利用其他孩子。

    这是 API 23 中ViewGroup.dispatchDraw(Canvas) 的 sn-p,我很确定它在 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

    正如源代码所说,普通视图和瞬态视图首先绘制,然后消失视图绘制。因此,消失的孩子总是会利用其他孩子。应用开发者不能更改顺序。

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

    编辑:

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

    你需要一个转储视图来让消失的孩子在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);
        }
    }
    

    【讨论】:

    • 感谢您对此进行调查。我不知道从哪里开始。我想我会选择使用 LayoutTransition 以外的其他东西——它似乎是最有效的经典快速修复之一,但不可避免地必须用自定义的东西替换。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-03
    • 1970-01-01
    相关资源
    最近更新 更多