一些非常基础的feature这里就不记录了,主要记录一些重难点。
看一下这次我们的成品:
主要几个feature如下:
我们一个一个来说。
有时候为了实现特定的效果,需要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的泛型变量 mHeaderView和mFooterView:
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来调用并将创建的headerview和foorterview传入,放入泛型中,找到此view在泛型中的位置,并用notifyItemInserted方法来插入到recyclerview中,移除的方法也是类似,还是很好理解的。
4.接下来就是重写RecyclerViewAdater的方法了,同样我们需要在
onCreateViewHolder中根据viewtype来判断需要传入给RecyclerView.ViewHolder的View;
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);
}
}
首先第一种实现方式一定是在adapter中通过(以点击为例)
1.创建点击接口OnItemClickListener,并且创建抽象手势方法OnItemClick()。
2.在需要回调抽象方法的类中传入此类的实例setOnItemClickListener(this)。
3.在holder.itemView监听事件的重写中通过传进来的实例调用抽象方法mOnItemClickListener.OnItemClick(position)。
4.在回调中实现OnItemClick()方法。
可以在原adapter或HeaderAndFooterAdapter中捕捉手势。
第二种方式是重写RecyclerView的OnItemTouchListener,并通过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;
}
1.Recyclerview中Item的添加删除
RecyclerView的Adpater里面相比较ListView的Adapter,主要多了这几个方法
那么我们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);
}
}
其实通过观察方法名字可以看出来,无论是notifyItemInserted还是notifyItemRemoved的方法名中,都没有“Changed”这个单词。所有notify开头的方法中,仅仅只有以下三个方法带有“Changed”单词且具有重新绑定数据与界面的功能:
notifyDataSetChanged();//通知重新绑定所有数据与界面
notifyItemChanged(int);//通知重新绑定某一个Item的数据与界面
notifyItemRangeChanged(int, int);//通知重新绑定某一范围内的的数据与界面
2.Item的局部更新
这里的局部更新并不是指对一个item的更新,而是一个item中某一个控件的局部更新,我们知道在RecyclerViewAdapter中有方法notifyItemChanged(position),作用是更新position对应的Item。
代码奉上:
public void updateDate(int position, String textStr) {
if (position < textList.size()) {
textList.set(position, textStr);
notifyItemChanged(position);
}
}
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);//通知数据与界面重新绑定
}
}
在查阅资料后发现,图片闪烁可能是
然而通过将iamgeview固定宽高,取消recyclerview的item animator动画,或是对网络库的动画进行限制等,都没有解决问题。
最终在notifyItemChanged的API中,我们发现多了参数payload的notifyItemChanged方法在对参数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参数的onBindViewHolder。
@Override
public void onBindViewHolder(TextViewHolder holder, int position, List
如果payload不为空,那么我们更新我们Item中的控件textview(注意不更新的控件不要写在此处),测试后发现,果然图片不再闪烁了!
我们知道在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);
}
}
这里比较重要的就是其中的drawVertical和drawHorizontal方法,我们拿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的动画
DefaultItemAnimator是Android OS中一个默认的RecyclerView动画实现类,如果产品需求没有特别复杂的动画要求,可以使用DefaultItemAnimator实现简单的动画效果。
animator = new DefaultItemAnimator();
animator.setAddDuration(500);
animator.setRemoveDuration(500);
animator.setChangeDuration(500);
animator.setMoveDuration(500);
recyclerView.setItemAnimator(animator);
这样,简单的动画效果就实现了。
在添加item之前,加上:
recyclerView.scrollToPosition(position);
就能够在动画显示前滑动到相应位置了。
7.滑动删除item和长按拖拽
ItemTouchHelper 是support-v7包中加入的一个帮助开发人员处理拖拽和滑动的实现类,它能够让你非常容易实现侧滑删除、拖拽的功能。
我们只需要实例化一个ItemTouchHelper,然后关联到RecyclerView就OK了:
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);
}
}
下拉刷新用了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);
}
});
完整代码地址:http://download.csdn.net/download/denglixuan1996/10265122