技术标签: RecyclerView inflate 崩溃 RecyclerView学习 Android Note RecyclerView RecyclerView 的LayoutManager RecyclerView内容不居中
Google 在Android 5.0 提出了design系列的控件,其中RecyclerView就是其中的一员。相对于ListView而言,RecyclerView功能强大的一批。之前总是对这个控件的使用模模糊糊,这里便仔西品味下这个控件。
(1)有关依赖引入
其实RecyclerView就是design库里面的,我们可以引入design库即可,这时你不仅可以使用RecyclerView控件,只要是design系列的控件都可以使用,比如CardView、NavigationView都可以使用。如果你就是仅仅使用RecyclerView那么只引入RecyclerView的依赖也行。
ps:依赖如下,根据自己的需求合理选择即可。
// design库依赖
implementation 'com.android.support:design:28.0.0'
//recyclerview依赖
implementation 'com.android.support:recyclerview-v7:21.0.0'
(2)Adapter中布局转换为View的注意点
这里主要总结下RecyclerView的Adapter中布局转换为View的注意点,其次吧具体的书写步骤附带下。
[MainActivity.java]
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private List<String> mList;
private MyRecyclerAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
/**
* 初始化数据
* */
private void initData() {
// recycler view 的数据
mList = new ArrayList<>();
for (int i = 0; i < 30; i++) {
mList.add("item" + i);
}
mAdapter = new MyRecyclerAdapter(mList, this);
//给recycler view 设置布局管理器
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
// 给recycler view 设置Adapter
mRecyclerView.setAdapter(mAdapter);
}
/**
* 初始化view
* */
private void initView() {
mRecyclerView = findViewById(R.id.recycle_view);
}
}
[MyRecyclerAdapter.java]
/**
* Created by sunnyDay on 2019/9/4 14:58
*/
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder> {
private List<String> mList;
private Context mContext;
public MyRecyclerAdapter(List<String> list, Context context) {
mContext = context;
mList = list;
}
public Context getContext() {
return mContext;
}
/**
* ViewHolder 创建时,这个方法回调。
*
* @param viewGroup 容器
* @param viewType 条目类型
* ps:这里可以做布局转换view的操作
*/
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
//重要事说三遍:最后一个参数必须为false、最后一个参数必须为false、最后一个参数必须为false
View mView = LayoutInflater.from(mContext).inflate(R.layout.layout_recyclerview_item, viewGroup, false);
return new MyViewHolder(mView);// 吧view传递给ViewHolder
}
/**
* 当绑定ViewHolder 时这个方法回调
* @param myViewHolder 自定义的ViewHolder
* ps:这里可以处理view的一些逻辑
*/
@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, int position) {
myViewHolder.text.setText(mList.get(position));
}
/**
* RecyclerView 的item数目
*/
@Override
public int getItemCount() {
return mList == null ? 0 : mList.size(); // 三目运算,非空处理,避免空指针。
}
/**
* RecyclerView的条目类型 默认为一种类型
* */
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
/**
* 提供ViewHolder
* 一般在viewHolder内部就行findViewById操作
*/
static class MyViewHolder extends RecyclerView.ViewHolder {
private final AppCompatTextView text;
MyViewHolder(@NonNull View itemView) {
super(itemView);
text = itemView.findViewById(R.id.atv_text);
}
}
}
如上效果图实现,这里就吧代码实现贴了出来。其实使用还是贼简单的。但是有几点是需要我们注意的,特别是刚从ListView迁移过来的小伙伴。
View mView = View.inflate(mContext, R.layout.layout_recyclerview_item, null);
return new MyViewHolder(mView);
1、刚从ListView迁移过来的小伙伴可能View.inflate的方式使用的多(反正我当年就是这样哈)但是View.inflate的方式在这里是不建议使用的因为你会碰见一个问题内容不居中,确切的说是你指定的layout.layout_recyclerview_item这个布局的根节点容器的属性失效。因为你给他的第三个参数传递了null,当你的layout.layout_recyclerview_item布局转换为view后是不会添加到任何容器中的,所以根节点的属性失效。
2、有的小伙伴该说了:我把onCreateViewHolder方法传递过来的参数viewGroup传过来不就行啦?不好意思回答你的是程序跑的时候直接崩溃了(java.lang.IllegalStateException: ViewHolder views must not be attached when created. Ensure that you are not passing ‘true’ to the attachToRoot parameter of LayoutInflater.inflate(…, boolean attachToRoot))
有人或许看过View.inflate的源码知道其底层是使用LayoutInflater的。所以你或许会这样使用,如下:
View mView = LayoutInflater.from(mContext).inflate(R.layout.layout_recyclerview_item, viewGroup, true);
return new MyViewHolder(mView);
不好意思,跑起来又炸了!!!还是上文同样的崩溃日志,既然看过View.inflate的源码,那么你仔细一想就会发现上文中View.inflate的root不为空时就是调用的LayoutInflater.from(mContext).inflate(R.layout.layout_recyclerview_item, viewGroup, true),这里我们需要坐下来分析下崩溃日志啦!!!
java.lang.IllegalStateException:
ViewHolder views must not be attached when created.
Ensure that you are not passing 'true' to the attachToRoot parameter of LayoutInflater.inflate
(..., boolean attachToRoot)
这里,ViewHolder 的View一定不能依附于root容器。确认下你的inflate中第三个参数没有传递true。否则就报这个错。
ps:RecyclerView 的ViewHolder要求你不能直接把View添加给RecyclerView,要吧view作为参数,先传递给 ViewHolder 。第三个参数为false就代表不直接加入容器。
LayoutInflater.from(mContext).inflate(R.layout.layout_recyclerview_item, viewGroup, false)第三个参数传递为false,然后把获得的view传递给ViewHolder即可。
ps:如果对上文inflate有迷惑,可以参考这篇文章: Android的inflate你所需要知道的一切
Recycler的多条目的实现其实也是蛮简单的:
1、这里你需要重写getItemViewType方法。
2、然后再onCreateViewHolder中根据不同的viewType加载不同的布局。
3、再onBindViewHolder中根据条目所代表的类型处理相关的逻辑即可。
其实这里有一种优雅的写法,在不破坏我们原来的MyRecyclerAdapter代码的基础下添加新的view类型,那就是使用装饰着设计模式(Decorator)
/**
* Created by sunnyDay on 2019/9/6 20:02
* recyclerView的多条目实现
*/
public class MyRecyclerAdapterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// 1、装饰者与被装饰着继承或实现相同的对象
private static final int TYPE_NORMAL = 0; //普通类型
private static final int TYPE_HEAD = 1; // 类型 头
private static final int TYPE_FOOT = 2; // 类型 尾
private MyRecyclerAdapter myRecyclerAdapter;// 2、装饰者持有被装饰者引用
/**
* 初始化引用
*/
public MyRecyclerAdapterWrapper(MyRecyclerAdapter myRecyclerAdapter) {
this.myRecyclerAdapter = myRecyclerAdapter;
}
/**
* @param viewType 代表条目类型
*/
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
if (viewType == TYPE_HEAD) {
return new HeadViewHolder(LayoutInflater.from(myRecyclerAdapter.getContext()).inflate(R.layout.layout_head, viewGroup, false));
} else if (viewType == TYPE_FOOT) {
return new FootViewHolder(LayoutInflater.from(myRecyclerAdapter.getContext()).inflate(R.layout.layout_foot, viewGroup, false));
} else {
return myRecyclerAdapter.onCreateViewHolder(viewGroup, viewType);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
if (position == 0 || position == myRecyclerAdapter.getItemCount() + 1) {
// 索引为 0和最后一个条目时 加载相应的布局
} else {
if (viewHolder instanceof MyRecyclerAdapter.MyViewHolder) {
myRecyclerAdapter.onBindViewHolder((MyRecyclerAdapter.MyViewHolder) viewHolder, position - 1);
}
}
}
@Override
public int getItemCount() {
return myRecyclerAdapter.getItemCount() + 2; // 相当于原来的基础上多添加两个
}
/**
* @param position 条目索引
* @function 根据条目的索引返回其相应的条目类型。相同类型item返回相同的数值。
*/
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TYPE_HEAD;
} else if (position == myRecyclerAdapter.getItemCount() + 1) {
return TYPE_FOOT;
} else
return TYPE_NORMAL;
}
class HeadViewHolder extends RecyclerView.ViewHolder {
public HeadViewHolder(@NonNull View itemView) {
super(itemView);
}
}
class FootViewHolder extends RecyclerView.ViewHolder {
public FootViewHolder(@NonNull View itemView) {
super(itemView);
}
}
}
这里的难点就是对getItemViewType这个方法的理解:根据条目的索引返回其相应的条目类型很简单但是有人可能会迷糊这里就画图说下。
这里就三种类型,还算简单的,其实如果种类再多时我们只需要指定索引范围为哪种类型即可。例如上文中的普通条目我们还可以写成这样:else if (position == 1 && position < myRecyclerAdapter.getItemCount() + 1) {return TYPE_FOOT; }
ps:阿里开源框架vlayout可以帮我们快速实现电商app首页的多itemType。
RecyclerView不像Listview那样提供了添加头尾的方法,需要开发者自己实现。其实搞过上文的多条目后我们添加头尾就很方便啦。
数据的更新一般分为两种,在顶部item时我们可以下拉刷新数据。当画到底部当前可见的最后一个item时,再次上拉(上滑)加载数据。
(1)下拉刷新
下拉刷新的逻辑其实还是有点复杂的,我们需要为RecyclerView添加个头布局,这个头布局里面一般包括刷新图标,这个图标还伴有动画,处理之外我们还要重写触摸事件,监听用户的滑动操作。不同的滑动操作对应刷新图标的动画。反正就是属于自定义拓展view的系列了。还好安卓supportv4给我们提供了这个控件帮助我们快速实现。
简单使用
//1、 要下拉刷新的控件放入这个容器即可
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/pull2refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
// java 代码中:
refreshLayout.setOnRefreshListener(this);// 下拉刷新
refreshLayout.setColorSchemeResources(R.color.colorAccent,R.color.colorPrimary);//刷新时进度条颜色变换,转一圈颜色变化一种
/**
* 下拉刷新回调,模拟数据更新
*/
@Override
public void onRefresh() {
//模拟耗时操作
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
refreshLayout.setRefreshing(false);//取消刷新
}
}, 2000);
mList.add("我是下拉刷新添加的数据");
mAdapter.notifyDataSetChanged();
Toast.makeText(this, "刷新数据成功", Toast.LENGTH_SHORT).show();
}
其实这种下拉刷新就是典型的圆形进度条转鸭转,如果我们想要花里胡哨的操作还是自定义算啦!!!
下拉刷新自定义实现参考:自己动手写RecyclerView的下拉刷新
(2)上拉加载
数据的上拉加载的实现逻辑相对来说是比较简单的,这里就手动总结下。简单实现思路如下:
1、滑动事件监听
2、判断条目是否是最后一个、可见的条目,且用户正在向上滑。
3、满足2时进行加载数据,更新UI。
// 滑动监听
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
boolean isSlide2Up = false;
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
//状态为静止没有滑动时
if (manager != null) {
int lastItemIndex = manager.findLastCompletelyVisibleItemPosition();//获取最后一个可见的条目索引
int itemCount = manager.getItemCount();//获取item的数量
// 当滑动到最后一个可见条目,且上拉时。加载数据
if (lastItemIndex == itemCount - 1 && isSlide2Up) {
mList.add("我是上拉加载的数据");
mAdapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "刷新数据成功", Toast.LENGTH_SHORT).show();
}
}
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 大于0表示正在向上滑动,小于等于0表示停止或向下滑动
isSlide2Up = dy > 0;
}
});
这里也是简要的实现下逻辑,还是那句话想要花里胡哨那就自定义吧!!!
newState的三种状态:1、SCROLL_STATE_IDLE :静止没有滚动
2、SCROLL_STATE_DRAGGING :正在被外部拖拽,一般为用户正在用手指滚动
3、SCROLL_STATE_SETTLING :用户画动后,手离开屏幕,条目自动滚动、滑动。注意滑动到最后一个可见条目,且上拉时的判断条件
注意向上向下滑动的判断(dy,左右时使用dx)
ps:参考 RecyclerView的滚动事件OnScrollListener研究
RecyclerView的Adapter写多了你就会发现存在着一些重复的工作,这时我们便思考能不能像ListView的adapter一样有个BaseAdapter就好啦。于是开始设计。
思路:想要打造万能的adapter必须先搞个通用的ViewHolder,有了通用的ViewHolder后再设计万能的adapter就方便多啦。具体实现如下:
(1)普通的通用Adapter(适合一种类型的条目)
/**
* Created by sunnyDay on 2019/9/16 16:36
* 通用的Adapter封装
*/
public abstract class BaseAdapter<T> extends RecyclerView.Adapter<BaseAdapter.BaseViewHolder> {
protected Context mContext;
protected int mLayoutId;
protected List<T> mData;
protected LayoutInflater mLayoutInflate;
public BaseAdapter(Context context, int layoutId, List<T> data) {
mContext = context;
mLayoutId = layoutId;
mData = data;
mLayoutInflate = LayoutInflater.from(context);
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
return BaseViewHolder.getViewHolder(mContext, viewGroup, mLayoutId);
}
// 有点小bug(用户使用时,可以重写RecyclerView.Adapter的onBindViewHolder)
@Override
public final void onBindViewHolder(@NonNull BaseViewHolder baseViewHolder, int position) {
convert(baseViewHolder, position);
}
public abstract void convert(BaseViewHolder baseViewHolder, int position);
/**
* 返回条目个数(进行啦非空处理)
*/
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
/**
* 通用的ViewHolder,内部使用SparseArray来缓存View对象
*/
public static class BaseViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> mViews;//键值对为int类型,存储相对于hashMap高效
private View mConvertView;
private Context mContext;
BaseViewHolder(Context context, @NonNull View itemView, ViewGroup parent) {
super(itemView);
mContext = context;
mConvertView = itemView;
mViews = new SparseArray<>();
}
/**
* 获取ViewHolder
*/
public static BaseViewHolder getViewHolder(Context context, ViewGroup parent, int layoutId) {
View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
return new BaseViewHolder(context, view, parent);
}
/**
* @param viewId view的id
* @function通过View Id 找到该控件
*/
@SuppressWarnings("unchecked")
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
}
}
简单使用
mRecyclerView.setAdapter(new BaseAdapter(this,R.layout.layout_recyclerview_item,mList) {
@Override
public void convert(BaseAdapter.BaseViewHolder baseViewHolder, int position) {
AppCompatTextView text = baseViewHolder.getView(R.id.atv_text);
text.setText("万能适配器:"+position);
}
});
哈哈,是不是6的一笔,真快鸭!!!直接给个RecyclerView的item布局,在给个数据集合完事。
ps:注意这里只重写convert即可不要重写onCreateViewHolder,否则,convert不生效。
参考:java的继承机制。
(2)多Item万能Adapter的实现
/**
* Created by sunnyDay on 2019/9/16 17:48
*/
public abstract class MultiItemBaseAdapter<T> extends BaseAdapter<T> {
protected MultiItemTypeSupport<T> mMultiItemTypeSupport;
public MultiItemBaseAdapter(Context context, List<T> data, MultiItemTypeSupport<T> multiItemTypeSupport) {
super(context, -1, data);
this.mMultiItemTypeSupport = multiItemTypeSupport;
}
@Override
public int getItemViewType(int position) {
return mMultiItemTypeSupport.getItemViewType(position, mData.get(position));
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
int layoutId = mMultiItemTypeSupport.getLayoutId(viewType);
return BaseViewHolder.getViewHolder(mContext, viewGroup, layoutId);
}
/**
*
* 我们的ViewHolder是通用的,唯一依赖的就是个layoutId。那么上述第二条就变成,
* 根据不同的itemView告诉我用哪个layoutId即可,生成viewholder这种事我们通用adapter来做。
* */
public interface MultiItemTypeSupport<T> {
int getLayoutId(int itemType);
int getItemViewType(int position, T t);
}
}
LayoutManager负责RecyclerView的布局,其中包含了Item View的获取与回收。
(1)常见的实现类:
// 参数:上下文,布局方向(垂直或者水平),布局是否翻转
// 默认为垂直效果
RecyclerView.LayoutManager manager = new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false);
// 参数:上下文、列数目(指定显示几列)、布局方向(指定可以水平或者垂直滑动)、是否翻转布局(翻转item)
manager = new GridLayoutManager(this,3);//两个参数的
manager = new GridLayoutManager(this,3,LinearLayoutManager.VERTICAL,false);//四个参数的
// 参数:显示列,布局走向
manager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
ps:这里条目的高度我们要手动设置下,否则和Grid的效果一致。
(2)LinearLayoutManager的常用方法
canScrollHorizontally();//能否横向滚动
canScrollVertically();//能否纵向滚动
scrollToPosition(int position);//滚动到指定位置
setOrientation(int orientation);//设置滚动的方向
getOrientation();//获取滚动方向
findViewByPosition(int position);//获取指定位置的Item View
findFirstCompletelyVisibleItemPosition();//获取第一个完全可见的Item位置
findFirstVisibleItemPosition();//获取第一个可见Item的位置
findLastCompletelyVisibleItemPosition();//获取最后一个完全可见的Item位置
findLastVisibleItemPosition();//获取最后一个可见Item的位置
RecyclerView的条目默认是没有分割线的,但是google 工程师却暴露了一个接口让用户自定义实现。
(1)接口
public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor)
(2)探究下RecyclerView.ItemDecoration
public abstract static class ItemDecoration {
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDraw(c, parent);
}
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDrawOver(c, parent);
}
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.getItemOffsets(outRect, ((RecyclerView.LayoutParams)view.getLayoutParams()).getViewLayoutPosition(), parent);
}
}
如上,吧弃用的方法去掉后就没有几个方法啦!!! 核心方法如下。
- onDraw(): 绘制分割线。
- getItemOffsets(): 设置分割线的宽、高。
(3)想法
既然让我们自己实现,我们只需继承此类,重写这两个方法即可。
(4)参考
让我们自己实现总要给些栗子吧。。。那不错栗子是有的就是google 给的simple: DividerItemDecoration,并且高版本的sdk中已经添加了,直接就可以看这个类的源码。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class DividerItemDecoration extends ItemDecoration {
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
private static final String TAG = "DividerItem";
private static final int[] ATTRS = new int[]{
16843284};
private Drawable mDivider;
private int mOrientation;
private final Rect mBounds = new Rect();
public DividerItemDecoration(Context context, int orientation) {
TypedArray a = context.obtainStyledAttributes(ATTRS);
this.mDivider = a.getDrawable(0);
if (this.mDivider == null) {
Log.w("DividerItem", "@android:attr/listDivider was not set in the theme used for this DividerItemDecoration. Please set that attribute all call setDrawable()");
}
a.recycle();
this.setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != 0 && orientation != 1) {
throw new IllegalArgumentException("Invalid orientation. It should be either HORIZONTAL or VERTICAL");
} else {
this.mOrientation = orientation;
}
}
public void setDrawable(@NonNull Drawable drawable) {
if (drawable == null) {
throw new IllegalArgumentException("Drawable cannot be null.");
} else {
this.mDivider = drawable;
}
}
public void onDraw(Canvas c, RecyclerView parent, State state) {
if (parent.getLayoutManager() != null && this.mDivider != null) {
if (this.mOrientation == 1) {
this.drawVertical(c, parent);
} else {
this.drawHorizontal(c, parent);
}
}
}
private void drawVertical(Canvas canvas, RecyclerView parent) {
canvas.save();
int left;
int right;
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom());
} else {
left = 0;
right = parent.getWidth();
}
int childCount = parent.getChildCount();
for(int i = 0; i < childCount; ++i) {
View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, this.mBounds);
int bottom = this.mBounds.bottom + Math.round(child.getTranslationY());
int top = bottom - this.mDivider.getIntrinsicHeight();
this.mDivider.setBounds(left, top, right, bottom);
this.mDivider.draw(canvas);
}
canvas.restore();
}
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
canvas.save();
int top;
int bottom;
if (parent.getClipToPadding()) {
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
canvas.clipRect(parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom);
} else {
top = 0;
bottom = parent.getHeight();
}
int childCount = parent.getChildCount();
for(int i = 0; i < childCount; ++i) {
View child = parent.getChildAt(i);
parent.getLayoutManager().getDecoratedBoundsWithMargins(child, this.mBounds);
int right = this.mBounds.right + Math.round(child.getTranslationX());
int left = right - this.mDivider.getIntrinsicWidth();
this.mDivider.setBounds(left, top, right, bottom);
this.mDivider.draw(canvas);
}
canvas.restore();
}
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
if (this.mDivider == null) {
outRect.set(0, 0, 0, 0);
} else {
if (this.mOrientation == 1) {
outRect.set(0, 0, 0, this.mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, this.mDivider.getIntrinsicWidth(), 0);
}
}
}
}
如上:
1、想要使用这个类提供的默认分割线直接
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, 1));
2、想要指定drawable作为分割线直接调用setDrawable(@NonNull Drawable drawable)给个drawable对象即可。
DividerItemDecoration官方文档
ps:其实还有一种方法就是在xml布局里面,条目的底部添加个分割线即可。最后一个条目的底部分割线隐藏即可。
RecyclerView能够通过mRecyclerView.setItemAnimator(ItemAnimator animator)设置添加、删除、移动、改变的动画效果。RecyclerView提供了默认的ItemAnimator实现类:DefaultItemAnimator。
(1)提供的默认的动画类及其继承关系图
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
(2)类分析
SimpleItemAnimator中的重要方法:
animateAdd(ViewHolder holder): 当Item添加时被调用。
animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY): 当Item移动时被调用。
animateRemove(ViewHolder holder): 当Item删除时被调用。
animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop): 当显式调用notifyItemChanged()或notifyDataSetChanged()时被调用。
(3)简单方式 实现自定义动画
使用第三方库:recyclerview-animators
而且自定义item动画使用这个也简单多啦,可以玩一些花里胡哨的操作啦。。。
//栗子:
mRecyclerView.setItemAnimator(new ScaleInAnimator());//使用第三方
...
public void doClick(View view) {
mList.remove(0);
mAdapter.notifyItemRemoved(0); // 刷新方法的使用需要留意
}
rv相比listview的事件点击、添加头尾还是麻烦点的。事件,这个使用接口回调的方式即可实现,下面就举个点击事件的栗子。。。
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder> {
private OnClickListener mOnClickListener;
//外部设置条目点击事件时回调 view的点击事件
public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, final int position) {
myViewHolder.text.setText(mList.get(position));
myViewHolder.layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnClickListener.clicked(v, position);
}
});
}
public void setOnClickListener(OnClickListener onClickListener) {
mOnClickListener = onClickListener;
}
public interface OnClickListener {
void clicked(View view, int position);
}
}
安卓系统提供了一个强大的工具类ItemTouchHelper,这个类处理了有关rv的拖拽、侧滑的相关的事情。
简单的使用步骤:
1、实现 ItemTouchHelper.Callback 回调
2、把 ItemTouchHelper 绑定到 RecyclerView 上
(1)实现接口
简单的拖拽到指定位置、侧滑删除效果
/**
* Created by sunnyDay on 2019/9/20 17:21
* <p>
* 侧滑 拖拽的实现
* 1、实现侧滑删除
* 2、实现拖动到指定位置
*/
public class MyTouchHelper extends ItemTouchHelper.Callback {
private MyRecyclerAdapter mRecyclerAdapter;
public MyTouchHelper(MyRecyclerAdapter recyclerAdapter) {
mRecyclerAdapter = recyclerAdapter;
}
/**
* 设置支持拖拽滑动的方向(内部通过makeMovementFlags方法设置)
* 规定条目滑动为:左右
* 规定条目拖拽为:上下
*/
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
int slideFlag = ItemTouchHelper.START | ItemTouchHelper.END; // 左右滑动
int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //上下拖拽
return makeMovementFlags(dragFlag, slideFlag);
}
/**
* 拖拽时回调
*/
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
int fromItem = viewHolder.getAdapterPosition();
int toItem = viewHolder1.getAdapterPosition();
String prev = mRecyclerAdapter.getmList().remove(fromItem);//删除长摁位置的条目,保存下删除的条目。
mRecyclerAdapter.getmList().add(toItem > fromItem ? toItem - 1 : toItem, prev);// 吧删除的条目添加到拖动停止的位置
mRecyclerAdapter.notifyItemMoved(fromItem, toItem);// 通知局部刷新
return true;
}
/**
* 滑动时回调
*/
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
int position = viewHolder.getAdapterPosition();
mRecyclerAdapter.getmList().remove(position);
mRecyclerAdapter.notifyItemRemoved(position);
}
/**
* 状态改变时回调
*/
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder.itemView.setBackgroundColor(Color.parseColor("#ff0000")); //设置拖拽和侧滑时的背景色
}
}
/**
* 拖拽滑动完成之后回调
*/
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(Color.parseColor("#FFFFFF"));
}
/**
* 如果想自定义动画,可以重写这个方法
* 根据偏移量来设置
*/
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
/**
* 是否支持长摁拖拽,默认为 true,设置false为关闭。
*/
@Override
public boolean isLongPressDragEnabled() {
return super.isLongPressDragEnabled();
}
}
(2)绑定rv
// rv的拖拽侧滑
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new MyTouchHelper(mAdapter));
itemTouchHelper.attachToRecyclerView(mRecyclerView);
SnapHelp 能够辅助 RecyclerView 在滚动结束时将 Item 对齐到某个位置。
SnapHelp 是一个抽象类,Android 提供了 LinearSnapHelper,可以让 RecyclerView 滚动停止时 Item 停留在中间位置,又提供了 PagerSnapHelper,可以让 RecyclerView 像 ViewPager 一样的效果,一次只能滑动一个,并且 Item 居中显示,和 LinearSnapHelper 的区别在于 LinearSnapHelper 支持惯性滑动,所以一次能滑动多个。
(1)使用贼简单
// 支持惯性滑动
LinearSnapHelper linearSnapHelper = new LinearSnapHelper();
linearSnapHelper.attachToRecyclerView(mRecyclerView);
// 类似vp效果
PagerSnapHelper pagerSnapHelper = new PagerSnapHelper();
pagerSnapHelper.attachToRecyclerView(mRecyclerView);
后续熟悉了再补。。。。。
出现方式:
(1)和CoordinatorLayout结合时
Android 5.0推出了嵌套滑动机制,在之前,一旦子View处理了触摸事件,父View就没有机会再处理这次的触摸事件,而嵌套滑动机制解决了这个问题.为了支持嵌套滑动,子View必须实现NestedScrollingChild接口,父View必须实现NestedScrollingParent接口,而RecyclerView实现了NestedScrollingChild接口,而CoordinatorLayout实现了NestedScrollingParent接口
ps:结合第一行代码的5.0UI章节理解。
(2)其他场景及其解决方案
(1)ListView的相对优点
(2)Rv的优势
只是把重要知识点过了一遍,懂了些基础、RecyclerView的设计原理、内部的设计模式啥的还没探讨。。。任重而道远。
参考文章:
最近工作中遇到同事经常要发送销售日报之类的,邮件内容极为相似,发送内容有:邮件主题,报表,报表附件,发送抄送不同人员。本分享实现内容,可以填入不同发送服务器,不同工作表、附件、发送给对应的接收人,邮件主题,邮件主要内容可以自定义,也可以默认进行发送。附件说明:工作表:“查询按钮”B1:b3,填写发送邮件服务器,发送邮箱,发送邮箱密码B9:c16填写邮件接收人、抄送人员邮箱,多个接收人用...
需要开机执行的sudo命令可以写在/etc/rc.local中,因为/etc/rc.local是以root身份去执行的。系统在启动时会先调用/etc/init.d/rc.local,在/etc/init.d/rc.local脚本中再调用/etc/rc.local/etc/init.d/rc.local中调用/etc/rc.local的部分如下:if [ -x /etc/rc....
Django提供了模板,用于编写HTML文件模板包括:静态部分:html、css、js。动态部分:模板语言Django处理模板分为两个阶段:加载:根据给定的路径找到模板文件,编译后放在内存中。渲染:使用上下文数据对模板插值并返回生成的字符串。模板语言变量标签过滤器注释变量模板变量的作用是计算并输出,变量名必须由字母、数字、下划线(不能以下划线开头)和点组成 {{ 变量 }}当模板引擎遇到点 如dog.name,会按照下列顺序解析:字典 dog[‘
往 期 趣 闻☞众编程语言之父大合集,不小心知道了什么 | 每日趣闻☞互联网人生存指南 | 每日趣闻☞一图读懂浏览器演变史 | 每日趣闻☞太真实!深刻解读论文里的话术| 每日趣闻☞我和我...
效果图:2.index.json代码:{ "usingComponents": {}, "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "计算器", "navigationBarTextStyle": "black"}3.index.wxsspage{ display: fl...
上个星期日,第三次参加了系统分析师考试。这次试题难度从总体上来说不是很大,早上考数学的题目比较多,中午考的是知识面的广度,下午的论文题目第一个直接没看懂(系统体系结构评估方法),第二个到第四个分别是敏捷、SOA、SaaS,毫不犹豫的选择了敏捷。还记得去年(2007年下半年)这个时候的考试论文也是敏捷,挂了。不知道这次能否通过,这次没怎么复习,公司项目时间紧啊。也许早上和中午挂的概率更大点,呵呵。。...
前言之前构建源码的时候就出现了这种明明jar包存在,却报找不到jar异常的问题,当时也是解决了很长时间发现把impl文件删除重新生成,才解决。因为像这样的问题,再网上没有一个明确的答案,因为每个人遇到的问题不一样,处理的方式也不一样。最近在导入别人代码的时候又遇到了这样的异常,但是我发现按照原先的方法解决不了这个问题。最后又是花费了很长时间才解决。由此就把所有的解决方式都记录一遍,方便你我他在这个问题上能够及时解决。问题出现场景构建源码导入别人的程序前几天还正常,现在不正常了问题出现原理
将上述程序修改,发送 "#0\r\n"依照前面的方法编译运行现象: 电机驱动板的灯亮,说明通信成功注意: Linux系统里面的换行是以\r\n结尾 ,而不是Windows里面的\n这里我使用的串口库是wiringPi...
在游戏中,有时候会遇到不规则的按钮,比如:三国类的地图上的按钮,根据国家的的大小,划分成不同大小的不规则按钮,我们知道,素材都是规则的矩形,只不过有些圆形,不规则等图形中,在Image中处理时的数据为0而已。具体的像素问题不是很了解,好了,直接上代码。IrregularButton.h#ifndef __IRREGULAR_BUTTON_H__#define __IRREGULA
目录系统变慢的原因如何做到系统的优化如何保障系统的安全常见的硬盘优化方法网速变慢的原因判断网络拥堵的方法配置windows系统更新失败蓝屏故障开机后,显示器没有画面,只听到连续的“bibibi”三声进入不了操作系统界面,或提示系统文件损坏电脑崩溃如何拯救C盘重要文件GHOST文件丢失电脑清灰重装系统分区格式和启动引导方式系统变慢的原因 1、在系统盘中安装了大量软件 2、很多软件设置成了开机启动 ...
一、线程与进程① 线程与进程的定义线程线程是进程的基本执行单元,一个进程的所有任务都在线程中执行;进程要想执行任务,必须得有线程,进程至少要有一条线程;程序启动会默认开启一条线程,这条线程被称为主线程或者 UI 线程。进程进程是指在系统中正在运行的一个应用程序;每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内;通过“活动监视器”可以查看 mac 系统中所开启的线程。② 线程与进程的关系地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的
前言:ION 的前任是 PMEM,ION,最显著的特点是它可以被用户空间的进程之间或者内核空间的模块之间进行内存共享,而且这种共享可以是零拷贝的。在实际使用中,ION 和 VIDEOBUF2、DMA-BUF、V4L2 等结合的很紧密。ION在内核中被当做一个misc设备来注册,通常user通过打开/dev/ion来对内存进行操作,在高通710平台的camera流程中,拿取camera参数与之前的660平台不太一样,660平台通过调用v4l-subdev的ioctl传递camera sensor的参数,在