1、横向循环(代码中有注解)
public class LooperLayoutManager extends RecyclerView.LayoutManager {
private static final String TAG = "LooperLayoutManager";
private boolean looperEnable = true;
public LooperLayoutManager() {}
public void setLooperEnable(boolean looperEnable) {
this.looperEnable = looperEnable;
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public boolean canScrollHorizontally() {
return true;
}
@Override
public boolean canScrollVertically() {
return false;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getItemCount() <= 0) {
return;
}
//preLayout主要支持动画,直接跳过
if (state.isPreLayout()) {
return;
}
//将视图分离放入scrap缓存中,以准备重新对view进行排版
detachAndScrapAttachedViews(recycler);
int autualWidth = 0;
for (int i = 0; i < getItemCount(); i++) {
//初始化,将在屏幕内的view填充
View itemView = recycler.getViewForPosition(i);
addView(itemView);
//测量itemView的宽高
measureChildWithMargins(itemView, 0, 0);
int width = getDecoratedMeasuredWidth(itemView);
int height = getDecoratedMeasuredHeight(itemView);
//根据itemView的宽高进行布局
layoutDecorated(itemView, autualWidth, 0, autualWidth + width, height);
autualWidth += width;
//如果当前布局过的itemView的宽度总和大于RecyclerView的宽,则不再进行布局
if (autualWidth > getWidth()) {
break;
}
}
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
//1.左右滑动的时候,填充子view
int travl = fill(dx, recycler, state);
if (travl == 0) {
return 0;
}
Log.e(TAG, "scrollHorizontallyBy: "+travl );
//2.滚动
offsetChildrenHorizontal(travl * -1);
//3.回收已经离开界面的
recyclerHideView(dx, recycler, state);
return travl;
}
/**
* 左右滑动的时候,填充
*/
private int fill(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (dx > 0) {
//标注1.向左滚动
View lastView = getChildAt(getChildCount() - 1);
if (lastView == null) {
return 0;
}
int lastPos = getPosition(lastView);
Log.e(TAG, "scrollHorizontallyBy: lastPos=="+lastPos+"==="+lastView.getRight()+"==="+getWidth() );
//标注2.可见的最后一个itemView完全滑进来了,需要补充新的
if (lastView.getRight() < getWidth()) {
View scrap = null;
//标注3.判断可见的最后一个itemView的索引,
// 如果是最后一个,则将下一个itemView设置为第一个,否则设置为当前索引的下一个
if (lastPos == getItemCount() - 1) {
if (looperEnable) {
scrap = recycler.getViewForPosition(0);
} else {
dx = 0;
}
} else {
scrap = recycler.getViewForPosition(lastPos + 1);
}
if (scrap == null) {
return dx;
}
//标注4.将新的itemViewadd进来并对其测量和布局
addView(scrap);
measureChildWithMargins(scrap, 0, 0);
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap,lastView.getRight(), 0,
lastView.getRight() + width, height);
Log.e(TAG, "scrollHorizontallyBy: dx==="+dx );
return dx;
}
} else {
//向右滚动
View firstView = getChildAt(0);
if (firstView == null) {
return 0;
}
int firstPos = getPosition(firstView);
Log.e(TAG, "scrollHorizontallyBy: firstPos="+firstPos );
if (firstView.getLeft() >= 0) {
View scrap = null;
if (firstPos == 0) {
if (looperEnable) {
scrap = recycler.getViewForPosition(getItemCount() - 1);
} else {
dx = 0;
}
} else {
scrap = recycler.getViewForPosition(firstPos - 1);
}
if (scrap == null) {
return 0;
}
addView(scrap, 0);
measureChildWithMargins(scrap,0,0);
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap, firstView.getLeft() - width, 0,
firstView.getLeft(), height);
}
}
Log.e(TAG, "scrollHorizontallyBy: dx=dx=="+dx );
return dx;
}
/**
* 回收界面不可见的view
*/
private void recyclerHideView(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view == null) {
continue;
}
if (dx > 0) {
//向左滚动,移除一个左边不在内容里的view
if (view.getRight() < 0) {
removeAndRecycleView(view, recycler);
Log.d(TAG, "循环: 移除 一个view childCount=" + getChildCount());
}
} else {
//向右滚动,移除一个右边不在内容里的view
if (view.getLeft() > getWidth()) {
removeAndRecycleView(view, recycler);
Log.d(TAG, "循环: 移除 一个view childCount=" + getChildCount());
}
}
}
}
}
2、竖向滑动(代码中有注解)
/**
* <pre>
* @author: Meng
* --->time: 2020/12/22
* ---> dec: 配合自动滑动的Rv 实现的无线循环
*
* <pre>
*/
class CustomLinearLayoutManager : LinearLayoutManager {
private var looperEnable = true
val TAG = "CustomLinearLayoutManager"
constructor(context: Context?) : super(context) {}
constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super(
context,
orientation,
reverseLayout
) {
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
}
override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams? {
return RecyclerView.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
override fun canScrollHorizontally(): Boolean {
return false
}
override fun canScrollVertically(): Boolean {
return true
}
override fun onLayoutChildren(
recycler: RecyclerView.Recycler,
state: RecyclerView.State
) {
if (itemCount <= 0) {
return
}
//preLayout主要支持动画,直接跳过
if (state.isPreLayout) {
return
}
//将视图分离放入scrap缓存中,以准备重新对view进行排版
detachAndScrapAttachedViews(recycler)
var autualHeiht = 0
for (i in 0 until itemCount) {
//初始化,将在屏幕内的view填充
val itemView = recycler.getViewForPosition(i)
addView(itemView)
//测量itemView的宽高
measureChildWithMargins(itemView, 0, 0)
val width = getDecoratedMeasuredWidth(itemView)
val height = getDecoratedMeasuredHeight(itemView)
//根据itemView的宽高进行布局
layoutDecorated(itemView, 0, autualHeiht, width, autualHeiht + height)
autualHeiht += height
//如果当前布局过的itemView的宽度总和大于RecyclerView的宽,则不再进行布局
if (autualHeiht > getHeight()) {
break
}
}
}
override fun scrollVerticallyBy(
dy: Int,
recycler: RecyclerView.Recycler,
state: RecyclerView.State
): Int {
//1.上下滑动的时候,填充子view
val travl = fill(dy, recycler, state)
if (travl == 0) {
return 0
}
Log.e(
TAG,
"scrollHorizontallyBy: $travl"
)
//2.滚动
offsetChildrenVertical(travl * -1)
//3.回收已经离开界面的
recyclerHideView(dy, recycler, state)
return travl
}
/**
* 上下滑动的时候,填充
*/
private fun fill(
dx: Int,
recycler: RecyclerView.Recycler,
state: RecyclerView.State
): Int {
var dx = dx
Log.e(
TAG,
"fill: $dx"
)
if (dx > 0) {
//标注1.向上滚动
val lastView = getChildAt(childCount - 1) ?: return 0
val lastPos = getPosition(lastView)
Log.e(
TAG,
"scrollHorizontallyBy: lastPos==" + lastPos + "==" + lastView.bottom + "==----==" + lastView.top + "===" + height
)
//标注2.可见的最后一个itemView完全滑进来了,需要补充新的
if (lastView.top < height) {
var scrap: View? = null
//标注3.判断可见的最后一个itemView的索引,
// 如果是最后一个,则将下一个itemView设置为第一个,否则设置为当前索引的下一个
if (lastPos == itemCount - 1) {
if (looperEnable) {
scrap = recycler.getViewForPosition(0)
} else {
dx = 0
}
} else {
scrap = recycler.getViewForPosition(lastPos + 1)
}
if (scrap == null) {
return dx
}
//标注4.将新的itemViewadd进来并对其测量和布局
addView(scrap)
measureChildWithMargins(scrap, 0, 0)
val width = getDecoratedMeasuredWidth(scrap)
val height = getDecoratedMeasuredHeight(scrap)
layoutDecorated(
scrap, 0, lastView.bottom,
width, lastView.bottom + height
)
return dx
}
} else {
//向下滚动
val firstView = getChildAt(0) ?: return 0
val firstPos = getPosition(firstView)
if (firstView.top >= 0) {
Log.e(
TAG,
"scrollHorizontallyBy: firstPos=" + firstPos + "==" + firstView.top + "==" + firstView.bottom
)
var scrap: View? = null
if (firstPos == 0) {
if (looperEnable) {
scrap = recycler.getViewForPosition(itemCount - 1)
} else {
dx = 0
}
} else {
scrap = recycler.getViewForPosition(firstPos - 1)
}
if (scrap == null) {
return 0
}
addView(scrap, 0)
measureChildWithMargins(scrap, 0, 0)
val width = getDecoratedMeasuredWidth(scrap)
val height = getDecoratedMeasuredHeight(scrap)
layoutDecorated(
scrap, 0, firstView.top - height,
width, firstView.top
)
}
}
Log.e(
TAG,
"scrollHorizontallyBy: dx=dx==$dx"
)
return dx
}
/**
* 回收界面不可见的view
*/
private fun recyclerHideView(
dx: Int,
recycler: RecyclerView.Recycler,
state: RecyclerView.State
) {
Log.e(
TAG,
"recyclerHideView: $dx"
)
for (i in 0 until childCount) {
val view = getChildAt(i) ?: continue
Log.e(
TAG,
"recyclerHideView: " + view.top + "===" + view.bottom
)
if (dx > 0) {
//向上滚动,移除一个左边不在内容里的view
if (view.bottom < 0) {
removeAndRecycleView(view, recycler)
Log.d(
TAG,
"循环: 移除 一个view childCount=$childCount"
)
}
} else {
//向下滚动,移除一个右边不在内容里的view
if (view.top > height) {
removeAndRecycleView(view, recycler)
Log.d(
TAG,
"循环: 移除 一个view childCount=$childCount"
)
}
}
}
}
override fun smoothScrollToPosition(
recyclerView: RecyclerView,
state: RecyclerView.State,
position: Int
) {
val linearSmoothScroller: LinearSmoothScroller =
object : LinearSmoothScroller(recyclerView.context) {
private
val MILLISECONDS_PER_INCH = 200f
override fun computeScrollVectorForPosition(targetPosition: Int): PointF? {
return this@CustomLinearLayoutManager
.computeScrollVectorForPosition(targetPosition)
}
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi
}
}
linearSmoothScroller.targetPosition = position
startSmoothScroll(linearSmoothScroller)
}
}
3、自动滚动的RV
/**
* <pre>
* @author: Meng
* --->time: 2020/12/22
* ---> dec: 自动滚动的Rv
* 实现的 LifecycleObserver 自动管理 countDownTimer 的 状态
* <pre>
*/
class AutoPollRecyclerView(context: Context, @Nullable attrs: AttributeSet?) :
RecyclerView(context, attrs), LifecycleObserver {
private val TAG = "AutoPollRecyclerView"
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private fun resume() {
countDownTimer.start()
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun stop() {
countDownTimer.cancel()
}
private val countDownTimer = object : CountDownTimer(Integer.MAX_VALUE.toLong(), 2000) {
override fun onFinish() {
}
override fun onTick(millisUntilFinished: Long) {
this@AutoPollRecyclerView.post {
this@AutoPollRecyclerView.smoothScrollBy(
0,
this@AutoPollRecyclerView.getChildAt(0).height
)
}
}
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
stop()
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_OUTSIDE -> {
resume()
}
}
return super.dispatchTouchEvent(ev)
}
// 实现渐变效果
var mPaint: Paint? = null
private var layerId = 0
private var linearGradient: LinearGradient? = null
private var preWidth = 0 // Recyclerview宽度动态变化时,监听每一次的宽度
fun doTopGradualEffect(itemViewWidth: Int) {
mPaint = Paint()
// dst_in 模式,实现底层透明度随上层透明度进行同步显示(即上层为透明时,下层就透明,并不是上层覆盖下层)
val xfermode: Xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
mPaint!!.setXfermode(xfermode)
addItemDecoration(object : ItemDecoration() {
override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: State) {
super.onDrawOver(canvas, parent, state)
// 当linearGradient为空即第一次绘制 或 Recyclerview宽度发生改变时,重新计算透明位置
if (linearGradient == null || preWidth != parent.width) {
// 透明位置从最后一个 itemView 的一半处到 Recyclerview 的最右边
linearGradient = LinearGradient(
(parent.width - itemViewWidth / 2).toFloat(),
0.0f,
parent.width.toFloat(),
0.0f,
intArrayOf(Color.BLACK, 0),
null,
Shader.TileMode.CLAMP
)
preWidth = parent.width
}
mPaint?.setXfermode(xfermode)
mPaint?.setShader(linearGradient)
canvas.drawRect(
0.0f, 0.0f, parent.right.toFloat(), parent.bottom.toFloat(),
mPaint!!
)
mPaint?.setXfermode(null)
canvas.restoreToCount(layerId)
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: State) {
super.onDraw(c, parent, state)
// 此处 Paint的参数这里传的null, 在传入 mPaint 时会出现第一次打开黑屏闪现的问题
// 注意 saveLayer 不能省也不能移动到onDrawOver方法里
layerId = c.saveLayer(
0.0f,
0.0f,
parent.width.toFloat(),
parent.height.toFloat(),
null,
Canvas.ALL_SAVE_FLAG
)
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: State
) {
super.getItemOffsets(outRect, view, parent, state)
}
})
}
}
4、代码调用
val scrollSpeedLinearLayoutManger = CustomLinearLayoutManager(this)
parentJoinClassStepRv.adapter = mAdp
parentJoinClassStepRv.layoutManager =scrollSpeedLinearLayoutManger
//生命周期关联
lifecycle.addObserver(parentJoinClassStepRv)
参考链接 :https://blog.csdn.net/user11223344abc/article/details/78080671