RecyclerView重点feature记录

一些非常基础的feature这里就不记录了,主要记录一些重难点。

 

看一下这次我们的成品: 



 

主要几个feature如下:


  • 多布局

  •  添加头部和底部

  •  添加点击事件,通过多种方式实现

  •  item的局部更新

  •  添加的定制的分割线

  •  item的动画

  •  滑动删除item和长按拖拽

  • 其他

 

我们一个一个来说。

 

    1.多布局


  • 实现:

 

有时候为了实现特定的效果,需要recycler view可以有多种布局格式,我们需要用ViewType进行区分。

 

RecyclerView提供了方法去区分ViewType

 

@Override

public int getItemViewType(int position) {

    return super.getItemViewType(position);

}

 

onCreateViewHolder的第二个参数int viewType就是getItemViewType的返回值。

 

@Override

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    return null;

}

 

所以我们只需要为不同的布局制定不同的viewholder,并

 

onbindviewholder中根据不同的holder类型;

oncreateviewholder中根据不同的viewtype

getitemviewtype中根据不同的position

 

而定制并选择不同的布局即可。

代码奉上:

@Override

public int getItemViewType(int position) {

    if (position % 2 == 0) {

        return TWO_ITEM;

    } else {

        return ONE_ITEM;

    }

}

class OneViewHolder extends RecyclerView.ViewHolder {

    TextView tv1;

 

    public OneViewHolder(View itemView) {

        super(itemView);

        tv1 = (TextView) itemView.findViewById(R.id.text_recycler_view);

    }

}

class TwoViewHolder extends RecyclerView.ViewHolder {

    TextView tv21, tv22;

     public TwoViewHolder(View itemView) {

        super(itemView);

        tv21 = (TextView) itemView.findViewById(R.id.text_recycler_view21);

        tv22 = (TextView) itemView.findViewById(R.id.text_recycler_view22);

    }

}

@Override

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    LayoutInflater mInflater = LayoutInflater.from(context);

    RecyclerView.ViewHolder holder = null;

    if (ONE_ITEM == viewType) {

        View v = mInflater.inflate(R.layout.item_viewtype1, parent, false);

        holder = new OneViewHolder(v);

    } else {

        View v = mInflater.inflate(R.layout.item_viewtype2, parent, false);

        holder = new TwoViewHolder(v);

    }

    return holder;

}

@Override

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

    if (holder instanceof OneViewHolder) {

        ((OneViewHolder) holder).tv1.setText("布局1" + position);

    } else {

        ((TwoViewHolder) holder).tv21.setText("布局2" + position);

        ((TwoViewHolder) holder).tv22.setText("布局2" + position);

    }

}

 

  •  遇到的问题:无。

  •  解决:无。

 


     2.添加头部和底部


  • 实现:

 

思路:用多布局的方式实现,由于在recyclerview中添加item和添加首部尾部是不一样的,所以我们可以在原有的recyclerview的适配器基础上封装一个首部尾部适配器,根据viewtype的值确定返回的类型是首部尾部还是recyclerview本身。

 

这么说可能不太容易理解,我们还是通过代码来理解:

 

1.首先创建新的适配器HeaderAndFooterAdapter

 

2.创建两个可以存入首部尾部view的泛型变量 mHeaderViewmFooterView

protected SparseArrayCompat mHeaderViews = new SparseArrayCompat<>();

protected SparseArrayCompat mFooterViews = new SparseArrayCompat<>();

 

3.其次我们需要创建这几个方法:

public void addHeaderView(View view) {

    mHeaderViews.put(BASE_ITEM_TYPE_HEADER++, view);

    int index = mHeaderViews.indexOfValue(view);

    notifyItemInserted(index);

}

public void addFooterView(View view) {

    mFooterViews.put(BASE_ITEM_TYPE_FOOTER++, view);

    int index = mFooterViews.indexOfValue(view) + getHeadersCount() + getRealItemCount();

    notifyItemInserted(index);

}

public void removeHeaderView(View view) {

    int index = mHeaderViews.indexOfValue(view);

    if (index < 0) return;

    mHeaderViews.removeAt(index);

    notifyItemRemoved(index);

}

public void removeFooterView(View view) {

    int index = mFooterViews.indexOfValue(view);

    if (index < 0) return;

    mFooterViews.removeAt(index);

    index = index + getHeadersCount() + getRealItemCount();

    notifyItemRemoved(index);

}


这些方法需要由adapter来调用并将创建的headerviewfoorterview传入,放入泛型中,找到此view在泛型中的位置,并用notifyItemInserted方法来插入到recyclerview中,移除的方法也是类似,还是很好理解的。

 

4.接下来就是重写RecyclerViewAdater的方法了,同样我们需要在

 

onCreateViewHolder中根据viewtype来判断需要传入给RecyclerView.ViewHolderView

 

onBindViewHolder中根据position判断是首部尾部还是RecyclerView本身的Item,如果是本身的Item,则调用原本的adapter

 

getItemViewType中根据position判断返回的int值;

 

代码奉上:

@Override

public int getItemCount() {

    return getRealItemCount() + getHeadersCount() + getFootersCount();

}

@Override

public int getItemViewType(int position) {

    if (isHeaderPosition(position)) {

        return mHeaderViews.keyAt(position);

    }

    if (isFooterPosition(position)) {

        return mFooterViews.keyAt(position - getHeadersCount() - getRealItemCount());

    }

    return mRealAdapter.getItemViewType(position - getHeadersCount());

}

@Override

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    if (isHeaderType(viewType)) {

        int headerPosition = mHeaderViews.indexOfKey(viewType);

        View headerView = mHeaderViews.valueAt(headerPosition);

        return new RecyclerView.ViewHolder(headerView) {

        };

    }

    if (isFooterType(viewType)) {

        int footerPosition = mFooterViews.indexOfKey(viewType);

        View footerView = mFooterViews.valueAt(footerPosition);

        return new RecyclerView.ViewHolder(footerView) {

        };

    }

    return mRealAdapter.onCreateViewHolder(parent, viewType);

}

@Override

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

    if (isHeaderPosition(position) || isFooterPosition(position)) {

    } else {

        final int realPosition = position - getHeadersCount();

        mRealAdapter.onBindViewHolder(holder, realPosition);

    }

}

  •  遇到的问题:无。

  •  解决:无。

     3.添加点击事件,通过多种方式实现


  • 实现:

 

首先第一种实现方式一定是在adapter中通过(以点击为例)

 

1.创建点击接口OnItemClickListener,并且创建抽象手势方法OnItemClick()

2.在需要回调抽象方法的类中传入此类的实例setOnItemClickListener(this)

3.holder.itemView监听事件的重写中通过传进来的实例调用抽象方法mOnItemClickListener.OnItemClick(position)

4.在回调中实现OnItemClick()方法。

 

可以在原adapterHeaderAndFooterAdapter中捕捉手势。

 

第二种方式是重写RecyclerViewOnItemTouchListener,并通过GestureDetector类捕捉手势,其他步骤和第一种格式类似,这里不再赘述。

 

第二种方式:


GestureDetector mGestureDetector;

private View childView;

private RecyclerView touchView;



public interface OnItemClickListener {

    void onItemClick(View view, int position);

    void onLongClick(View view, int posotion);

}

public RecyclerItemClickListener(Context context, final RecyclerItemClickListener.OnItemClickListener mListener) {

    mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {

        @Override

        public boolean onSingleTapUp(MotionEvent ev) {

            if (childView != null && mListener != null) {

                mListener.onItemClick(childView, touchView.getChildPosition(childView));

            }

            return true;

        }

        @Override

        public void onLongPress(MotionEvent ev) {

            if (childView != null && mListener != null) {

                mListener.onLongClick(childView, touchView.getChildPosition(childView));

            }

        }

    });

}

@Override

public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

    mGestureDetector.onTouchEvent(e);

    childView = rv.findChildViewUnder(e.getX(), e.getY());

    touchView = rv;

    return false;

}

  •  遇到的问题:无。

  •  解决:无。

 

     4.item的添加删除与局部更新


  • 实现:

 

1.RecyclerviewItem的添加删除

 

RecyclerViewAdpater里面相比较ListViewAdapter,主要多了这几个方法

 

  1. notifyItemChanged(int position) 更新列表position位置上的数据可以调用
  2. notifyItemInserted(int position) 列表position位置添加一条数据时可以调用,伴有动画效果
  3. notifyItemRemoved(int position) 列表position位置移除一条数据时调用,伴有动画效果
  4.  notifyItemMoved(int fromPosition, int toPosition) 列表fromPosition位置的数据移到toPosition位置时调用,伴有动画效果
  5.  notifyItemRangeChanged(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项进行数据刷新
  6.  notifyItemRangeInserted(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项批量添加数据时调用,伴有动画效果
  7.  notifyItemRangeRemoved(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项批量删除数据时调用,伴有动画效果

 

那么我们item的添加和删除也就出来了:

public void addData(int position, String urlStr) {

    url.add(position, urlStr);

    notifyItemInserted(position);

}

public void removeData(int position) {

    if (position < url.size()) {

        url.remove(position);

        notifyItemRemoved(position);

    }

}

  •  遇到的问题:然而我们发现添加了新的item以后itemposition发生了错位,查询资料后得知这两个方法仅仅是起到界面动画的效果,实际上并没有进行数据与界面的重新绑定,所以我们需要在后面加上:

notifyItemRangeChanged(position,itemCount);


  • 解决:

 

其实通过观察方法名字可以看出来,无论是notifyItemInserted还是notifyItemRemoved的方法名中,都没有“Changed”这个单词。所有notify开头的方法中,仅仅只有以下三个方法带有“Changed”单词且具有重新绑定数据与界面的功能:

 

notifyDataSetChanged();//通知重新绑定所有数据与界面  

notifyItemChanged(int);//通知重新绑定某一个Item的数据与界面  

notifyItemRangeChanged(intint);//通知重新绑定某一范围内的的数据与界面 

 

2.Item的局部更新

 

这里的局部更新并不是指对一个item的更新,而是一个item中某一个控件的局部更新,我们知道在RecyclerViewAdapter中有方法notifyItemChangedposition),作用是更新position对应的Item

 

代码奉上:

 

public void updateDate(int position, String textStr) {

    if (position < textList.size()) {

        textList.set(position, textStr);

        notifyItemChanged(position);

    }

}

 

  • 遇到的问题:在RecyclerViewLayoutManagerGridLayoutManager时更新布局中的TextView控件,布局中的ImageView控件闪烁。

  • 解决:

 

public void addData(int position, String urlStr) {

    url.add(position, urlStr);

    notifyItemInserted(position);//通知演示插入动画  

    notifyItemRangeChanged(position, url.size() - position);//通知数据与界面重新绑定

}

 

public void removeData(int position) {

    if (position < url.size()) {

        url.remove(position);

        notifyItemRemoved(position);//通知演示插入动画  

        notifyItemRangeChanged(0, url.size() - position);//通知数据与界面重新绑定

    }

}

在查阅资料后发现,图片闪烁可能是 

RecyclerView重点feature记录_第1张图片

然而通过将iamgeview固定宽高,取消recyclerviewitem animator动画,或是对网络库的动画进行限制等,都没有解决问题。

 

最终在notifyItemChangedAPI中,我们发现多了参数payloadnotifyItemChanged方法在对参数payload的描述中有这样一句:

 

payload Optional parameter, use null to identify a "full" update

 

/**

 * Notify any registered observers that the item at position has changed with

 * an optional payload object.

 *(省略)

 * @param position Position of the item that has changed

 * @param payload Optional parameter, use null to identify a "full" update

 *

 * @see #notifyItemRangeChanged(int, int)

 */

public final void notifyItemChanged(int position, Object payload) {

    mObservable.notifyItemRangeChanged(position, 1, payload);

}

也就是说如果payload参数是null,那么就会来一个完整的更新,也就是说会全部更新。

 

我们再看一下mRecyclerViewAdapter.notifyItemChanged(position)的源码:

/**

 * Notify any registered observers that the item at position has changed with

 * an optional payload object.

 *(省略)

 * @param position Position of the item that has changed

 * @param payload Optional parameter, use null to identify a "full" update

 *

 * @see #notifyItemRangeChanged(int, int)

 */

public final void notifyItemChanged(int position, Object payload) {

    mObservable.notifyItemRangeChanged(position, 1, payload);

}

从源码中看到,notifyItemChanged(position)调用了 notifyItemRangeChanged(int positnStart, int itemCount)方法,源码如下:
public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {

    // since onItemRangeChanged() is implemented by the app, it could do anything, including

    // removing itself from {@link mObservers} - and that could cause problems if

    // an iterator is used on the ArrayList {@link mObservers}.

    // to avoid such problems, just march thru the list in the reverse order.

    for (int i = mObservers.size() - 1; i >= 0; i--) {

        mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);

    }

}

 

notifyItemRangeChanged(int positionStart, int itemCount)方法最终还是调用了notifyItemRangeChanged(int positionStart, int itemCount, Object payload)方法,只是payload参数是null

 

那么如果payload传一个不为null的参数,就可以实现对列表项中的具体控件更新了吗?我们通过代码验证下。

 

这里我们payload复制为一个无意义的字符串“fugui”,然后重写带有List payloads参数的onBindViewHolder

 

@Override

public void onBindViewHolder(TextViewHolder holder, int position, List payloads) {

    if (payloads.isEmpty()) {

        onBindViewHolder(holder, position);

    } else {

        holder.textView.setText(title);

    }

} 
  


如果payload不为空,那么我们更新我们Item中的控件textview(注意不更新的控件不要写在此处),测试后发现,果然图片不再闪烁了!

    5.添加的定制的分割线

 

  • 实现:

 

我们知道在recyclerview中有addItemDecoration方法,我们的需求是可定制分割线的颜色,高度,Item顶部和底部是否有分割线等。

 

1.首先,创建CustomerDecoration继承自RecyclerView.ItemDecoration,除了

 

divider在我们传入的颜色基础上由paint画笔重新绘制;

还有顶部底部是否显示的top参数需要更改;

 

以外,其他部分和官方提供的DividerItemDecoration很相似。我们需要重写的方法有

 

onDraw()                 (负责绘制)

getItemOffets()       (负责得到分割线的尺寸)

 

2.CustomerDecoration的构造方法中传入定制的参数(上下文,方向,高度,颜色,是否显示最顶部分割线,是否显示自底部分割线)。

 

recyclerView.addItemDecoration(new CustomerDecoration(this

        , LinearLayoutManager.HORIZONTAL, 5, R.color.gray, false, false));

 

并在构造方法中根据颜色初始化画笔Paint

 

public CustomerDecoration(Context context, int orientation, int dividerHeight

        , int dividerColor, boolean up, boolean down) {

    this(context, orientation);

    this.mContext = context;

    mDividerHeight = dividerHeight;

    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    mPaint.setColor(dividerColor);

    mPaint.setStyle(Paint.Style.FILL);

    this.up = up;

    this.down = down;

}


3.然后重写getItemOffsets方法(根据传入的分割线高度mDividerHeight设置分割线矩形):

//获取分割线尺寸

@Override

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

    super.getItemOffsets(outRect, view, parent, state);

    outRect.set(0, 0, 0, mDividerHeight);

}

 

4.然后是onDraw方法:

 

@Override

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

    super.onDraw(c, parent, state);

    if (mOrientation == LinearLayoutManager.VERTICAL) {

        drawVertical(c, parent);

    } else {

        drawHorizontal(c, parent);

    }

}


这里比较重要的就是其中的drawVerticaldrawHorizontal方法,我们拿drawHorizontal来讲解。

 

private void drawHorizontal(Canvas canvas, RecyclerView parent) {

    final int left = parent.getPaddingLeft();

    final int right = parent.getMeasuredWidth() - parent.getPaddingRight();

    final int childSize = parent.getChildCount();

    int top = 0,bottom;

    View child = null;

    RecyclerView.LayoutParams layoutParams = null;

    for (int i = 0; i < childSize; i++) {

        child = parent.getChildAt(i);

        layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();

        top = child.getBottom() + layoutParams.bottomMargin;

        bottom = top + mDividerHeight;

        if (mDivider != null) {

            mDivider.setBounds(left, top, right, bottom);

            mDivider.draw(canvas);

        }

        if (mPaint != null) {

            canvas.drawRect(left, top, right, bottom, mPaint);

        }

    }

}

首先通过onDraw中的第二个参数Recyclerview的实例获得recyclerview距左右的内边距,也就是分割线的左右位置,及Item的个数,接下来在for 循环中通过获取每个Item底部长度Item的底部边距来确定分割线的顶部位置,然后加上分割线高度得到分割线底部位置。

 

纵向绘制道理也是一样的。

 

通过这样的绘制流程,我们能够绘制出每个item底部的分割线,那么最顶部的如何绘制呢。

答案很简单,只要在此方法中判断绘制顶部的参数是否为true,若为true,则修改顶部值为:

 

top = child.getTop() - layoutParams.bottomMargin;

 

 

top = child.getBottom() - layoutParams.bottomMargin - child.getHeight();

即可。

 

由于最底部本来就已经绘制,所以如果想去掉底部,则在for循环之前添加:

 

if (!down) {

    childSize = childSize - 1;

}

则若将参数down设置为false传入,则底部分割线就不会显示啦!

  •  遇到的问题:无。

  •  解决:无。

 

     6.item的动画


  • 实现:

 

DefaultItemAnimatorAndroid OS中一个默认的RecyclerView动画实现类,如果产品需求没有特别复杂的动画要求,可以使用DefaultItemAnimator实现简单的动画效果。

animator = new DefaultItemAnimator();

animator.setAddDuration(500);

animator.setRemoveDuration(500);

animator.setChangeDuration(500);

animator.setMoveDuration(500);

 
recyclerView.setItemAnimator(animator);

 

这样,简单的动画效果就实现了。

  •  遇到的问题:当给position0的位置添加item时,由于不能自动滑动到新添加的item位置,所以这里的item动画效果我们是看不见的。

  • 解决:

 

在添加item之前,加上:

  

recyclerView.scrollToPosition(position);

就能够在动画显示前滑动到相应位置了。

 

     7.滑动删除item和长按拖拽

  • 实现:

 

ItemTouchHelper support-v7包中加入的一个帮助开发人员处理拖拽和滑动的实现类,它能够让你非常容易实现侧滑删除、拖拽的功能。

我们只需要实例化一个ItemTouchHelper,然后关联到RecyclerViewOK了:

 

 

ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mCallback);

itemTouchHelper.attachToRecyclerView(recyclerView);

 

ItemTouchHelper的实现:

ItemTouchHelper.SimpleCallback mCallback = new ItemTouchHelper.SimpleCallback(

        ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT

                | ItemTouchHelper.RIGHT, ItemTouchHelper.START | ItemTouchHelper.END) {

    @Override

    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {

        int fromPosition = viewHolder.getAdapterPosition();

        int toPosition = target.getAdapterPosition();

        textAdapter.moveDate(fromPosition, toPosition);

        return true;

    }

    @Override

    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

        int position = viewHolder.getAdapterPosition();

        textAdapter.removeData(position);

    }

};
 

public void moveDate(int i, int j) {

    String t = url.get(i);

    url.set(i, url.get(j));

    url.set(j, t);

    notifyItemMoved(i, j);

}

public void removeData(int position) {

    if (position < url.size()) {

        url.remove(position);

        notifyItemRemoved(position);

        notifyItemRangeChanged(0,url.size()-position);

    }

}

  •  遇到的问题:无。

  •  解决:无。

 

    8.其他


  • 效果: 



  • 实现:

 

下拉刷新用了android.support.v4.widget包中的SwipeRefreshLayout,用法比较简单:

 



 

    

 



 

@BindView(R.id.swipeRefreshLayout)

SwipeRefreshLayout refreshLayout;

 

ButterKnife.bind(this);

 

refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {

    @Override

    public void onRefresh() {

        textAdapter.updateAllData(App.urlNew);

        refreshLayout.setRefreshing(false);

 

    }

});

 

网络库用了Universal-Image-Loader

 

ImageLoader.getInstance().displayImage(url.get(position), holder.imageView, mOptions, new SimpleImageLoadingListener() {

    @Override

    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {

        super.onLoadingComplete(imageUri, view, loadedImage);

        holder.imageView.setImageBitmap(loadedImage);

    }

});

 

  •  遇到的问题:无。

  •  解决:无。

 

到这里重点feature就总结完成了!


完整代码地址:http://download.csdn.net/download/denglixuan1996/10265122

你可能感兴趣的:(邓丽璇的个人文档)