摘自:http://blog.csdn.net/lmj623565791/article/details/45059587
参考:http://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/
一、RecyclerView简介
是Google2014年发布的用来代替ListView、GridView的控件
优点:
①、能够控制显示的方法,设定自己的布局。
②、能够自定义分割线(Divider)
③、能够自定义Item动画。
④、相对于ListView,提高了item的复用性。极大的封装了Item与Adapter的创建
缺点:
没有点击事件,也就是说点不了显示的item.需要自定义。
可能有些SDK没有自带RecyclerVIew所以可以添加:
compile 'com.android.support:recyclerview-v7:23.1.0'
从本地SDK中添加RecyclerVIew。
二、RecyclerView的使用
①、ReyclerView的基础结构
mRecyclerView = findView(R.id.id_recyclerview); //设置布局管理器 mRecyclerView.setLayoutManager(layout); //设置adapter mRecyclerView.setAdapter(adapter) //设置Item增加、移除动画 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); //添加分割线 mRecyclerView.addItemDecoration(new DividerItemDecoration( getActivity(), DividerItemDecoration.HORIZONTAL_LIST));通过上面的例子:可以将Recycler分为四部分。
①、设定布局(LayoutManager):
作用:设定RecyclerView的布局和显示。
RecyclerView默认使用线性竖直布局(同ListView的显示方法)
布局部分(自带布局):
LinearLayoutManager:线性布局管理器。 包含纵向布局(VERTICAL)、横向布局(HORIZONTAL)
示例:
LinearLayoutManager layout = new LinearLayoutManager(context); layout.setOrientation(LinearLayoutManager.VERTICAL);方式与创建LinearLayout不多。
GridLayoutManager:网格布局。 输入参数:每行有几列。
GridLayoutManager layout = newGridLayoutManager(context, 4);//表示有四列StaggeredGridLayoutManager:瀑布布局。 输入参数:列数,横向布局(左右滑动)、还是纵向布局(上下滑动)
new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL)//表示有四列,纵向滑动
显示部分:
findFirstVisibleItemPosition:获取当前显示在界面上的第一个item的position
findLastVisibleItemPosition:获取当前显示在界面上的第最后一个item的position
findFirstCompletelyVisibleItemPosition:获取当前完全显示在界面上的第一个item的position
findLastCompletelyVisibleItemPosition:获取当前完全显示在界面上的第最后一个item的position
②、设定Adapter
基本原理:首先通过ViewHolder实现对item视图的封装。 然后通过Adapter调动onCreateViewHolder()创建ViewHolder,当item显示在屏幕的时候,会回调onBindViewHolder()所以在该方法中实现Item的逻辑
首先创建item的布局
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!--就一个TextView--> <TextView android:id="@+id/tv_content" android:layout_width="100dp" android:layout_height="100dp" android:gravity="center" android:layout_gravity="center" /> </FrameLayout>然后设定Adapter:
/** * Created by PC on 2016/8/17. * 1、继承RecyclerView.Adapter * 2、创建ViewHolder类,继承RecyclerView.ViewHolder,用来初始化封装item视图。Adapter只接受ViewHolder * 3、结合数据,设定 */ public class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder> { private Context mContext; private List<String> mStrItems; public HomeAdapter(Context context,List<String> strItems) { mContext = context; mStrItems = strItems; } //将View封装 @Override public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View v = LayoutInflater.from(mContext).inflate(R.layout.item_content,viewGroup,false); MyViewHolder viewHolder = new MyViewHolder(v); return viewHolder; } //当item出现在屏幕的时候调用 @Override public void onBindViewHolder(MyViewHolder s, int i) { s.tvContent.setText(mStrItems.get(i)); } @Override public int getItemCount() { return mStrItems.size(); } class MyViewHolder extends RecyclerView.ViewHolder{ public TextView tvContent; public MyViewHolder(View itemView) { super(itemView); tvContent = (TextView)itemView.findViewById(R.id.tv_content); } } }
最后:在Activity中调用
public class MainActivity extends AppCompatActivity { private RecyclerView mRecyclerView; private HomeAdapter mHomeAdapter; private List<String> mStrItems; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRecyclerView = (RecyclerView) findViewById(R.id.main_recycler); //自定义数据 mStrItems = new ArrayList<>(); for(int i=0; i<26; ++i){ char c = 'A'; char data = (char)(c+i); mStrItems.add(Character.toString(data)); } mHomeAdapter = new HomeAdapter(this,mStrItems); //设定布局管理器 mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); //加载Adapter mRecyclerView.setAdapter(mHomeAdapter); } }结果:
③、实现分割线效果(以LinearLayoutManager布局)
第一种方法:直接在layout.xml中的FrameLayout中加上marginBottom。
第二种:继承ItemDecorate。
1、我们先看下ItemDecorate需要重写的方法
onDraw(Canvas c, RecyclerView parent, State state)
//用Canvas来绘制divider。所以说divider是画图绘制上去,而不是添加控件到item视图中。所以不会改变item视图的大小。
onDrawOver(Canvas c, RecyclerView parent, State state)
//同样是绘制divider。跟onDraw()的差别在于绘制流程。decoration 的 onDraw,child view 的 onDraw,decoration 的 onDrawOver,这三者是依次发生的。而由于 onDrawOver 是绘制在最上层的,所以它的绘制位置并不受限制。
getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
//可以理解为利用outRect设置item视图的padding。就是说outRect(10,0,0,0);相当于item.paddingLeft = 10;
为什么用outRect 不用 left、top、right、bottom表示。据说是为了能够达到复用的效果。
2、所有的方法都介绍完了,那么开始制作吧。
首先:制作divider的分割线图
然后:绘制分割线
最后:为了不让分割线占用原本item视图的大小,调用getItemOffset()方法,为item设置padding
示例:
/** * Created by PC on 2016/8/17. * 要做的事情 * 1、判断RecyclerView是竖着还是横着 * 2、利用android自带的listDivider来设置分割线 * 3、因为分割线是draw上去的,还需要设置Item之间的间距,来设置 */ public class DividerLinearItemDecorate extends RecyclerView.ItemDecoration { //获取android自带的listDivider private static final int [] STYLEABLE = {android.R.attr.listDivider}; private static final int VERTICAL = LinearLayoutManager.VERTICAL; private static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL; private Context mContext; private Drawable mDivider; private int mOrientation; public DividerLinearItemDecorate(Context context, int orientation) { mContext = context; initWidget(orientation); } private void initWidget(int orientation){ //获取Android自带的divider TypedArray a = mContext.obtainStyledAttributes(STYLEABLE); mDivider = a.getDrawable(0); //判定 输入的orientation是否有错误 switch (orientation){ case VERTICAL: mOrientation = orientation; break; case HORIZONTAL: mOrientation = orientation; break; default: throw new IllegalArgumentException("传入的orientation错误"); } } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); //绘制divider if (mOrientation == VERTICAL){ drawVertical(c,parent); } else { drawHorizontal(c,parent); } } private void drawVertical(Canvas c,RecyclerView parent){ int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight(); for(int i=0; i<parent.getChildCount(); ++i){ View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int top = child.getBottom()+params.bottomMargin; int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left,top,right,bottom); mDivider.draw(c); } } private void drawHorizontal(Canvas c,RecyclerView parent){ int top = parent.getPaddingTop(); int bottom = parent.getHeight() - parent.getPaddingBottom(); for(int i=0; i<parent.getChildCount(); ++i){ View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int left = child.getLeft()+params.rightMargin; int right = left+mDivider.getIntrinsicWidth(); //divider绘制的区域 mDivider.setBounds(left,top,right,bottom); mDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); if (mOrientation == VERTICAL){ outRect.set(0,0,0,mDivider.getIntrinsicHeight()); } else { outRect.set(0,0,mDivider.getIntrinsicWidth(),0); } } }使用:
mRecyclerView.setItemDecoreation(new DividerLinearItemDecorate(context,LinearLayoutManager.VERTICAL));
遇到的问题:
①、获取android自带的divider的原理
首先需要看下鸿洋:深入理解Android自定义属性
补充:
TypedArray a = mContext.obtainStyledAttributes(STYLEABLE);会发现,obtainStyledAttributes();根本没有载入attribute,那么是a是如何获得值的呢?
原因是,android在生成的时候,其实内部已经初始化了自己的原有attibute。(就是style中的AppTheme)
然后,我们通过obtainStyledAttibutes(attr)其实表示的是自身在layout定义的属性,覆盖原先的attribute,所得到的集合。
所以可以获取原生listDivider的值。
那么使用这种方式的优点在哪里?
因为我们使用的从android中获取的dividier。所以能够在style中直接修改
<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> <!--在style中直接修改--> <item name="android:listDivider">@drawable/divider_home</item> </style>
首先在ActionBar上添加按钮。
了解:menu的使用
然后,我们增加或者删除数据的时候如何刷新呢?
ListView使用的是notifyChanged()。
但是使用RecyclerView就变成了
public void addData(int pos){ //pos表示在第几个位置插入 mStrItems.add(pos,"数据"); notifyItemInserted(pos); } public void removeData(int pos){ //pos表示在第几个位置插入 mStrItems.remove(pos); notifyItemRemoved(pos); }
⑤、由于Recycler没有点击事件,所以需要自定义点击事件
//第一步:设置监听器接口 public interface OnItemSelectedListener{ void itemSelected(int position); } //第二步:添加设置监听器方法 public void setOnItemSeletedListener(OnItemSelectedListener listener){ mListener = listener; } //第三步:设置回调 @Override public void onBindViewHolder(MyViewHolder s, int i) { s.tvContent.setText(mStrItems.get(i)); //设置回调 if (mListener != null){ mListener.itemSelected(i); } }