آموزش RecyclerView و CardView در برنامه نویسی اندروید

سلام دوستان در این سری از آموزش های برنامه نویسی اندروید به آموزش RecyclerView و CardView در برنامه نویسی اندروید میپردازیم یکی از بهترین روش های متریال دیزاین پیاده سازی همزمان cardview و RecyclerView است و باعث میشه کاربر ارتباط بیشتری با برنامه شما برقرار کند و زمانی که کاربر در حال استفاده از برنامه شما است دیگر حوصله اش سر نمی رود و بهتر است بدانید CardView یکی از بهترین متریال دیزاین های جهان به حساب می آید در ادامه با ما همراه باشید.
قبل از شروع ابتدا یک پیش نمایشی از ماحصل نهایی کار ببینید بعد ادامه دهید.

 
ابتدا باید کتاب خانه های CardView , Recylerview و Glide را اضافه کنیم. قبلا نحوه استفاده از هر کدام از کتابخانه ها را به صورت مجزا گفته ایم کافی است در سایت سرچ کرده و آموزش ها را دنبال کنید.
پس وارد فایل Build.gradle شده نوع module خط های زیر را در قسمت dependencies قرار دهید.

    // RecyclerView
    compile 'com.android.support:recyclerview-v7:23.3.+'
    // CardView
    compile 'com.android.support:cardview-v7:23.3.+'
    // Glide
    compile 'com.github.bumptech.glide:glide:3.7.0'

پروژه را sync کرده و در ادمه با ما همراه باشید. (ما قبلا علت خطاهای گریدل را بررسی کردیم در سایت جستجو کنید)
فایل zip زیر را دانلود کرده این فایل حاوی عکس های استفاده شده در برنامه است.
دانلود
سپس عکس ها را در فولدر مربوطه قرار دهید (copy و past کنید)
وارد فایل string.xml (در مسیر res/values قرار دارد ) شده آن را به شکل زیر ویرایش کنید.

<resources>
    <string name="app_name">Programchi.ir</string>
    <string name="action_settings">Settings</string>
    <string name="action_add_favourite">Add to Favourites</string>
    <string name="action_play_next">Play Next</string>
    <string name="backdrop_title">LOVE MUSIC</string>
    <string name="backdrop_subtitle">This season top 20 albums</string>
</resources>

وارد فایل colors.xml شده و کد های زیر را در آن قرار دهید (اضافه کنید) .

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#F50057</color>
    <color name="colorPrimaryDark">#F50057</color>
    <color name="colorAccent">#FF4081</color>
    <color name="viewBg">#f1f5f8</color>
    <color name="album_title">#4c4c4c</color>
</resources>

سپس باید فاصله یا dimens را تعریف کنیم
وارد مسیر res/values شده اگر فایل dimens.xml وجود نداشت آن را ایجاد کنید سپس کد های زیر را در آن قرار دهید.

<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <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_album_radius">0dp</dimen>
    <dimen name="album_cover_height">160dp</dimen>
    <dimen name="album_title_padding">10dp</dimen>
    <dimen name="album_title">15dp</dimen>
    <dimen name="songs_count_padding_bottom">5dp</dimen>
    <dimen name="songs_count">12dp</dimen>
    <dimen name="ic_album_overflow_width">20dp</dimen>
    <dimen name="ic_album_overflow_height">30dp</dimen>
    <dimen name="ic_album_overflow_margin_top">10dp</dimen>
</resources>

کاری که این کد های بالا انجام می دهند تنظیم فاصله است (ما براتون به بهترین شکل ایجاد کرده ایم می توانید خودتان هم آنها را تغییر دهید)
حال یک کلاس باید ایجاد کنیم که به کلاس model معروف است و این کلاس کارش denot کردن یا نشان دادن نام و تعداد آهنگ و عکس برای هر cardview است پس یک فایل به نام  Album.java ایجاد کرده و کد های زیر را در آن قرار دهید.

package ir.programchi;
/**
 * Created by Jefferson on 18/05/16.
 */
public class Album {
    private String name;
    private int numOfSongs;
    private int thumbnail;
    public Album() {
    }
    public Album(String name, int numOfSongs, int thumbnail) {
        this.name = name;
        this.numOfSongs = numOfSongs;
        this.thumbnail = thumbnail;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getNumOfSongs() {
        return numOfSongs;
    }
    public void setNumOfSongs(int numOfSongs) {
        this.numOfSongs = numOfSongs;
    }
    public int getThumbnail() {
        return thumbnail;
    }
    public void setThumbnail(int thumbnail) {
        this.thumbnail = thumbnail;
    }
}

کلاس بالا به getter و setter نیز معروف است.
حالا باید آلبوم های هرکدام در cardview نمایش دهیم به این منظور باید یک فایل به نام album_card.xml ایجاد کرده و ویژگی های مثل نام و تعداد اهنگ و عکس را در آن پیاده سازی کنیم.
پس فایل album_card.xml همانند زیر می شود.

<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_album_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/album_cover_height"
                android:background="?attr/selectableItemBackgroundBorderless"
                android:clickable="true"
                android:scaleType="fitXY" />
            <TextView
                android:id="@+id/title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/thumbnail"
                android:paddingLeft="@dimen/album_title_padding"
                android:paddingRight="@dimen/album_title_padding"
                android:paddingTop="@dimen/album_title_padding"
                android:textColor="@color/album_title"
                android:textSize="@dimen/album_title" />
            <TextView
                android:id="@+id/count"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/title"
                android:paddingBottom="@dimen/songs_count_padding_bottom"
                android:paddingLeft="@dimen/album_title_padding"
                android:paddingRight="@dimen/album_title_padding"
                android:textSize="@dimen/songs_count" />
            <ImageView
                android:id="@+id/overflow"
                android:layout_width="@dimen/ic_album_overflow_width"
                android:layout_height="@dimen/ic_album_overflow_height"
                android:layout_alignParentRight="true"
                android:layout_below="@id/thumbnail"
                android:layout_marginTop="@dimen/ic_album_overflow_margin_top"
                android:scaleType="centerCrop"
                android:src="@drawable/ic_dots" />
        </RelativeLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

حالا زمان ایجاد یک منو برای سه نقطه ای که زیر هر البوم است اگر به عکس با دقت نگاه کرده باشید هر البوم یک سه نقطه نیز دارد در اینجا ما منوی آن را ایجاد می کنیم.
پس یک فایل به نام menu_album.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_add_favourite"
        android:orderInCategory="100"
        android:title="@string/action_add_favourite" />
    <item
        android:id="@+id/action_play_next"
        android:orderInCategory="101"
        android:title="@string/action_play_next" />
</menu>

شامل دو گزینه ی add to favorite و next است.
برای اینکه ما RecyclerView خودمان را render کنیم باید یک آداپتور ایجاد کنیم تا album_card.xml ما رو inflate کند پس یک کلاس به نام  AlbumsAdapter.java ایجاد کرده کد های زیر را در آن قرار دهید.قبلتر علت ایجاد آداپتور را مفصل توضیح داده ایم در سایت جستجو کنید.

package ir.programchi;
import android.content.Context;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import java.util.List;
/**
 * Created by jefferson on 18/05/16.
 */
public class AlbumsAdapter extends RecyclerView.Adapter<AlbumsAdapter.MyViewHolder> {
    private Context mContext;
    private List<Album> albumList;
    public class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView title, count;
        public ImageView thumbnail, overflow;
        public MyViewHolder(View view) {
            super(view);
            title = (TextView) view.findViewById(R.id.title);
            count = (TextView) view.findViewById(R.id.count);
            thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
            overflow = (ImageView) view.findViewById(R.id.overflow);
        }
    }
    public AlbumsAdapter(Context mContext, List<Album> albumList) {
        this.mContext = mContext;
        this.albumList = albumList;
    }
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.album_card, parent, false);
        return new MyViewHolder(itemView);
    }
    @Override
    public void onBindViewHolder(final MyViewHolder holder, int position) {
        Album album = albumList.get(position);
        holder.title.setText(album.getName());
        holder.count.setText(album.getNumOfSongs() + " songs");
        // loading album cover using Glide library
        Glide.with(mContext).load(album.getThumbnail()).into(holder.thumbnail);
        holder.overflow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showPopupMenu(holder.overflow);
            }
        });
    }
    /**
     * 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_album, 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 menuItem) {
            switch (menuItem.getItemId()) {
                case R.id.action_add_favourite:
                    Toast.makeText(mContext, "Add to favourite", Toast.LENGTH_SHORT).show();
                    return true;
                case R.id.action_play_next:
                    Toast.makeText(mContext, "Play next", Toast.LENGTH_SHORT).show();
                    return true;
                default:
            }
            return false;
        }
    }
    @Override
    public int getItemCount() {
        return albumList.size();
    }
}

کد های بالا ابتدا view هر یک از object ها رو پیدا کرده سپس کارهای لازم را با آنها انجام میدهند مثل قرار دادن متن عکس و… و در بخشی کلیک مرتبط سه نقطه نیر فعال کردیم تا با کلیک بروی آن یک منو دو واحدی نمایش داده شود و زمانی که بروی آنها کلیک شود Toast نمایش داده می شود.onBindViewHolder هم مقدار text را از model ما دریافت کرده سپس برای هر کدام از card ها ست می کند.
حالا یک فایل به نام activity_main.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"
    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">
            <RelativeLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
                <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/white"
                        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/white"
                        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/content_main" />
</android.support.design.widget.CoordinatorLayout>

حالا یک فایل به نام content_main.xml که به نحوی بخش دیگری از همان activity_main.xml ما می باشد و کد های زیر را در آن قرار داده.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:background="@color/viewBg"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="ir.programchi.MainActivity"
    tools:showIn="@layout/activity_main">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:clipToPadding="false"
        android:scrollbars="vertical" />
</RelativeLayout>

و در آخر بخش MainActivtiy ما که چند کار مهم را انجام میدهد
initCollapsingToolbar() نمایش یا hide کردن Toolbar
prepareAlbums() یکسری داده sample را در برنامه قرار می دهد.
GridLayoutManager برای نمایش Recyclerview در حالت Grid به جای نمایش آن به صورت لیست استفاده شده است.
GridSpacingItemDecoration برای به دست آوردن فاصله دقیق در اطراف هر Cardview
AlbumsAdapter برای قرار دادن هر کارد و محتویات آنها در Recyclerview استفاده شده است.
پس کد های زیر را در MainActivity.java قرار داده.

package ir.programchi;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private AlbumsAdapter adapter;
    private List<Album> albumList;
    @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);
        albumList = new ArrayList<>();
        adapter = new AlbumsAdapter(this, albumList);
        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);
        prepareAlbums();
        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 albums for testing
     */
    private void prepareAlbums() {
        int[] covers = new int[]{
                R.drawable.album1,
                R.drawable.album2,
                R.drawable.album3,
                R.drawable.album4,
                R.drawable.album5,
                R.drawable.album6,
                R.drawable.album7,
                R.drawable.album8,
                R.drawable.album9,
                R.drawable.album10,
                R.drawable.album11};
        Album a = new Album("True Romance", 13, covers[0]);
        albumList.add(a);
        a = new Album("Xscpae", 8, covers[1]);
        albumList.add(a);
        a = new Album("Maroon 5", 11, covers[2]);
        albumList.add(a);
        a = new Album("Born to Die", 12, covers[3]);
        albumList.add(a);
        a = new Album("Honeymoon", 14, covers[4]);
        albumList.add(a);
        a = new Album("I Need a Doctor", 1, covers[5]);
        albumList.add(a);
        a = new Album("Loud", 11, covers[6]);
        albumList.add(a);
        a = new Album("Legend", 14, covers[7]);
        albumList.add(a);
        a = new Album("Hello", 11, covers[8]);
        albumList.add(a);
        a = new Album("Greatest Hits", 17, covers[9]);
        albumList.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()));
    }
}

 
این آموزش هم به پایان رسید دوستان فکر نکنید ما کم توضیح دادیم ما برای هر بخش به صورت مجزا یک مطلب ایجاد کرده ایم بهتر است ابتدا آنها را مطالعه کنید سپس از این آموزش استفاده کنید.
 
موفق و موید باشید.

مطالعه بیشتر