آموزش RecyclerView و CardView در برنامه نویسی اندروید
سلام دوستان در این سری از آموزش های برنامه نویسی اندروید به آموزش RecyclerView و CardView در برنامه نویسی اندروید میپردازیم یکی از بهترین روش های متریال دیزاین پیاده سازی همزمان cardview و RecyclerView است و باعث میشه کاربر ارتباط بیشتری با برنامه شما برقرار کند و زمانی که کاربر در حال استفاده از برنامه شما است دیگر حوصله اش سر نمی رود و بهتر است بدانید CardView یکی از بهترین متریال دیزاین های جهان به حساب می آید در ادامه با ما همراه باشید.
قبل از شروع ابتدا یک پیش نمایشی از ماحصل نهایی کار ببینید بعد ادامه دهید.
ابتدا باید کتاب خانه های CardView , Recylerview و Glide را اضافه کنیم. قبلا نحوه استفاده از هر کدام از کتابخانه ها را به صورت مجزا گفته ایم کافی است در سایت سرچ کرده و آموزش ها را دنبال کنید.
پس وارد فایل Build.gradle شده نوع module خط های زیر را در قسمت dependencies قرار دهید.
1 2 3 4 5 6 | // 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 قرار دارد ) شده آن را به شکل زیر ویرایش کنید.
1 2 3 4 5 6 7 8 | <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 شده و کد های زیر را در آن قرار دهید (اضافه کنید) .
1 2 3 4 5 6 7 8 | <?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 وجود نداشت آن را ایجاد کنید سپس کد های زیر را در آن قرار دهید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <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 ایجاد کرده و کد های زیر را در آن قرار دهید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 همانند زیر می شود.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | <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 ایجاد کرده کدهای زیر را در آن قرار دهید.
1 2 3 4 5 6 7 8 9 10 11 12 | <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 ایجاد کرده کد های زیر را در آن قرار دهید.قبلتر علت ایجاد آداپتور را مفصل توضیح داده ایم در سایت جستجو کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | 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 ایجاد کرده کد های زیر را در آن قرار دهید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | <?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 ما می باشد و کد های زیر را در آن قرار داده.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?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 قرار داده.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | 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())); } } |
این آموزش هم به پایان رسید دوستان فکر نکنید ما کم توضیح دادیم ما برای هر بخش به صورت مجزا یک مطلب ایجاد کرده ایم بهتر است ابتدا آنها را مطالعه کنید سپس از این آموزش استفاده کنید.
موفق و موید باشید.
ببخشید استاد ولی یادتون رفت بگید که کتابخونه design رو اضافه کنند …
سلام اگر کمی کسری دارد متاسفم به خاطر مشغله کاری زیاد نمی رسم
عالی بود
سلام
خواهش می کنم
موفق باشید.
سلام
menu_album.xml
رو در کدام پوشه باید ایجاد کرد؟
سلام
در پوشه menu اگر ندارید در بخش res یک پوشه به نام menu ایجاد کنید.