控件 -- RecyclerView

一、概念

从Android 5.0开始,谷歌公司推出了一个用于大量数据展示的新控件RecylerView,可以用来代替传统的ListView,更加强大和灵活。RecyclerView的出现会让很多开源项目被废弃,例如横向滚动的ListView、横向滚动的GridView、瀑布流控件,因为RecyclerView能够实现所有这些功能。

二、特点

● RecyclerView封装了ViewHolder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单,并且直接省去了ListView中convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
● 提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。
● 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式。例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的是StaggeredGridLayoutManager等),也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView等多种效果。
● 可设置Item的间隔样式(可绘制),通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去书写代码。
● 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。
● 但是关于Item的点击和长按事件,需要用户自己去实现。

ListView相比RecyclerView,有一些优点:
● addHeaderView()、addFooterView()添加头视图和尾视图。
● 通过"android:divider"设置自定义分割线。
● setOnItemClickListener()和setOnItemLongClickListener()设置点击事件和长按事件。
这些功能在RecyclerView中都没有直接的接口,要自己实现,虽然实现起来也很简单。

RecyclerView相比ListView,有一些优点:
● 默认已经实现了View的复用,不需要类似if(convertView == null)的实现,而且回收机制更加完善。
● 默认支持局部刷新。
● 容易实现添加item、删除item的动画效果。
● 容易实现拖拽、侧滑删除等功能。
RecyclerView是一个插件式的实现,对各个功能进行解耦,从而扩展性比较好。

三、使用

//添加依赖
compile 'com.android.support:recyclerview-v7:23.4.0'

//Activity
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);  
LinearLayoutManager layoutManager = new LinearLayoutManager(this);  
//设置布局管理器  
recyclerView.setLayoutManager(layoutManager);  
//设置为垂直布局,这也是默认的  
layoutManager.setOrientation(OrientationHelper.VERTICAL);  
//设置Adapter  
recyclerView.setAdapter(recycleAdapter);  
 //设置分隔线  
recyclerView.addItemDecoration(new DividerGridItemDecoration(this));  
//设置增加或删除条目的动画  
recyclerView.setItemAnimator(new DefaultItemAnimator());

四、四大组成

1.LayoutManager

LayoutManager负责RecyclerView的布局,其中包含了Item View的获取与回收。RecyclerView提供了三种布局管理器:
LinerLayoutManager:以垂直或者水平列表方式展示Item;
GridLayoutManager:以网格方式展示Item;
StaggeredGridLayoutManager:以瀑布流方式展示Item。

常用方法:

canScrollHorizontally();//能否横向滚动
canScrollVertically();//能否纵向滚动
scrollToPosition(int position);//滚动到指定位置

setOrientation(int orientation);//设置滚动的方向
getOrientation();//获取滚动方向

findViewByPosition(int position);//获取指定位置的Item View
findFirstCompletelyVisibleItemPosition();//获取第一个完全可见的Item位置
findFirstVisibleItemPosition();//获取第一个可见Item的位置
findLastCompletelyVisibleItemPosition();//获取最后一个完全可见的Item位置
findLastVisibleItemPosition();//获取最后一个可见Item的位置

网格样式:

网格样式的管理器是GridLayoutManager。
常用构造方法:

GridLayoutManager(Context context, int spanCount)
//spanCount,每列或者每行的item个数,设置为1,就是列表样式
//该构造函数默认是竖直方向的网格样式

GridLayoutManager(Context context, int spanCount, int orientation,boolean reverseLayout)
//spanCount,每列或者每行的item个数,设置为1,就是列表样式
//网格样式的方向,水平(OrientationHelper.HORIZONTAL)或者竖直(OrientationHelper.VERTICAL)
//reverseLayout,是否逆向,true:布局逆向展示,false:布局正向显示

例子:

//md_divider.xml


    
    


//styles.xml


//MDGridRvDividerDecoration
public class MDGridRvDividerDecoration extends RecyclerView.ItemDecoration {
    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
    // 用于绘制间隔样式
    private Drawable mDivider;
    
    public MDGridRvDividerDecoration(Context context) {
        // 获取默认主题的属性
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        // 绘制间隔,每一个item,绘制右边和下方间隔样式
        int childCount = parent.getChildCount();
        int spanCount = ((GridLayoutManager)parent.getLayoutManager()).getSpanCount();
        int orientation = ((GridLayoutManager)parent.getLayoutManager()).getOrientation();
        boolean isDrawHorizontalDivider = true;
        boolean isDrawVerticalDivider = true;
        int extra = childCount % spanCount;
        extra = extra == 0 ? spanCount : extra;
        for(int i = 0; i < childCount; i++) {
            isDrawVerticalDivider = true;
            isDrawHorizontalDivider = true;
            // 如果是竖直方向,最右边一列不绘制竖直方向的间隔
            if(orientation == OrientationHelper.VERTICAL && (i + 1) % spanCount == 0) {
                isDrawVerticalDivider = false;
            }

            // 如果是竖直方向,最后一行不绘制水平方向间隔
            if(orientation == OrientationHelper.VERTICAL && i >= childCount - extra) {
                isDrawHorizontalDivider = false;
            }

            // 如果是水平方向,最下面一行不绘制水平方向的间隔
            if(orientation == OrientationHelper.HORIZONTAL && (i + 1) % spanCount == 0) {
                isDrawHorizontalDivider = false;
            }

            // 如果是水平方向,最后一列不绘制竖直方向间隔
            if(orientation == OrientationHelper.HORIZONTAL && i >= childCount - extra) {
                isDrawVerticalDivider = false;
            }

            if(isDrawHorizontalDivider) {
                drawHorizontalDivider(c, parent, i);
            }

            if(isDrawVerticalDivider) {
                drawVerticalDivider(c, parent, i);
            }
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int spanCount = ((GridLayoutManager) parent.getLayoutManager()).getSpanCount();
        int orientation = ((GridLayoutManager)parent.getLayoutManager()).getOrientation();
        int position = parent.getChildLayoutPosition(view);
        if(orientation == OrientationHelper.VERTICAL && (position + 1) % spanCount == 0) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
            return;
        }

        if(orientation == OrientationHelper.HORIZONTAL && (position + 1) % spanCount == 0) {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
            return;
        }

        outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
    }

    /* 绘制竖直间隔线
     * @param canvas
     * @param parent   父布局,RecyclerView
     * @param position item在父布局中所在的位置     */
    private void drawVerticalDivider(Canvas canvas, RecyclerView parent, int position) {
        final View child = parent.getChildAt(position);
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                .getLayoutParams();
        final int top = child.getTop() - params.topMargin;
        final int bottom = child.getBottom() + params.bottomMargin + mDivider.getIntrinsicHeight();
        final int left = child.getRight() + params.rightMargin;
        final int right = left + mDivider.getIntrinsicWidth();
        mDivider.setBounds(left, top, right, bottom);
        mDivider.draw(canvas);
    }

    /* 绘制水平间隔线
     * @param canvas
     * @param parent   父布局,RecyclerView
     * @param position item在父布局中所在的位置     */
    private void drawHorizontalDivider(Canvas canvas, RecyclerView parent, int position) {
        final View child = parent.getChildAt(position);
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                .getLayoutParams();
        final int top = child.getBottom() + params.bottomMargin;
        final int bottom = top + mDivider.getIntrinsicHeight();
        final int left = child.getLeft() - params.leftMargin;
        final int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth();
        mDivider.setBounds(left, top, right, bottom);
        mDivider.draw(canvas);
    }
}

//Activity
// 竖直方向的网格样式,每行四个Item
mLayoutManager = new GridLayoutManager(this, 4, OrientationHelper.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.addItemDecoration(new MDGridRvDividerDecoration(this));

瀑布流样式

RecyclerView的瀑布流布局管理器是StaggeredGridLayoutManager。
常用构造方法:

StaggeredGridLayoutManager(int spanCount, int orientation)
//spanCount代表每行或每列的Item个数,orientation代表列表的方向,竖直或者水平。

例子:

//view_rv_staggered_item.xml


    


//view_rv_staggered_item_two.xml


    


//layout


//Activity
// 初始化布局管理器
mLayoutManager = new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL);
// 设置布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 设置adapter
mRecyclerView.setAdapter(mAdapter);

// MDStaggeredRvDividerDecotation类是直接拷贝网格样式的间隔线绘制类,显示有问题,有些地方没有间隔
// 可以在item布局中设置四周间隔padding/margin,以达到设置间隔的目的,该方法有点挫
// mRecyclerView.addItemDecoration(new MDStaggeredRvDividerDecotation(this));

2.Adapter

// ① 创建Adapter
public class NormalAdapter extends RecyclerView.Adapter{
    //② 创建ViewHolder
    public static class VH extends RecyclerView.ViewHolder{
        public final TextView title;
        public VH(View v) {
            super(v);
            title = (TextView) v.findViewById(R.id.title);
        }
    }
    
    private List mDatas;
    public NormalAdapter(List data) {
        this.mDatas = data;
    }

    //③ 在Adapter中实现3个方法
    @Override
    public void onBindViewHolder(VH holder, int position) {
        holder.title.setText(mDatas.get(position));
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //item 点击事件
            }
        });
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    @Override
    public VH onCreateViewHolder(ViewGroup parent, int viewType) {
        //LayoutInflater.from指定写法
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
        return new VH(v);
    }
}

3.Item Decoration间隔样式

RecyclerView通过addItemDecoration()方法添加item之间的分割线。自定义间隔样式需要继承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()这两个方法都是用于绘制间隔样式,我们只需要复写其中一个方法即可。

4.Item Animator动画

RecyclerView能够通过mRecyclerView.setItemAnimator(ItemAnimator animator)设置添加、删除、移动、改变的动画效果。RecyclerView提供了默认的ItemAnimator实现类:DefaultItemAnimator。如果没有特殊的需求,默认使用这个动画即可。

// 设置Item添加和移除的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());

DefaultItemAnimator继承自SimpleItemAnimator,SimpleItemAnimator继承自ItemAnimator。

ItemAnimator类常用方法:
animateAppearance():当ViewHolder出现在屏幕上时被调用(可能是add或move)。
animateDisappearance():当ViewHolder消失在屏幕上时被调用(可能是remove或move)。
animatePersistence():在没调用notifyItemChanged()和notifyDataSetChanged()的情况下布局发生改变时被调用。
animateChange():在显式调用notifyItemChanged()或notifyDataSetChanged()时被调用。
runPendingAnimations():RecyclerView动画的执行方式并不是立即执行,而是每帧执行一次,比如两帧之间添加了多个Item,则会将这些将要执行的动画Pending住,保存在成员变量中,等到下一帧一起执行。该方法执行的前提是前面animateXxx()返回true。
isRunning(): 是否有动画要执行或正在执行。
dispatchAnimationsFinished():当全部动画执行完毕时被调用。

SimpleItemAnimator类常用方法:
animateAdd(ViewHolder holder):当Item添加时被调用。
animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY):当Item移动时被调用。
animateRemove(ViewHolder holder):当Item删除时被调用。
animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop):当显式调用notifyItemChanged()或notifyDataSetChanged()时被调用。

自定义Item Animator好麻烦,需要继承SimpleItemAnimator类,然后实现一堆方法,我们可以使用开源动画recyclerview-animators,该库提供了一系列的Animator,比如FadeInAnimator、ScaleInAnimator等,如果没有满意的动画,该库提供了BaseItemAnimator类,该类继承自SimpleItemAnimator,进一步封装了自定义Item Animator的代码,使得自定义Item Animator更方便,我们只需要关注动画本身。如果要实现DefaultItemAnimator的代码,只需要以下实现:

public class DefaultItemAnimator extends BaseItemAnimator {

  public DefaultItemAnimator() {
  }

  public DefaultItemAnimator(Interpolator interpolator) {
    mInterpolator = interpolator;
  }

  @Override protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
    ViewCompat.animate(holder.itemView)
        .alpha(0)
        .setDuration(getRemoveDuration())
        .setListener(new DefaultRemoveVpaListener(holder))
        .setStartDelay(getRemoveDelay(holder))
        .start();
  }

  @Override protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
    ViewCompat.setAlpha(holder.itemView, 0); //透明度先变为0
  }

  @Override protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
    ViewCompat.animate(holder.itemView)
        .alpha(1)
        .setDuration(getAddDuration())
        .setListener(new DefaultAddVpaListener(holder))
        .setStartDelay(getAddDelay(holder))
        .start();
  }
}

五、点击事件

RecyclerView并没有像ListView一样暴露出Item点击事件或者长按事件处理的API,也就是说使用RecyclerView时候,需要我们自己来实现Item的点击和长按等事件的处理。可以通过在绑定ViewHolder的时候设置监听,然后通过Apater回调出去。
代码如下:

//Adapter
public class MyAdapter extends RecyclerView.Adapter{
    // 展示数据
    private ArrayList mData;
    // 事件回调监听
    private MyAdapter.OnItemClickListener onItemClickListener;
    public MyAdapter(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);
    }

    // ① 定义点击回调接口
    public interface OnItemClickListener {
        void onItemClick(View view, int position);
        void onItemLongClick(View view, int position);
    }
    
    // ② 定义一个设置点击监听器的方法
    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));
        //③ 对RecyclerView的每一个itemView设置点击事件
        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);
        }
    }
}


//Activity
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MDRvActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MDRvActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();
    }
});

六、添加HeaderView和FooterView

RecyclerView默认没有提供类似addHeaderView()和addFooterView()的API,如果你已经实现了一个Adapter,现在想为这个Adapter添加addHeaderView()和addFooterView()接口,则需要在Adapter中添加几个Item Type,然后修改getItemViewType()、onCreateViewHolder()、onBindViewHolder()、getItemCount()等方法,并添加switch语句进行判断。

这里引入装饰器(Decorator)设计模式,该设计模式通过组合的方式,在不破坏原有类代码的情况下,对原有类的功能进行扩展。具体实现思路其实很简单,创建一个继承RecyclerView.Adapter的类,并重写常见的方法,然后通过引入ITEM TYPE的方式实现:

//Adapter
public class NormalAdapterWrapper extends RecyclerView.Adapter{

    enum ITEM_TYPE{
        HEADER,
        FOOTER,
        NORMAL
    }

    private NormalAdapter mAdapter;
    private View mHeaderView;
    private View mFooterView;

    public NormalAdapterWrapper(NormalAdapter adapter){
        mAdapter = adapter;
    }

    @Override
    public int getItemViewType(int position) {
        if(position == 0){
            return ITEM_TYPE.HEADER.ordinal();
        } else if(position == mAdapter.getItemCount() + 1){
            return ITEM_TYPE.FOOTER.ordinal();
        } else{
            return ITEM_TYPE.NORMAL.ordinal();
        }
    }

    @Override
    public int getItemCount() {
        return mAdapter.getItemCount() + 2;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if(position == 0){
            return;
        } else if(position == mAdapter.getItemCount() + 1){
            return;
        } else{
            mAdapter.onBindViewHolder(((NormalAdapter.VH)holder), position - 1);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if(viewType == ITEM_TYPE.HEADER.ordinal()){
            return new RecyclerView.ViewHolder(mHeaderView) {};
        } else if(viewType == ITEM_TYPE.FOOTER.ordinal()){
            return new RecyclerView.ViewHolder(mFooterView) {};
        } else{
            return mAdapter.onCreateViewHolder(parent,viewType);
        }
    }

    public void addHeaderView(View view){
        this.mHeaderView = view;
    }
    public void addFooterView(View view){
        this.mFooterView = view;
    }
}

//Activity
NormalAdapter adapter = new NormalAdapter(data);
NormalAdapterWrapper newAdapter = new NormalAdapterWrapper(adapter);
View headerView = LayoutInflater.from(this).inflate(R.layout.item_header, mRecyclerView, false);
View footerView = LayoutInflater.from(this).inflate(R.layout.item_footer, mRecyclerView, false);
newAdapter.addFooterView(footerView);
newAdapter.addHeaderView(headerView);
mRecyclerView.setAdapter(newAdapter);

七、添加setEmptyView

ListView提供了setEmptyView()设置Adapter数据为空时的View视图。RecyclerView虽然没提供直接的API,但是也可以很简单地实现。
● 创建一个继承RecyclerView的类,记为EmptyRecyclerView。
● 通过getRootView().addView(emptyView)将空数据时显示的View添加到当前View的层次结构中。
● 通过AdapterDataObserver监听RecyclerView的数据变化,如果adapter为空,那么隐藏RecyclerView,显示EmptyView。
具体实现如下:

//EmptyRecyclerView
public class EmptyRecyclerView extends RecyclerView{
    private View mEmptyView;
    private AdapterDataObserver mObserver = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            Adapter adapter = getAdapter();
            if(adapter.getItemCount() == 0){
                mEmptyView.setVisibility(VISIBLE);
                EmptyRecyclerView.this.setVisibility(GONE);
            } else{
                mEmptyView.setVisibility(GONE);
                EmptyRecyclerView.this.setVisibility(VISIBLE);
            }
        }

        public void onItemRangeChanged(int positionStart, int itemCount) {onChanged();}
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {onChanged();}
        public void onItemRangeRemoved(int positionStart, int itemCount) {onChanged();}
        public void onItemRangeInserted(int positionStart, int itemCount) {onChanged();}
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {onChanged();}
    };

    public EmptyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public void setEmptyView(View view){
        this.mEmptyView = view;
        ((ViewGroup)this.getRootView()).addView(mEmptyView); //加入主界面布局
    }

    public void setAdapter(RecyclerView.Adapter adapter){
        super.setAdapter(adapter);
        adapter.registerAdapterDataObserver(mObserver);
        mObserver.onChanged();
    }
} 

//layout



    

        

        
    

    


//Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_5);
    mRv = (EmptyRecyclerView) findViewById(R.id.rv);
    mRv.setLayoutManager(new LinearLayoutManager(this));
    mData = new ArrayList<>();
    mAdapter = new NormalAdapter(mData);
    //View view = LayoutInflater.from(this).inflate(R.layout.empty, null);
    View view = findViewById(R.id.text_empty);
    mRv.setEmptyView(view);
    mRv.setAdapter(mAdapter);
}

八、拖拽、侧滑删除

Android提供了ItemTouchHelper类,使得RecyclerView能够轻易地实现滑动和拖拽,此处我们要实现上下拖拽和侧滑删除。
使用方法:
1.创建ItemTouchHelper.Callback类
首先创建一个继承自ItemTouchHelper.Callback的类,并重写以下方法:
getMovementFlags():设置支持的拖拽和滑动的方向,此处我们支持的拖拽方向为上下,滑动方向为从左到右和从右到左,内部通过makeMovementFlags()设置。
onMove():拖拽时回调。
onSwiped():滑动时回调。
onSelectedChanged():状态变化时回调,一共有三个状态,分别是ACTION_STATE_IDLE(空闲状态),ACTION_STATE_SWIPE(滑动状态),ACTION_STATE_DRAG(拖拽状态)。此方法中可以做一些状态变化时的处理,比如拖拽的时候修改背景色。
clearView():用户交互结束时回调。此方法可以做一些状态的清空,比如拖拽结束后还原背景色。
isLongPressDragEnabled():是否支持长按拖拽,默认为true。如果不想支持长按拖拽,则重写并返回false。
具体实现如下:

public class SimpleItemTouchCallback extends ItemTouchHelper.Callback {

    private NormalAdapter mAdapter;
    private List mData;
    public SimpleItemTouchCallback(NormalAdapter adapter, List data){
        mAdapter = adapter;
        mData = data;
    }

    //设置支持的拖拽、滑动的方向
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //s上下拖拽
        int swipeFlag = ItemTouchHelper.START | ItemTouchHelper.END; //左->右和右->左滑动
        return makeMovementFlags(dragFlag,swipeFlag);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        int from = viewHolder.getAdapterPosition();
        int to = target.getAdapterPosition();
        Collections.swap(mData, from, to);
        mAdapter.notifyItemMoved(from, to);
        return true;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        int pos = viewHolder.getAdapterPosition();
        mData.remove(pos);
        mAdapter.notifyItemRemoved(pos);
    }

    //状态改变时回调
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
        if(actionState != ItemTouchHelper.ACTION_STATE_IDLE){
            NormalAdapter.VH holder = (NormalAdapter.VH)viewHolder;
            holder.itemView.setBackgroundColor(0xffbcbcbc); //设置拖拽和侧滑时的背景色
        }
    }

    //拖拽或滑动完成之后调用,用来清除一些状态
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        NormalAdapter.VH holder = (NormalAdapter.VH)viewHolder;
        holder.itemView.setBackgroundColor(0xffeeeeee); //背景色还原
    }
}

2.给RecyclerView设置ItemTouchHelper

ItemTouchHelper helper = new ItemTouchHelper(new SimpleItemTouchCallback(adapter, data));
helper.attachToRecyclerView(recyclerview);

触摸拖拽

前面拖拽的触发方式只有长按,如果想支持触摸Item中的某个View实现拖拽,则核心方法为helper.startDrag(holder)。
具体实现如下:

//Adapter 
public class NormalAdapter extends RecyclerView.Adapter{

    private List mDatas;
    private OnStartDragListener mListener;
    public NormalAdapter(List data, OnStartDragListener listener) {
        this.mDatas = data;
        mListener = listener;
    }

    @Override
    public void onBindViewHolder(final VH holder, int position) {
        ObjectModel model = mDatas.get(position);
        holder.title.setText(model.title);
        holder.number.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(event.getAction() == MotionEvent.ACTION_DOWN){
                    mListener.startDrag(holder);
                }
                return false;
            }
        });
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    @Override
    public VH onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_3, parent, false);
        return new VH(v);
    }

    public static class VH extends RecyclerView.ViewHolder{
        public final TextView title;
        public final ImageView number;
        public VH(View v) {
            super(v);
            title = (TextView) v.findViewById(R.id.title);
            number = (ImageView) v.findViewById(R.id.icon);
        }
    }
}

//Activity
public class Activity3 extends AppCompatActivity implements OnStartDragListener{
    private RecyclerView mRv;
    private NormalAdapter mAdapter;
    private ItemTouchHelper mHelper;
    private List mData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_3);
        mRv = (RecyclerView) findViewById(R.id.rv);
        mRv.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new NormalAdapter(mData = initData(), this);
        mRv.setAdapter(mAdapter);
        mHelper = new ItemTouchHelper(new SimpleItemTouchCallback(mAdapter, mData));
        mHelper.attachToRecyclerView(mRv);

    }

    public ArrayList initData(){
        ArrayList models = new ArrayList<>();
        String[] titles = getResources().getStringArray(R.array.title_array);
        for(int i=0;i

九、嵌套滑动机制

Android 5.0推出了嵌套滑动机制,在之前,一旦子View处理了触摸事件,父View就没有机会再处理这次的触摸事件,而嵌套滑动机制解决了这个问题。为了支持嵌套滑动,子View必须实现NestedScrollingChild接口,父View必须实现NestedScrollingParent接口。而RecyclerView实现了NestedScrollingChild接口,而CoordinatorLayout实现了NestedScrollingParent接口。
例子:

//layout



    

        
        
            
        
    
    


//Adapter
public abstract class QuickAdapter extends RecyclerView.Adapter{

    private List mDatas;

    public QuickAdapter(List datas){
        this.mDatas = datas;
    }

    public abstract int getLayoutId(int viewType);

    @Override
    public VH onCreateViewHolder(ViewGroup parent, int viewType) {
        return VH.get(parent,getLayoutId(viewType));
    }

    @Override
    public void onBindViewHolder(VH holder, int position) {
        convert(holder, mDatas.get(position), position);
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    public abstract void convert(VH holder, T data, int position);

    static class VH extends RecyclerView.ViewHolder{
        private SparseArray mViews;
        private View mConvertView;

        private VH(View v){
            super(v);
            mConvertView = v;
            mViews = new SparseArray<>();
        }

        public static VH get(ViewGroup parent, int layoutId){
            View convertView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
            return new VH(convertView);
        }

        public  T getView(int id){
            View v = mViews.get(id);
            if(v == null){
                v = mConvertView.findViewById(id);
                mViews.put(id, v);
            }
            return (T)v;
        }

        public void setText(int id, String value){
            TextView view = getView(id);
            view.setText(value);
        }
    }
}

//Activity
public class Activity6 extends AppCompatActivity {
    private RecyclerView mRv;
    private QuickAdapter mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_6);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        mRv = (RecyclerView) findViewById(R.id.rv);
        mRv.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
        mAdapter = new QuickAdapter(initData()) {
            @Override
            public int getLayoutId(int viewType) {
                return R.layout.item_6;
            }

            @Override
            public void convert(VH holder, Integer data, int position) {
                ImageView imageView = holder.getView(R.id.image);
                Picasso.with(Activity6.this).load(data).into(imageView);
                //holder.itemView.setOnClickListener();  此处添加点击事件
            }

            @Override
            public int getItemViewType(int position) {
                return super.getItemViewType(position);
            }
        };
        mAdapter.setHasStableIds(true);
        ((SimpleItemAnimator)mRv.getItemAnimator()).setSupportsChangeAnimations(false);
        mRv.setAdapter(mAdapter);

    }

    public List initData(){
        Integer[] images = {R.drawable.s1, R.drawable.s2, R.drawable.s3, R.drawable.s4, R.drawable.s5,
                        R.drawable.s6, R.drawable.s7, R.drawable.s8, R.drawable.s9, R.drawable.s10
                };
        ArrayList list = new ArrayList<>();
        for(int i=0;i<2;i++){
            for(Integer image:images){
                list.add(image);
            }
        }
        return list;
    }
}

十、局部刷新

ListView实现局部刷新:
ListView通过adapter.notifyDataSetChanged()实现ListView的更新,这种更新方法的缺点是全局更新,即对每个Item View都进行重绘。但事实上很多时候,我们只是更新了其中一个Item的数据,其他Item其实可以不需要重绘,ListView实现局部更新的方法如下:

public void updateItemView(ListView listview, int position, Data data){
    int firstPos = listview.getFirstVisiblePosition();
    int lastPos = listview.getLastVisiblePosition();
    if(position >= firstPos && position <= lastPos){  //可见才更新,不可见则在getView()时更新
        //listview.getChildAt(i)获得的是当前可见的第i个item的view
        View view = listview.getChildAt(position - firstPos);
        VH vh = (VH)view.getTag();
        vh.text.setText(data.text);
    }
} 

RecyclerView实现局部刷新:
RecyclerView提供了notifyItemInserted()、notifyItemRemoved()、notifyItemChanged()等API更新单个或某个范围的Item视图。

十一、缓存机制

缓存层级

ListView和RecyclerView缓存机制基本一致:
● mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;
● mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用。

RecyclerView的优势在于:
● mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;
● mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势。

客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。

缓存内容

● RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
● ListView缓存View。

你可能感兴趣的:(控件 -- RecyclerView)