How to create Parallax Scrolling in Android

How to create Parallax Scrolling in Android

Parallax scrolling is a technique in computer graphics and web design, where background images move by the camera slower than foreground images, creating an illusion of depth in a 2D scene and adding to the immersion.

Before android developed some layouts and widgets like CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout, Toolbar etc it was very difficult to make parallax header view in android because you need to write a lot of code for that

Before we start jumping in and see all types of scrolling behavior, we needs to be clear about the basic setup and implementation. Use Design Support Library to achieve AppBar scrolling behavior. This library provides many of the material design components.

In app build.gradle

dependencies {
    ...
    compile 'com.android.support:design:25.0.1'
    compile 'com.android.support:recyclerview-v7:25.0.1'
    compile 'com.android.support:cardview-v7:25.0.1'
}

Open your app XML layout file and add CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout using design support library and inside CollapsingToolbarLayout add an ImageView for header parallax image and Toolbar for appbar/actionbar. Following is the complete content of XML layout file.

<?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"
    android:id="@+id/coordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

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

            <ImageView
                android:id="@+id/ivParallax"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/city"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7" />

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

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

        <android.support.v7.widget.RecyclerView
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/rvItems"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />

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

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

We have a couple of new Layout types which we’ve added in here. The outer one is CoordinatorLayout. This is the main workhorse which does an awful lot for us yet requires very little in the way of configuration. It is essentially a marshaller which applies externally defined behaviours to the child Views within CoordinatorLayout.

CoordinatorLayout

<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">

This should be the top level element in your fragment or activity. The CoordinatorLayout is a new type of element introduced in the Design Library – basically it "coordinates" behavior between its child views. The behavior is specified by attributes set in its child views that are specific to the CoordinatorLayout.

AppBarLayout

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

Basically, an AppBarLayout is a LinearLayout with steroids, their children are placed vertically, with certain parameters the children can manage their behavior when the content is scrolled.

This is another new element from the Design Library that declares a layout for the Toolbar. AppBarLayout is introduced from Lollipop onwards, but through Design Support Library you can add this feature for Android 2.1 or above devices. The children of this element will define the behavior of the elements within the Toolbar.

AppBarLayout is actually required to be children of CoordinatorLayout, and are also required to have a sibling element that has the layout_behavior attribute set to AppBarLayout.ScrollingViewBehavior. Setting this attribute on the sibling element binds that element to the AppBarLayout, which lets it know when it should scroll. We bind the AppBarLayout to the NestedScrollView, which is discussed farther below.

AppBarLayout allows your Toolbar and other views (like TabLayout) to react to scroll events in a sibling view.

CollapsingToolbarLayout

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

This element is the child of the AppBarLayout and declares the "collapsing" behavior we want. The children of this element will declare how the toolbar is collapsing. Toolbar title will automatically appear larger when the layout is fully visible, then transition to its default size as it is collapsed.

The layout_scrollFlags attribute is a requirement of the CollapsingToolbarLayout, ​because it needs to know how to scroll its children, depending on the scroll events it receives.

  • scroll - it will be shown and hidden when the specified view is scrolled.
  • enterAlways - when we scroll up, the view appears immediately, instead of waiting until the scroll finishes.
  • snap - if the view remains semi-visible when scroll stops, it will be animated until it's completely shown or hidden.

Once app:layout_scrollFlags added to CollapsingToolbarLayout, the content view (either a NestedScrollView or `RecyclerView) needs to have app:layout_behavior tag.

ImageView

<ImageView
    android:id="@+id/ivParallax"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/city"
    android:scaleType="centerCrop"
    app:layout_collapseMode="parallax"
    app:layout_collapseParallaxMultiplier="0.7"
    />

Just a normal ImageView, with a few exceptions: we are setting the layout_collapseMode to parallax, which causes the ImageView to move as the user scrolls at a specific ratio. You can set this ratio with the (optional) layout_collapseParallaxMultiplier setting.

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"
    app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
</android.support.v7.widget.Toolbar>

Again, this is just a normal element you’ve seen before, except for the layout behavior set by the layout_collapseMode property. For this element we are setting it to pin which causes it to stick to the top when the user scrolls the view up.

NestedScrollView

The official widget to make a scroll layout in Android is ScrollView. Basically, adding a ScrollView inside ScrollView can be difficult . Most of the times it won’t end well. You will end up adding few workarounds and a person maintaining your code will probably hate you for the rest of his life. Fortunately, with the appearance of Material Design technology, NestedScrollView was released and this becomes much easier. NestedScrollView supports acting as both a nested scrolling parent and child on both new and old versions of Android.

For example, NestedScrollView can be used as a ListView item, so with this design, each row can be scrolled.

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

This is the sibling element to the AppBarLayout mentioned above. This is just a normal NestedScrollView, with the exception of the layout_behavior attribute set. As mentioned above, setting this attribute binds it to its sibling element, the AppBarLayout. In another words, you should specify which view you want to observe the scroll from. It can be a NestedScrollView, a RecyclerView or any other view that implements NestedScrollingChild.

Next, you have to add different method to make collapsing/animated/parallax header view and for listview. Following is the complete code snippet of java activity file.

public class MainActivity extends AppCompatActivity {
    Toolbar toolbar;
    RecyclerView rvItems;

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

        toolbar = (Toolbar) findViewById(R.id.toolbar);
        rvItems = (RecyclerView) findViewById(R.id.rvItems);

        setSupportActionBar(toolbar);
        getSupportActionBar().setHomeButtonEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        RecyclerAdapter adapter = new RecyclerAdapter(this);
        rvItems.setAdapter(adapter);
        rvItems.setHasFixedSize(true);
        rvItems.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    }
}

Let's define simple adapter for RecyclerView.

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.RecyclerViewHolder>{
    Context context;
    LayoutInflater inflater;
    ArrayList<String> items = new ArrayList<>();

    public class RecyclerViewHolder extends RecyclerView.ViewHolder {
        TextView tv1;

        public RecyclerViewHolder(View itemView) {
            super(itemView);
            tv1 = (TextView) itemView.findViewById(R.id.title);
        }
    }

    public RecyclerAdapter(Context context) {
        this.context = context;
        inflater = LayoutInflater.from(context);

        for (int i = 1; i < 20; i++) {
            items.add("City " + i);
        }
    }

    @Override
    public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = inflater.inflate(R.layout.rv_item, parent, false);

        RecyclerViewHolder viewHolder = new RecyclerViewHolder(v);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerViewHolder holder, int position) {
        holder.tv1.setText(items.get(position));
    }

    @Override
    public int getItemCount() {
        return items.size();
    }
}

And rv_item.xml layout for item of RecyclerView.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cardView"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:layout_margin="4dp"
    android:background="#C5CAE9"
    android:foreground="?attr/selectableItemBackground"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="16dp"
        android:gravity="center_vertical"
        android:text="Title"
        android:textColor="@android:color/black"
        android:textAppearance="?attr/textAppearanceListItem"
        android:textSize="16sp" />
</android.support.v7.widget.CardView>

Now, run your application and scroll the screen, you will see that the header image is collapsing slowly and app title text will be shown at toolbar.

Result

android_parallax1.png android_parallax2.png

Useful links

comments powered by Disqus