最近看了不少 RecyclerView 的使用文章,一直晕乎乎的,彻底不知道套路是啥。不少人都是直接上代码,可是却没有详细说明代码的使用,因而打算本身写写,理理思路。顺便帮助那些正在学习 Android 的新人。android
本文源码参见 https://github.com/huanshen/Learn-Android/tree/master/recycleTestgit
首先 recycleView 须要咱们引入,因此在 build.gradle ( model ) 中引入:github
compile 'com.android.support:recyclerview-v7:26.0.+'
下面咱们开始写布局文件:canvas
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/my_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical"/> </RelativeLayout>
这至关于在屏幕上占了个位置给 recyclerView 用,可是咱们还得为其添加 item 项,item 也得有本身的布局呢,因而咱们继续编写下面这个布局。数组
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="wenzi " android:id="@+id/text"/> </LinearLayout>
布局写好了,接下去就是怎么把数据在 view 上进行显示。这个固然得用到 适配器啦, recycleView 有本身的适配器,咱们只须要继承就好,而后编写具体的处理代码,具体以下,后面有详细的分析的。app
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { public String[] datas = null; public MyAdapter(String[] data) { datas = data; } //建立新View,被LayoutManager所调用 @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item,viewGroup,false); ViewHolder vh = new ViewHolder(view); return vh; } //将数据与界面进行绑定的操做 @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { viewHolder.mTextView.setText(datas[position]); } //获取数据的数量 @Override public int getItemCount() { return datas.length; } //自定义的ViewHolder,持有每一个Item的的全部界面元素 public class ViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public ViewHolder(View view){ super(view); mTextView = (TextView) view.findViewById(R.id.text); } } }
这里咱们再来细细分析下继承以后要写的东西,首先是构造函数 MyAdapter,用来传入所须要的数据。通常都是数组或者链表之类的,这样才能造成列表啊。ide
其次是 onCreateViewHolder, 我把它翻译成 “建立视图容器”,就是用来装 item 视图的。先获取 item 的视图,而后再把它放进容器便可。函数
接下去就是 onBindViewHolder,就是对容器里的 item 的每个项进行绑定,这样咱们才能将数据映射到 view 上进行显示啊。布局
而后就是 getItemCount 了,它其实就是返回一个数量,就是最后到底建立了几个。性能
最后呢,咱们自定义了一个 ViewHolder ,继承于RecyclerView.ViewHolder。 这个就更好理解啦,就是咱们要把 item 中的每一项都先装进ViewHolder 这个大容器里面,这样咱们才能进行前面 绑定啊。这里定义了一个 mTextView, 注意它也出如今了 onBindViewHolder 中噢。
把代码好好看一遍,而后再阅读一遍上面的分析,你应该就知道怎么用了呢。
下一步,咱们就要开始用它们啦,具体见代码以下:
private RecyclerView mRecyclerView; private RecyclerView.LayoutManager mLayoutManager; private RecyclerView.Adapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRecyclerView = (RecyclerView)findViewById(R.id.my_recycler_view); //建立默认的线性LayoutManager mLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(mLayoutManager); //若是能够肯定每一个item的高度是固定的,设置这个选项能够提升性能 mRecyclerView.setHasFixedSize(true); //建立并设置Adapter mAdapter = new MyAdapter(new String[]{"1231","43252345","2342342"}); mRecyclerView.setAdapter(mAdapter); }
首先咱们得找到原来的 RecyclerView 这个大容器,而后咱们建立一个默认的线性 Layoutmanger,并设置 RecyclerView 为线性得得。 而后咱们在将数据传入到 MyAdapter 中,并建立一个它的实例,最后调用 setAdapter 便可。
咱们前面展示的是 只有一个 item,而且样式都是同样的,那咱们能不能有多种不一样的样式呢?答案是能够。
如上图所示,咱们让它奇偶采用不一样的样式。固然,咱们彻底还能够自由发挥实现有图和无图的。
那这个具体怎么实现的呢,仍是在上一个代码基础上进行修改。
首先,咱们添加一个新的样式,叫 item1.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:id="@+id/root" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="56dp" android:text="wenzi " android:background="@color/colorAccent" android:layout_margin="10dp" android:id="@+id/text"/> </LinearLayout>
而后咱们开始添加实现脸多个item 的代码,主要添加在 咱们本身写的适配器里面,activity 的代码不用动。
private final int ITEM = 1; private final int ITEM1 = 2;
首先咱们定义了两个常量来表示 两种不一样的类型。由于有两种类型,天然要定义两种不一样的 viewhold 了。具体代码以下:
//自定义的ViewHolder,持有每一个Item的的全部界面元素 public class ImageHolder extends RecyclerView.ViewHolder { public TextView mTextView; public ImageHolder(View view){ super(view); mTextView = (TextView) view.findViewById(R.id.text); } } public class ColorHolder extends RecyclerView.ViewHolder{ public TextView mTextView; public ColorHolder(View view){ super(view); mTextView = (TextView) view.findViewById(R.id.text); } }
一样的咱们也要建立两种不一样的 viewHold 容器,那咱们怎样才能知道咱们须要建立哪种呢?
这时候,getItemViewType 就能够派上用场了,这个函数就是根据不一样的位置,为咱们肯定不一样的类型的。
因为咱们的数据比较简单,我是用奇偶来划分的。那若是真的操做的时候,咱们必须在提供数据的同时,还要提供数据的类别,这样咱们才知道那种数据采用哪一种 view 进行展现。
public int getItemViewType(int position) { if (position % 2 == 0){ return ITEM; } return ITEM1; }
当咱们把数据类型划定好了,就能够来建立 viewHold 了。
//建立新View,被LayoutManager所调用 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { if (viewType == ITEM) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false); ImageHolder vh = new ImageHolder (view); return vh; }else{ View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item1, viewGroup, false); ColorHolder vh = new ColorHolder(view); return vh; } }
上面的代码,咱们根据不一样的类型,来引用不一样的布局。
建立好以后,就是对数据进行绑定啦。具体代码以下:
//将数据与界面进行绑定的操做 @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { if (viewHolder instanceof ImageHolder) { //Toast.makeText(MainActivity.this, datas[position], Toast.LENGTH_SHORT).show(); ((ImageHolder)viewHolder).mTextView.setText(datas[position]); viewHolder.itemView.setTag(position); }else { ((ColorHolder)viewHolder).mTextView.setText(datas[position]); viewHolder.itemView.setTag(position); } }
绑定的时候,咱们要对 viewHold 的类型进行断定,只有这样咱们才能正确的将数据绑定到 view 上。
好了,到这里咱们就完成了呢,相信你应该可以掌握 recycleView 了。
咱们让每一个 item 点击的时候,弹出相对应的 position 。
首先,咱们要实现 View.OnClickListener 接口,让 item 能够点击。
public class ImageHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public TextView mTextView; public ImageHolder(View view){ super(view); mTextView = (TextView) view.findViewById(R.id.text); view.setOnClickListener(this); } @Override public void onClick(View view) { if (mOnRvItemClick != null) mOnRvItemClick.onItemClick(view, getAdapterPosition()); } }
这里,咱们在 ImageHolder 实现该接口,重写了 onClick 方法。方法里面的内容后面再说。该方法的参数并无 position ,所以须要咱们在作一些处理。position 位置的获取主要是经过 getAdapterPosition 来实现的。
咱们定义了一个 item 的点击接口
public interface onRecyclerViewItemClick { void onItemClick(View v, int position); }
这里咱们定义了一个 onRecyclerViewItemClick 接口,其内部方法则是 onItemClick,能够看到咱们有 position 参数了。
public MyAdapter(String[] data, onRecyclerViewItemClick onRvItemClick) { datas = data; mOnRvItemClick = onRvItemClick; }
而后咱们在初始化的时候,将 onRecyclerViewItemClick 的实例 onRvItemClick 传给私有参数 mOnRvItemClick;而后再 onClick 中进行调用,具体见前面的所说的。
最后,咱们在调用便可。
mAdapter = new MyAdapter(data, new MyAdapter.onRecyclerViewItemClick() { @Override public void onItemClick(View v, int position) { Toast.makeText(MainActivity.this, "第" + position + "行", Toast.LENGTH_SHORT).show(); } }); mRecyclerView.setAdapter(mAdapter);
recyclerView 有一个默认的分割线方法:DividerItemDecoration。
只需这样调用就能够:
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
分割线也是分水平仍是竖直的。若是咱们须要自定义本身的分割线也是能够的。咱们只须要 extends RecyclerView.ItemDecoration 重写其中的一些方法就能够。
1 package com.example.shenjiaqi.httpshiyong; 2 3 import android.annotation.SuppressLint; 4 import android.content.Context; 5 import android.content.res.TypedArray; 6 import android.graphics.Canvas; 7 import android.graphics.Rect; 8 import android.graphics.drawable.Drawable; 9 import android.support.annotation.NonNull; 10 import android.support.v7.widget.LinearLayoutManager; 11 import android.support.v7.widget.RecyclerView; 12 import android.view.View; 13 import android.widget.LinearLayout; 14 15 /** 16 * Created by shenjiaqi on 2017/10/22. 17 */ 18 19 public class MyDecoration extends RecyclerView.ItemDecoration { 20 /** 21 * 22 * @param outRect 边界 23 * @param view recyclerView ItemView 24 * @param parent recyclerView 25 * @param state recycler 内部数据管理 26 *//* 27 @Override 28 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 29 //设定底部边距为1px 30 *//*outRect.set(0, 0, 0, 30);*//* 31 }*/ 32 33 public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 34 public static final int VERTICAL = LinearLayout.VERTICAL; 35 36 private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; 37 38 private Drawable mDivider; 39 40 /** 41 * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}. 42 */ 43 private int mOrientation; 44 45 private final Rect mBounds = new Rect(); 46 47 /** 48 * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a 49 * {@link LinearLayoutManager}. 50 * 51 * @param context Current context, it will be used to access resources. 52 * @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}. 53 */ 54 public MyDecoration(Context context, int orientation) { 55 final TypedArray a = context.obtainStyledAttributes(ATTRS); 56 mDivider = a.getDrawable(0); 57 a.recycle(); 58 setOrientation(orientation); 59 } 60 61 /** 62 * Sets the orientation for this divider. This should be called if 63 * {@link RecyclerView.LayoutManager} changes orientation. 64 * 65 * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} 66 */ 67 public void setOrientation(int orientation) { 68 if (orientation != HORIZONTAL && orientation != VERTICAL) { 69 throw new IllegalArgumentException( 70 "Invalid orientation. It should be either HORIZONTAL or VERTICAL"); 71 } 72 mOrientation = orientation; 73 } 74 75 /** 76 * Sets the {@link Drawable} for this divider. 77 * 78 * @param drawable Drawable that should be used as a divider. 79 */ 80 public void setDrawable(@NonNull Drawable drawable) { 81 if (drawable == null) { 82 throw new IllegalArgumentException("Drawable cannot be null."); 83 } 84 mDivider = drawable; 85 } 86 87 @Override 88 public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 89 if (parent.getLayoutManager() == null) { 90 return; 91 } 92 if (mOrientation == VERTICAL) { 93 drawVertical(c, parent); 94 } else { 95 drawHorizontal(c, parent); 96 } 97 } 98 99 @SuppressLint("NewApi") 100 private void drawVertical(Canvas canvas, RecyclerView parent) { 101 canvas.save(); 102 final int left; 103 final int right; 104 if (parent.getClipToPadding()) { 105 left = parent.getPaddingLeft(); 106 right = parent.getWidth() - parent.getPaddingRight(); 107 canvas.clipRect(left, parent.getPaddingTop(), right, 108 parent.getHeight() - parent.getPaddingBottom()); 109 } else { 110 left = 0; 111 right = parent.getWidth(); 112 } 113 114 final int childCount = parent.getChildCount(); 115 for (int i = 0; i < childCount; i++) { 116 final View child = parent.getChildAt(i); 117 parent.getDecoratedBoundsWithMargins(child, mBounds); 118 final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); 119 final int top = bottom - mDivider.getIntrinsicHeight(); 120 mDivider.setBounds(left, top, right, bottom); 121 mDivider.draw(canvas); 122 } 123 canvas.restore(); 124 } 125 126 @SuppressLint("NewApi") 127 private void drawHorizontal(Canvas canvas, RecyclerView parent) { 128 canvas.save(); 129 final int top; 130 final int bottom; 131 if (parent.getClipToPadding()) { 132 top = parent.getPaddingTop(); 133 bottom = parent.getHeight() - parent.getPaddingBottom(); 134 canvas.clipRect(parent.getPaddingLeft(), top, 135 parent.getWidth() - parent.getPaddingRight(), bottom); 136 } else { 137 top = 0; 138 bottom = parent.getHeight(); 139 } 140 141 final int childCount = parent.getChildCount(); 142 for (int i = 0; i < childCount; i++) { 143 final View child = parent.getChildAt(i); 144 parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); 145 final int right = mBounds.right + Math.round(child.getTranslationX()); 146 final int left = right - mDivider.getIntrinsicWidth(); 147 mDivider.setBounds(left, top, right, bottom); 148 mDivider.draw(canvas); 149 } 150 canvas.restore(); 151 } 152 153 @Override 154 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 155 RecyclerView.State state) { 156 int childAdapterPosition = parent.getChildAdapterPosition(view); 157 158 int lastCount = parent.getAdapter().getItemCount() - 1; 159 //若是当前条目与是最后一个条目,就不设置divider padding 160 if (childAdapterPosition == lastCount) { 161 outRect.set(0, 0, 0, 0); 162 return; 163 } 164 if (mOrientation == VERTICAL) { 165 outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); 166 } else { 167 outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 168 } 169 } 170 }
为了用咱们本身的分割线
在 drawable 中建一个 divider.xml 的文件
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <gradient android:centerColor="#ff00ff00" android:endColor="#ff0000ff" android:startColor="#ffff0000" android:type="linear" /> <size android:height="4dp"/> </shape>
而后咱们再在 style 文件中放一个 item ,name 就叫 android:listDivider;引入上面的文件。
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="android:listDivider">@drawable/divider</item> </style> </resources>