Tunji_D Tunji_D - 4 months ago 55
Android Question

CoordinatorLayout with CollapsingToolbarLayout breaks with Keyboard in Dialog fragment

I recently decided to move my app to using the new Support design library, and recently discovered a very nasty bug.

Say I have a CoordinatorLayout hosting an AppBarLayout and any scrollable view, be it a ViewPager, NestedScrollView, or even a RecyclerView with the required scroll behavior; choosing to display a dialog fragment that displays the keyboard causes the AppBarLayout to disconnect from the scrolling view and they don't scroll together anymore.

Furthermore, The scrolling view is forced to resize to half the screen at the bottom, and the AppBar layout takes the top half, despite it not needing it.

A video of the bug is here:
Youtube Link

EDIT:

Setting the the softInputMode of the Keyboard in the Activity to "adjustPan" fixes the issue. Apparently the CoordinatorLayout does not like being resized dynamically by the keyboard.

The offending XML looks like this, but I have multiple XML variations that exhibit this behavior, with the common element among them all being they use a CollapsingToolbarLayout:

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="192dp"
android:background="@color/transparent">

<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="144dp"
android:elevation="4dp"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginBottom="@dimen/quadruple_margin"
app:layout_collapseParallaxMultiplier="0.7"
app:layout_scrollFlags="scroll|exitUntilCollapsed">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="@color/primary"
android:orientation="vertical">

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/ranking_background" />

<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_gravity="bottom"
android:background="@color/black_40" />

<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/profile_picture"
android:layout_width="@dimen/double_margin"
android:layout_height="@dimen/double_margin"
android:layout_alignBottom="@+id/text"
android:layout_marginLeft="@dimen/double_margin"
android:layout_marginStart="@dimen/double_margin"
android:src="@drawable/image_placeholder"
android:visibility="gone" />

<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_marginLeft="@dimen/single_margin"
android:layout_marginStart="@dimen/single_margin"
android:layout_marginTop="@dimen/quadruple_margin"
android:layout_toRightOf="@+id/profile_picture"
android:text="@string/my_places_for"
android:textColor="@color/white"
android:textSize="20sp"
android:visibility="gone" />

<TextView
android:id="@+id/sub_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/text"
android:layout_marginLeft="@dimen/single_margin"
android:layout_marginStart="@dimen/single_margin"
android:layout_marginTop="@dimen/single_margin"
android:text="@string/pick_category_or_business"
android:textColor="@color/white"
android:textSize="16sp"
android:visibility="gone" />
</RelativeLayout>

<android.support.v7.widget.Toolbar
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/abc_action_bar_default_height_material"
app:contentInsetLeft="@dimen/triple_margin"
app:contentInsetStart="@dimen/triple_margin"
app:layout_collapseMode="pin"
app:popupTheme="@style/Theme.AppCompat.NoActionBar"
app:theme="@style/Theme.AppCompat.NoActionBar" />
</android.support.design.widget.CollapsingToolbarLayout>

<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/primary"
app:layout_scrollFlags="scroll">

<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:background="@color/primary"
android:elevation="4dp"
app:layout_scrollFlags="enterAlways"
app:tabGravity="fill"
app:tabMode="fixed"
app:tabSelectedTextColor="@color/white"
app:tabTextColor="@color/grey_400" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>



Answer

The answer provided by Tunji_D will solve the problem, but not necessarily the underlying issue.

The scroll behavior on the coordinator layout has an issue right now during its invalidation step on its child views: https://code.google.com/p/android/issues/detail?id=176373#c5

So in order to fix it use this workaround provided by junya, and set your view's behavior with

app:layout_behavior="com.app.common.PatchedScrollingViewBehavior"

and use:

public class PatchedScrollingViewBehavior extends AppBarLayout.ScrollingViewBehavior {

    public PatchedScrollingViewBehavior() {
        super();
    }

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

    @Override
    public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
        if(child.getLayoutParams().height == -1) {
            List dependencies = parent.getDependencies(child);
            if(dependencies.isEmpty()) {
                return false;
            }

            AppBarLayout appBar = findFirstAppBarLayout(dependencies);
            if(appBar != null && ViewCompat.isLaidOut(appBar)) {
                if(ViewCompat.getFitsSystemWindows(appBar)) {
                    ViewCompat.setFitsSystemWindows(child, true);
                }

                int scrollRange = appBar.getTotalScrollRange();
//                int height = parent.getHeight() - appBar.getMeasuredHeight() + scrollRange;
                int parentHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
                int height = parentHeight - appBar.getMeasuredHeight() + scrollRange;
                int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);
                parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);
                return true;
            }
        }

        return false;
    }


    private static AppBarLayout findFirstAppBarLayout(List<View> views) {
        int i = 0;

        for(int z = views.size(); i < z; ++i) {
            View view = (View)views.get(i);
            if(view instanceof AppBarLayout) {
                return (AppBarLayout)view;
            }
        }

        return null;
    }
}