RecycleView的操作(自定义SnapHelper、ItemDecoration)

简介: RecycleView的操作(自定义SnapHelper、ItemDecoration)

RecycleView的操作(自定义SnapHelper、ItemDecoration)

设置RecycleView的Item每次滑动的个数


通过重写SnapHelper来实现RecycleView每次滑动的Item个数,

使用方法: PagerSnapHelper( 6 ).attachToRecyclerView(Rv)

import android.view.View
import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper
/**
 * <pre>
 *  @author: Meng
 * --->time: 2020/11/27
 * ---> dec:
 *@param itemCount 每次滑动Item个数
 * <pre>
 */
class PagerSnapHelper(private val itemCount: Int) : SnapHelper() {
    private val TAG = "PagerSnapHelper"
    //垂直滑动
    private var mVerticalHelper: OrientationHelper? = null
    //水平滑动
    private var mHorizontalHelper: OrientationHelper? = null
    //款
    private var mRecyclerViewWidth = 0
    //高
    private var mRecyclerViewHeight = 0
    //需要滑动距离
    private var mCurrentScrolledX = 0
    private var mCurrentScrolledY = 0
    //滑动距离
    private var mScrolledX = 0
    private var mScrolledY = 0
    //防止惯性滑动
    private var mFlung = false
    /**
     * RecyclerView 滑动监听器 获取滑动总距离  
     */
    private val mScrollListener = object : RecyclerView.OnScrollListener() {
        private var scrolledByUser = false
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) scrolledByUser = true
            if (newState == RecyclerView.SCROLL_STATE_IDLE && scrolledByUser) {
                scrolledByUser = false
            }
        }
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            mScrolledX += dx
            mScrolledY += dy
            if (scrolledByUser) {
                mCurrentScrolledX += dx
                mCurrentScrolledY += dy
            }
        }
    }
    override fun attachToRecyclerView(recyclerView: RecyclerView?) {
        super.attachToRecyclerView(recyclerView)
        recyclerView?.run {
            addOnScrollListener(mScrollListener)
            post {
                println("$TAG RecyclerViewWidth: $width===$height")
                mRecyclerViewWidth = width
                mRecyclerViewHeight = height
            }
        }
    }
    /**
     * Override this method to snap to a particular point within the target view or the container
     * view on any axis.
     * <p>
     * This method is called when the {@link SnapHelper} has intercepted a fling and it needs
     * to know the exact distance required to scroll by in order to snap to the target view.
     *
     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}
     * @param targetView the target view that is chosen as the view to snap
     *
     * @return the output coordinates the put the result into. out[0] is the distance
     * on horizontal axis and out[1] is the distance on vertical axis.
     *
     * 找到需要对齐的 ItemView
     * @TODO out[0] 是水平轴的距离 (x)   out[1] 垂直轴的距离(y)
     *
     */
    override fun calculateDistanceToFinalSnap(
        layoutManager: RecyclerView.LayoutManager,
        targetView: View
    ): IntArray? {
        val out = IntArray(2)
        if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager))
            out[1] = 0
        } else if (layoutManager.canScrollVertically()) {
            out[0] = 0
            out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager))
        }
        return out
    }
    private fun distanceToStart(targetView: View, orientationHelper: OrientationHelper): Int {
        return orientationHelper.getDecoratedStart(targetView) - orientationHelper.startAfterPadding
    }
    /**
     * Override to provide a particular adapter target position for snapping.
     *
     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}
     * @param velocityX fling velocity on the horizontal axis
     * @param velocityY fling velocity on the vertical axis
     *
     * @return the target adapter position to you want to snap or {@link RecyclerView#NO_POSITION}
     *         if no snapping should happen
     *
     *计算 snapView 到要对齐的位置之间的距离。
     */
    override fun findTargetSnapPosition(
        layoutManager: RecyclerView.LayoutManager?,
        velocityX: Int,
        velocityY: Int
    ): Int {
        //获取当前滑动到的位置 @TODO targetPosition如果为-1代表滑动到头了  不能在进行滑动
        val targetPosition = getTargetPosition()
        mFlung = targetPosition != RecyclerView.NO_POSITION
        println("$TAG findTargetSnapPosition, pos: $targetPosition")
        return targetPosition
    }
    /**
     *
     * Override this method to provide a particular target view for snapping.
     * <p>
     * This method is called when the {@link SnapHelper} is ready to start snapping and requires
     * a target view to snap to. It will be explicitly called when the scroll state becomes idle
     * after a scroll. It will also be called when the {@link SnapHelper} is preparing to snap
     * after a fling and requires a reference view from the current set of child views.
     * <p>
     * If this method returns {@code null}, SnapHelper will not snap to any view.
     *
     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}
     *
     * @return the target view to which to snap on fling or end of scroll
     *
     * 是用来帮助对齐 ItemView 的
     */
    override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
        if (mFlung) {
            resetCurrentScrolled()
            mFlung = false
            return null
        }
        if (layoutManager == null) return null
        val targetPosition = getTargetPosition()
        println("$TAG findSnapView, pos: $targetPosition")
        if (targetPosition == RecyclerView.NO_POSITION) return null
        layoutManager.startSmoothScroll(createScroller(layoutManager).apply {
            this?.targetPosition = targetPosition
        })
        return null
    }
    private fun getTargetPosition(): Int {
        println("$TAG getTargetPosition, mScrolledX: $mScrolledX, mCurrentScrolledX: $mCurrentScrolledX")
        /**
         * @param page 获取需要滑动Item个数
         */
        val page = when {
            mCurrentScrolledX > 0 -> mScrolledX / mRecyclerViewWidth + 1
            mCurrentScrolledX < 0 -> mScrolledX / mRecyclerViewWidth
            mCurrentScrolledY > 0 -> mScrolledY / mRecyclerViewHeight + 1
            mCurrentScrolledY < 0 -> mScrolledY / mRecyclerViewHeight
            else -> RecyclerView.NO_POSITION
        }
        resetCurrentScrolled()
        return if (page == RecyclerView.NO_POSITION) RecyclerView.NO_POSITION else page * itemCount
    }
    //重新赋值xy
    private fun resetCurrentScrolled() {
        mCurrentScrolledX = 0
        mCurrentScrolledY = 0
    }
    private fun getVerticalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
        if (mVerticalHelper == null || mVerticalHelper!!.layoutManager !== layoutManager) {
            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager)
        }
        return mVerticalHelper!!
    }
    private fun getHorizontalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
        if (mHorizontalHelper == null || mHorizontalHelper!!.layoutManager !== layoutManager) {
            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager)
        }
        return mHorizontalHelper!!
    }
}

每次滑动一整屏

和 PagerSnapHelper 使用方法一样。原理也差不多

import androidx.recyclerview.widget.RecyclerView
import kotlin.math.absoluteValue
/**
 * <pre>
 *  @author: Meng
 * --->time: 2020/11/27
 * ---> dec:   每次滑动一整屏
 *   会出现回弹效果  在第一屏滑动快速滑动到第三瓶 会回滚会第二瓶
 * <pre>
 */
class FullPagerSnapHelper {
    private var mRecyclerView: RecyclerView? = null
    private var mHalfItemWidth = 0
    private var mFullPageWidth = 0
    private var mScrolled = false
    private val mScrollListener = object : RecyclerView.OnScrollListener() {
        private var mTotalScrolledX = 0
        private var mTotalScrolledY = 0
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            println("tag=== onScrollStateChanged, state: $newState")
            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                mScrolled = true
            }
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                if (mScrolled) {
                    mScrolled = false
                    if (mTotalScrolledX.absoluteValue > 0 || mTotalScrolledY.absoluteValue > 0) {
                        snapToTargetExistingView(mTotalScrolledX, mTotalScrolledY)
                    }
                }
                mTotalScrolledX = 0
                mTotalScrolledY = 0
            }
        }
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            mTotalScrolledX += dx
            mTotalScrolledY += dy
        }
    }
    private val onFlingListener = object : RecyclerView.OnFlingListener() {
        //会出现回弹效果  在第一屏滑动快速滑动到第三瓶 会回滚会第二瓶 
        override fun onFling(velocityX: Int, velocityY: Int): Boolean {
            println("tag=== onFling, vX: $velocityX, vY: $velocityY")
            return false
        }
    }
    @Throws(IllegalStateException::class)
    fun attachToRecyclerView(recyclerView: RecyclerView) {
        recyclerView.post {
            mFullPageWidth = recyclerView.measuredWidth
            mHalfItemWidth = recyclerView.measuredWidth / 6
            println("tag=== full: $mFullPageWidth, half: $mHalfItemWidth")
        }
        if (mRecyclerView != null) {
            destroyCallbacks()
        }
        mRecyclerView = recyclerView
        if (mRecyclerView != null) {
            setupCallbacks()
        }
    }
    @Throws(IllegalStateException::class)
    private fun setupCallbacks() {
        mRecyclerView?.addOnScrollListener(mScrollListener)
        mRecyclerView?.onFlingListener = onFlingListener
    }
    private fun destroyCallbacks() {
        mRecyclerView?.removeOnScrollListener(mScrollListener)
        mRecyclerView?.onFlingListener = null
    }
    private fun snapToTargetExistingView(dx: Int, dy: Int) {
        when {
            dx.absoluteValue >= mHalfItemWidth -> mRecyclerView?.smoothScrollBy(
                if (dx > 0) mFullPageWidth - dx else -mFullPageWidth - dx, 0
            )
            dy.absoluteValue >= mHalfItemWidth -> mRecyclerView?.smoothScrollBy(
                0, if (dy > 0) mFullPageWidth - dy else -mFullPageWidth - dy
            )
            else -> mRecyclerView?.smoothScrollBy(-dx, -dy)
        }
    }
}

设置RecycleView每一个Item距离


重写ItemDecoration 原理:就是给RV添加了一条没有颜色的分割线

使用方法:Rv.addItemDecoration(SpaceItemDecoration(SizeUtils.dp2px(50f)))

import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
/**
 * <pre>
 *  @author: Meng
 *  @time: 2020/11/26
 *  @dec:   Rv设置每一个Item间隔的
 *   @param mSpace 间隔距离 现在是px 开发中需要转成dp
 * <pre>
 */
 class SpaceItemDecoration(var mSpace: Int) : ItemDecoration() {
    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        super.getItemOffsets(outRect, view, parent, state)
        outRect.left = mSpace
        outRect.right = mSpace
        outRect.bottom = mSpace
        if (parent.getChildAdapterPosition(view) == 0) {
            outRect.top = mSpace
        }
    }
}

Grid布局分割线

import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.View
import androidx.recyclerview.widget.RecyclerView
/**
 * <pre>
 *  @author: Meng
 * --->time: 2020/11/27
 * ---> dec:    Grid 布局分割线
 *
 * <pre>
 */
class GridItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
    private val mBounds = Rect()
    private var mDivider: Drawable = ColorDrawable(context.resources.getColor(android.R.color.holo_purple))
    private var mDividerWidth = 10
    /**
     * @param divider 设置分割线样式
     * @param dividerWidth 设置分割线宽度
     */
    fun setDivider(divider: Drawable = mDivider, dividerWidth: Int = mDividerWidth) {
        mDivider = divider
        mDividerWidth = dividerWidth
    }
    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        if (parent.layoutManager!!.canScrollHorizontally()) {
            drawHorizontal(c, parent)
        } else if (parent.layoutManager!!.canScrollVertically()) {
            drawVertical(c, parent)
        }
    }
    private fun drawHorizontal(c: Canvas, parent: RecyclerView) {
        c.save()
        val count = parent.childCount
        for (i in 0 until count) {
            val child = parent.getChildAt(i)
            parent.getDecoratedBoundsWithMargins(child, mBounds)
            //画竖线
            mDivider.setBounds(mBounds.right - mDividerWidth, mBounds.top, mBounds.right, mBounds.bottom)
            mDivider.draw(c)
            //画横线
            mDivider.setBounds(mBounds.left, mBounds.bottom - mDividerWidth, mBounds.right, mBounds.bottom)
            mDivider.draw(c)
        }
        c.restore()
    }
    private fun drawVertical(c: Canvas, parent: RecyclerView) {
        c.save()
        val count = parent.childCount
        for (i in 0 until count) {
            val child = parent.getChildAt(i)
            parent.getDecoratedBoundsWithMargins(child, mBounds)
            //画横线
            mDivider.setBounds(mBounds.left, mBounds.bottom - mDividerWidth, mBounds.right, mBounds.bottom)
            //画竖线
            mDivider.setBounds(mBounds.right - mDividerWidth, mBounds.top, mBounds.right, mBounds.bottom)
            mDivider.draw(c)
        }
        c.restore()
    }
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        outRect.set(0, 0, mDividerWidth, mDividerWidth)
    }
}


目录
相关文章
|
XML Android开发 数据格式
进入Activity时,为何页面布局内View#onMeasure会被调用两次?
进入Activity时,为何页面布局内View#onMeasure会被调用两次?
|
Android开发 开发者
RecyclerView定制:通用ItemDecoration及全展开RecyclerView的实现
RecyclerView定制:通用ItemDecoration及全展开RecyclerView的实现
456 0
RecyclerView定制:通用ItemDecoration及全展开RecyclerView的实现
|
Android开发
【RecyclerView】 九、为 RecyclerView 设置不同的布局样式
【RecyclerView】 九、为 RecyclerView 设置不同的布局样式
284 0
【RecyclerView】 九、为 RecyclerView 设置不同的布局样式
|
存储 缓存 开发工具
RecyclerView#Adapter#notifyDataSetChanged方法后,为何还会新建ViewHolder?
RecyclerView#Adapter#notifyDataSetChanged方法后,为何还会新建ViewHolder?
ScrollView和HorizontalScrollView无法设置点击事件的源码解析
最近的开发过程中,发现存在ScrollView和HorizontalScrollView无法设置点击事件的现象。 我们知道,通常在设置点击事件时,位于View树上方的子View的OnClickListener,会优先于父View的OnClickListener执行。 开发过程中我们会经常使用类似的方式来给布局设置点击事件,比如给ListView的Item背景设置OnClickListener,用于点击item空白区域的跳转操作;然后再给item内部的子元素分别设置OnClickListener用于各自不同的点击操作。
|
Android开发
同一页面实现recycleView三种布局【recycleView + adapter】
同一页面实现recycleView三种布局【recycleView + adapter】
158 0
|
Java Android开发
【RecyclerView】 七、RecyclerView.ItemDecoration 条目装饰 ( getItemOffsets 边距设置 )
【RecyclerView】 七、RecyclerView.ItemDecoration 条目装饰 ( getItemOffsets 边距设置 )
149 0
【RecyclerView】 七、RecyclerView.ItemDecoration 条目装饰 ( getItemOffsets 边距设置 )
|
Android开发
【RecyclerView】 八、RecyclerView.ItemDecoration 条目装饰 ( onDraw 和 onDrawOver 绘制要点 )(二)
【RecyclerView】 八、RecyclerView.ItemDecoration 条目装饰 ( onDraw 和 onDrawOver 绘制要点 )(二)
162 0
【RecyclerView】 八、RecyclerView.ItemDecoration 条目装饰 ( onDraw 和 onDrawOver 绘制要点 )(二)
|
前端开发 容器
【RecyclerView】 八、RecyclerView.ItemDecoration 条目装饰 ( onDraw 和 onDrawOver 绘制要点 )(一)
【RecyclerView】 八、RecyclerView.ItemDecoration 条目装饰 ( onDraw 和 onDrawOver 绘制要点 )(一)
415 0
【RecyclerView】 八、RecyclerView.ItemDecoration 条目装饰 ( onDraw 和 onDrawOver 绘制要点 )(一)
SwipeRefreshLayout 嵌套 RecyclerView滑动冲突
SwipeRefreshLayout 嵌套 RecyclerView滑动冲突
332 0

热门文章

最新文章