itemdecoration(ItemDecoration工具类)
谈谈Android中的Divider是个什么东东
在Android应用开发中会经常碰到一个叫divider的东西,就是两个View之间的分割线。最近工作中注意到这个divider并分析了一下,竟然发现内有乾坤,惊为天人…
ListView的divider
1. 定制divider的边距
ListView的divider默认是左右两头到底的,如何简单的设置一个边距呢?
利用inset或者layer-list都可以简单的实现,代码如下:
!-- 方法一 --?xml version="1.0" encoding="utf-8"?inset xmlns:android=""android:insetLeft="16dp" shape android:shape="rectangle" solid android:color="#f00" //shape/inset!-- 方法二 --?xml version="1.0" encoding="utf-8"?layer-list xmlns:android=""item android:left="16dp"shape android:shape="rectangle"solid android:color="#f00" //shape/item/layer-list
其中inset除了左边距insetLeft, 还有insetTop、insetRight、insetBottom, 效果图:
2. 最后一项的divider
很多同学可能发现了,ListView最后一项的divider有时候有,有时候又没有。
我画个图大家就都能理解了:
上面是数据不足的显示效果,如果数据满屏的话,都是看不多最后的divider的。
真相是,当ListView高度是不算最后一项divider的,所以只有在match_parent的情况下,ListView的高度是有余的,才能画出最后的那个divider。
ps:网上很多资料,把最后一项的divider和footerDividersEnabled混在一起了,这个是不对的,两个从逻辑上是独立的,类似的还有一个headerDividersEnabled,headerDividersEnabled和footerDividersEnabled不会影响到默认情况下最后的divider的绘制,他们是给header和footer专用的,特此说明。
RecyclerView的Divider
RecyclerView的Divider叫做ItemDecoration,RecyclerView.ItemDecoration本身是一个抽象类,官方没有提供默认实现。
官方的Support7Demos例子中有个DividerItemDecoration, 我们可以直接参考一下,位置在sdk的这里:
extras/android/support/samples/Support7Demos/src/…/…/decorator/DividerItemDecoration.java
但是这个DividerItemDecoration有三个问题:
只支持系统默认样式,不支持自定义Drawable类型的divider
里面的算法对于无高宽的Drawable(比如上面用到的InsetDrawable)是画不出东西的水平列表的Divider绘制方法drawHorizontal()的right计算有误,导致垂直Divider会绘制不出来,应该改为:final int right = left + mDivider.getIntrinsicWidth();;
RecyclerView详解
RecyclerView作为ListView和GridView的替代,但是和ListView不一样的是,RecyclerView不再负责Item的摆放等显示方面的功能,所有和布局、绘制等方面的工作都拆分成不同的类进行管理。
RecyclerView与ListView的不同点,主要在于以下几个特性:
如果你想使用RecyclerView,需要做以下操作:
我们可以从下图更直观的了解到RecyclerView的基本结构:
RecyclerView不再负责Item视图的布局及显示,所以RecyclerView也没有为Item开放OnItemClick等点击事件。可以通过以下方式进行:因为在ViewHolder我们可以拿到每个Item的根布局,所以如果我们为根布局设置单独的OnClick监听并将其开放给Adapter,那么就可以在组装RecyclerView时就能够设置ItemClickListener,只不过这个Listener不是设置到RecyclerView上而是设置到Adapter。
多Item布局,getItemViewType方法,用法和ListView没有任何区别,这里要注意的是函数onCreateViewHolder(ViewGroup parent, int viewType)的第二个参数就是View的类型,可以根据这个类型判断去创建不同item的ViewHolder,从而完成多Item布局。
在RecylerView中,Adapter扮演着两个角色:一是根据不同viewType创建与之相应的的itemView,二是访问数据集合并将数据绑定到正确的View上。这就需要我们实现以下两个函数:
另外我们还需要重写另一个方法,像ListView-Adapter那样,同样地告诉RecyclerView-Adapter列表Items的总数:
ViewHolder描述RecylerView中某个位置的itemView和元数据信息,属于Adapter的一部分,其实现类通常用于保存findViewById的结果。 主要元素组成有:
关于ViewHolder,这里主要介绍mFlags:
FLAG_BOUND——ViewHolder已经绑定到某个位置,mPosition、mItemId、mItemViewType都有效
FLAG_UPDATE——ViewHolder绑定的View对应的数据过时需要重新绑定,mPosition、mItemId还是一致的
FLAG_INVALID——ViewHolder绑定的View对应的数据无效,需要完全重新绑定不同的数据
FLAG_REMOVED——ViewHolder对应的数据已经从数据集移除
FLAG_NOT_RECYCLABLE——ViewHolder不能复用
FLAG_RETURNED_FROM_SCRAP——这个状态的ViewHolder会加到scrap list被复用。
FLAG_CHANGED——ViewHolder内容发生变化,通常用于表明有ItemAnimator动画
FLAG_IGNORE——ViewHolder完全由LayoutManager管理,不能复用
FLAG_TMP_DETACHED——ViewHolder从父RecyclerView临时分离的标志,便于后续移除或添加回来
FLAG_ADAPTER_POSITION_UNKNOWN——ViewHolder不知道对应的Adapter的位置,直到绑定到一个新位置
FLAG_ADAPTER_FULLUPDATE——方法addChangePayload(null)调用时设置
LayoutManager主要作用是,测量和摆放RecyclerView中itemView,以及当itemView对用户不可见时循环复用处理。
当我们想在某些item上加一些特殊的UI时,往往都是在itemView中先布局好,然后通过设置可见性来决定哪些位置显示不显示。RecyclerView将itemView和装饰UI分隔开来,装饰UI即ItemDecoration,主要用于绘制item间的分割线、高亮或者margin等。其源码如下:
过去AdapterView的item项操作往往是没有动画的。现在RecyclerView的ItemAnimator使得item的动画实现变得简单而样式丰富,我们可以自定义item项不同操作(如添加,删除)的动画效果。
Recycler用于管理已经废弃或与RecyclerView分离的(scrapped or detached)item view,便于重用。Scrapped view指依附于RecyclerView,但被标记为可移除或可复用的view。
LayoutManager获取Adapter某一项的View时会使用Recycler。当复用的View有效(clean)时,View能直接被复用,反之若View失效(dirty)时,需要重新绑定View。对于有效的View,如果不主动调用request layout,则不需要重新测量大小就能复用。在分析Recycler的复用原理之前,我们先了解下如下两个类:
RecyclerViewPool用于多个RecyclerView之间共享View。只需要创建一个RecyclerViewPool实例,然后调用RecyclerView的setRecycledViewPool(RecycledViewPool)方法即可。RecyclerView默认会创建一个RecyclerViewPool实例。
通过源码我们可以看出mScrap是一个viewType, List的映射, mMaxScrap 是一个viewType, maxNum的映射,这两个成员变量代表可复用View池的基本信息。调用 setMaxRecycledViews(int viewType, int max) 时,当用于复用的 mScrap 中viewType对应的ViewHolder个数超过maxNum时,会从列表末尾开始丢弃超过的部分。调用 getRecycledView(int viewType) 方法时从 mScrap 中移除并返回viewType对应的List的末尾项。
ViewCacheExtension是一个由开发者控制的可以作为View缓存的帮助类。调用Recycler.getViewForPosition(int)方法获取View时,Recycler先检查attached scrap和一级缓存,如果没有则检查ViewCacheExtension.getViewForPositionAndType(Recycler, int, int),如果没有则检查RecyclerViewPool。注意:Recycler不会在这个类中做缓存View的操作,是否缓存View完全由开发者控制。
现在大家熟悉了RecyclerViewPool和ViewCacheExtension的作用后,下面开始介绍Recycler。 如下是Recycler的几个关键成员变量和方法:
获取某个位置需要展示的View,先检查是否有可复用的View,没有则创建新View并返回。具体过程为:
注:以上每步匹配过程都可以匹配position或itemId(如果有stableId)。
安卓吸顶如何禁止
android 条件筛选吸顶
android 条件筛选吸顶,自定义吸顶LayoutManager
?
chao wang
转载
关注
0点赞·164人阅读
吸顶效果
RecyclerView已经成为在Android Native开发过程中的明星组件,出镜率超高,只要需要列表展示的内容,我们第一想到的就是使用RecyclerView。RecyclerView确实是一个很容易上手功能又很强大的组件,通过设置不同的LayoutManager就可以实现不同的显示样式列表、网格等。在日常的开发过程中我经常会遇到“吸顶”这种情况,就是列表中的某些Item在滚动到列表的顶部的时候需要固定住,如上图的效果。要实现这种效果的两种最常见的方案是使用ItemDecoration和组合布局的方式,这两种方案分别有个字的优缺点这里我们简单的分析一下。
1. 使用组合布局
xmlns:tools=""
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:id="@+id/rlv"
android:layout_width="match_parent"
android:layout_height="match_parent" /
大体实现方案如上所示,将要吸顶的ViewHolder(为方便后面的描述我们这里把显示在RecyclerView中的ViewHolder叫真ViewHolder,飘在RecyclerView上面的叫假ViewHolder)的布局放在RecyclerView布局上层,在业务层的代码中通过监听RecyclerView的滚动事件,控制假ViewHolder的显示、隐藏以及移动等,目前市面上大部分App使用的都是这种方案(我是怎么知道的?用AS的ViewTree工具分析一下就知道了?),但是这种方案存在以下缺点:
如果有多种不同的ViewHolder需要吸顶的时候,业务处理的复杂度会呈几何级数上升,这会导致bug层出不穷。
吸顶的ViewHolder如果是可交互的(例如响应横向滚动,选中等)就需要做真假ViewHolder的数据和状态的双向同步工作,如果吸顶的ViewHolder业务比较复杂,这一定是一个让人心力憔悴的活。
扩展能力弱,相似的功能复用成本很高,总是要修修补补才能复用。
也许你会问,如果真如你所说有这么多问题,那为什么还有这么多人使用这种方案?呵呵,因为简单啊,这个方案是最容易想到的不是吗?说实话这个方案我也用过,否则我咋知道会有这么多问题?。
2. 使用ItemDecoration
class II extends RecyclerView.ItemDecoration{
@Override
public void getItemOffsets(@NonNull Rect outRect,
@NonNull View view,
@NonNull RecyclerView parent,
@NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
@Override
public void onDrawOver(@NonNull Canvas c,
@NonNull RecyclerView parent,
@NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
ItemDecoration通常用来实现RecyclerView中item的分割线效果,利用其本身的一些特性也能做出吸顶效果来,大体思路如下:
通过ItemDecoration的getItemOffsets方法将吸顶区域空出来
通过View.getDrawingCache()拿到需要吸顶ViewHolder的bitmap
通过ItemDecoration的onDrawOver将吸顶ViewHolder的bitmap绘制在吸顶区域中
该方案跟上面的使用组合布局的方案比起来,通用性要好很多,复用起来也比较方便,但是该方案也有一个致命的缺点,那就是吸顶的ViewHolder不能响应事件,如果需要吸顶的ViewHolder中有动态的内容如Gif或视频等,也不能做到很好的兼容。
3. 自定义LayoutManager
除了这两种方案还有没有别的方案?答案肯定是有的,使用LayoutManager!对,没错!我肯定我不是第一个想到这个方案的人,稍微对RecyclerView有点了解的人都会想到这个解决方案,目前我在网上还没发现(可能有只是我没找到)使用LayoutManager解决这个问题的成熟方案。RecyclerView加LayoutManager大约有1万多行代码,要想从头读到尾确实需要费点时间,我觉得其实我们也没必要从头读到尾把所有的技术细节都弄明白,只要能达到自己的目的就可以了,就拿创建一个自定义LayoutManager这件事来说我们只需要弄明白RecyclerView的缓存策略和布局流程,我觉得就可以了,如果你时间和精力充足要把它扒个底朝天那也很棒,下面我们就简单分析阅读下这两部分的源码。
真爱生命,远离源码??
3.1 缓存策略
RecyclerView的缓存策略一直是RecyclerView的热门知识点,不管你是想斩offer还是吹牛*这个是必备。在RecyclerView中ViewHolder复用相关的逻辑都封装在Recycler中,按照顺讯分为四层:
mAttachedScrap 和 mChangedScrap
有人说这一级缓存是告诉缓存,我就有点纳闷,“高速”是咋体现出来的?我是没看出来!这四层缓存如果按照适用场景来划分我觉得会更容易理解
mAttachedScrap -- 当前RecyclerView中已经有ViewHolder填充,RecyclerView又触发onLayoutChildren的时候,当前正在显示的这部分ViewHolder会被回收到mAttachedScrap中,在layoutChunk方法中被重新取出。
mChangedScrap -- 只会被用在预布局中
mAttachedScrap 和 mChangedScrap 只有在onLayoutChildren()方法调用的时候才会用到,在滚动的过程中没用,只有触发requestLayout()的时候才会调用。
mCachedViews
在滚动过程中滚出屏幕区域而被回收的ViewHolder会被加入到该层缓存,缓存数量支持自定义默认为2,按照先进先出的规则溢出。
mViewCacheExtension
用户自定义缓存
mRecyclerPool
该层缓存用于存储从mCachedView缓存中溢出的ViewHolder。
RecyclerView缓存的访问顺序存取是保持一致的,回收部分的源码:
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
return;
}
if (viewHolder.isInvalid() !viewHolder.isRemoved()
!mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
//回收到mCachedViews或mRecyclerPool中
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
//回收到mAttachedScrap 或 mChangedScrap中
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
缓存复用最终会调用到tryGetViewHolderForPositionByDeadline方法,这个方法源码巨长省略不相关源码,核心源码如下:
@NonNull
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
//从 1和2级缓存中取
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
...
}
}
if (holder == null) {
...
if (holder == null mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
// 从三级自定义缓存中取
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
....
}
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
//从四级缓存中取
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
...
//通过Adapter重新创建新的ViewHolder实例
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
}
...
//绑定数据相关逻辑省略
return holder;
}
3.2 布局流程
RecyclerView的布局分为两部分非别为初始布局和滚动过程中的布局,两者的处理逻辑有所不同。初始布局相关业务逻辑主要由onLayoutChildren()方法承载,滚动过程中的布局相关逻辑主要由scrollVerticallyBy()承载。其中有一个比较核心的方法是fill()方法,该方法是ViewHolder布局的核心方法。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
//判断是否产生有效滚动
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
//检查时候有需要回收的ViewHolder
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace 0) layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
...
//布局ViewHolder
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable layoutChunkResult.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
注意:RecyclerView在滚动布局过程中如果没有新的ViewHolder产生的时候是不会掉用fill()方法的。
3.3 实现方案
有了上面那西基础做铺垫我们就可以开始动手写一个LayoutManager了,整体思路如下:
在RecyclerView现有的四层缓存之上,再创建一层缓存,用于缓存吸顶的ViewHolder
筛选出需要吸顶的ViewHolder加入自定义缓存
向上滚动(手指上滑)的过程中,在目标ViewHolder到达上边缘的位置的吸顶位置时候阻止其继续滚动,将目标ViewHolder强制绘制在屏幕的上部,并将其加入吸顶ViewHolder缓存(止其进入RecyclerView的内部回收机制)。
向下滚动(手指下滑)的过程中,在目标ViewHolder离开吸顶区域后,将其从吸顶缓存中移除,并将其重新放回到RecyclerView内部的缓存中。
总结起来就两句话:吸顶的ViewHolder加到新增的自定义缓存中,将LinearLayoutManager排完的ViewHolder重新排列一下。
3.3.1 吸顶协议
整体的开发思路我们已经确定,首先我们要解决的问题就是如何将要吸顶的ViewHolder筛选出来呢?这里我的方案是定义一个协议接口Section,通过检测该ViewHolder是否实现该接口判断该ViewHolder是否需要被吸顶。
/**
* 协议接口所有实现该接口的`ViewHolder`在滚动的过程中都会被吸顶
* @author Rango on 2020/11/6
*/
public interface Section {
}
public class SectionViewHolder extends RecyclerView.ViewHolder implements Section {
public TextView tv;
public SectionViewHolder(@NonNull View v) {
super(v);
}
}
3.3.2 自定义缓存
因为一次只有一个ViewHolder吸顶,当列表中有多个可以吸顶的ViewHolder的时候,在向上滚动的时候新出现的吸顶ViewHolder会将当前正在吸顶的ViewHolder顶上去,我们需要将这些被顶上去的ViewHolder保存起来(阻止进入系统缓存),这样在向下滚动的时候这些ViewHolder重新显示的时候才会保持之前的状态,否则会进入系统缓存被重新绑定数据,导致之前的状态丢失。所以我们需要创建一个缓存栈(后进先出)用于保存吸顶的ViewHolder,在列表向上滚动的过程中,有符合条件的ViewHolder出现的时候我们就将其入栈,在列表向下滚动的过程中如果吸顶ViewHolder离开吸顶位置的时候我们就将其出栈。这个缓存栈就是我们新加的自定义缓存,栈顶的ViewHolder就是当前吸顶的ViewHolder,代码如下:
/**
* 吸顶ViewHolder的缓存
*
* @author Rango on 2020/11/17
*/
public class SectionCache extends Stack {
private Map
filterMap = new HashMap(16, 64);
@Override
public RecyclerView.ViewHolder push(RecyclerView.ViewHolder item) {
if (item == null) {
return null;
}
int position = item.getLayoutPosition();
//避免存在重复的Value
if (filterMap.containsKey(position)) {
//返回null说明没有添加成功
return null;
}
filterMap.put(position, item);
return super.push(item);
}
@Override
public synchronized RecyclerView.ViewHolder peek() {
if (size() == 0) {
return null;
}
return super.peek();
}
/**
* 栈顶清理,在快速滚动的情境下可能会出现一次多个吸顶的ViewHolder出栈的情况,这个时候需要
* 根据LayoutPosition清理栈顶,保证栈内ViewHolder和列表当前的状态一致。
*
* @param layoutPosition 大于position的内容会被清理
*/
public List clearTop(int layoutPosition) {
List removedViewHolders = new LinkedList();
Iterator it = iterator();
while (it.hasNext()) {
RecyclerView.ViewHolder top = it.next();
if (top.getLayoutPosition() layoutPosition) {
it.remove();
filterMap.remove(top.getLayoutPosition());
removedViewHolders.add(top);
}
}
return removedViewHolders;
}
}
3.3.3 过滤ViewHolder
这里我们需要把当前正在显示的目标ViewHolder过滤出来并根据当前的dy判断是否会滚动到吸顶位置,不幸的是LayoutManager并没有提供获取ViewHolder的api,只提供了获取childView()的方法。查阅源码发现ViewHolder中有这样一个api
getChildViewHolderInt
childView对应的ViewHolder会保存在其LayoutParams.mViewHolder中,通过这个方案我们可以把当前正在显示的ViewHolder过滤出来。
for (int i = 0; i getChildCount(); i++) {
View itemView = getChildAt(i);
RecyclerView.ViewHolder vh = getViewHolderByView(itemView);
if (!(vh instanceof Section) || sectionCache.peek() == vh) {
continue;
}
if (dy 0 vh.itemView.getTop() dy) {
sectionCache.push(vh);
} else {
break;
}
}
注意并不是说所有显示出来的需要吸顶的ViewHolder都需要立即加入到我们的自定义缓存中,只有向上滚动到吸顶位置的吸顶ViewHolder加入缓存栈。
image.png
假设A和E是两个可以吸顶的ViewHolder,当前屏幕正在向上滚动,此时A需要加入缓存栈,但是E不需要加入缓存队列。E只有持续向上滚动到A所在的位置的时候才会被加入我们自定义的缓存栈。
3.3.4 拦截
在列表的滚动过程中我们除了要将这些需要吸顶的ViewHolder加入到我们自定义的缓存栈中,我们还要阻止其进入RecylverView的缓存中,否则列表继续向上滚动ViewHolder A就会滚出屏幕,如下图所示,这个时候ViewHolder A就会被Recycler回收,放入第二层缓存(mCachedViews)中,再有吸顶ViewHolder滚动出来的时候之前回收的RecyclerView就会被复用和重新绑定数据,之前的ViewHolder A的状态就会丢失。
图二
在列表上滑过程中图三是我们所期望的结果,ViewHolder A在上滑到顶部的时候我们需要将其固定在RecyclerView的顶部。
图三
RecyclerView滚动相关的业务逻辑主要是在scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state)方法中,该方法有三个参数作用如下:
dy -- 本次滚动的距离,dy 0是向上滚动(手指上滑),反之下滑
recycler -- 缓存器,定义了四层缓存策略
state -- 用于传递数据信息,例如是否是预布局等
在LinearLayoutManager中该方法的源码如下
/**
* {@inheritDoc}
*/
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
这里我们要做的就是在scollBy()之前插入我们的回收代码,在之后加入我们的重新布局代码。因为要兼容LienarLayoutManager所以我们不对scrollBy内部的内容进行修改,这样我们就可以保证兼容性。
我们分析RecyclerView四层缓存的时候我们已经了解了内部实现的一些细节问题缓存复用和滚动处理等等,scrollBy()是包访问权限,我们无法对其进行重载,所以我们只能从scrollVerticallyBy()方法下手了,其实我们也没必要关心scrollBy()方法内被
for (RecyclerView.ViewHolder viewHolder : sectionCache) {
removeView(viewHolder.itemView);
}
在scrollBy()方法调用之前我们把吸顶的ViewHolder remove掉就可以阻止其进入Recycler的缓存中,因为ViewHolder相关的信息保存在itemView.layoutParams中,移除View就可以阻止其回收。就这么简单?对就这么简单!
3.3.5 重新布局
如果现在使用我们自定义的LayoutManager应该是 图四 这种效果,当吸顶ViewHodler进入吸顶位置后就会变成空白。
image-20201123101155909.png
我们需要将Remove掉的ViewHolder重新加回到RecyclerView中并将其布局在合适的位置,这里有几个关键点需要注意下:
dy可能大于一个ViewHolder的高度
如果当前吸顶位置已经有吸顶ViewHolder占据的时候,后来的吸顶ViewHolder需要将其顶上去
在向下滚动(手指下滑)的时候,由于吸顶的ViewHolder都没有进入Recycler的缓存,所以在向下滚动的时候RecyclerView会重新创建ViewHolder实例,我们需要将其替换为我们自定义缓存中保存的实例。
具体实现代码如下:
//检查栈顶
RecyclerView.ViewHolder vh = getViewHolderByView(getChildAt(0));
RecyclerView.ViewHolder attachedSection = sectionCache.peek();
if ((vh instanceof Section)
attachedSection != null
attachedSection.getLayoutPosition() == vh.getLayoutPosition()) {
removeViewAt(0);
}
// 处理向下滚动
for (RecyclerView.ViewHolder removedViewHolder : sectionCache.clearTop(findFirstVisibleItemPosition())) {
Log.i(tag, "移除ViewHolder:" + removedViewHolder.toString());
for (int i = 0; i getChildCount(); i++) {
RecyclerView.ViewHolder attachedViewHolder = getViewHolderByView(getChildAt(i));
if (removedViewHolder.getLayoutPosition() == attachedViewHolder.getLayoutPosition()) {
View attachedItemView = attachedViewHolder.itemView;
int left = attachedItemView.getLeft();
int top = attachedItemView.getTop();
int bottom = attachedItemView.getBottom();
int right = attachedItemView.getRight();
//这里的remvoe 和 add 是为了重新布局
removeView(attachedItemView);
addView(removedViewHolder.itemView, i);
removedViewHolder.itemView.layout(left, top, right, bottom);
break;
}
}
}
//重新布局
RecyclerView.ViewHolder section = sectionCache.peek();
if (section != null) {
View itemView = section.itemView;
if (!itemView.isAttachedToWindow()) {
addView(itemView);
}
View subItem = getChildAt(1);
if (getViewHolderByView(subItem) instanceof Section) {
int h = itemView.getMeasuredHeight();
int top = Math.min(0, -(h - subItem.getTop()));
int bottom = Math.min(h, subItem.getTop());
itemView.layout(0, top, itemView.getMeasuredWidth(), bottom);
} else {
itemView.layout(0, 0, itemView.getMeasuredWidth(), itemView.getMeasuredHeight());
}
}
每段代码的作用已经用注释描述,这里不再赘述,效果如下:
未命名.gif
源码地址
如有错误或意见欢迎在评论区讨论。
作为一个码农,脑袋偷懒身体受苦 --- 但是领导总是喜欢那些不动脑筋拼命加班的人。。。
android 条件筛选吸顶
?
关于RecyclerView
这篇毫无头绪,供个人查阅。
RecyclerView 必知必会
抽丝剥茧RecyclerView - 化整为零
RecyclerView的四大组成是:
Android中使用RecyclerView + SnapHelper实现类似ViewPager效果
让你明明白白的使用RecyclerView——SnapHelper详解
Android SnapHelper扒皮分析
SnapHelper硬核讲解
用RecyclerView做一个小清新的Gallery效果 ,之前项目中也做过卡片滑动,过程中缩放的功能。
,卡片滑动,过程中缩放的功能。
Android开发之分组列表悬浮顶部栏(吸顶效果) --做了offset的示意图,很直观。
ItemDecoration --比较简单,注意的四点:0、系统提供的默认的decoration;1、就是addItemDecoration方法,add会重复添加,自己做项目也遇到过;2、添加的decoration的点击问题;3、关于ViewGroup的onDraw()方法的调用问题
ItemDecoration实现方式,并带touch点击事件 --点击事件的一个思路吧
自定义ItemDecoration这个问题你真的注意到了吗
RecyclerView系列之二ItemDecoration --几个例子
解决RecyclerView执行动画后item边距错误的问题
RecyclerView的滚动事件研究
三种方式实现RecyclerView的Item点击事件
RecycleView 的findChildViewUnder()方法简单介绍
掌握自定义LayoutManager(一) 系列开篇 常见误区、问题、注意事项,常用API
学不动也要学!深入了解ViewPager2 ,实现(1)中卡片布局
官方 Viewpager 升级版 - ViewPager2 实战
RecyclerView小结
目录
1. RecyclerView与ListView的异同
2. RecyclerView的使用
- 简单使用步骤
- 关于Item点击事件的监听
- 关于滚动事件的监听
- 设置Decoration
- 设置Animation
RecyclerView和ListView一样是用于展示大量数据集的部件,两者都能够回收和复用不可见的view来节约资源提高性能。与ListView不同的是,RecyclerView具有更好的灵活性,这主要得益于其插件化和充分解耦的设计:
RecyclerView与ListView的主要差异:
使用RecyclerView时一般会用到一下几个RecyclerView的内部类:
一种简单的方式就是在 onBindViewHolder 时调用view的 setOnClickListener() 方法;或者定义ViewHolder时让其操作 OnClickListener 接口:
如果需要在activity或者fragment中处理点击事件,则可以在adapter中设计一个接口供外部调用:
在activity或fragment中使用:
RecyclerView的滚动事件可以使用 addOnScrollListener 方法监听:
滚动的过程一般分为2种:
对应到 onScrollStateChanged 中的newState值:
所以上面 onScrollStateChanged 中的条件可以翻译为:滚动停止 倒数第二个item已经可见 不在加载过程中
onScrolled 中dx和dy的含义:
google提供了一个RecyclerView.ItemDecoration的实现类DividerItemDecoration作为默认的divider,使用方法如下:
DividerItemDecoration的码源
DividerItemDecoration实现主要包括三个方法:
其绘制过程大致为:
在最新版的DividerItemDecoration中还提供了一个 setDrawable(Drawable drawable) 方法,方便我们自己定制divider的样式。比如我在 res/drawable 目录下新建一个 divider_drawable.xml 文件:
然后在构造decoration时用这个文件替换默认的divider资源文件:
替换后效果如下:
可以看到由于默认的 getItemOffsets() 设定了bottom的padding值,所以在divider的左右两边露出了RecyclerView下面一层的背景色。对于这种情况,可以仿造默认的DividerItemDecoration自己继承RecyclerView.ItemDecoration实现一个decoration,将绘制方法改为 onDrawOver() ,并在 getItemOffsets() 中不设置padding值,就可以让divider绘制在item的上方。
当然设置divider还有一种更简单的方法,直接在item的布局文件中添加一个ImageView画一条线就好了( ̄Д ̄)?
同样的google也提供了一个默认的动画DefaultItemAnimation,可以使用 setItemAnimation() 方法来设置。
我们也可以继承RecyclerView.ItemAnimation来自己定义item动画。这里推荐一个第三方动画库 recyclerview-animatiors ,简单好用可拓展。
RecyclerView整体理解和使用
点击事件
滚动事件
完整项目在 我的github 上,如果碰巧能帮到您不妨去点个star吧 ( ̄? ̄)
RecyclerView设置分割线---DividerItemDecoration
官方提供的分割线处理方案,也是继承的 RecyclerView.ItemDecoration 实现的。
用法很简单,注释里面有demo。同时也可以通过自定义drawable来实现divider的自定义。
1.用系统提供的高度和颜色,不做自定义。
2.DividerItemDecoration 可以通过 setDrawable(Drawable drawable) 来设置具体的分割线内容
自定义shape可以设置分割线的高度和颜色。
使用图片的话,分割线的高度就是图片的高度,图片会有拉伸。
最后通过 DividerItemDecoration.setDrawable(Drawable drawable) 就能看到效果了,省了不少力气。