Android – CardView and RecyclerView
CardView and recyclerView are two widgets introduced with Android Lollipop. CardView shows information inside customizable cards, while recyclerView is ‘a more advanced version of the ListView widget’.
In this tutorial we’ll see an introduction of each of these components, and we’ll create a beautiful sample app to demonstrate how to use both components together.
What are we gonna build ?
We’ll build an app that lists movies, or ‘movies posters’ ! Here are few screenshots from the app we’ll create by the end of the tutorial.
Before getting started, let’s create our project and add the CardView and RecyclerView
dependencies in build.gradle file:
dependencies { ... compile 'com.android.support:cardview-v7:25.1.1' compile 'com.android.support:recyclerview-v7:25.1.1' }
RecyclerView
As mentionned above, the recyclerview widget found in the latest support-v7 version ,is supposed to work as listviews or gridviews. Except that it has a more extensible framework, for example, it provides the ability to implement both horizontal and vertical layouts.
Also, it provides animation support for ListView items whenever they are added or removed, which had been extremely difficult to do in the current implementation. RecyclerView is to be used when you have data collections whose elements change at runtime based on user action or network events.
If you are learning how to use a recyclerview within your layout, then you’re certainly used to listviews and how to manage them. And you certainly know that if we want to increase ListView’s performance we can use a pattern called ViewHolder. This pattern avoids looking up the UI components all the time the system shows a row in the list. Even if this pattern introduces some benefits, we can implement the ListView without using it at all. RecyclerView forces us to use the ViewHolder pattern.
Using or implementing the recyclerView is similar to doing so with listViews. After adding the support library to build.gradle, you need to:– define a model class to use as the data source
– create an XML layout file to use as a custom row layout
– add the recyclerView to your activity
– create RecyclerView.Adapter and viewHolder to render the item
– bind the adapter to the data source to populate the recyclerView with data.
We’ll see all these implementations in next sections. But to start with, here is how to add a recyclerView to your activity:
You add the recycler view widget to your XML layout, get a reference of it in your Java class, and set an Adapter, a LayoutManager and an ItemAnimator.
activity_main.xml
public class MainActivity extends AppCompatActivity { private List moviesList = new ArrayList<>(); private RecyclerView recyclerView; private MoviesAdapter moviesAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = (RecyclerView) findViewById(R.id.recycler_view); moviesAdapter = new MoviesAdapter(movieList); RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext()); recyclerView.setLayoutManager(mLayoutManager); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(mAdapter); setMoviesListData(); }
As you can notice, to use a RecyclerView, you will need to work with these 3 components:
RecyclerView.Adapter
: to handle the data collection and bind it to the view.LayoutManager
: helps in positioning the items.ItemAnimator
: to animate the items for common operations such as addition or removal of items.
Android CardView
CardViews can be used to display a card sort of a layout in android. Mostly it displays views on top of each other, with shadows. As Android material design is inspired from paper and ink concept, Android CardView is such a view which has all material design properties, most importantly showing shadows according to the elevation. The CardView widget extends FrameLayout and it can be displayed on all the platforms of android since it’s available through the Support v7 library.
Like any other ViewGroup, a cardView can be added to your Activity or Fragment using a layout XML file:
<LinearLayout 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:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.CardView xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_gravity="center" android:layout_width="250dp" android:layout_height="250dp" app:cardCornerRadius="4dp"> </android.support.v7.widget.CardView> </LinearLayout>
Intergrating recyclerView and cardView
First you need to add the following gradle dependencies (to the already added recyclerview and cardview dependencies) :
compile 'com.android.support:design:25.1.1' compile 'com.github.bumptech.glide:glide:3.7.0'
We’ll need the design library to use the CollapsingToolbarLayout widget. And the Glide library to display the images.
Then add below strings, colors and dimens resources to strings.xml, colors.xml and dimens.xml files :
dimens.xml:
<resources> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="fab_margin">16dp</dimen> <dimen name="item_offset">10dp</dimen> <dimen name="detail_backdrop_height>250dp</dimen> <dimen name="backdrop_title">30dp</dimen> <dimen name="backdrop_subtitle">18dp</dimen> <dimen name="card_margin">5dp</dimen> <dimen name="card_movie_radius">0dp</dimen> <dimen name="movie_cover_height">160dp</dimen> <dimen name="movie_title_padding">10dp</dimen> <dimen name="movie_title">15dp</dimen> <dimen name="songs_count_padding_bottom">5dp</dimen> <dimen name="songs_count">12dp</dimen> <dimen name="ic_movie_overflow_width">20dp</dimen> <dimen name="ic_movie_overflow_height">30dp</dimen> <dimen name="ic_movie_overflow_margin_top">10dp</dimen> </resources>
strings.xml:
<resources> <string name="app_name">MoviesSampleApp</string> <string name="action_settings">Settings</string> <string name="action_rate">Rate</string> <string name="action_share">Share</string> <string name="backdrop_title">Movies Fanatic</string> <string name="backdrop_subtitle">This season top 10 movies</string> </resources>
colors.xml:
<resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> <color name="viewBg">#f1f5f8</color> <color name="movies_title">#4c4c4c</color> </resources>
Next step is to create a model class Movie.java:
public class Movie { private String name; private int thumbnail; private int rating; public Movie(){ } public Movie(String name, int rating, int thumbnail) { this.name = name; this.rating = rating; this.thumbnail = thumbnail; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getThumbnail() { return thumbnail; } public void setThumbnail(int thumbnail) { this.thumbnail = thumbnail; } public int getRating() { return rating; } public void setRating(int rating) { this.rating = rating; } }
Then we’ll need an xml layout for the movie card, and an adapter that binds data between this layout and the data model class.
So you have to create the movie_card.xml layout. And add the movie_menu.xml to the res folder. This is the menu at the corner of the card.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.CardView android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:layout_margin="@dimen/card_margin" android:elevation="3dp" card_view:cardCornerRadius="@dimen/card_movie_radius"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/thumbnail" android:layout_width="match_parent" android:layout_height="@dimen/movie_cover_height" android:background="?attr/selectableItemBackgroundBorderless" android:clickable="true" android:scaleType="fitXY" /> <TextView android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/thumbnail" android:paddingLeft="@dimen/movie_title_padding" android:paddingRight="@dimen/movie_title_padding" android:paddingTop="@dimen/movie_title_padding" android:textColor="@color/movie_title" android:textSize="@dimen/movie_title" /> <TextView android:id="@+id/rating" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/name" android:paddingBottom="@dimen/songs_count_padding_bottom" android:paddingLeft="@dimen/movie_title_padding" android:paddingRight="@dimen/movie_title_padding" android:textSize="@dimen/songs_count" /> <ImageView android:id="@+id/overflow" android:layout_width="@dimen/ic_movie_overflow_width" android:layout_height="@dimen/ic_movie_overflow_height" android:layout_alignParentRight="true" android:layout_below="@id/thumbnail" android:layout_marginTop="@dimen/ic_movie_overflow_margin_top" android:scaleType="centerCrop" android:src="@drawable/ic_dots" /> </RelativeLayout> </android.support.v7.widget.CardView> </LinearLayout>
movie_menu.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"> <item android:id="@+id/action_rate" android:orderInCategory="100" android:title="@string/action_rate" /> <item android:id="@+id/action_share" android:orderInCategory="101" android:title="@string/action_share" /> </menu>
In the adapter which takes a context and a moviesList as its constructor’s parameters, we’ll define our ViewHolder class, which contains two TextViews and two ImageViews to render those ui items. We’ll Inflate the movie_card.xml in the onCreateViewHolder(), and bind the ” moviesList.get(position).getACertainProperty() ” to the appropriate ui elements in the onBindViewHolder() method. We’ll aslo manage the popup menu in the cards .
MoviesAdapter.java:
public class MoviesAdapter extends RecyclerView.Adapter { private Context mContext; private List moviesList; public MoviesAdapter(Context mContext, List moviesList) { this.mContext = mContext; this.moviesList = moviesList; } public class MyViewHolder extends RecyclerView.ViewHolder { public TextView name, rating; public ImageView thumbnail, overflow; public MyViewHolder(View view) { super(view); name = (TextView) view.findViewById(R.id.name); rating = (TextView) view.findViewById(R.id.rating); thumbnail = (ImageView) view.findViewById(R.id.thumbnail); overflow = (ImageView) view.findViewById(R.id.overflow); } } @Override public MoviesAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.movie_card, parent, false); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(final MoviesAdapter.MyViewHolder holder, int position) { Movie movie = moviesList.get(position); holder.name.setText(movie.getName()); holder.rating.setText("Rating: "+String.valueOf(movie.getRating())); /** loading movie posters using Glide library **/ Glide.with(mContext).load(movie.getThumbnail()).into(holder.thumbnail); holder.overflow.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { showPopupMenu(holder.overflow); } }); } @Override public int getItemCount() { return moviesList.size(); } /** showing popup menu when tapping on 3 dots **/ private void showPopupMenu(View view) { // inflate menu PopupMenu popup = new PopupMenu(mContext, view); MenuInflater inflater = popup.getMenuInflater(); inflater.inflate(R.menu.menu_movie, popup.getMenu()); popup.setOnMenuItemClickListener(new MyMenuItemClickListener()); popup.show(); } /** click listener for popup menu items **/ class MyMenuItemClickListener implements PopupMenu.OnMenuItemClickListener { public MyMenuItemClickListener() { } @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.action_add_favourite: Toast.makeText(mContext, "Rate", Toast.LENGTH_SHORT).show(); return true; case R.id.action_play_next: Toast.makeText(mContext, "Download", Toast.LENGTH_SHORT).show(); return true; default: } return false; } } }
Finally, we’ll need to add the MainActivity class to put all this together. In the activity_main.java we’ll add the collapsingToolbar shown in the demo video, and include a main_content layout that containes a recylerView:
activity_main.xml
<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/main_content" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:fitsSystemWindows="true"> <android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="@dimen/detail_backdrop_height" android:fitsSystemWindows="true" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:expandedTitleMarginEnd="64dp" app:expandedTitleMarginStart="48dp" app:expandedTitleTextAppearance="@android:color/transparent" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:id="@+id/backdrop" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:scaleType="centerCrop" app:layout_collapseMode="parallax" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center_horizontal" android:orientation="vertical"> <TextView android:id="@+id/love_music" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/backdrop_title" android:textColor="@android:color/black" android:textSize="@dimen/backdrop_title" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/backdrop_subtitle" android:textColor="@android:color/black" android:textSize="@dimen/backdrop_subtitle" /> </LinearLayout> </RelativeLayout> <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> <include layout="@layout/main_content" /> </android.support.design.widget.CoordinatorLayout>
In the MainActivity.java class, we’ll get a reference to our recyclerView and set its LayoutManager, ItemAnimator, and Adapter. For the adapter we’ll instanciate a new MoviesAdapter with an ArrayList<Movie> populated through prepareMovies() method.
MainActivity.java
public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private MoviesAdapter adapter; private List moviesList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); initCollapsingToolbar(); recyclerView = (RecyclerView) findViewById(R.id.recycler_view); moviesList = new ArrayList<>(); adapter = new MoviesAdapter(this, moviesList); RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(this, 2); recyclerView.setLayoutManager(mLayoutManager); recyclerView.addItemDecoration(new GridSpacingItemDecoration(2, dpToPx(10), true)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(adapter); prepareMovies(); try { Glide.with(this).load(R.drawable.cover).into((ImageView) findViewById(R.id.backdrop)); } catch (Exception e) { e.printStackTrace(); } } /** * Initializing collapsing toolbar * Will show and hide the toolbar title on scroll */ private void initCollapsingToolbar() { final CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); collapsingToolbar.setTitle(" "); AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar); appBarLayout.setExpanded(true); // hiding & showing the title when toolbar expanded & collapsed appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { boolean isShow = false; int scrollRange = -1; @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { if (scrollRange == -1) { scrollRange = appBarLayout.getTotalScrollRange(); } if (scrollRange + verticalOffset == 0) { collapsingToolbar.setTitle(getString(R.string.app_name)); isShow = true; } else if (isShow) { collapsingToolbar.setTitle(" "); isShow = false; } } }); } /** * Adding few movies for testing */ private void prepareMovies() { int[] posters = new int[]{ R.drawable.movie1, R.drawable.movie2, R.drawable.movie3, R.drawable.movie4, R.drawable.movie5, R.drawable.movie6, R.drawable.movie7, R.drawable.movie8, R.drawable.movie9, R.drawable.movie10,}; Movie a = new Movie("Arrival",3, posters[0]); moviesList.add(a); a = new Movie("Collateral Beauty", 5, posters[1]); moviesList.add(a); a = new Movie("The pursuit of happiness", 11, posters[2]); moviesList.add(a); a = new Movie("Passengers", 12, posters[3]); moviesList.add(a); a = new Movie("Assassins Creed", 14, posters[4]); moviesList.add(a); a = new Movie("Revolutionary Road", 1, posters[5]); moviesList.add(a); a = new Movie("Fences", 11, posters[6]); moviesList.add(a); a = new Movie("Into the wild", 14, posters[7]); moviesList.add(a); a = new Movie("Eat Pray Love", 11, posters[8]); moviesList.add a = new Movie("Divergent", 11, posters[9]); moviesList.add(a); adapter.notifyDataSetChanged(); } /** * RecyclerView item decoration - give equal margin around grid item */ public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { private int spanCount; private int spacing; private boolean includeEdge; public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) { this.spanCount = spanCount; this.spacing = spacing; this.includeEdge = includeEdge; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = parent.getChildAdapterPosition(view); // item position int column = position % spanCount; // item column if (includeEdge) { outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) if (position < spanCount) { // top edge outRect.top = spacing; } outRect.bottom = spacing; // item bottom } else { outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) if (position >= spanCount) { outRect.top = spacing; // item top } } } } /** * Converting dp to pixel */ private int dpToPx(int dp) { Resources r = getResources(); return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics())); } }
That’s all coders ! If you are familiar with listViews, this should be easy to get. If not, it is easy to get too!
You can download the source code for this sample here.
Recent Comments