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) {
[email protected] {
[email protected](
0,
[email protected](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