Во время LayoutTransition.DISAPPEARING аниматор Gone'd view поверх рисунков

Я использую LayoutTransition для постепенного появления и исчезновения изображения с полупрозрачным фоном, когда я устанавливаю видимость на VISIBLE и GONE соответственно. Стандартные вещи. У меня есть представление со сплошным фоном поверх (после, в XML) этого переходного представления. Я ожидаю, что пользователь увидит этот вид сверху со сплошным фоном, неизмененным на протяжении всего перехода, что является полной противоположностью анимации, которая запускается при появлении наложенного вида.

Аниматор APPEARING работает так, как ожидалось: пользователь может видеть вид сверху на протяжении всей анимации. Аниматор DISAPPEARING работает не так, как ожидалось: наложенный вид рисуется поверх всех остальных видов.

Возможно, стоит отметить, что это происходит, даже если вы не устанавливаете свой собственный LayoutTransition и вместо этого полагаетесь на android:animateLayoutChanges="true" в XML; Я добавил свой собственный, чтобы увеличить продолжительность, чтобы было легче увидеть переход.

Любые мысли о том, как обойти эту проблему? Я предполагаю, что это довольно распространено, и я должен упустить что-то очевидное, поскольку это поведение по умолчанию. Я пробовал несколько вещей, таких как добавление AnimatorUpdateListener, чтобы сделать вид сверху недействительным в каждом кадре, установка моего собственного DISAPPEARING ObjectAnimator с прослушивателем обновлений, который делает недействительным вид сверху в каждом кадре, и замена наложенного вида на TextView и другие типы просмотра только в случай 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. По сути, я создал совершенно новый проект и изменил сгенерированные MainActivity и activity_main.xml, чтобы они почти точно соответствовали этим вставленным файлам (для краткости я исключил только строки import и package).


person Community    schedule 30.09.2015    source источник


Ответы (1)


Сегодня я столкнулся с той же проблемой, поэтому я проверил исходный код ViewGroup.java. В результате исчезающие дети всегда привлекают других.

Это фрагмент ViewGroup.dispatchDraw(Canvas) в API 23, и я почти уверен, что он почти такой же в 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);
    }
}
person Hippo Seven    schedule 29.01.2016
comment
Спасибо за расследование. Я не мог понять, с чего начать. Думаю, я предпочту использовать что-то другое, а не LayoutTransition — похоже, это одно из тех классических быстрых исправлений, которые в основном работают, но неизбежно должны быть заменены чем-то пользовательским. - person ; 15.02.2016