RecyclerView用于在有限的窗口展现大量的数据,其实早已经有了类似的控件,如ListView、GridView,那么相比它们,RecyclerView有什么样优势呢?
RecyclerView标准化了ViewHolder,而且异常的灵活,可以轻松实现ListView实现不了的样式和功能,通过布局管理器LayoutManager可控制Item的布局方式,通过设置Item操作动画自定义Item添加和删除的动画,通过设置Item之间的间隔样式,自定义间隔。
设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式。
可设置Item操作的动画(删除或者添加等)
可设置Item的间隔样式(可绘制)
关于Item的点击和长按事件,需要用户自己去实现
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
LinearLayoutManager mLayoutManager=new LinearLayoutManager(this);
// 设置布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 设置adapter
mRecyclerView.setAdapter(mAdapter);
// 设置Item添加和移除的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// 设置Item之间间隔样式
mRecyclerView.addItemDecoration(mDividerItemDecoration);
首先需要在在 build.gradle 文件中引入 RecyclerView 类
compile 'com.android.support:recyclerview-v7:23.4.0'
package com.demo.fragment;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.demo.R;
import com.demo.adapter.VideoRecyclerViewAdapter;
import com.demo.bean.VideoBean;
import java.util.ArrayList;
import java.util.List;
public class ListViewFragment extends Fragment{
public static ListViewFragment newInstance() {
return new ListViewFragment();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_recycler_view, container, false);
initView(view);
return view;
}
private void initView(View view) {
RecyclerView recyclerView = view.findViewById(R.id.rv);
LinearLayoutManager layoutManager=new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);// 设置布局管理器
DefaultItemAnimator itemAnimator = new DefaultItemAnimator();
recyclerView .setItemAnimator(itemAnimator);// 设置Item添加和移除的动画
itemAnimator.setSupportsChangeAnimations(false);
recyclerView.setAdapter(new VideoRecyclerViewAdapter(getVideoList(), getActivity()));
}
public List getVideoList() {
List videoList = new ArrayList<>();
//...添加数据
return videoList;
}
}
R.layout.activity_recycler_view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" 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"/>
LinearLayout>
package com.demo.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.demo.R;
import com.demo.bean.VideoBean;
import com.demo.view.CustomVideoView ;
import java.util.List;
public class VideoRecyclerViewAdapter extends RecyclerView.Adapter.VideoHolder> {
private List videos;
private Context context;
public VideoRecyclerViewAdapter(List videos, Context context) {
this.videos = videos;
this.context = context;
}
@Override
public VideoHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(context).inflate(R.layout.item_video_auto_play, parent, false);
return new VideoHolder(itemView);
}
@Override
public void onBindViewHolder(final VideoHolder holder, int position) {
VideoBean videoBean = videos.get(position);
holder.title.setText(videoBean.getTitle());
holder.videoView .setPlayUrl(videoBean.getUrl());
}
@Override
public int getItemCount() {
return videos.size();
}
public class VideoHolder extends RecyclerView.ViewHolder {
private CustomVideoView videoView;
private TextView title;
VideoHolder(View itemView) {
super(itemView);
videoView= (CustomVideoView )itemView.findViewById(R.id.video_player);
int widthPixels = context.getResources().getDisplayMetrics().widthPixels;
videoView .setLayoutParams(new LinearLayout.LayoutParams(widthPixels, widthPixels / 16 * 9));
title = itemView.findViewById(R.id.tv_title);
}
}
}
上述代码中mLayoutManager 对象是布局管理器,RecyclerView提供了三种布局管理器:
RecyclerView.Adapter的使用方式和ListView的ListAdapter 类似,向RecyclerView提供显示的数据。
但是RecyclerView.Adapter做了一件了不起的优化,那就是RecyclerView.Adapter的
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
方法能够保证当前RecyclerView是确实需要你创建一个新的ViewHolder对象。而ListView的ListAdapter 对应的方法
@Override
public View getView(int i, View view, ViewGroup viewGroup)
需要在方法内部判断是重新创建一个View还是刷新一个View的数据,而不明所以的客户可能每次都会返回一个新创建的View造成ListView的卡顿和资源的浪费;创建和刷新作为两个不同的功能本来就应该在两个方法中实现—庆幸的是RecyclerView.Adapter解决了这个问题
RecyclerView中强制客户使用ViewHolder,谈及ListView的时候就经常说到使用ViewHolder来进行优化。使用ViewHolder其中一点好处是能够避免重复调用方法findViewById(),对当前item的View中的子View进行管理。
ViewHolder 描述RecylerView中某个位置的itemView和元数据信息,属于Adapter的一部分。其实该类通常用于保存 findViewById 的结果
ViewHolder的mFlags属性
展示效果和ListView基本上无差别,但是Item之间并没有分割线,在xml去找divider属性的时候,发现RecyclerView没有divider属性,当然也可以在Item布局中加上分割线,但是这样做并不是很优雅。
其实RecyclerView是支持自定义间隔样式的。通过
mRecyclerView.addItemDecoration()
来设置我们定义好的间隔样式
自定义间隔样式需要继承RecyclerView.ItemDecoration类,该类是个抽象类,主要有三个方法
onDraw()和onDrawOver()这两个方法都是用于绘制间隔样式,我们只需要复写其中一个方法即可。直接来看一下自定义的间隔样式的实现,参考官方实例
public class MyDividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
/**
* 用于绘制间隔样式
*/
private Drawable mDivider;
/**
* 列表的方向,水平/竖直
*/
private int mOrientation;
public MyDividerItemDecoration(Context context, int orientation) {
// 获取默认主题的属性
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// 绘制间隔
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
private void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
/**
* 绘制间隔
*/
private void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin +
Math.round(ViewCompat.getTranslationY(child));
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
/**
* 绘制间隔
*/
private void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin +
Math.round(ViewCompat.getTranslationX(child));
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
然后在代码中设置RecyclerView的间隔样式
mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL));
RecyclerView可以设置列表中Item删除和添加的动画,在v7包中给我们提供了一种默认的Item删除和添加的动画,如果没有特殊的需求,默认使用这个动画即可
// 设置Item添加和移除的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
设置的动画用于在 item 项数据变化时的动画效果
当调用 Adapter 的 notifyItemChanged、notifyItemInserted、notifyItemMoved 等方法,会触发该对象显示相应的动画。
RecyclerView 的 ItemAnimator 使得 item 的动画实现变得简单而样式丰富,我们可以自定义 item 项不同操作(如添加,删除)的动画效果;
ItemAnimator 触发于以下三种事件:
注意:notifyDataSetChanged(),会触发列表的重绘,并不会出现任何动画效果
使用:Animator使用到的逻辑比较多,因此最方便的就是使用第三方库:https://github.com/wasabeef/recyclerview-animators
RecyclerView并没有像ListView一样暴露出Item点击事件或者长按事件处理的api,也就是说使用RecyclerView时候,需要我们自己来实现Item的点击和长按等事件的处理。
实现方法有很多
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter .ViewHolder>{
/**
* 展示数据
*/
private ArrayList mData;
/**
* 事件回调监听
*/
private RecyclerViewAdapter.OnItemClickListener onItemClickListener;
public RecyclerViewAdapter(ArrayList data) {
this.mData = data;
}
public void updateData(ArrayList data) {
this.mData = data;
notifyDataSetChanged();
}
/**
* 添加新的Item
*/
public void addNewItem() {
if(mData == null) {
mData = new ArrayList<>();
}
mData.add(0, "new Item");
notifyItemInserted(0);
}
/**
* 删除Item
*/
public void deleteItem() {
if(mData == null || mData.isEmpty()) {
return;
}
mData.remove(0);
notifyItemRemoved(0);
}
/**
* 设置回调监听
*
* @param listener
*/
public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {
this.onItemClickListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 实例化展示的view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);
// 实例化viewholder
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
// 绑定数据
holder.mTv.setText(mData.get(position));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemClick(holder.itemView, pos);
}
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemLongClick(holder.itemView, pos);
}
//表示此事件已经消费,不会触发单击事件
return true;
}
});
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTv;
public ViewHolder(View itemView) {
super(itemView);
mTv = (TextView) itemView.findViewById(R.id.item_tv);
}
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
}
Activity 设置 Adapter 事件监听
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MyActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MyActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();
}
});
参考 Hugo 的文章:Getting your clicks on RecyclerView
<resources>
<item name="item_click_support" type="id" />
resources>
public class ItemClickSupport {
private final RecyclerView mRecyclerView;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override public void onClick(View v) {
if (mOnItemClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
}
};
private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
@Override public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
return false;
}
};
private RecyclerView.OnChildAttachStateChangeListener mAttachListener = new RecyclerView.OnChildAttachStateChangeListener() {
@Override public void onChildViewAttachedToWindow(View view) {
if (mOnItemClickListener != null) {
view.setOnClickListener(mOnClickListener);
}
if (mOnItemLongClickListener != null) {
view.setOnLongClickListener(mOnLongClickListener);
}
}
@Override public void onChildViewDetachedFromWindow(View view) {
}
};
private ItemClickSupport(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
mRecyclerView.setTag(R.id.item_click_support, this);
mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
}
public static ItemClickSupport addTo(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support == null) {
support = new ItemClickSupport(view);
}
return support;
}
public static ItemClickSupport removeFrom(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support != null) {
support.detach(view);
}
return support;
}
public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
return this;
}
public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
return this;
}
private void detach(RecyclerView view) {
view.removeOnChildAttachStateChangeListener(mAttachListener);
view.setTag(R.id.item_click_support, null);
}
// 点击接口
public interface OnItemClickListener {
void onItemClicked(RecyclerView recyclerView, int position, View v);
}
// 长按接口
public interface OnItemLongClickListener {
boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
}
}
// 点击
ItemClickSupport.addTo(rv).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override public void onItemClicked(RecyclerView recyclerView, int position, View v) {
Toast.makeText(MainActivity.this, mDatas.get(position), Toast.LENGTH_SHORT).show();
}
});
// 长按
ItemClickSupport.addTo(rv).setOnItemLongClickListener(new ItemClickSupport.OnItemLongClickListener() {
@Override public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
Toast.makeText(MainActivity.this, "长按" + mDatas.get(position) + "已删除", Toast.LENGTH_SHORT).show();
// 需要自己处理position在集合中的位置(需考虑头、身、脚布局数量)
mDatas.remove(position);
if (lastVisible + 1 == mAdapter.getItemCount()) {
addmore();
}
mAdapter.notifyItemRemoved(position);
// 消耗事件
return true;
}
});
目前而言,我们已经知道RecyclerView的一些功能如下
所以在项目中如果再遇见列表类的布局,就可以优先考虑使用 RecyclerView,更灵活更快捷的使用方式会给编码带来不一样的体验