Android(安卓)使用RecycleView实现吸附小标题的Demo(附源码)
先上,效果图
源码地址
GitHub: https://github.com/aiyangtianci/StickyDecoration
码云 : https://gitee.com/AiYangDian/StickyDecoration
因为实现列表展示的数据和基础实现在上一章讲解了,请看上一篇:
Android 探究onCreateViewHolder和onBindViewHolder两者关系和调用次数当然,那篇文章还算优美,如果同学只想知道如何实现吸附的小标题,也可以忽略上一篇。请继续往下看。
介绍:RecyclerView.ItemDecoration
ItemDecoration是RecyclerView的一个 抽象内部类,这里的效果需要实现它。它有三个重写的方法,如下: //可以实现类似padding的效果,实现列表列表间隔 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) //可以实现类似绘制背景的效果,内容在上面 public void onDraw(Canvas c, RecyclerView parent, State state) //可以绘制在内容的上面,覆盖内容 public void onDrawOver(Canvas c, RecyclerView parent, State state)
其中,我们本篇需要的一个非常关键的方法 onDrawOver。
它会先在初始化中执行一次,后面会随着列表滑动重复的调用,非常适合我们实现本篇效果。
onDrawOver与onDraw的调用顺序?
在官方的开发文档中有指出,onDraw是在itemview绘制之前,onDrawOver是在itemview绘制之后。
为什么说onDrawOver是在itemview绘制之后调用呢?
相信稍微了解过Android中View的绘制流程的都知道,View先会调用draw方法,在draw方法中调用onDraw方法。 在RecyclerView的draw方法中会先通过super.draw() 调用父类的draw方法,再调用OnDraw方法。ItemDecoration的onDraw方法也就在此时会被调用。RecyclerView执行完super.draw()之后,ItemDecoration的onDrawOver方法才被调用。(可忽略)
上代码嘞!
记得上一篇说Adapter的onCreateViewHolder()方法参数 int viewType,并且在方法中判断设置item.setTag(boolean);
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View item = LayoutInflater.from(mContext).inflate(R.layout.item_layout , parent ,false); if (viewType == 1){//标题 item.setTag(true); }else{ item.setTag(false); } return new ViewHolde(item); }
其实,这个就是为了设置子项是否是吸附的标题。
StickyItemDecoration.java 自定义类
创建 StickyItemDecoration.java类 , 继承RecyclerView.ItemDecoration 。
使用的时候必须在setAdapter前,通过recyclerView.addItemDecoration()方法设置。
案例调用,如下:
public class MainActivity extends AppCompatActivity { RecyclerView mRecyclerView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRecyclerView =findViewById(R.id.recyclelist); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mRecyclerView.addItemDecoration(new StickyItemDecoration()); // 在setAdapter之前。 mRecyclerView.setAdapter(new RecyclerAdapter(this, data.getDataList())); }}
索性,我们直接看这个自定义类的全部代码。
呃呃。。。。其实很简单,只是代码有点繁琐,但是每行关键代码我都有注释。在阅读代码之前,我先捋一下思路会阅读更轻松。
首先,前面介绍 onDrawOver 说 “它会先在初始化中执行一次,后面会随着列表滑动重复的调用”。所以我们在阅读代码时,先忽略关于判断标题在滑动时候的绘制位置相关代码。建议debug调试跟着初始化走一遍。
其次,要理解最外层for循环,遍历的是获取屏幕可见子项Item位置,然后判断可见的第几个子元素是标题。
最后,就细细跟着我蹩脚的注释去理解绘制计算布局相关的代码了。 咳咳,我来个祝愿吧~~~ - -。
▇▇▇▇ . ▇▇▇▇ . ▇▇▇▇ ▇▇▇▇
◢▇▇▇▇◣ ◢▇▇▇▇◣ ◢▇▇▇▇◣ ◢▇▇▇▇◣
▇春节快乐▇. ▇生活愉快▇ ▇吉祥如意▇ ▇合家欢乐▇
◥▇▇▇▇◤ .◥▇▇▇▇◤ ◥▇▇▇▇◤ ◥▇▇▇▇◤
▇▇▇▇ . ▇▇▇▇ . ▇▇▇▇ ▇▇▇▇
| | | | | | | | | | | |
/** * Created by aiyang on 2018/4/25. */public class StickyItemDecoration extends RecyclerView.ItemDecoration{ /** * Adapter :托管数据集合,为每个子项创建视图 */ private RecyclerView.Adapter mAdapter; /** * 标记:UI滚动过程中是否找到标题 */ private boolean mCurrentUIFindStickView; /** * 标题距离顶部距离 */ private int mStickyItemViewMarginTop; /** * 标题布局高度 */ private int mItemViewHeight; /** * 标题的视图View */ private View mStickyItemView; /** * 承载子项视图的holder */ private RecyclerView.ViewHolder mViewHolder; /** * 子项布局管理 */ private LinearLayoutManager mLayoutManager; /** * 绑定数据的position */ private int mBindDataPosition = -1; /** * 所有标题的position list */ private List mStickyPositionList = new ArrayList<>(); @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); if (parent.getAdapter().getItemCount() <= 0) return; //非空判断 mCurrentUIFindStickView = false;//标记默认不存在小标题 mLayoutManager = (LinearLayoutManager) parent.getLayoutManager();//获取布局管理方式 for (int i =0 ,size = parent.getChildCount() ; i < size ; i++){//viewgroup.getChildCount():获取所有可见子元素个数。 View item = parent.getChildAt(i); //循环得到每一个子项 if ((boolean)item.getTag() == true){//判断第几个子项是标题(值在Adapter中设置) mCurrentUIFindStickView =true;//标记为true getStickyViewHolder(parent);//得到标题的 viewHolder cacheStickyViewPosition(i); //收集标题的 position if (item.getTop() <= 0) {//标题和父布局的距离。(一般初始化时候先进入) bindDataForStickyView(mLayoutManager.findFirstVisibleItemPosition(), parent.getMeasuredWidth());//将第一个可见子项位置 和 父布局宽 传入 } else { if (mStickyPositionList.size() > 0) { if (mStickyPositionList.size() == 1) {//若只缓存一个标题 bindDataForStickyView(mStickyPositionList.get(0), parent.getMeasuredWidth()); } else { int currentPosition = mLayoutManager.findFirstVisibleItemPosition() + i;//得到标题在RecyclerView中的position int indexOfCurrentPosition = mStickyPositionList.lastIndexOf(currentPosition);//根据标题的position获得所在缓存列表中的索引 bindDataForStickyView(mStickyPositionList.get(indexOfCurrentPosition - 1), parent.getMeasuredWidth()); } } } if (item.getTop() > 0 && item.getTop() <= mItemViewHeight) {//处理两个标题叠在一起的绘制效果 mStickyItemViewMarginTop = mItemViewHeight - item.getTop(); } else { mStickyItemViewMarginTop = 0; View nextStickyView = getNextStickyView(parent);//得到下一个标题view if (nextStickyView != null && nextStickyView.getTop() <= mItemViewHeight) {//若两标题叠在一起了 mStickyItemViewMarginTop = mItemViewHeight - nextStickyView.getTop();//第二个标题盖住第一个标题多少了 } } drawStickyItemView(c);// 准备工作已就绪,开始画出吸附的标题 break; //结束循环 } } if (!mCurrentUIFindStickView) {//取反判断(因为它默认值是false)表示:若存在小标题则进入 mStickyItemViewMarginTop = 0; //判断子元素等于item总数并且缓存数大于0 if (mLayoutManager.findFirstVisibleItemPosition() + parent.getChildCount() == parent.getAdapter().getItemCount() && mStickyPositionList.size() > 0) { bindDataForStickyView(mStickyPositionList.get(mStickyPositionList.size() - 1), parent.getMeasuredWidth()); } drawStickyItemView(c);//绘制图层 } } /** * 得到下一个标题 * @param parent * @return */ private View getNextStickyView(RecyclerView parent) { int num = 0; View nextStickyView = null; for (int m = 0, size = parent.getChildCount(); m < size; m++) { View view = parent.getChildAt(m);//循环获取每个子项 if ((boolean)view.getTag() == true) {//拿到标题 nextStickyView = view; num++; } if (num == 2) break;//拿到第二个标题 ,就结束循环。 } return nextStickyView; } /** * 得到标题的 viewHolder * @param recyclerView */ private void getStickyViewHolder(RecyclerView recyclerView) { if (mAdapter != null) return; //判断是否已创建 mAdapter = recyclerView.getAdapter(); mViewHolder = mAdapter.onCreateViewHolder(recyclerView, 1); //该方法属于Adapter中的重写Override mStickyItemView = mViewHolder.itemView;//得到布局 } /** * 收集标题的 position * @param i */ private void cacheStickyViewPosition(int i) { int position = mLayoutManager.findFirstVisibleItemPosition() + i;//得到标题在RecyclerView中的position if (!mStickyPositionList.contains(position)) {//防止重复 mStickyPositionList.add(position); } } /** * 给StickyView绑定数据 * @param position */ private void bindDataForStickyView(int position, int width) { if (mBindDataPosition == position || mViewHolder == null) return;//已经是吸附位置了 或 视图不存在 mBindDataPosition = position; mAdapter.onBindViewHolder(mViewHolder, mBindDataPosition);//改变标题的展示效果,该方法在Adapter中 measureLayoutStickyItemView(width);//设置布局位置及大小 mItemViewHeight = mViewHolder.itemView.getBottom() - mViewHolder.itemView.getTop();//计算标题布局高度 } /** * 设置布局位置及大小 * @param parentWidth 父布局宽度 */ private void measureLayoutStickyItemView(int parentWidth) { if (mStickyItemView == null || !mStickyItemView.isLayoutRequested()) return; int widthSpec = View.MeasureSpec.makeMeasureSpec(parentWidth, View.MeasureSpec.EXACTLY); int heightSpec; ViewGroup.LayoutParams layoutParams = mStickyItemView.getLayoutParams(); if (layoutParams != null && layoutParams.height > 0) { heightSpec = View.MeasureSpec.makeMeasureSpec(layoutParams.height, View.MeasureSpec.EXACTLY); } else { heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); } mStickyItemView.measure(widthSpec, heightSpec); /** * view.layout(l,t,r,b) ; 子布局相对于父布局的绘制的位置及大小。 * l 和 t 是控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离。r 和 b是控件右边缘和下边缘相对于父类控件左边缘和上边缘的距离。 */ mStickyItemView.layout(0, 0, mStickyItemView.getMeasuredWidth(), mStickyItemView.getMeasuredHeight()); } /** * 绘制标题 * @param canvas */ private void drawStickyItemView(Canvas canvas) { if (mStickyItemView == null) return; int saveCount = canvas.save();//保存当前图层 canvas.translate(0, -mStickyItemViewMarginTop);//图层转换位移 mStickyItemView.draw(canvas); canvas.restoreToCount(saveCount); //恢复指定层的图层 }}
源码地址
GitHub: https://github.com/aiyangtianci/StickyDecoration
码云 : https://gitee.com/AiYangDian/StickyDecoration
更多相关文章
- android 绘制图片的一部分
- Android(安卓)欢迎全屏图片详解及实例代码
- Android(安卓)自定义标题栏
- android六大布局和UI组件
- Android(安卓)标题栏和状态栏随ScrollView滑动颜色改变轻松实现
- Android(安卓)SurfaceView使用详解(很好的实战例子)
- Android的图形与图像处理之一 使用简单图片&绘图
- android TextInputLayout 更换系统自带眼睛图标
- LayoutInflater的使用