Android控件之RecyclerView

一.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的继承关系如下:

这里写图片描述

它提供了有关删除,添加和移动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即可。

这里写图片描述

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

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

发布了18 篇原创文章 · 获赞 5 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/zplxl99/article/details/81266081
今日推荐