Search code examples
androidandroid-collapsingtoolbarlayoutandroid-appbarlayout

Is it possible to easily center the content of the view below AppBarLayout, inside CoordinatorLayout?


Background

Suppose you have a scrolling activity, with CoordinatorLayout and AppBarLayout, where you have a top header that can collapse upon scrolling at the bottom (which might have a RecyclerView or NestedScrollView).

Something like this:

enter image description here

Now, you need to put a loading view (say, a ProgressBar and a TextView inside a LinearLayout) in the center of the bottom area (where the text is shown), before the content appears.

The problem

I used a ViewAnimator to switch between the states, but if I choose to center the loading view, it is shown below the center, because the bottom area takes more space that is really shown (as you can scroll it).

enter image description here

What I tried

There are 2 solutions I've conducted that worked fine: 1. Get the size of the loading view and its parent, and use it to calculate the top margin of the loading view 2. Set the bottom padding of the loading view to be half of the upper area size (90dp in this case, which is half of the 180dp of the upper area). This is a bit easier, but if the UI of anything in the activity or the parent of the bottom area changes, it will ruin the fix for being in the center.

The code

Almost the same as the one from the IDE wizard, but here:

ScrollingActivity.java

public class ScrollingActivity extends AppCompatActivity {
    private ViewAnimator mViewAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
        mViewAnimator = findViewById(R.id.viewAnimator);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_scrolling, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            mViewAnimator.showNext();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

res/layout/activity_scrolling.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true" android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent"
            android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed" app:toolbarId="@+id/toolbar">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar" android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

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

    <ViewAnimator
        android:id="@+id/viewAnimator" android:layout_width="match_parent" android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:id="@+id/loader" android:layout_width="match_parent" android:layout_height="match_parent"
            android:gravity="center" android:orientation="vertical">

            <ProgressBar
                android:layout_width="wrap_content" android:layout_height="wrap_content"/>

            <TextView
                android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="loading"/>

        </LinearLayout>

        <android.support.v4.widget.NestedScrollView
            android:id="@+id/contentView" android:layout_width="match_parent" android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content" android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_margin" android:text="@string/large_text"/>

        </android.support.v4.widget.NestedScrollView>
    </ViewAnimator>

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

res/menu/menu_scrolling.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools" tools:context="com.example.user.myapplication.ScrollingActivity">
    <item
        android:id="@+id/action_settings" android:orderInCategory="100" android:title="click"
        app:showAsAction="always"/>
</menu>

The question

Thing is, I could calculate things myself, but I wonder if there is a different, easy way to do it. Maybe a flag of some sort that I'm unaware of ?

I ask this because the bottom area in the real app is more complex (Sample above is for demonstration of the issue), having a fragment or even a TabLayout&ViewPager with multiple fragments, so it's a bit weird to calculate things of the parent Activity in those fragments for this purpose.

Not only that, but I also have a tabLayout at the bottom (belongs to the activity) which makes it even more annoying to have into account.


Solution

  • I think that removing app:layout_behavior="@string/appbar_scrolling_view_behavior" from the ViewAnimator should do the trick. Or if that does not work wrapping the ViewAnimator in a RelativeLayout without the behvariour so that that ViewGroup gets behind the AppBarLayout, hence your view will be centered.

    Update 1 (not working)

    Tested my suggestion of 2 views and it worked:

    <android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">
    
    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true" android:theme="@style/AppTheme.AppBarOverlay">
    
        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent"
            android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed" app:toolbarId="@+id/toolbar">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar" android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>
    
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>
    
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/contentView" android:layout_width="match_parent" android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
        <TextView
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:layout_margin="@dimen/text_margin" android:text="@string/large_text"/>
    
    </android.support.v4.widget.NestedScrollView>
    
    <LinearLayout
        android:id="@+id/loader" android:layout_width="match_parent" android:layout_height="match_parent"
        android:gravity="center" android:orientation="vertical" android:visibility="gone">
    
        <ProgressBar
            android:layout_width="wrap_content" android:layout_height="wrap_content"/>
    
        <TextView
            android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="loading"/>
    
    </LinearLayout>
    
    </android.support.design.widget.CoordinatorLayout>
    

    Just swap the visibility of @+id/loader and @+id/contentView in your View depending on your logic and that should work but sadly you miss the animation of ViewAnimator.

    Update 2

    I tested it with exactly this code (just changed your custom styles for android ones) and it centers the loader in the whole view. Just try adding/removing app:layout_behavior="@string/appbar_scrolling_view_behavior" in "@+id/loader" so you can see that the position of the loader changes:

    With the behaviour it is centered on the area below the AppBarLayout.

    Loading centered below AppBarLayout

    Wihout it, it is centered on the screen.

    Loading centered on the screen

    <android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android" 
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools" 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.user.myapplication.ScrollingActivity">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/app_bar" 
            android:layout_width="match_parent" 
            android:layout_height="180dp" 
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
    
            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/toolbar_layout" 
                android:layout_width="match_parent" 
                android:layout_height="match_parent"
                app:contentScrim="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed" 
                app:toolbarId="@+id/toolbar">
    
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar" 
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize" 
                    app:layout_collapseMode="pin"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
    
            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v4.widget.NestedScrollView
            android:id="@+id/contentView" 
            android:layout_width="match_parent" 
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <TextView
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content"
                android:layout_margin="10dp" 
                android:text="@string/large_text"/>
    
        </android.support.v4.widget.NestedScrollView>
    
        <LinearLayout
            android:id="@+id/loader" 
            android:layout_width="match_parent" 
            android:layout_height="match_parent"
            android:gravity="center" 
            android:orientation="vertical" 
            android:visibility="gone">
    
            <ProgressBar
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content"/>
    
            <TextView
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:text="loading"/>
    
        </LinearLayout>
    
    </android.support.design.widget.CoordinatorLayout>
    

    Update 3

    Apparently when the CollapsingToolbarLayout is extended, it pushes the view below it downwards (if it has the app:layout_behavior="@string/appbar_scrolling_view_behavior") taking also into account the height of the inner Toolbar, that's why when it is in this state, the content of that view is not centered.

    So one way to workaround this in this case that the view we want to center has transparent background and only occupies the center of the area is to take advantage of app:behavior_overlapTop / setOverlayTop(int) and overlap that view with the AppBarLayout with the height of the Toolbar (?attr/actionBarSize):

    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.user.myapplication.ScrollingActivity">
        <android.support.design.widget.AppBarLayout
            android:id="@+id/app_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/toolbar_layout"
                android:layout_width="match_parent"
                android:layout_height="180dp"
                app:contentScrim="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
                app:toolbarId="@+id/toolbar">
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>
        <android.support.v4.widget.NestedScrollView
            android:id="@+id/contentView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:text="@string/large_text" />
        </android.support.v4.widget.NestedScrollView>
        <LinearLayout
            android:id="@+id/loader"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="vertical"
            android:visibility="gone"
            app:behavior_overlapTop="?attr/actionBarSize"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
            <ProgressBar
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content"/>
    
            <TextView
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:text="loading"/>
        </LinearLayout>
    </android.support.design.widget.CoordinatorLayout>
    

    Loading centered below CollapsingToolbarLayout extended

    Hope it helps