摘自: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);
}
}