自定义View:循环垂直滚动的Recyclerview

自定义View,循环滚动的Recycler view

前沿

产品规划要做一个循环滚动展示用户评价的效果,如下图所示,好吧,百度一下,谷歌一下,git上找找,看有没有能用的轮子,一番操作后,TMD,还真没找到能用的。虽然我不提倡重复造轮子,但自己一定要具备造轮子的能力啊,那就自己弄一个呗

在这里插入图片描述

自己写一个AutoScrollView

在干事之前,得先分析一下思路,算法复杂的画流程图,逻辑不清的画思维导图,分析思路,化繁为简,一步步来

实现思路分析:

  1. 自定义View继承自RecyclerView
  2. 循环滚动通过handler进行弱引用实现
  3. 数据定位通过对真实数据集大小取模实现(adapter里面)
  4. 自定义View接收一个pageSize参数,用来指定要显示出来的item 的个数,如上图,pageSize就是2
  5. 自定义View接收一个enable_scroll的参数,作为是否开启滚动的开关
  6. 自定义View接收一个scroll_interval的参数,用来决定滚动的间隔时间
  7. 自定义View接收一个scroll_orientation的参数,用来决定滚动的方向,如上图是从上往下滚动,那顺便把从下往上滚动的效果也加上(搞不好哪天产品头一热,就要改个滚动方向呢),所以自定义View一定的扩展能力还是要有的,当然也不需要太过,这个度还是要靠自己把握。
实现过程
1、定义adapter
public class RecycleCommentWheelAdapterV2 extends BaseQuickAdapter<CommentItemInfo, BaseViewHolder> {
    public RecycleCommentWheelAdapterV2(@Nullable List<CommentItemInfo> data) {
        super(R.layout.recycle_rv_item_new_comment_wheel, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, CommentItemInfo item) {
        helper.setText(R.id.tv_phone, item.getMobile());
        helper.setText(R.id.tv_time, item.getCreate_time());
        TextView moneyTv = helper.getView(R.id.tv_money);
        moneyTv.setText(StringUtils.getHighlightText(item.getAccount_type() + "收款¥" + item.getRe_money(),
                "¥" + item.getRe_money(), Color.parseColor("#FF1A1A")));
        RecycleHelper.INSTANCE.setTypefaceDIN(mContext, moneyTv);
        helper.setText(R.id.tv_comment, item.getComment());
    }

    @Nullable
    @Override
    public CommentItemInfo getItem(int position) {
        int realPosition = position % getData().size();
        return getData().get(realPosition);
    }

    @Override
    public int getItemViewType(int position) {
        int count = getHeaderLayoutCount() + getData().size();
        if (count <= 0) {
            count = 1;
        }
        int realPosition = position % count;
        return super.getItemViewType(realPosition);
    }

    @Override
    public int getItemCount() {
        return Integer.MAX_VALUE;
    }
}

关键代码1:getItemCount,返回最大的整形值
关键代码2:getItem,获取到正确位置的数据实体

2、定义自定义属性文件,在attrs.xml中加入
    <declare-styleable name="AutoScrollView">
        <attr name="asv_page_size" format="integer" />
        <attr name="asv_enable_scroll" format="boolean" />
        <attr name="asv_scroll_interval" format="integer" />
        <attr name="asv_scroll_orientation" format="enum">
            <enum name="top_2_bottom" value="0" />
            <enum name="bottom_2_top" value="1" />
        </attr>
    </declare-styleable>
3、自定义AutoScrollView

不啰嗦,直接上完整代码

package com.hdp.testview

import android.content.Context
import android.os.Handler
import android.os.Message
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
import com.huodao.module_recycle.R
import java.lang.ref.WeakReference
import kotlin.math.abs

/**
 * author:hdp
 * email:yisper88@gmail.com
 * on:2020/4/21 10:46
 * desc:
 * 自动滚动的RecyclerView
 */
class AutoScrollView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {


    private var mPageSize = 2                       //可视的条数
    private var mEnableScroll: Boolean = true       //是否自动滚动
    private var mScrollInterval: Long = 3000        //滚动时间间隔
    private var mScrollOrientation = 1              //滚动方向

    private var mHandler: AutoScrollHandler? = null

    companion object {
        val TAG = "AutoScrollView"
        val SCROLL_MSG = 1001
        val COMMENT_MAX_COUNT = 1000L               //最多条目
        val ORITATION_TOP_2_BOTTOM = 0
        val ORITATION_BOTTOM_2_TOP = 1
    }


    init {
        mHandler = AutoScrollHandler(this)
        attrs?.let {
            val typedArray = context.obtainStyledAttributes(it, R.styleable.AutoScrollView)
            mPageSize = typedArray.getInt(R.styleable.AutoScrollView_asv_page_size, 2)
            mEnableScroll = typedArray.getBoolean(R.styleable.AutoScrollView_asv_enable_scroll, true)
            mScrollInterval = typedArray.getInt(R.styleable.AutoScrollView_asv_scroll_interval, 3000).toLong()
            mScrollOrientation = typedArray.getInt(R.styleable.AutoScrollView_asv_scroll_orientation, 0)
            typedArray.recycle()
        }
    }


    private fun startLoop() {
        if (!mEnableScroll) return
//        LogUtils.eTag(TAG, "startLoop")
        val layoutManager = layoutManager as LinearLayoutManager
        val initPosition = layoutManager.findFirstVisibleItemPosition()
        if (initPosition < 10) {
            if (mScrollOrientation == ORITATION_TOP_2_BOTTOM) {
                scrollToPosition(999)
            }
        }
        mHandler?.sendEmptyMessageDelayed(SCROLL_MSG, 2000)
    }


    override fun onVisibilityChanged(changedView: View, visibility: Int) {
        super.onVisibilityChanged(changedView, visibility)
//        LogUtils.eTag(TAG, "onVisibilityChanged:${visibility == View.VISIBLE}")
        if (View.VISIBLE != visibility) {
            mHandler?.removeCallbacksAndMessages(null)
        } else {
            startLoop()
        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
//        LogUtils.eTag(TAG, "onDetachedFromWindow")
        mHandler?.removeCallbacksAndMessages(null)
    }

    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        try {
            super.onInterceptTouchEvent(e)
        } catch (e1: Exception) {
            e1.printStackTrace()
        }
        return false
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        return false
    }

    class AutoScrollHandler(weakView: AutoScrollView) : Handler() {
        private var weakReference: WeakReference<AutoScrollView>? = null

        init {
            weakReference = WeakReference(weakView)
        }

        override fun handleMessage(msg: Message?) {
            when (msg?.what) {
                SCROLL_MSG -> {
                    val autoScrollView = weakReference?.get()
                    autoScrollView?.let {

                        val layoutManger = it.layoutManager as LinearLayoutManager
                        val lastVisiPosition = layoutManger.findLastVisibleItemPosition()
                        var toPosition = 0L
                        if (it.mScrollOrientation == ORITATION_TOP_2_BOTTOM) {
                            //从上往下滚动
                            val smoothScroll = object : LinearSmoothScroller(autoScrollView.context) {
                                override fun getVerticalSnapPreference(): Int {
                                    return SNAP_TO_START
                                }

                                override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
                                    return 3f / displayMetrics.density
                                }
                            }
                            toPosition = abs(lastVisiPosition - it.mPageSize) % COMMENT_MAX_COUNT
                            if (toPosition == 1L) {
                                toPosition = COMMENT_MAX_COUNT
                            }
                            if (toPosition > lastVisiPosition || abs(lastVisiPosition - toPosition) >= it.mPageSize + 1) {
                                layoutManger.scrollToPosition(toPosition.toInt())
                            } else {
                                if (layoutManger.isSmoothScrolling.not()) {
                                    smoothScroll.targetPosition = toPosition.toInt()
                                    layoutManger.startSmoothScroll(smoothScroll)
                                }
                            }
                        } else {
                            //从下往上滚动
                            val smoothScroll = object : LinearSmoothScroller(autoScrollView.context) {
                                override fun getVerticalSnapPreference(): Int {
                                    return SNAP_TO_END
                                }

                                override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
                                    return 3f / displayMetrics.density
                                }
                            }
                            toPosition = abs(lastVisiPosition + 1) % COMMENT_MAX_COUNT
                            if (toPosition < lastVisiPosition || abs(lastVisiPosition - toPosition) >= it.mPageSize + 1) {
                                layoutManger.scrollToPosition(toPosition.toInt())
                            } else {
                                if (layoutManger.isSmoothScrolling.not()) {
                                    smoothScroll.targetPosition = toPosition.toInt()
                                    layoutManger.startSmoothScroll(smoothScroll)
                                }
                            }
                        }
//                        LogUtils.eTag(TAG, "lastPosition==$lastVisiPosition,toPosition=$toPosition")
                        autoScrollView.mHandler?.sendEmptyMessageDelayed(SCROLL_MSG, autoScrollView.mScrollInterval)
                    }
                }
            }
        }
    }

}

核心逻辑就是在handlemessage里面,分别处理两个方向的滚动

  • 当目标位置(toPosition)和最后可视的item的距离大于pageSize+1时候,直接滚动到目标位置,调用LinearLayoutManager的scrollToPosition(没有平滑的效果)
  • 否则,通过LinearSmoothScroller,使用layoutManger.startSmoothScroll(smoothScroll),平滑滚动到目标位置,实际就是滚动一个Item的位置
  • 最后,通过handler发送一次延迟消息,实现不停的滚动
4、善后处理
  1. 屏蔽RecyclerView的触摸滚动
    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        try {
            super.onInterceptTouchEvent(e)
        } catch (e1: Exception) {
            e1.printStackTrace()
        }
        return false
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        return false
    }
  1. 处理生命周期,当可视的时候,开始滚动
    override fun onVisibilityChanged(changedView: View, visibility: Int) {
        super.onVisibilityChanged(changedView, visibility)
//        LogUtils.eTag(TAG, "onVisibilityChanged:${visibility == View.VISIBLE}")
        if (View.VISIBLE != visibility) {
            mHandler?.removeCallbacksAndMessages(null)
        } else {
            startLoop()
        }
    }
  1. 当从窗口移除的时候,清空handler的message
  override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
//        LogUtils.eTag(TAG, "onDetachedFromWindow")
        mHandler?.removeCallbacksAndMessages(null)
    }
布局中使用
  <com.hdp.testview.AutoScrollView
            android:id="@+id/rv_comment"
            android:layout_width="match_parent"
            android:layout_height="208dp"
            android:layout_marginTop="16dp"
            android:overScrollMode="never"
            android:scrollbars="none"
            android:visibility="gone"
            app:asv_enable_scroll="true"
            app:asv_page_size="2"
            app:asv_scroll_interval="3000"
            app:asv_scroll_orientation="top_2_bottom"
             >
注意点

AutoScrollView需要设置一个固定的总高度,总高度=一个Item的高度*pageSize,笔者这里一个Item的高度是104,显示2个,故总高度设置成208,pageSize设置成2

thinks 完!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值