使用 removeView() 从 AppBarLayout 中删除 TabLayout 时出现故障

Glitch when removing TabLayout from AppBarLayout with removeView()

当一个项目在 recyclerview 中被点击时,我称之为

appbar.removeView(tabs)

This 视频展示了发生的事情

好像是把没有动画的TabLayout完全去掉,然后加回来,再用动画去掉。我已经放慢了过渡以展示它在做什么。

像这样将它们添加回来时也会发生这种情况

if (tabs.parent != null) {
        (tabs.parent as ViewGroup).removeView(tabs)
    }
    appbar.addView(tabs)
    appbar.setExpanded(true, true)

这是我的布局

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinator_main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:theme="@style/Theme.AppCompat.Light.NoActionBar">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:animateLayoutChanges="true"
        app:elevation="0dp"
        android:theme="@style/ThemeOverlay.AppCompat.ActionBar">

        <android.support.v7.widget.Toolbar
            xmlns:android="http://schemas.android.com/apk/res/android"
            style="@style/MyToolbar"
            android:id="@+id/toolbar"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            app:layout_scrollFlags="scroll|enterAlways|snap"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabTextAppearance="@style/TabText"
            android:layout_marginRight="@dimen/small_spacing"
            android:layout_marginLeft="@dimen/small_spacing"
            app:tabMode="fixed"
            app:tabGravity="fill"
            app:tabIndicatorHeight="2dp"
            app:layout_scrollFlags="enterAlways"/>

    </android.support.design.widget.AppBarLayout>


    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/appbar"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" >

        <***.***.CustomViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </FrameLayout>

</android.support.design.widget.CoordinatorLayout>

从视频来看,似乎有三个片段根据 TabLayout 加载。 MyPhotosFragmentMyAlbumsFragmentMyFavouritesFragment.

如果我没理解错的话,你是将 TabLayout 放在 activity 中,这三个片段直接加载到 activity 中。 When “Hidden albums” is selected on the drawer, the appbar is shown/added and it is hidden/removed when another item is selected.

你应该做的是制作一个新的片段作为容器。我们称它为 HiddenAlbumsFragment。然后你应该从 activity 中删除 TabLayoutViewPager 并将其放在 HiddenAlbumsFragment 中。单击选项卡项时,HiddenAlbumsFragment 应加载相应的片段作为子片段。 然后,当您通过从抽屉中选择一个来替换另一个 HiddenAlbumsFragment 时,tablayout 将自动消失。

你可以像这样用动画隐藏标签布局。

tabLayout.animate().scaleY(0).setInterpolator(new AccelerateInterpolator()).start();

我这边还没有测试过。如果您对此有任何问题,请发表评论。 可能这应该有效。

这是怎么回事?

除了您提到的闪光灯外,一切似乎都正常。我采用了 Chris Banes 的 Cheese Square 应用程序并进行了一些修改以复制您所看到的内容。单击 FAB 可删除和添加选项卡。

这是我想出的视频。

这是删除 TabLayout 时问题的屏幕截图。如您所见,RecyclerView 覆盖了应用栏。

这是重新添加 TabLayout 时问题的屏幕截图。在这里您可以看到 RecyclerView 在添加视图后向下移动到它的位置。

消失的 TabLayout 的布局转换正在用 LayoutTransition 完成。要进行有效的过渡,LayoutTransition 必须确定最终布局的外观,以便可以构建一组动画。这需要布置过渡后屏幕的外观。 似乎 最终布局在动画 运行 之前短暂显示。 (我的猜测。)这可能是竞争条件,也可能与 this caveat:

有关

This class, and the associated XML flag for containers, animateLayoutChanges="true", provides a simple utility meant for automating changes in straightforward situations. Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the interrelationship of the various levels of layout. Also, a container that is being scrolled at the same time as items are being added or removed is probably not a good candidate for this utility, because the before/after locations calculated by LayoutTransition may not match the actual locations when the animations finish due to the container being scrolled as the animations are running. You can work around that particular issue by disabling the 'changing' animations by setting the CHANGE_APPEARING and CHANGE_DISAPPEARING animations to null, and setting the startDelay of the other animations appropriately.

对能够弄清楚到底发生了什么的人表示敬意和声誉。 (此问题可能是 LayoutTransition 代码中以下神秘注释中提到的工件:)

/**
 * Controls whether changing animations automatically animate the parent hierarchy as well.
 * This behavior prevents artifacts when wrap_content layouts snap to the end state as the
 * transition begins, causing visual glitches and clipping.
 * Default value is true.
 */
private boolean mAnimateParentHierarchy = true;

如何解决?

我建议您完全放弃 LayoutTransitions (android:animateLayoutChanges="false") 并继续 TransitionManager

我们将使用便捷方法 TransitionManager#beginDelayedTransition:

beginDelayedTransition

void beginDelayedTransition (ViewGroup sceneRoot, Transition transition)

Convenience method to animate to a new scene defined by all changes within the given scene root between calling this method and the next rendering frame. Calling this method causes TransitionManager to capture current values in the scene root and then post a request to run a transition on the next frame. At that time, the new values in the scene root will be captured and changes will be animated. There is no need to create a Scene; it is implied by changes which take place between calling this method and the next frame when the transition begins.

这是我删除和添加选项卡的代码。确保设置 animateLayoutChanges="false".

fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (mTabsAdded) {
            // Get rid of the indicator due to an on-screen artifact.
            tabs.setSelectedTabIndicatorHeight(0);
            TransitionSet set = new TransitionSet()
                .addTransition(new Fade(OUT))
                .addTransition(new ChangeBounds());
            TransitionManager.beginDelayedTransition(layout, set);
            mAppBar.removeView(tabs);
        } else {
            // Add tab indicator back in.
            int indicatorHeight = (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 2,
                getResources().getDisplayMetrics());
            tabs.setSelectedTabIndicatorHeight(indicatorHeight);
            TransitionSet set = new TransitionSet()
                .addTransition(new Fade(IN))
                .addTransition(new ChangeBounds());
            TransitionManager.beginDelayedTransition(layout, set);
            mAppBar.addView(tabs);
        }
        mTabsAdded = !mTabsAdded;
    }
});