RecyclerView+SnapHelper实现ViewPager滑动效果

RecyclerView+SnapHelper实现ViewPager滑动效果

SnapHelper结合RecyclerView使用,能很方便的实现ViewPager滑动效果。SnapHelper是一个抽象类,Google内置了两个默认实现类,LinearSnapHelper和PagerSnapHelper。

LinearSnapHelper的使用方法

使当前Item居中显示,常用场景是横向的RecyclerView, 类似ViewPager效果,但是又可以快速滑动多个条目。

1
2
3
4
5
LinearLayoutManager manager = new LinearLayoutManager(getContext());
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(manager);
LinearSnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

PagerSnapHelper的使用方法

使RecyclerView像ViewPager一样的效果,每次只能滑动一页。

1
2
3
4
5
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
PagerSnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

原文地址:https://developer.aliyun.com/article/665537

RecyclerView滑动到指定Item并置顶

RecyclerView滑动到指定Item并置顶

0x01 TopLinearSmoothScroller

1
2
3
4
5
6
7
8
9
10
11
12
import android.content.Context
import androidx.recyclerview.widget.LinearSmoothScroller

class TopLinearSmoothScroller(context: Context?) : LinearSmoothScroller(context) {
public override fun getVerticalSnapPreference(): Int {
return SNAP_TO_START
}

override fun getHorizontalSnapPreference(): Int {
return SNAP_TO_START
}
}

0x02 TopScrollLinearLayoutManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class TopScrollLinearLayoutManager(context: Context?, orientation: Int, reverseLayout: Boolean) :
LinearLayoutManager(context, orientation, reverseLayout) {

override fun smoothScrollToPosition(
recyclerView: RecyclerView?,
state: RecyclerView.State?,
position: Int
) {
val linearSmoothScroller = TopLinearSmoothScroller(recyclerView?.context)
linearSmoothScroller.targetPosition = position
startSmoothScroll(linearSmoothScroller)
}
}

0x03 RecyclerView中使用

设置recyclerView的layoutManager为自定义的TopScrollLinearLayoutManager,然后直接调用 smoothScrollToPosition() 方法就可以滚动到指定的位置并且置顶了。

1
2
3
4
5
6
7
recyclerView.layoutManager = TopScrollLinearLayoutManager(
this,
LinearLayoutManager.HORIZONTAL,
false
)
// ...
recyclerView.smoothScrollToPosition(1)

RecyclerView 根据滑动位置动态改变背景透明度

RecyclerView 根据滑动位置动态改变背景透明度

根据滑动位置动态改变背景透明度,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onRecyclerScrolled(recyclerView, dx, dy)
}

override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
}
})

private val dp180 = dp2px(180)
private var distanceY = 0
private var current = 0
private fun onRecyclerScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
distanceY += dy
when {
distanceY >= dp180 -> {
if (current == 1) return
recyclerView.setBackgroundColor(Color.argb(255, 246, 248, 250))
current = 1
}
distanceY <= 0 -> {
if (current == 0) return
recyclerView.setBackgroundColor(Color.argb(0, 246, 248, 250))
current = 0
}
else -> {
recyclerView.setBackgroundColor(
Color.argb(
distanceY * 255 / dp180,
246, 248, 250
)
)
current = -1
}
}
}

RecyclerViewHelper

RecyclerViewHelper

提供了注册加载更多,和判断是否不足一屏等工具方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager

object RecyclerViewHelper {
/**
* RecyclerView 注册加载更多监听
*/
@JvmStatic
fun addOnScrollListener(recyclerView: RecyclerView, loadMore: () -> Unit) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManager = recyclerView.layoutManager
if (layoutManager is LinearLayoutManager) {
val lastPosition = layoutManager.findLastCompletelyVisibleItemPosition()
val count = layoutManager.itemCount
if (lastPosition >= count - 2) {
loadMore()
return@onScrollStateChanged
}
} else if (layoutManager is StaggeredGridLayoutManager) {
val spanCount = layoutManager.spanCount
val count = layoutManager.itemCount
val result = IntArray(spanCount)
layoutManager.findLastCompletelyVisibleItemPositions(result)
for (it in result) {
if (it >= count - spanCount - 1) {
loadMore()
return@onScrollStateChanged
}
}
} else if (layoutManager is GridLayoutManager) {
val spanCount = layoutManager.spanCount
val count = layoutManager.itemCount
val lastPosition = layoutManager.findLastCompletelyVisibleItemPosition()
if (lastPosition >= count - spanCount - 1) {
loadMore()
return@onScrollStateChanged
}

}
}
}
})
}


/**
* 判断是否一屏显示
*
* 错误或者空了返回 false
*/
@JvmStatic
fun isOneScreen(recyclerView: RecyclerView?): Boolean {
recyclerView?.let {
val layoutManager = recyclerView.layoutManager
if (layoutManager is LinearLayoutManager) {
// include GridLayoutManager
val count = layoutManager.itemCount
return count > 0 &&
layoutManager.findFirstCompletelyVisibleItemPosition() == 0 &&
layoutManager.findLastCompletelyVisibleItemPosition() == count - 1
} else if (layoutManager is StaggeredGridLayoutManager) {
val spanCount = layoutManager.spanCount
val count = layoutManager.itemCount
val last = IntArray(spanCount)
val first = IntArray(spanCount)
layoutManager.findLastCompletelyVisibleItemPositions(last)
layoutManager.findFirstCompletelyVisibleItemPositions(first)
return count > 0 && first.min() == 0 && last.max() == count - 1
}
}

return false
}

private fun IntArray.min(): Int {
if (this.isNotEmpty()) {
var result = this[0]
this.forEach {
if (result > it) result = it
}
return result
}
return -1
}

private fun IntArray.max(): Int {
if (this.isNotEmpty()) {
var result = this[0]
this.forEach {
if (result < it) result = it
}
return result
}
return -1
}
}

Problems专题:RecyclerView

Problems专题:RecyclerView

0x01 Called attach on a child which is not detached

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
java.lang.IllegalArgumentException: Called attach on a child which is not detached: BaseViewHolder{2b241e1 position=12 id=-1, oldPos=-1, pLpos:-1} androidx.recyclerview.widget.RecyclerView{afecb06 VFED..... ......ID 0,0-1080,2055 #7f09236e app:id/recycler_view_xxx}, adapter:com.xxxx.adapter.XxxAdapter@cfc75c7, layout:androidx.recyclerview.widget.LinearLayoutManager@24af7f4, context:com.xxxx.XxxActivity@1ed75e2
at androidx.recyclerview.widget.RecyclerView$5.attachViewToParent(RecyclerView.java:917)
at androidx.recyclerview.widget.ChildHelper.attachViewToParent(ChildHelper.java:239)
at androidx.recyclerview.widget.RecyclerView.addAnimatingView(RecyclerView.java:1438)
at androidx.recyclerview.widget.RecyclerView.animateDisappearance(RecyclerView.java:4377)
at androidx.recyclerview.widget.RecyclerView$4.processDisappeared(RecyclerView.java:616)
at androidx.recyclerview.widget.ViewInfoStore.process(ViewInfoStore.java:242)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep3(RecyclerView.java:4210)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3864)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4410)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1131)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at androidx.viewpager.widget.ViewPager.onLayout(ViewPager.java:1775)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1131)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:905)
at android.view.View.layout(View.java:22213)
at android.view.ViewGroup.layout(ViewGroup.java:6340)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3286)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2757)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1865)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7933)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1018)
at android.view.Choreographer.doCallbacks(Choreographer.java:837)
at android.view.Choreographer.doFrame(Choreographer.java:767)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1003)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:230)
at android.app.ActivityThread.main(ActivityThread.java:7951)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:526)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034)

问题分析

对同一个 position 位置同时进行notifyItemRemoved(position)notifyItemInserted(position) 操作导致。

解决方案

避免同时对同一个位置先 notifyItemRemoved 再 notifyItemInserted,使用 notifyItemChanged。

1
adapter?.notifyItemChanged(position)

0x02 RecyclerView设置最大高度、宽度

当RecyclerView属性设置为wrap_content+maxHeight时,maxHeight没有效果。

问题分析

当RecyclerView的LayoutManager#isAutoMeasureEnabled()返回true时,RecyclerView高度取决于children view的布局高度,并非取决于RecyclerView自身的测量高度。

解决方案

因此,我们只需要重写LayoutManager的public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec)方法即可为RecyclerView设置最大宽高。

1
2
3
4
5
6
7
8
9
recyclerView.layoutManager = object : LinearLayoutManager(context, RecyclerView.VERTICAL, false) {
override fun setMeasuredDimension(childrenBounds: Rect?, wSpec: Int, hSpec: Int) {
val height = View.MeasureSpec.getSize(hSpec)
val maxHeight = getScreenHeight() * 4 / 5
val realHeight = height.coerceAtMost(maxHeight)
val realHeightSpec = View.MeasureSpec.makeMeasureSpec(realHeight, AT_MOST)
super.setMeasuredDimension(childrenBounds, wSpec, realHeightSpec)
}
}

作者:猫爸iYao
链接:https://www.jianshu.com/p/0dec79ff70df
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

RecyclerView Item 嵌套 ScrollView

RecyclerView Item 嵌套 ScrollView

RecyclerView Item 嵌套 ScrollView 产生 Touch 事件冲突,通过自定义ScrollView来拦截和处理事件

image-20210908211849925

自定义 ItemScrollView 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ScrollView

class ItemScrollView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
parent.requestDisallowInterceptTouchEvent(true)
return super.onInterceptTouchEvent(ev)
}

private var lastY: Float = 0f

override fun onTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
lastY = ev.y
}

MotionEvent.ACTION_MOVE -> {
val currentY = ev.y
this.scrollBy(0, (lastY - currentY).toInt())
lastY = currentY
}

MotionEvent.ACTION_UP -> {
lastY = 0f
}
}

return canScroll()
}

private fun canScroll(): Boolean {
val child = getChildAt(0)
child?.let {
return height < child.height
}
return false
}
}

RecyclerView的几种Decoration

RecyclerView的几种Decoration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import android.content.res.Resources
import android.graphics.Rect
import android.util.TypedValue
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class SimplePaddingDecoration(
spaceDp: Int,
val orientation: Int = RecyclerView.VERTICAL
) : RecyclerView.ItemDecoration() {
private val dividerHeight: Int = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
spaceDp.toFloat(),
Resources.getSystem().displayMetrics
).toInt()

override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = (view.layoutParams as RecyclerView.LayoutParams).viewLayoutPosition

if (orientation == RecyclerView.VERTICAL) {
// 竖直
if (position + 1 != parent.adapter?.itemCount) {
outRect.set(0, 0, 0, dividerHeight)
} else {
outRect.set(0, 0, 0, 0)
}
} else {
// 水平
if (position + 1 != parent.adapter?.itemCount) {
outRect.set(0, 0, dividerHeight, 0)
} else {
outRect.set(0, 0, 0, 0)
}
}
}
}

RecyclerView实现瀑布流以及细节问题

RecyclerView实现瀑布流以及细节问题

1)使用 StaggeredGridLayoutManager

1
2
3
4
5
6
7
8
9
10
val layoutManager = StaggeredGridLayoutManager(2, RecyclerView.VERTICAL)
layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE// 禁止左右交换
recyclerView.layoutManager = layoutManager
// decoration
recyclerView.addItemDecoration(StaggeredDividerItemDecoration(16, true))
// adapter
mAdapter = PjListVPAdapter()
recyclerView.adapter = mAdapter
// animator
recyclerView.itemAnimator = DefaultItemAnimator()

2)如果需要Item之间的间隔,就需要自定义 ItemDecoration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 瀑布流ItemDecoration
* 必须配合RecyclerView的StaggeredGridLayoutManager一起使用
*
* @author Dench
* @data 2020-09-04
*/
class StaggeredDividerItemDecoration(
space: Int, // 间隔 pix
private val includeEdge: Boolean = false // 是否显示边距
) : ItemDecoration() {
private val space = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
space.toFloat(),
Resources.getSystem().displayMetrics
).toInt()

override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val lp = view.layoutParams as StaggeredGridLayoutManager.LayoutParams
val lm = parent.layoutManager as StaggeredGridLayoutManager
val spanIndex: Int = lp.spanIndex
val spanCount: Int = lm.spanCount
if (includeEdge) {
outRect.left = space * (spanCount - spanIndex) / spanCount
outRect.right = space * (spanIndex + 1) / spanCount
outRect.top = space
} else {
outRect.left = space * spanIndex / spanCount
outRect.right = space * (spanCount - spanIndex - 1) / spanCount
outRect.bottom = space
}
}
}
阅读更多
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×