Whatsapp like Collapsing Toolbar
Asked Answered
G

4

16

In my app, I would like to implement Whatsapp home page like collapsible toolbar. That is, while scrolling down the list, the toolbar should go up and the tabs should pin at the top. How can I achieve this?

Here is my appbar layout

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/appbarLayout"
    android:theme="@style/AppTheme.AppBarOverlay">

    <android.support.design.widget.CollapsingToolbarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/collapsibleToolBarLayout"
        android:fitsSystemWindows="true"
        app:titleEnabled="false"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">                 

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            app:tabTextColor="@color/product_page_btn_grey"
            app:tabSelectedTextColor="@color/upcomer_background_red"
            app:tabIndicatorColor="@color/upcomer_background_red"
            android:layout_gravity="bottom"
            app:layout_scrollFlags="scroll|enterAlways"
            app:tabContentStart="72dp" />

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

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/title_image_view"
                android:contentDescription="@null"
                android:layout_gravity="start|center_vertical"
                android:visibility="gone"
                android:src="@drawable/title"/>
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/title_layout"
                android:visibility="gone">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:id="@+id/toolbarText"
                    style="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/send_button"
                    android:layout_marginRight="10dp"
                    android:layout_marginEnd="10dp"
                    android:text="@string/send_text"
                    android:textColor="@color/white"
                    android:textSize="18sp"
                    android:textStyle="bold"
                    android:visibility="gone"
                    android:layout_centerVertical="true"
                    android:layout_alignParentRight="true"
                    android:layout_alignParentEnd="true"/>

            </RelativeLayout>
    
        </android.support.v7.widget.Toolbar>

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

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

Now the TabLayout is not visible and the Toolbar stays there even though the list in the ViewPager below is scrolled. Kindly help.

Graveclothes answered 13/3, 2016 at 7:58 Comment(0)
D
42

In order to achieve this functionality a CollapsingToolbarLayout is actually not needed, you can just collapse the Toolbar that is set as the ActionBar.

Here is sample code using a Toolbar for the ActionBar that will collapse, and a TabLayout with a ViewPager.

First ensure that the style used for MainActivity is one that does not have an ActionBar, e.g.:

<style name="AppTheme" parent="@style/Theme.AppCompat.Light.NoActionBar">
    <!-- ....... -->
</style>

MainActivity.java, that has the FragmentPagerAdapter and sets up the tabs:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Get the ViewPager and set it's PagerAdapter so that it can display items
        ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
        PagerAdapter pagerAdapter =
                new PagerAdapter(getSupportFragmentManager(), MainActivity.this);
        viewPager.setAdapter(pagerAdapter);

        // Give the TabLayout the ViewPager
        TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
        tabLayout.setupWithViewPager(viewPager);

        // Iterate over all tabs and set the custom view
        for (int i = 0; i < tabLayout.getTabCount(); i++) {
            TabLayout.Tab tab = tabLayout.getTabAt(i);
            tab.setCustomView(pagerAdapter.getTabView(i));
        }

    }


    @Override
    public void onResume() {
        super.onResume();
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    class PagerAdapter extends FragmentPagerAdapter {

        String tabTitles[] = new String[] { "Tab One", "Tab Two", "Tab Three" };
        Context context;

        public PagerAdapter(FragmentManager fm, Context context) {
            super(fm);
            this.context = context;
        }

        @Override
        public int getCount() {
            return tabTitles.length;
        }

        @Override
        public Fragment getItem(int position) {

            switch (position) {
                case 0:
                    return new BlankFragment();
                case 1:
                    return new BlankFragment();
                case 2:
                    return new BlankFragment();
            }

            return null;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            // Generate title based on item position
            return tabTitles[position];
        }

        public View getTabView(int position) {
            View tab = LayoutInflater.from(MainActivity.this).inflate(R.layout.custom_tab, null);
            TextView tv = (TextView) tab.findViewById(R.id.custom_text);
            tv.setText(tabTitles[position]);
            return tab;
        }

    }
}

activity_main.xml

The important parts:

  • Use a CoordinatorLayout
  • Use app:layout_scrollFlags="scroll|enterAlways|snap" in the Toolbar properties
  • Use app:layout_behavior="@string/appbar_scrolling_view_behavior" in the ViewPager properties

Here is the activity_main.xml file:

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/main_layout"
    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=".MainActivity">


    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:elevation="6dp">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:elevation="0dp"
        app:layout_scrollFlags="scroll|enterAlways|snap"
        />

    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        app:tabMode="fixed"
        android:layout_below="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        app:elevation="0dp"
        app:tabTextColor="#d3d3d3"
        app:tabSelectedTextColor="#ffffff"
        app:tabIndicatorColor="#ff00ff"
        android:minHeight="?attr/actionBarSize"
        />

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

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

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

custom_tab.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/custom_text"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:background="?attr/selectableItemBackground"
        android:gravity="center"
        android:textSize="16dip"
        android:textColor="#ffffff"
        android:singleLine="true"
        />
</LinearLayout>

BlankFragment.java, this simply adds enough items to make it scroll:

import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;;

public class BlankFragment extends Fragment {

    public BlankFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.fragment_blank, container, false);

        RecyclerView rv = (RecyclerView) rootView.findViewById(R.id.rv_recycler_view);
        rv.setHasFixedSize(true);
        MyAdapter adapter = new MyAdapter(new String[]{"test one", "test two", "test three", "test four", "test five" , "test six" , "test seven", "test eight" , "test nine"});
        rv.setAdapter(adapter);

        LinearLayoutManager llm = new LinearLayoutManager(getActivity());
        rv.setLayoutManager(llm);

        return rootView;
    }

}

fragment_blank.xml, it's important to use a RecyclerView, or any other View that supports nested scrolling, such as a NestedScrollView

(side note: you can call setNestedScrollingEnabled(true) on api-21 and up to make it work with a ListView):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.SearchView
        android:id="@+id/sv_search"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Search!"
        android:singleLine="true"
        android:inputType="textNoSuggestions"
        android:layout_gravity="start"
        android:layout_marginRight="18dp"
        android:ems="10" >
    </android.support.v7.widget.SearchView>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_recycler_view"
        android:layout_below="@+id/sv_search"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </android.support.v7.widget.RecyclerView>
</RelativeLayout>

MyAdapter.java, the RecyclerView Adapter:

import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private String[] mDataset;

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        public CardView mCardView;
        public TextView mTextView;
        public MyViewHolder(View v) {
            super(v);

            mCardView = (CardView) v.findViewById(R.id.card_view);
            mTextView = (TextView) v.findViewById(R.id.tv_text);
        }
    }

    // Provide a suitable constructor (depends on the kind of dataset)
    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
                                                     int viewType) {
        // create a new view
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.card_item, parent, false);
        // set the view's size, margins, paddings and layout parameters
        MyViewHolder vh = new MyViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.mTextView.setText(mDataset[position]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }
}

card_item.xml, the circle image and the "blah blah blah" String are both static content, only the tv_text TextView is updated from the data source for this simple example:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="68dp" >

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_margin="10dp"
        android:layout_height="62dp"
        card_view:cardCornerRadius="4dp"
        card_view:elevation="14dp">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <ImageView
            android:id="@+id/iv_image"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:src="@drawable/abc_btn_radio_material">
        </ImageView>

        <TextView
            android:id="@+id/tv_text"

            android:layout_toRightOf ="@+id/iv_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center" >
        </TextView>

            <TextView
                android:id="@+id/tv_blah"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="blah blah blah......"
                android:layout_below="@+id/tv_text"
                android:layout_toRightOf="@+id/iv_image"
                android:layout_toEndOf="@+id/iv_image">
            </TextView>

        </RelativeLayout>
    </android.support.v7.widget.CardView>

</RelativeLayout>

build.gradle dependencies:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
    compile 'com.android.support:cardview-v7:23.0.1'
    compile 'com.android.support:recyclerview-v7:23.0.1'
}

Result:

enter image description here

Dune answered 13/3, 2016 at 8:58 Comment(6)
My problem is i don't have tab inside activity . I am having Tabs inside each fragment . So how can i achieve same effect like this ?Eggbeater
This is the wonderful solution. I have little bit different requirement I want to use a bottom navigation with the same layout you provided. Each bottom navigation fragment need to have such view pager with the tabs but the main actionbar should remain at the root level.Any idea to implement it ?Leftwich
The solution is very nice. But it misses the Toolbar snap (I am not sure if that particular feature was added in WhatsApp after the creation of this post), which can be implemented by add snap flag in toolbar scroll flags.Azaria
@Abhi Thank you, I took a look at the edit and it looks good, I went ahead and approved it.Dune
@DanielNugent Please answer this one too https://mcmap.net/q/747635/-how-to-drive-motionlayout-animation-while-scrolling-like-google-chrome/8663316Eudemonics
@Azaria Please answer this one too https://mcmap.net/q/747635/-how-to-drive-motionlayout-animation-while-scrolling-like-google-chrome/8663316Eudemonics
A
2

This library will help you

This is a very simple library for Android that allows you to stick an header to a scrollable view and easily apply animation to it

EDIT: To use the StikkyHeader library, you just need 3 lines:

StikkyHeaderBuilder.stickTo(mListView)
.setHeader(R.id.header, containerLayout)
.minHeightHeader(250)
.build();

Example:

public class IconAnimator extends HeaderStikkyAnimator {

@Override
public AnimatorBuilder getAnimatorBuilder() {

    View viewToAnimate = getHeader().findViewById(R.id.icon);
    Point point = new Point(50,100) // translate to the point with coordinate (50,100);
    float scaleX = 0.5f //scale to the 50%
    float scaleY = 0.5f //scale to the 50%
    float fade = 0.2f // 20% fade

    AnimatorBuilder animatorBuilder = AnimatorBuilder.create()
        .applyScale(viewToAnimate, scaleX, scaleY)
        .applyTranslation(viewToAnimate, point)
        .applyFade(viewToAnimate, fade);

    return animatorBuilder;
}
}

and then set the animator to the StikkyHeader:

 StikkyHeaderBuilder.stickTo(mListView)
.setHeader(R.id.header, containerLayout)
.minHeightHeader(250)
.animator(new IconAnimator())
.build();
Allotment answered 13/3, 2016 at 8:7 Comment(0)
V
2

Ok, so in order to pass the scrolling event of your listview/recyclerview to the appbarlayout, you need to put the below flag in your framelayout/relativelayout/linearlayout containing your scrolling view, i.e listview/recyclerview :

app:layout_behavior="@string/appbar_scrolling_view_behavior"

for example, in your case it should be like:

<android.support.design.widget.CoordinatorLayout ....>
  <android.support.design.widget.AppBarLayout ...>
        <android.support.design.widget.CollapsingToolbarLayout
            app:layout_scrollFlags="scroll|exitUntilCollapsed" ..>                 
            <android.support.design.widget.TabLayout
                app:layout_scrollFlags="scroll|enterAlways" ../>
            <android.support.v7.widget.Toolbar >
                ..........
            </android.support.v7.widget.Toolbar>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>


    <FrameLayout 
       app:layout_behavior="@string/appbar_scrolling_view_behavior"
       ....>

     // Frame containing your listview/recyclerview/ scrollingview

    <FrameLayout>


</android.support.design.widget.CoordinatorLayout >
Voelker answered 13/3, 2016 at 8:19 Comment(0)
L
1

Exactly what you want (Just copy and paste it)

<?xml version="1.0" encoding="utf-8"?>


<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.arkatechnolabs.designpractice.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:id="@+id/appbarLayout"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        >

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsibleToolBarLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="?attr/colorPrimary"
            app:expandedTitleMarginStart="10dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">


            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:layout_gravity="bottom"
                app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
            </android.support.design.widget.TabLayout>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
       </android.support.v7.widget.Toolbar>

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

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




    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">


        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/title_layout"
            android:visibility="gone">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:id="@+id/toolbarText"
                />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/send_button"
                android:layout_marginRight="10dp"
                android:layout_marginEnd="10dp"
                android:text="Send Text"
                android:textColor="@android:color/white"
                android:textSize="18sp"
                android:textStyle="bold"
                android:visibility="gone"
                android:layout_centerVertical="true"
                android:layout_alignParentRight="true"
                android:layout_alignParentEnd="true"/>

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

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab_btn"
        android:layout_gravity="right|bottom"
        android:layout_marginRight="5dp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_fab"
        app:fabSize="normal">
    </android.support.design.widget.FloatingActionButton>

</android.support.design.widget.CoordinatorLayout>
Legpull answered 13/3, 2016 at 9:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.