Android itemDecoration接口实现吸顶悬浮标题

手机APP/开发
236
0
0
2023-06-17
标签   Android
目录
  • 方案
  • 了解ItemDecoration
  • 利用ItemDecoration来绘制悬浮标题栏
  • 代码

方案

1.设置一个悬浮的视图挂在recycleView顶部,随着item的移动位置,悬浮标题自动跟随移动或者是保持原地不动。

2.使用recyclerView的ItemDecoration,给指定的item设置不同的itemDecoration,并且跟随item的移动而移动或者保持不变。

本文采用第二种方式实现,效果图:

了解ItemDecoration

这是个接口,一共有六个方法:

public static abstract class ItemDecoration {
    /**
     * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
     * Any content drawn by this method will be drawn before the item views are drawn,
     * and will thus appear underneath the views.
     *
     * @param c Canvas to draw into
     * @param parent RecyclerView this ItemDecoration is drawing into
     * @param state The current state of RecyclerView
     */
    public void onDraw(Canvas c, RecyclerView parent, State state) {
        onDraw(c, parent);
    }
    /**
     * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
     * Any content drawn by this method will be drawn after the item views are drawn
     * and will thus appear over the views.
     *
     * @param c Canvas to draw into
     * @param parent RecyclerView this ItemDecoration is drawing into
     * @param state The current state of RecyclerView.
     */
    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
        onDrawOver(c, parent);
    }
    /**
     * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
     * the number of pixels that the item view should be inset by, similar to padding or margin.
     * The default implementation sets the bounds of outRect to and returns.
     *
     * <p>
     * If this ItemDecoration does not affect the positioning of item views, it should set
     * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
     * before returning.
     *
     * <p>
     * If you need to access Adapter for additional data, you can call
     * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
     * View.
     *
     * @param outRect Rect to receive the output.
     * @param view    The child view to decorate
     * @param parent  RecyclerView this ItemDecoration is decorating
     * @param state   The current state of RecyclerView.
     */
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                parent);
    }
    /**
     * @deprecated
     * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
     */
    @Deprecated
    public void onDrawOver(Canvas c, RecyclerView parent) {
    }
    /**
     * @deprecated
     * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
     */
    @Deprecated
    public void onDraw(Canvas c, RecyclerView parent) {
    }
    /**
     * @deprecated
     * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
     */
    @Deprecated
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
        outRect.set(, 0, 0, 0);
    }
}

其中有三个方法是@deprecated的,那么我们只需要看以下三个方法:

	public void onDraw(Canvas c, RecyclerView parent, State state) {
		onDraw(c, parent);
	}
	public void onDrawOver(Canvas c, RecyclerView parent, State state) {
    	onDrawOver(c, parent);
    }
	public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
		getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
	                parent);
	}

第一个方法的意思是绘制分割线本身;

第二个方法是在item项目绘制完成之后进行的绘制操作(这个会覆盖在item上面);

第三个方法是设置分割线的左间距,上间距,右间距,下间距,保存在outRect中。

如图所示:

其中最底层黄色部分大小是getItemOffsets方法返回的itemDecoration的矩阵设置边距宽度,onDraw方法根据设置的间距宽度来进行绘制黄色区域,其中棕红色部分是onDrawOver方法覆盖绘制在item上层的部分。

利用ItemDecoration来绘制悬浮标题栏

我们给每个需要title的item设置rect.top = titleHeight(标题栏高度);其他的间距可以先不考虑,不重要;

重写onDraw方法,绘制我们的itemDecoration标题栏;

我们需要重写onDrawOver方法,在滑动的时候去判断,

1)如果顶部标题区域是在该标题的items范围之内的滑动的话,那么我们需要覆盖绘制一个处于recyclerView.getpaddingTop位置的title,这样的话这个范围内滑动就有一个悬停的标题栏;

2)如果顶部的标题栏区域恰好下面紧跟着下一个标题栏,那么继续向上滑动的时候,需要下面的标题栏把上面的标题栏顶出界面之外。那么绘制的顶部标题栏的起始位置就是所处的item.bottom - titleHeight的位置。

使用以上三个步骤就可以做出一个流畅并且定制化很高的悬浮标题栏功能了。

代码

class MyDecoration(context: Context): RecyclerView.ItemDecoration() {
    var mPaint:Paint? = null
    var mPaint:Paint? = null
    var mTextPaint:Paint? = null
    var mTitleHeight:Int? = null
    var mTitleHeight:Int? = null
    var mTitleTextSize:Float? = null
    init {
        mTitleHeight =
            TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,f,
                context.getResources().getDisplayMetrics()
            ).toInt()
        mTitleHeight =
            TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,f,
                context.getResources().getDisplayMetrics()
            ).toInt()
        mTitleTextSize =
            TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP,f,
                context.getResources().getDisplayMetrics()
            )
        mTextPaint = Paint()
        mTextPaint?.let {
            it.setTextSize(mTitleTextSize!!)
            it.setAntiAlias(true)
            it.setColor(Color.WHITE)
        }
        mPaint = Paint()
        mPaint?.let {
            it.setAntiAlias(true)
            it.setColor(Color.RED)
        }
        mPaint = Paint()
        mPaint?.let {
            it.setAntiAlias(true)
            it.setColor(Color.BLUE)
        }
    }
    /**
     * recyclerView绘制onDraw -> item.onDraw -> onDrawOver
     */
    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        for (index in until parent.childCount) {
            val childView = parent.getChildAt(index)
            childView?.let {
                val rect = Rect()
                val position = parent.getChildAdapterPosition(it)
                if (isTitleItem(position)) {
                    rect.top = childView.top - mTitleHeight!!
                    rect.bottom = childView.top
                } else {
                    rect.top = childView.top - mTitleHeight!!
                    rect.bottom = childView.top
                }
                rect.left = parent.paddingLeft
                rect.right = parent.width - parent.paddingRight
                if (isTitleItem(position)) {
                    mPaint?.let { it -> c.drawRect(rect, it1) }
                    mTextPaint?.let { it ->
                        c.drawText(
                            getTitleStr(position),f,
                            rect.top.toFloat() + (mTitleHeight?.div(.00f)?:0f),
                            it)}
                } else {
                    mPaint?.let { it1 -> c.drawRect(rect, it1) }
                }
            }
        }
    }
    /**
     * recyclerView绘制onDraw -> item.onDraw -> onDrawOver
     * 绘制覆盖在item上面的分割线效果
     */
    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        val childView = parent.getChildAt()
        var nextView:View? = null;
        if ( < parent.childCount) {
            nextView = parent.getChildAt()
        }
        childView?.let {
            val rect = Rect()
            val position = parent.getChildAdapterPosition(it)
            mTitleHeight?.let { height ->
                if (nextView != null
                    && it.bottom - height < parent.paddingTop
                    && isTitleItem(parent.getChildAdapterPosition(nextView))
                    && !isSameTitle(parent.getChildAdapterPosition(nextView),position)) {
                    rect.top = it.bottom - height
                    rect.bottom = it.bottom
                } else {
                    rect.top = parent.paddingTop
                    rect.bottom = rect.top + height
                }
            }
            rect.left = parent.paddingLeft
            rect.right = parent.width - parent.paddingRight
            mPaint?.let { it -> c.drawRect(rect, it1) }
            mTextPaint?.let { it ->
                c.drawText(
                    getTitleStr(position),f,
                    rect.top + (mTitleHeight?.div(.00f)?:0f),
                    it)}
        }
    }
    /**
     * 用于设置item周围的偏移量的,类似于设置padding喝margin效果,
     * 空出的效果用于绘制分割线
     */
    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        val position:Int = parent.getChildAdapterPosition(view)
        if (position % == 0) {
            outRect.top = mTitleHeight!!
        } else{
            outRect.top = mTitleHeight!!
        }
    }
    fun isTitleItem(position: Int):Boolean {
        return position % == 0
    }
    fun getTitleStr(position: Int):String {
        return "标题:${position /}"
    }
    fun isSameTitle(position: Int,position2: Int):Boolean {
        return (position / 4) == (position2 / 4)
    }
}