欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android控件之RecyclerView

程序员文章站 2022-05-14 20:02:13
...

一.RecyclerView

作用:用来替换ListView和GridView。
好处:具有高度的解耦和效率。可以实现丰富多样的效果;
缺点:列表分割线需要自定义;列表的点击事件需要自行实现;

1.基本用法:

一般在xml文件中定义一个recyclerview,java中去实现(调用setAdap
ter加载适配器,显示recyclerview的layout及加载数据)。这个和ListView的方法是相同的,这个也是最基础的。

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    TextView textView;
    RecyclerView.ViewHolder viewHolder;

    private Context mContext;


    public MyAdapter(Context context){
        this.mContext = context;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.itemview, null, false);

        textView = view.findViewById(R.id.item_text);
        viewHolder = new RecyclerView.ViewHolder(view) {
            @Override
            public String toString() {
                return super.toString();
            }
        };
        viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return false;
            }
        });

        viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

    }

    @Override
    public int getItemCount() {
        return 30;
    }
}

主要方法:

(1)onCreateViewHolder()

这个方法作用映射Item Layout Id,创建VH并返回。

    /**
     * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
     * an item.
     * <p>
     * This new ViewHolder should be constructed with a new View that can represent the items
     * of the given type. You can either create a new View manually or inflate it from an XML
     * layout file.
     * <p>
     * The new ViewHolder will be used to display items of the adapter using
     * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
     * different items in the data set, it is a good idea to cache references to sub views of
     * the View to avoid unnecessary {@link View#findViewById(int)} calls.
     *
     * @param parent The ViewGroup into which the new View will be added after it is bound to
     *               an adapter position.
     * @param viewType The view type of the new View.
     *
     * @return A new ViewHolder that holds a View of the given view type.
     * @see #getItemViewType(int)
     * @see #onBindViewHolder(ViewHolder, int)
     */
    public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);

(2)onBindViewHolder

这个方法的作用是将视图和数据进行绑定。为holder设置指定数据。

    /**
     * Called by RecyclerView to display the data at the specified position. This method should
     * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
     * position.
     * <p>
     * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
     * again if the position of the item changes in the data set unless the item itself is
     * invalidated or the new position cannot be determined. For this reason, you should only
     * use the <code>position</code> parameter while acquiring the related data item inside
     * this method and should not keep a copy of it. If you need the position of an item later
     * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
     * have the updated adapter position.
     *
     * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
     * handle efficient partial bind.
     *
     * @param holder The ViewHolder which should be updated to represent the contents of the
     *        item at the given position in the data set.
     * @param position The position of the item within the adapter's data set.
     */
    public abstract void onBindViewHolder(VH holder, int position);

2.布局

这里加载RecyclerView的布局可以如上面这个例子中所写的,我们加
载xml。而RecyclerView则为我们提供了LayouManager来负责布局,包括对item的回收与获取

LayoutManager中主要的方法:

1.onLayoutChildren()
对RecyclerView进行布局的入口方法。这里在LayoutManager中仅是打
印出一个log信息:

    public void onLayoutChildren(Recycler recycler, State state) {
        Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
    }

那就是各个继承子类中会去实现onLayoutChildren(),我们以上例子
中的LinearLayoutManager来看:

注释写的很明确:
布局算法:

(1)通过检查子项和其他变量,找到锚点坐标和锚点项目位置。

(2)填充朝向开始,从底部堆叠

(3)向末端填充,从顶部堆叠

(4)滚动以满足从底部堆栈的要求。

/**
 * {@inheritDoc}
 */
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    // layout algorithm:
    // 1) by checking children and other variables, find an anchor coordinate and an anchor
    //  item position.
    // 2) fill towards start, stacking from bottom
    // 3) fill towards end, stacking from top
    // 4) scroll to fulfill requirements like stack from bottom.
    // create layout state
    if (DEBUG) {
        Log.d(TAG, "is pre layout:" + state.isPreLayout());
    }
    if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
        if (state.getItemCount() == 0) {
            removeAndRecycleAllViews(recycler);
            return;
        }
    }

其中关键的是方法为:

(1).fill():这个是对view的填充,看具体实现:

/**
 * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
 * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
 * and with little change, can be made publicly available as a helper class.
 *
 * @param recycler        Current recycler that is attached to RecyclerView
 * @param layoutState     Configuration on how we should fill out the available space.
 * @param state           Context passed by the RecyclerView to control scroll steps.
 * @param stopOnFocusable If true, filling stops in the first focusable new child
 * @return Number of pixels that it added. Useful for scroll functions.
 */
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    // max offset we should set is mFastScroll + available
    final int start = layoutState.mAvailable;
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        // TODO ugly bug fix. should not happen
        if (layoutState.mAvailable < 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        if (VERBOSE_TRACING) {
            TraceCompat.beginSection("LLM LayoutChunk");
        }
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        if (VERBOSE_TRACING) {
            TraceCompat.endSection();
        }
        if (layoutChunkResult.mFinished) {
            break;
        }
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
        /**
         * Consume the available space if:
         * * layoutChunk did not request to be ignored
         * * OR we are laying out scrap children
         * * OR we are not doing pre-layout
         */
        if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResult.mConsumed;
            // we keep a separate remaining space because mAvailable is important for recycling
            remainingSpace -= layoutChunkResult.mConsumed;
        }

        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        if (stopOnFocusable && layoutChunkResult.mFocusable) {
            break;
        }
    }
    if (DEBUG) {
        validateChildOrder();
    }
    return start - layoutState.mAvailable;
}

很明显,这里就是对剩余空间不断的调用layoutChunk()方法,那接着看这个方法的实现:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler);
    if (view == null) {
        if (DEBUG && layoutState.mScrapList == null) {
            throw new RuntimeException("received null view when unexpected");
        }
        // if we are laying out views in scrap, this may return null which means there is
        // no more items to layout.
        result.mFinished = true;
        return;
    }
    LayoutParams params = (LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addView(view);
        } else {
            addView(view, 0);
        }
    } else {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addDisappearingView(view);
        } else {
            addDisappearingView(view, 0);
        }
    }
    measureChildWithMargins(view, 0, 0);
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    int left, top, right, bottom;
    if (mOrientation == VERTICAL) {
        if (isLayoutRTL()) {
            right = getWidth() - getPaddingRight();
            left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
        } else {
            left = getPaddingLeft();
            right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            bottom = layoutState.mOffset;
            top = layoutState.mOffset - result.mConsumed;
        } else {
            top = layoutState.mOffset;
            bottom = layoutState.mOffset + result.mConsumed;
        }
    } else {
        top = getPaddingTop();
        bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            right = layoutState.mOffset;
            left = layoutState.mOffset - result.mConsumed;
        } else {
            left = layoutState.mOffset;
            right = layoutState.mOffset + result.mConsumed;
        }
    }
    // We calculate everything with View's bounding box (which includes decor and margins)
    // To calculate correct layout position, we subtract margins.
    layoutDecoratedWithMargins(view, left, top, right, bottom);
    if (DEBUG) {
        Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
    }
    // Consume the available space if the view is not removed OR changed
    if (params.isItemRemoved() || params.isItemChanged()) {
        result.mIgnoreConsumed = true;
    }
    result.mFocusable = view.hasFocusable();
}

这个方法主要实现:

(1)addView():加入view;

(2)measureChildWithMargins():计算view的大小;

(3)layoutDecoratedWithMargins():View布局

3.分割线

这里可以从截图中看到,这个列表是一个类似于listView的,但是是分割线的。这里我们可以调用addItemDecoration()方法来加入。Google默认是没有分割线的,因此需要自定义,这里就体现了灵活性。

主要需要重写如下两个方法:

1.onDraw(): 绘制分割线。

2.getItemOffsets(): 设置分割线的宽、高。

    mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDraw(c, parent, state);
            float left = parent.getPaddingLeft();
            float right = parent.getWidth() - parent.getPaddingRight();

            int childCount = parent.getChildCount();
            Paint paint = new Paint();
            paint.setColor(Color.RED);

            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
                float top = child.getBottom();
                float bottom = child.getBottom() + 1.0f;
                c.drawRect(left, top, right, bottom, paint);
                }

        }
    });

这里:
A.分割线可以叠加的,即可以调用多次addItemDecoration方法;

B.分割线可以做横向的分割也可以纵向的分割;

C.onDraw()和onDrawOver的区别:

RecyclerView首先重写draw()方法,随后super.draw()调用View的draw()
,这个方法会先调用onDraw()方法,而RecyclerView中重写了onDraw(),再调用dispatchDraw()绘制children。

因此,区别在于:onDraw()是在绘制item之前调用;
onDrawover()是在绘制item之后调用;

@Override
    public void draw(Canvas c) {
        super.draw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
        // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
        // need find children closest to edges. Not sure if it is worth the effort.
        boolean needsInvalidate = false;
        if (mLeftGlow != null && !mLeftGlow.isFinished()) {
            final int restore = c.save();
            final int padding = mClipToPadding ? getPaddingBottom() : 0;
            c.rotate(270);
            c.translate(-getHeight() + padding, 0);
            needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
            c.restoreToCount(restore);
        }

4.点击事件

上述的方法实现好,可以看到一个和ListView一样的界面展示出来了,可以上下拖动,这时发现无法去点击。我们需要添加点击事件实现。由于这里没有ListView的onItemClickListener的实现,我们需要在adapter中定义接口并提供回调。

Adapter中实现:

private OnItemClickListener mItemClickListener;

    public static interface OnItemClickListener {
        void onItemClick(View view);
        void onItemLongClick(View view);
    }

    public void setItemClickListener(OnItemClickListener itemClickListener) {
        mItemClickListener = itemClickListener;
    }

在重写onBindViewHolder的地方对item中的控件进行事件监听并且回调我们的自定义的监听:

    viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            mItemClickListener.onItemLongClick(v);
            return true;
        }
    });

    viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mItemClickListener.onItemClick(v);
        }
    });

在Activity中进行监听:

    adapter.setItemClickListener(new MyAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(View view) {
            int position = mRecyclerView.getChildAdapterPosition(view);
            Toast.makeText(MainActivity.this,"点击第" + (position+1)+ "条",Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onItemLongClick(View view) {
            int position = mRecyclerView.getChildAdapterPosition(view);
            Toast.makeText(MainActivity.this,"长按第" + (position+1)+ "条",Toast.LENGTH_SHORT).show();
        }
    });

5.动画效果

RecyclerView可以通过setItemAnimator()方法来实现动画效果,RecyclerView默认的是DefaultItemAnimator。

那我们来看DefaultItemAnimator的继承关系如下:

Android控件之RecyclerView

它提供了有关删除,添加和移动RecyclerView中的项目所发生事件的基本动画。

其中主要的方法为:

(1)animateAdd

boolean animateAdd (RecyclerView.ViewHolder holder)

将项目添加到RecyclerView时调用。 实现者可以选择是否以及如何为该更改设置动画,但必须在完成后立即调用dispatchAddFinished(ViewHolder),或者立即调用(如果没有动画),或者在动画实际完成之后调用。 返回值指示是否已设置动画以及是否应在下一次机会调用ItemAnimator的runPendingAnimations()方法。

(2)animateChange

boolean animateChange (RecyclerView.ViewHolder oldHolder,
RecyclerView.ViewHolder newHolder,
int fromX,
int fromY,
int toX,
int toY)

在RecyclerView中更改项目时调用,如对notifyItemChanged(int)或notifyItemRangeChanged(int,int)的调用所示。

实现者可以选择是否以及如何为更改设置动画,但必须始终为每个非空的不同ViewHolder调用dispatchChangeFinished(ViewHolder,boolean),或者立即调用(如果没有动画),或者在动画实际完成之后调用。如果oldHolder与newHolder是同一个ViewHolder,则必须只调用一次dispatchChangeFinished(ViewHolder,boolean)。在这种情况下,dispatchChangeFinished的第二个参数将被忽略。

返回值指示是否已设置动画以及是否应在下一次机会调用ItemAnimator的runPendingAnimations()方法。

(3)animateMove

boolean animateMove (RecyclerView.ViewHolder holder,
int fromX,
int fromY,
int toX,
int toY)

在RecyclerView中移动项目时调用。 实现者可以选择是否以及如何为该更改设置动画,但必须始终在完成后立即调用dispatchMoveFinished(ViewHolder)(如果不会发生动画)或动画实际完成后调用。 返回值指示是否已设置动画以及是否应在下一次机会调用ItemAnimator的runPendingAnimations()方法。

(4)animateRemove

boolean animateRemove (RecyclerView.ViewHolder holder)

从RecyclerView中删除项目时调用。实现者可以选择是否以及如何为该更改设置动画,但必须始终在调用dispatchRemoveFinished(ViewHolder)时立即调用(如果不会发生动画)或动画实际完成后调用。返回值指示是否已设置动画以及是否应在下一次机会调用ItemAnimator的runPendingAnimations()方法。

(5)canReuseUpdatedViewHolder

boolean canReuseUpdatedViewHolder (RecyclerView.ViewHolder viewHolder,
List payloads)

当项目被更改时,ItemAnimator可以决定是否要为动画重复使用相同的ViewHolder,或者RecyclerView应该创建项目的副本,而ItemAnimator将使用它们来运行动画(例如交叉淡入淡出)。

请注意,只有在RecyclerView.ViewHolder仍具有相同类型(getItemViewType(int))时才会调用此方法。 否则,ItemAnimator将始终在animateChange(ViewHolder,ViewHolder,ItemHolderInfo,ItemHolderInfo)方法中接收RecyclerView.ViewHolders。

(6)endAnimation

void endAnimation (RecyclerView.ViewHolder item)

应立即结束视图上的动画时调用的方法。 当其他事件(如滚动)发生时,可能会发生这种情况,因此可以将动画视图快速放入正确的结束位置。 实现应确保取消在项目上运行的任何动画,并将受影响的属性设置为其最终值

(7)runPendingAnimations

void runPendingAnimations ()

等待启动等待动画时调用。 此状态由animateAppearance(),animateChange()animatePersistence()和animateDisappearance()的返回值控制,它们告知RecyclerView以后要调用ItemAnimator以启动关联的动画。 runPendingAnimations()将被安排在下一帧上运行。

这个方法要多看下,这里在上述的一些方法的返回值为true后都会被调用。我们看下方法的实现:

A.首先判断是否有动画要实现:

if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
            // nothing to animate
            return;
        }

B.执行移除动画:

for (ViewHolder holder : mPendingRemovals) {
            animateRemoveImpl(holder);
        }
        mPendingRemovals.clear();

C.执行移动动画:

if (movesPending) {
            final ArrayList<MoveInfo> moves = new ArrayList<>();
            moves.addAll(mPendingMoves);
            mMovesList.add(moves);
            mPendingMoves.clear();
            Runnable mover = new Runnable() {
                @Override
                public void run() {
                    for (MoveInfo moveInfo : moves) {
                        animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                                moveInfo.toX, moveInfo.toY);
                    }
                    moves.clear();
                    mMovesList.remove(moves);
                }
            };
            if (removalsPending) {
                View view = moves.get(0).holder.itemView;
                ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
            } else {
                mover.run();
            }
        }

D.执行变更动画,和移动动画并行处理:

if (changesPending) {
            final ArrayList<ChangeInfo> changes = new ArrayList<>();
            changes.addAll(mPendingChanges);
            mChangesList.add(changes);
            mPendingChanges.clear();
            Runnable changer = new Runnable() {
                @Override
                public void run() {
                    for (ChangeInfo change : changes) {
                        animateChangeImpl(change);
                    }
                    changes.clear();
                    mChangesList.remove(changes);
                }
            };
            if (removalsPending) {
                ViewHolder holder = changes.get(0).oldHolder;
                ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
            } else {
                changer.run();
            }
        }

E.执行添加动画:

    if (additionsPending) {
        final ArrayList<ViewHolder> additions = new ArrayList<>();
        additions.addAll(mPendingAdditions);
        mAdditionsList.add(additions);
        mPendingAdditions.clear();
        Runnable adder = new Runnable() {
            @Override
            public void run() {
                for (ViewHolder holder : additions) {
                    animateAddImpl(holder);
                }
                additions.clear();
                mAdditionsList.remove(additions);
            }
        };
        if (removalsPending || movesPending || changesPending) {
            long removeDuration = removalsPending ? getRemoveDuration() : 0;
            long moveDuration = movesPending ? getMoveDuration() : 0;
            long changeDuration = changesPending ? getChangeDuration() : 0;
            long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
            View view = additions.get(0).itemView;
            ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
        } else {
            adder.run();
        }
    }

执行顺序为:

remove动画最先执行,随后move和change并行执行,最后是add动画.

我们需要再看下父类ItemAnimator中的重要方法:

(1).animateDisappearance

当ViewHolder消失在屏幕上时被调用(可能是remove或move)

(2).animateAppearance

当ViewHolder出现在屏幕上时被调用(可能是add或move)

(3).animatePersistence

在没调用notifyItemChanged()和notifyDataSetChanged()的情况下布局发生改变时被调用

(4).animateChange

在显式调用notifyItemChanged()或notifyDataSetChanged()时被调用

综上,如过自定义ItemAnimator,需要继承SimpleItemAnimator,并且实现上述的关键方法。

6.扩展

(1)添加headerView和footerView

这里引入装饰器设计模式(Decorator):这个模式通过组合的方式,在不破坏原类代码的情况下,完成对原类的扩展。

为原有的Adapter(这里命名为NormalAdapter)添加addHeaderView()和addFooterView()接口:

        MyAdapter adapter = new MyAdapter(this);
        MyAdapterWrapper newAdapter = new MyAdapterWrapper(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.addHeaderView(headerView);
        newAdapter.addFooterView(footerView);
        mRecyclerView.setAdapter(newAdapter);

接着是MyAdapterWrapper的实现:

public class MyAdapterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    enum ITEM_TYPE{
        HEADER,
        FOOTER,
        NORMAL
    }

    private MyAdapter mAdapter;
    private View mHeaderView;
    private View mFooterView;

    public MyAdapterWrapper(MyAdapter myAdapter){
        mAdapter = myAdapter;
    }

    @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);
        }
    }

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

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

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

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

(2)空布局

ListView中有一个方法setEmptyView(),是当Adapter数据为空的时候的View视图,而RecyclerView没有这个API。可以通过监听者模式监听RecyclerView的数据变化,如果adapter为空,那么隐藏RecyclerView,显示EmptyView

(3)RcyclerView拖曳滑动实现

有一个ItemTouchHelper类,可以直接使用,也可以自定义继承ItemTouchHelper.Callback类,并重写一些重要方法,如:

A.getMovementFlags():设置支持的拖拽和滑动的方向,此处我们支持的拖拽方向为上下,滑动方向为从左到右和从右到左,内部通过makeMovementFlags()设置。

B.onMove():拖曳时回调。

C.onSwiped():滑动时回调。

D.isLongPressDragEnabled():是否支持长按推动,默认为true。若要关闭则重写后返回false。

    ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
        @Override
        public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
            return 0;
        }

        @Override
        public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
            return false;
        }

        @Override
        public void onSwiped(ViewHolder viewHolder, int direction) {

        }
    });
    helper.attachToRecyclerView(mRecyclerView);

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

interface OnStartDragListener{
    void startDrag(RecyclerView.ViewHolder holder);
}

然后让Activity实现该接口:

public MainActivity extends Activity implements OnStartDragListener{
    ...
    public void startDrag(RecyclerView.ViewHolder holder) {
        mHelper.startDrag(holder);
    }
}

如果要对ViewHolder的text对象支持触摸拖拽,则在Adapter中的onBindViewHolder()中添加:

holder.text.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_DOWN){
            mListener.startDrag(holder);
        }
        return false;
    }
});

其中mListener是在创建Adapter时将实现OnStartDragListener接口的Activity对象作为参数传进来。

(4).RecyclerView回收机制

这个后续会和ListView的回收机制一起做个对比。

注意:

1.Android Stuido关于在V7包下找不到recyclerview的解决办法

ctrl + alt + shift + s 打开Project
Structure,选择app,点击右上角加号选择第一项Library dependence。搜索recyclerview,将结果添加进去,重新build即可。

Android控件之RecyclerView

2.RecyclerView如果不显示内容的解决方法:

A.Adapter中的getItemCount()需要有返回值>0;
B.setAdapter之前要调用setLayoutManager();