Android RecyclerView*

一、RechclerView简介。

RecyclerView比listview更先进更灵活,对于很多的视图它就是一个容器,可以有效的重用和滚动。

1.可以通过设置LayoutManager可以实现Listview和横向Listview,GridView,横向Gridview和瀑布流等效果。

2.可以通过addItemDecoration添加Item分割线。

3.可以通过setItemAnimator()设置Item的增加和移除动画。

二、RecyclerView相关类介绍。

1、RecyclerView.Adapter:负责托管数据集,为每一项Item创建布局并绑定数据。

2、RecyclerView.ItemDecoration,给Item添加分割线。需要继承该类自定义一个类。

3、RecyclerView.ItemAnimator负责处理Item增加或删除时的动画效果,系统提供了一个默认的动画类DefaultItemAnimator()。

4、RecyclerView.ViewHolder:负责承载Item视图的子布局。

5、RecyclerView.LayoutManager:布局管理器,负责Item视图的布局的显示管理。分为:

(1)、LinearLayoutManager,类似Listview
他有两个构造函数:
LinearLayoutManager(Context context)//默认方向为垂直方向。
LinearLayoutManager(Context context, int orientation, boolean reverseLayout)
其中第二个参数orientation表示布局的方向,可以取两个值:垂直和水平。分别是纵向Listview的效果和横向Listview的效果。第三个参数reverseLayout表示是否反向布局(即纵向Listview上下颠倒),若为true,纵向Listview默认在最底部,而且第一项在最低下。
(2)、GridLayoutManager,类似GridView
三种构造函数:
GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) //可以直接在XMl中设置RecyclerView 属性”layoutManager”.
GridLayoutManager(Context context, int spanCount) //spanCount为列数,默认方向vertical
GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)
//spanCount为列数,orientation为布局方向,reverseLayout决定布局是否反向。
(3)、StaggeredGridLayoutManager流式布局
两个构造函数:
StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
StaggeredGridLayoutManager(int spanCount, int orientation) //spanCount为列数,orientation为布局方向

Item Animator动画

RecyclerView能够通过mRecyclerView.setItemAnimator(ItemAnimator animator)设置添加、删除、移动、改变的动画效果

RecyclerView提供了默认的ItemAnimator实现类:DefaultItemAnimator。如果没有特殊的需求,默认使用这个动画即可。

Item Decoration间隔样式

RecyclerView通过addItemDecoration()方法添加item之间的分割线。Android并没有提供实现好的Divider,因此任何分割线样式都需要自己实现

自定义间隔样式需要继承RecyclerView.ItemDecoration类,该类是个抽象类,官方目前并没有提供默认的实现类,主要有三个方法。

  • onDraw(Canvas c, RecyclerView parent, State state),在Item绘制之前被调用,该方法主要用于绘制间隔样式。
  • onDrawOver(Canvas c, RecyclerView parent, State state),在Item绘制之前被调用,该方法主要用于绘制间隔样式。
  • getItemOffsets(Rect outRect, View view, RecyclerView parent, State state),设置item的偏移量偏移的部分用于填充间隔样式,即设置分割线的宽、高;在RecyclerView的onMesure()中会调用该方法。

onDraw()onDrawOver()这两个方法都是用于绘制间隔样式,我们只需要复写其中一个方法即可。

局部刷新闪屏问题解决

对于RecyclerView的Item Animator,有一个常见的坑就是“闪屏问题”。
这个问题的描述是:当Item视图中有图片和文字,当更新文字并调用notifyItemChanged()时,文字改变的同时图片会闪一下。这个问题的原因是当调用notifyItemChanged()时,会调用DefaultItemAnimator的animateChangeImpl()执行change动画,该动画会使得Item的透明度从0变为1,从而造成闪屏。

解决办法很简单,在rv.setAdapter()之前调用((SimpleItemAnimator)rv.getItemAnimator()).setSupportsChangeAnimations(false)禁用change动画

点击事件

RecyclerView并没有像ListView一样暴露出Item点击事件或者长按事件处理的api,也就是说使用RecyclerView时候,需要我们自己来实现Item的点击和长按等事件的处理。
实现方法有很多:

  • 可以监听RecyclerView的Touch事件然后判断手势做相应的处理,
  • 也可以通过在绑定ViewHolder的时候设置监听,然后通过Apater回调出去

RecylerView相对于ListView的优点罗列如下:

  • RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。
    直接省去了listview中convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
  • 提供了一种插拔式的体验高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。
  • 设置布局管理器以控制Item布局方式横向竖向以及瀑布流方式
  • 可设置Item的间隔样式(可绘制)通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去书写代码。
  • 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。

     但是关于Item的点击和长按事件,需要用户自己去实现。

三、基本使用

1、导入android-support-v7-recyclerview

2、Activity布局文件

<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="com.raphets.recyclerview.MainActivity" >

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

3、Item的布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="#0099ff"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="120dp"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:gravity="center" />

</LinearLayout>

4、Activity类,RecyclerView的主要代码

public class MainActivity extends Activity {
    private RecyclerView mRecyclerView;
    private List<String> mDatas;
    private MyRecylerViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化数据
        initData();
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        adapter = new MyRecylerViewAdapter(this, mDatas);
        //绑定适配器
        mRecyclerView.setAdapter(adapter);
        // 给每个item添加分割线
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
        // 设置item增加和移除的动画
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        // 设置布局管理器
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(linearLayoutManager);

    }

    /*
     * 初始化数据
     */
    private void initData() {
        mDatas = new ArrayList<String>();
        for (int i = 0; i <= 50; i++) {
            mDatas.add("item---" + i);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        int id = item.getItemId();
        switch (id) {
        case R.id.listview:
            mRecyclerView.setLayoutManager(new LinearLayoutManager(this, OrientationHelper.VERTICAL, false));
            break;
        case R.id.gridView:
            mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3));
            break;
        case R.id.horizonalListview:
            mRecyclerView.setLayoutManager(new LinearLayoutManager(this, OrientationHelper.HORIZONTAL, false));
            break;
        case R.id.horizonalGridview:
            mRecyclerView.setLayoutManager(new GridLayoutManager(this, 5, OrientationHelper.HORIZONTAL, false));
            break;

        case R.id.add:
            adapter.notifyItemInserted(1);
            break;
        case R.id.delete:
            adapter.notifyItemRemoved(1);
            break;
        default:
            break;
        }

        return super.onOptionsItemSelected(item);
    }
}

5、适配器Adapter

public class MyRecylerViewAdapter extends Adapter<MyViewHolder> {
    private Context mContext;
    private List<String> mDatas;

    public MyRecylerViewAdapter(Context context, List<String> datas) {
        this.mContext = context;
        this.mDatas = datas;
    }

    @Override
    public int getItemCount() {
        // TODO Auto-generated method stub
        return mDatas.size();
    }


    @Override
    public void onBindViewHolder(MyViewHolder arg0, int arg1) {
        arg0.textView.setText(mDatas.get(arg1));

    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup arg0, int arg1) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item, arg0, false);
        MyViewHolder holder = new MyViewHolder(view);
        return holder;
    }

}

class MyViewHolder extends ViewHolder {
    // Item子布局上的一个元素
    TextView textView;

    public MyViewHolder(View itemView) {
        super(itemView);
        // 关联引动该元素 ,在item.xml中findView,注意不要忘写(itemview.)
        textView = (TextView) itemView.findViewById(R.id.textView);
    }
}

下拉后从上端刷新

(在demo中是名为PullDownRefresh的module) 
下拉从上端刷新,这个比较简单。在布局文件里,用SwipeRefreshLayout把RecyclerView包在里面,然后再在java代码里面写下拉的响应事件就好了。下面直接写代码: 
1.布局文件,把RecyclerView放在SwipeRefreshLayout里:

<android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/srl"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

2.java代码:

  //列表
        recyclerView= (RecyclerView) findViewById(R.id.rv);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        //添加数据
        list=new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add("第"+i+"项");
        }
        adapter=new ItemAdapter(list,this);
        recyclerView.setAdapter(adapter);

        //下拉加载控件
        swipeRefreshLayout= (SwipeRefreshLayout) findViewById(R.id.srl);
        swipeRefreshLayout.setColorSchemeColors(Color.BLUE);//设置旋转圈的颜色
        //下拉监听
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                list.add(0,"下拉加载出现的:"+i++);
                adapter.notifyDataSetChanged();
                swipeRefreshLayout.setRefreshing(false);//设置成true的话,下拉过后就会一直在那里转
            }
        });

(在demo中是名为PullUpRefresh的module)

3.上拉从下端刷新

设置一个监听器,在上拉到开始显示最下面一项时,加载更多项。 
监听器EndLessOnScrollListener代码:


public abstract class EndLessOnScrollListener extends RecyclerView.OnScrollListener {

    private static final String TAG = "EndLessOnScrollListener";

    LinearLayoutManager linearLayoutManager;

    //当前所在页
    private int currentPage=0;

    //已经加载出来的item数
    private int totalItemCount=0;

    //用来存储上一个totalItemCount
    private int previousTotal=0;

    //屏幕可见的item数量
    private int visibleItemCount;

    //屏幕可见第一个Item的位置
    private int firstVisibleItem;

    //是否上拉数据
    private boolean loading=true;

    public EndLessOnScrollListener(LinearLayoutManager linearLayoutManager) {
        this.linearLayoutManager = linearLayoutManager;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        visibleItemCount=recyclerView.getChildCount();
        totalItemCount=linearLayoutManager.getItemCount();
        firstVisibleItem=linearLayoutManager.findFirstVisibleItemPosition();
//去掉loading也可以,但是性能会下降,在每次滑动时都会判断,所以的加上
        if(loading){
            Log.d(TAG, "firstVisibleItem: " + firstVisibleItem);
            Log.d(TAG, "totalItemCount:" + totalItemCount);
            Log.d(TAG, "visibleItemCount:" + visibleItemCount);
            Log.d(TAG, "currentPage:" + currentPage);
            if(totalItemCount>previousTotal){
                //说明数据项已经加载结束
                loading=false;
                previousTotal=totalItemCount;
            }
        }
        //实际效果是滑动到已加载页最后一项可见的瞬间,添加下一页
        if(!loading&&totalItemCount-visibleItemCount<=firstVisibleItem){
            currentPage++;
            onLoadMore(currentPage);
            loading=true;
        }

    }

    /**
     * 提供一个抽闲方法,在Activity中监听到这个EndLessOnScrollListener
     * 并且实现这个方法
     * 这个方法在可见的页的最后一项,可见时调用
     * currentPage是加载到的页面编号
     */
    public abstract void onLoadMore(int currentPage);

给recyclerview添加上拉监听事件即可,这里我让它每次加5项:

  recyclerView.addOnScrollListener(new EndLessOnScrollListener(linearLayoutManager) {
            @Override
            public void onLoadMore(int currentPage) {
                for (int i = count; i < 5+count; i++) {
                    list.add("上拉加载"+i);
                }
                adapter.notifyDataSetChanged();
                count+=5;
            }
        });

4.添加尾部首部分别添加footer和Head

(在demo中是名为HeaderAndFooter的module) 
实现方法,主要是在适配器里实现。要在适配器必须写的方法里面和getItemViewType()方法里,考虑可能最前和最后一项分别是header和footer情况。

1.temAdapter里的代码


    private static final int TYPE_HEADER = 0;
    private static final int TYPE_FOOTER = 1;
    private static final int TYPE_NORMAL = 2;


    public ItemAdapter(List<String> list, Context context) {
        this.list = list;
        this.context = context;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (headerView != null && viewType == TYPE_HEADER) {
            return new MyViewHolder(headerView);
        }
        if (footerView != null && viewType == TYPE_FOOTER) {
            return new MyViewHolder(footerView);
        }

        MyViewHolder holder = new MyViewHolder(LayoutInflater.from(context).
                inflate(R.layout.item_layout, parent, false));
        return holder;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, final int position) {
        if (getItemViewType(position) == TYPE_NORMAL) {
            holder.tv.setText(list.get(position - 1));
            return;
        } else if (getItemViewType(position) == TYPE_HEADER) {
            return;
        } else
            return;
    }

    /**
     * 重写这个方法,很重要,是加入Header和Footer的关键,我们通过判断item的类型,从而绑定不同的view
     */
    @Override
    public int getItemViewType(int position) {
        if (headerView == null && footerView == null) {
            return TYPE_NORMAL;
        }
        if (position == 0) {
            //第一个item应该加载Header
            return TYPE_HEADER;
        }
        if (position == getItemCount() - 1) {
            //最后一个,应该加载Footer
            return TYPE_FOOTER;
        }
        return TYPE_NORMAL;
    }

    @Override
    public int getItemCount() {
        if (headerView == null && footerView == null) {
            return list.size();
        } else if (headerView == null && footerView != null) {
            return list.size() + 1;
        } else if (headerView != null && footerView == null) {
            return list.size() + 1;
        } else {
            return list.size() + 2;
        }
    }

    public View getHeaderView() {
        return headerView;
    }

    public void setHeaderView(View headerView) {
        this.headerView=headerView;
        notifyItemInserted(0);
    }

    public View getFooterView() {
        return footerView;
    }

    public void setFooterView(View footerView) {
        this.footerView=footerView;
        notifyItemInserted(getItemCount()-1);
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        TextView tv;

        public MyViewHolder(View itemView) {
            super(itemView);
            tv = itemView.findViewById(R.id.tv);
        }
    }

活动里面的代码:

RecyclerView recyclerView;
    ItemAdapter adapter;
    List<String>list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
 private void initView() {
        list= new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add("第"+i+"项");
        }
        adapter=new ItemAdapter(list,this);

        recyclerView= (RecyclerView) findViewById(R.id.rv);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        //注意,以下两个方法必须在setAdapter()之后调用,否则长和宽会变成wrap_content
        addHeader();
        addFooter();

    }

    private void addHeader(){
        View header= LayoutInflater.from(this).inflate(R.layout.header_layout,recyclerView,false);
        adapter.setHeaderView(header);
    }

    private  void addFooter(){
        View footer= LayoutInflater.from(this).inflate(R.layout.footer_layout,recyclerView,false);
        adapter.setFooterView(footer);
    }

tem拖拽和滑动删除

ItemTouchHelper是一个处理RecyclerView的滑动删除和拖拽的辅助类,RecyclerView 的item拖拽移动和滑动删除就靠它来实现。

ItemTouchHelper的监听如下

    itemTouchHelper=new ItemTouchHelper(new ItemTouchHelper.Callback() {
 
        //用于设置拖拽和滑动的方向
        @Override
        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            int dragFlags=0,swipeFlags=0;
            if(recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager||recyclerView.getLayoutManager() instanceof GridLayoutManager){
               //网格式布局有4个方向
               dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
            }else if(recyclerView.getLayoutManager() instanceof LinearLayoutManager){
               //线性式布局有2个方向
               dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN;
 
               swipeFlags = ItemTouchHelper.START|ItemTouchHelper.END; //设置侧滑方向为从两个方向都可以
            }
            return makeMovementFlags(dragFlags,swipeFlags);//swipeFlags 为0的话item不滑动
        }
 
        //长摁item拖拽时会回调这个方法
        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            int from=viewHolder.getAdapterPosition();
            int to=target.getAdapterPosition();
 
            Meizi moveItem=meizis.get(from);
            meizis.remove(from);
            meizis.add(to,moveItem);//交换数据链表中数据的位置
 
            mAdapter.notifyItemMoved(from,to);//更新适配器中item的位置
            return true;
        }
 
        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        //这里处理滑动删除
        }
 
        @Override
        public boolean isLongPressDragEnabled() {
            return false;//返回true则为所有item都设置可以拖拽
        }
    });

 

itemTouchHelper需要与recyclerView绑定才有效果,在recyclerView初始化的时候调用

     itemTouchHelper.attachToRecyclerView(recyclerview);

因为我想要网格布局中的图片item可拖拽,而页数item不可拖拽,所以我isLongPressDragEnabled()方法返回的false,而在item的长点击事件监听中做具体处理。

       mAdapter.setOnItemClickListener(new GridAdapter.OnRecyclerViewItemClickListener() {
       @Override
       public void onItemClick(View view) {
       }
 
        @Override
        public void onItemLongClick(View view) {
              itemTouchHelper.startDrag(recyclerview.getChildViewHolder(view));//设置拖拽item
           }
       });

 

如果你想为item设置拖拽和滑动时的响应动画效果,可以利用ItemTouchHelper的下面三个方法。用线性布局示例:

//当item拖拽开始时调用
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
 if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
       viewHolder.itemView.setBackgroundColor(Color.LTGRAY);//拖拽时设置背景色为灰色
    }
}
 
//当item拖拽完成时调用
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
 viewHolder.itemView.setBackgroundColor(Color.WHITE);//拖拽停止时设置背景色为白色
}
 
 //当item视图变化时调用
 @Override
 public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
 super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
  //根据item滑动偏移的值修改item透明度。screenwidth是我提前获得的屏幕宽度
  viewHolder.itemView.setAlpha(1-Math.abs(dX)/screenwidth);
 }

 


拖拽和滑动删除.gif

 

 

 

 
posted @ 2018-01-04 14:19  chenxibobo  阅读(446)  评论(0编辑  收藏  举报