在上一篇文章中介绍了RecyclerView的基本使用,这节课我们继续介绍RecyclerView的一些高级特性。
轻量化的通知
前面提到RecyclerView的Adapter对数据变动的通知作了优化,更加的精准与轻量。现在我们就来介绍怎么使用。
Adapter的通知分为单体通知和群体通知。在底层的实现中,实际上是最终调用的群体通知。
public final void notifyItemInserted(int position) {
mObservable.notifyItemRangeInserted(position, 1);
}
public final void notifyItemMoved(int fromPosition, int toPosition) {
mObservable.notifyItemMoved(fromPosition, toPosition);
}
public final void notifyItemRangeInserted(int positionStart, int itemCount) {
mObservable.notifyItemRangeInserted(positionStart, itemCount);
}
现在我们就来一个个的进行操作。
比如增加一项数据:
packageInfoList.add(0, packageInfo);
adapter.notifyItemInserted(0);
recyclerView.scrollToPosition(0); \\可选,主要作用是让recyclerview滚动至新增加的一行
删除一项数据
packageInfoList.remove(0);
adapter.notifyItemRemoved(0);
更新一项数据,
adapter.notifyItemChanged(0);
对于批量的修改也很简单,只要在以上notify系列的第二个参数加上批量的个数即可。
adapter.notifyItemInserted(0, 100); //从0开始,插入100个数据。
ItemAnimator
由于Adapter的通知非常精准,因此,在对数据删除增加时出动画是非常方便的。只要继承RecyclerView.ItemAnimator这个类。
PS:有点麻烦的是这个类在23.1的版本作了修改,与之前的版本不兼容,Google为了解决这个问题,提供了一个android.support.v7.widget.SimpleItemAnimator来向下兼容,当你的项目升级到23.1而报错的话,只需要把你的基类RecyclerView.ItemAnimator改为SimpleItemAnimator即可。
当然,实现这个类还是比较复杂的,要实现多个方法,指定在不同场景,如添加,删除等动画效果,幸运的是有大牛已经为我们写好一些实现方式,可以满足大多数的需要,使用时只要引用这个库就好。
compile 'jp.wasabeef:recyclerview-animators:2.0.0'
对于23.1以下的编译环境,要使用。原因就是上面我说的不兼容问题导致的。
compile 'jp.wasabeef:recyclerview-animators:1.3.0'
引入这个库后,我们就可以使用代码:
recyclerView.setItemAnimator(new SlideInLeftAnimator());
recyclerView.getItemAnimator().setAddDuration(500);
recyclerView.getItemAnimator().setRemoveDuration(500);
recyclerView.getItemAnimator().setMoveDuration(500);
recyclerView.getItemAnimator().setChangeDuration(500);
ItemDecoration
ListView中有个属性叫divider,用于定义Item之间的分隔线。RecyclerView把这个divider的概念进行了扩展,改为ItemDecoration。这样,我们可以实现的效果就更加灵活,不必从上到下都使用统一的divider效果,完全可以根据不同的item定制不同的特殊效果。当然,灵活带了的问题就是实现上会比之前要复杂一点。
这里用一个例子来讲它的一个实现。
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
//这个是重点方法,用于真正绘制所需要的装饰效果,这里分为水平列表和垂直列表,通常情况我们都使用垂直列表,所以这里以垂直列表为例
@Override
public void onDraw(Canvas c, RecyclerView parent,RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
//首先,我们获取parent的padding,我们的画线不能画到padding外面去吧。
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
//这里,对每一个ChildView都要进行计算和绘制,如果有些ChildView有不一样的地方,可以在这里进行处理。
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
//确定divider的位置,这里是画在每一个childView的下面,当然,你可以画在上面画,根据你的需要。
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
//这个类主要是返回两个View之间的间距,返回值设置在outRect中,如果你的divider不占空间,则需要把outRect的大小设置为0。
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
遗留问题
由于ItemDecoration不是一个View,因此在动画的时候,不能随着ItemAnimator进行。这样整个效果看起来就比较奇怪。我设想的一种解决方法是不使用这个ItemDecoration机制,把相关的效果直接写在ViewHolder中,不知大家有没有更好的办法。