在 Android笔记(二) Activity和布局 中,使用一个 String[] 模拟了一些数据,然后写入到一个 TextView 或者 ScrollView 中。
但这样有两个弊端:
ScrollView是一次性将内容绘制完毕,如果数据量很大,会导致内存消耗。
无法通过点击 String[] 里面的某一个 String 进入详细页面
于是我们引入了 RecyclerView 。想象一下,我们平时刷微博、刷知乎,随着我们不断地向下刷,数据是动态加载出来的。 这就是RecyclerView。 当然,如果在 RecyclerView 里面嵌套 CardView 就能显示很丰富的内容了。
RecyclerView原理 RecyclerView 有一个适配器 Adapter
Adapter 用于在必要时将某些数据源与 View 绑定,同时向 RecyclerView 提供新的 View。
那它如何提供呢? Adapter 是通过一个叫 ViewHolder 的对象来提供。 ViewHolder 包含了那些 View 的 Root View 。并且,ViewHolder 缓存了一些 View, 以降低请求更新的成本。
最后,Layout Manager 会告诉 RecyclerView 如何布局所有得到的这些 View , 例如,是垂直排列,还是水平、网格之类。
知道了以上原理之后,开发步骤就很明朗了:
添加 recyclerView 的依赖
在 layout 中创建一个 RecyclerView
创建单个 item 的layout resource file
创建 Adapter 类,实现内部类AdapterHolder
重写 Adapter 的三个方法
添加 Layout Manager
添加依赖 在 app/build.gradle 里面的 dependencies 添加依赖
1 2 3 dependencies { implementation 'com.android.support:recyclerview-v7:27.1.1' }
注意: 在新版本的 Gradle 中, compile 命令已经变更为 implementation ,或者 API
Layout 添加 recyclerView 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" > <android.support.v7.widget.RecyclerView android:id ="@+id/news_recycler_view" android:scrollbars ="vertical" android:layout_width ="match_parent" android:layout_height ="match_parent" /> </FrameLayout >
为每个子项添加布局 在 res/layout 创建一个新的 layout resource file
number_list_item.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 <?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 ="wrap_content" > <android.support.v7.widget.CardView xmlns:card_view ="http://schemas.android.com/apk/res-auto" android:id ="@+id/card_view" android:layout_width ="match_parent" android:layout_height ="wrap_content" card_view:cardBackgroundColor ="#FFFFFF" card_view:cardCornerRadius ="8dp" card_view:cardUseCompatPadding ="true" android:layout_gravity ="center" > <RelativeLayout android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" > <ImageView android:id ="@+id/news_photo" android:layout_width ="match_parent" android:layout_height ="240dp" android:layout_alignParentTop ="true" android:scaleType ="centerCrop" /> <TextView android:id ="@+id/news_title" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_below ="@id/news_photo" android:gravity ="center" android:maxLines ="1" android:padding ="5dp" android:textColor ="#ffffff" android:textSize ="20sp" /> </RelativeLayout > </android.support.v7.widget.CardView > </LinearLayout >
以上布局为一个 CardView 里面有一个 ImageView 和 TextView
创建 Adapter 类 当 RecyclerView 需要显示内容的时候,它首先会去找 Adapter 问应该显示哪些 items ,然后 RecyclerView 要求 Adapter 创建 ViewHolder 对象。
具体来说,Adapter主要做以下几件事:
为每个 RecyclerView 项目创建 ViewHolder 对象。
将数据来源的数据与每个项目绑定
返回数据来源中的项目数量
扩展将显示的每个项目视图
Adapter 类需要我们重写三个方法:
onCreateViewHolder()
:ViewHolder将被创建的时候调用,负责从xml中映射并创建View,并返回一个 ViewHolder 对象。
onBindViewHolder()
:数据源与View进行绑定的时候调用
getItemCount()
:返回计数器表示第几个 item
在写这三个方法前,我们先定义一个内部类作为 ViewHolder:
ViewHolder 的作用是将 xml 中的内容映射成 View 对象,它决定如何显示单个item 。
ViewHolder 将在 onCreateViewHolder()
方法中被实例化。之后,在 onBindViewHolder()
方法中填充每个项的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static class NewsViewHolder extends RecyclerView .ViewHolder{ CardView cardView; ImageView newsImage; TextView newsTitle; NewsViewHolder(final View itemView){ super (itemView); cardView = itemView.findViewById(R.id.card_view); newsImage = itemView.findViewById(R.id.news_photo); newsTitle = itemView.findViewById(R.id.news_title); newsTitle.setBackgroundColor(Color.argb(20 , 0 , 0 , 0 )); } }
然后重写 Adapter 的三个方法
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 public class NewsAdapter extends RecyclerView .Adapter<NewsAdapter.NewsViewHolder> { private List<News> newsList; private Context context; NewsAdapter(List<News> newsList, Context context) { this .newsList = newsList; this .context = context; } @NonNull @Override public NewsViewHolder onCreateViewHolder (@NonNull ViewGroup parent, int viewType) { View v = LayoutInflater.from(context).inflate(R.layout.news_item, parent, false ); return new NewsViewHolder (v); } @Override public void onBindViewHolder (@NonNull NewsViewHolder holder, int position) { News oneNews = newsList.get(position); holder.newsTitle.setText(oneNews.getTitle()); Glide.with(context).load("https://jerrysheh.github.io/images/Learn_Android/Android.jpg" ).into(holder.newsImage); } @Override public int getItemCount () { return newsList.size(); } }
LayoutManager ViewHolder 决定如何显示单个 item, 而 LayoutManager 则决定如何显示一堆 item。包括以下三种方式:
LayoutManager 同时负责回收不再需要的 view。
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 public class MainActivity extends AppCompatActivity { NewsAdapter mAdapter; RecyclerView mRecyclerView; List<News> newsList; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); newsList = new ArrayList <>(); String url = "https://jerrysheh.github.io/images/Learn_Android/Android.jpg" ; for (int i = 1 ; i < 11 ; i++) { newsList.add(new News (String.valueOf(i), url)); } mRecyclerView = findViewById(R.id.news_recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager (this ); mAdapter = new NewsAdapter (newsList, this ); mRecyclerView.setLayoutManager(layoutManager); mRecyclerView.setHasFixedSize(true ); mRecyclerView.setAdapter(mAdapter); } }
实现点击事件
其实可以在 RecyclerView 里嵌套 CardView, CardView 自带点击事件,就可以不用自己实现了。
Adapter类添加点击监听接口 在我们的 Adapter 中加入一个内部接口,然后定义一个 ListItemClickListener 点击监听器
1 2 3 4 5 final private ListItemClickListener mOnClickListener;public interface ListItemClickListener { void onListItemClick (int clickedItemIndex) ; }
修改Adapter构造函数 修改 Adapter 类的构造函数,添加第二个参数(ListItemClickListener类型的监听器listener)
1 2 3 4 5 public GreenAdapter (int numberOfItems, ListItemClickListener listener) { mNumberItems = numberOfItems; mOnClickListener = listener; viewHolderCount = 0 ; }
ViewHolder内部类实现点击监听接口
用 implements OnClickListener
语句实现接口
重写点击方法
ViewHolder的构造函数中调用点击方法
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 class NumberViewHolder extends RecyclerView .ViewHolder implements OnClickListener { TextView listItemNumberView; TextView viewHolderIndex; public NumberViewHolder (View itemView) { super (itemView); listItemNumberView = (TextView) itemView.findViewById(R.id.tv_item_number); viewHolderIndex = (TextView) itemView.findViewById(R.id.tv_view_holder_instance); itemView.setOnClickListener(this ); } void bind (int listIndex) { listItemNumberView.setText(String.valueOf(listIndex)); } @Override public void onClick (View v) { int clickedPosition = getAdapterPosition(); mOnClickListener.onListItemClick(clickedPosition); } }
MainActivity.java
implements GreenAdapter.ListItemClickListener
重写按钮监听方法
实例化 Adapter 的时候传入 this
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 public class MainActivity extends AppCompatActivity implements GreenAdapter .ListItemClickListener { private static final int NUM_LIST_ITEMS = 100 ; private GreenAdapter mAdapter; private RecyclerView mNumbersList; private Toast mToast; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); mNumbersList = (RecyclerView) findViewById(R.id.rv_numbers); LinearLayoutManager layoutManager = new LinearLayoutManager (this ); mNumbersList.setLayoutManager(layoutManager); mNumbersList.setHasFixedSize(true ); mAdapter = new GreenAdapter (NUM_LIST_ITEMS, this ); mNumbersList.setAdapter(mAdapter); } @Override public boolean onCreateOptionsMenu (Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true ; } @Override public boolean onOptionsItemSelected (MenuItem item) { int itemId = item.getItemId(); switch (itemId) { case R.id.action_refresh: mAdapter = new GreenAdapter (NUM_LIST_ITEMS, this ); mNumbersList.setAdapter(mAdapter); return true ; } return super .onOptionsItemSelected(item); } @Override public void onListItemClick (int clickedItemIndex) { if (mToast != null ) { mToast.cancel(); } String toastMessage = "Item #" + clickedItemIndex + " clicked." ; mToast = Toast.makeText(this , toastMessage, Toast.LENGTH_LONG); mToast.show(); }