Android自定义组件-在水平方向上根据传入的百分比进行位置变化的滚动条

介绍:


该例子初始化了一个商品分类列表(imageUrl,title),并设置了 RecyclerView 的布局和适配器。同时,它通过监听 RecyclerView 的滚动事件来同步更新自定义滚动条的位置,以反映用户的滚动进度。

这个自定义组件的功能是创建一个可以在水平方向上根据传入的百分比进行位置变化的滚动条,并且具有圆角外观。滚动条的位置是根据视图宽度的百分比来确定的,并且当视图尺寸改变时,滚动条的位置会相应地更新。

1.自定义组件ScrollBar滚动条

package edu.stu.mall.compent

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.Point
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat
import edu.stu.mall.R

class ScrollBar @JvmOverloads constructor(
    //通过 @JvmOverloads 注解提供了多个重载的构造函数,以支持不同的参数组合。
    context: Context,
    attr: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attr, defStyleAttr) {

    // 定义滚动条宽度和高度
    private val RECT_WIDTH = 30f
    private val RECT_HEIGHT = 10f
    // 定义滚动条圆角半径
    private val CORNER_RADIUS = 10f
    // 定义滚动条与边界的间距
    private val MARGIN = 5f

    // 当前滚动条的位置
    private lateinit var currentPoint: Point

    // 画笔,用于绘制滚动条
    private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = ContextCompat.getColor(context, R.color.colorPrimary)
    }

    // 标记是否已经初始化
    private var isInit = false

    // 获取滚动条是否已经初始化
    fun getInitialState() = isInit

    // 重写 onDraw 方法,绘制滚动条
    @SuppressLint("Recycle", "DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // 获取视图中心的 y 坐标
        val centerY = height / 2

        // 初始化 currentPoint 为视图左边中心位置
        if (!::currentPoint.isInitialized) {
            currentPoint = Point((MARGIN + RECT_WIDTH / 2).toInt(), centerY)
        }

        // 绘制圆角矩形作为滚动条
        val left = currentPoint.x - RECT_WIDTH / 2
        val top = currentPoint.y - RECT_HEIGHT / 2
        val right = currentPoint.x + RECT_WIDTH / 2
        val bottom = currentPoint.y + RECT_HEIGHT / 2
        val rect = RectF(left, top, right, bottom)
        canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, mPaint)

        // 标记为已初始化
        isInit = true
    }

    // 动画到指定的百分比位置
    fun animateTo(percent: Float) {
        // 根据百分比计算目标位置的 x 坐标
        var targetX = (percent * width).toInt()
        val halfRectWidth = (RECT_WIDTH / 2).toInt()

        // 确保目标位置不会超出视图边界
        targetX = when {
            targetX < halfRectWidth + MARGIN.toInt() -> (halfRectWidth + MARGIN.toInt())
            targetX > width - halfRectWidth - MARGIN.toInt() -> (width - halfRectWidth - MARGIN.toInt())
            else -> targetX
        }

        // 更新当前滚动条的位置
        currentPoint = Point(targetX, currentPoint.y)
        // 重新绘制视图
        invalidate()
    }
}

2.在布局文件中添加ScrollBar组件

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rlv_classification"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <edu.stu.mall.compent.ScrollBar
        android:id="@+id/scroll_bar1"
        android:layout_width="@dimen/dp_30"
        android:background="@drawable/box_grey_bg"
        android:layout_height="@dimen/dp_8"
        android:layout_marginBottom="@dimen/dp_5"
        android:layout_gravity="center" />
</LinearLayout>

3.在Fragment中引用并使用ScrollBar


package edu.stu.mall.fragment.home


import android.content.Intent
import android.os.Bundle
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.youth.banner.adapter.BannerImageAdapter
import com.youth.banner.holder.BannerImageHolder
import com.youth.banner.indicator.CircleIndicator
import edu.mall.base.BaseFragment
import edu.mall.base.SingleLiveEvent
import edu.mall.base.view.NoScrollLayoutManager
import edu.stu.mall.BR
import edu.stu.mall.R
import edu.stu.mall.activity.recommended.ProductDetailActivity
import edu.stu.mall.adapter.HomeItemClassificationAdapter
import edu.stu.mall.adapter.RecommendedProductAdapter
import edu.stu.mall.databinding.FragmentHomeBinding
import edu.stu.mall.repository.data.ItemModel
import edu.stu.mall.repository.data.RecommendedProductDataItem

class FragHome : BaseFragment<FragmentHomeBinding, FragHomeViewModel>() {

    
    private val homeItemClassificationAdapter = HomeItemClassificationAdapter()

    override fun getLayoutId(): Int {
        return R.layout.fragment_home
    }

    override fun getViewModelId(): Int {
        return BR.homeVm
    }

    override fun initViewData() {
        initListView()
        initObserverData()
    }

    private fun initObserverData() {
      

   
        initClass()
    }

   private fun initClass() {
    // 创建商品分类列表
    val items = listOf(
        ItemModel(R.drawable.ic_class__1_, "眼睛"),
        ItemModel(R.drawable.ic_class__2_, "包包"),
        ItemModel(R.drawable.ic_class__3_, "耳机"),
        ItemModel(R.drawable.ic_class__4_, "鞋子"),
        ItemModel(R.drawable.ic_class__5_, "化妆品"),
        ItemModel(R.drawable.ic_class__6_, "母婴"),
        ItemModel(R.drawable.ic_class__7_, "裙子"),
        ItemModel(R.drawable.ic_class__8_, "手机"),
        ItemModel(R.drawable.ic_class__9_, "书籍"),
        ItemModel(R.drawable.ic_class__10_, "游戏"),
        ItemModel(R.drawable.ic_class__11_, "游戏"),
        ItemModel(R.drawable.ic_class__12_, "游戏"),
        ItemModel(R.drawable.ic_class__13_, "游戏"),
        ItemModel(R.drawable.ic_class__14_, "泳圈"),
        ItemModel(R.drawable.ic_class__15_, "冰淇淋"),
        ItemModel(R.drawable.ic_class__16_, "甜甜圈"),
        ItemModel(R.drawable.ic_class__17_, "香水"),
        ItemModel(R.drawable.ic_class__18_, "网球"),
        ItemModel(R.drawable.ic_class__19_, "桌椅"),
        ItemModel(R.drawable.ic_class__5_, "化妆品"),
        ItemModel(R.drawable.ic_class__20_, "汽车"),
        ItemModel(R.drawable.ic_class__21_, "相机")
    )

    // 创建一个网格布局管理器,每行2个item,水平滚动
    val layoutManager = GridLayoutManager(context, 2, GridLayoutManager.HORIZONTAL, false)
    binding?.rlvClassification?.layoutManager = layoutManager
    binding?.rlvClassification?.adapter = homeItemClassificationAdapter

    // 设置 ViewModel 的初始数据
    viewModel?.setInitialData(items)
    viewModel?.classList?.observe(viewLifecycleOwner) { list ->
        // 更新适配器数据
        homeItemClassificationAdapter.setData(list)
    }

    // 添加滚动监听器
    binding?.rlvClassification?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)

            // 计算并更新滚动条的位置
            val totalScrollRange = recyclerView.computeHorizontalScrollRange()
            val scrollOffset = recyclerView.computeHorizontalScrollOffset()
            val scrollExtent = recyclerView.computeHorizontalScrollExtent()
            val scrollPercent = if (totalScrollRange - scrollExtent > 0) {
                scrollOffset.toFloat() / (totalScrollRange - scrollExtent).toFloat()
            } else {
                0f
            }
            if (binding!!.scrollBar1.getInitialState()) {
                binding!!.scrollBar1.animateTo(scrollPercent)
            }
        }
    })
}

}

4.FragHomeViewModel

package edu.stu.mall.fragment.home

import android.app.Application
import android.util.Log
import androidx.lifecycle.viewModelScope
import edu.mall.base.BaseViewModel
import edu.mall.base.SingleLiveEvent
import edu.stu.mall.repository.Repository
import edu.stu.mall.repository.data.BannerListData
import edu.stu.mall.repository.data.ItemModel
import edu.stu.mall.repository.data.RecommendedProductData
import edu.stu.mall.repository.data.RecommendedProductDataItem
import kotlinx.coroutines.launch

class FragHomeViewModel(application: Application) : BaseViewModel(application) {

    var classList = SingleLiveEvent<List<ItemModel>?>()

    fun setInitialData(items: List<ItemModel>) {
        // 直接设置初始数据
        classList.value = items
    }

}

5.ItemModel

data class ItemModel(val imageUrl: Int, val title: String) // 假设 imageUrl 是资源 ID

6.HomeItemClassificationAdapter

package edu.stu.mall.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import edu.stu.mall.databinding.ItemClassificationLayoutBinding
import edu.stu.mall.repository.data.ItemModel
import edu.stu.mall.service.loadImage

class HomeItemClassificationAdapter :
    RecyclerView.Adapter<HomeItemClassificationAdapter.HomeItemClassificationViewHolder>() {

    private var dataList: List<ItemModel> = mutableListOf()
    fun setData(list: List<ItemModel>?) {
        if (list != null && list.isNotEmpty()) {
            dataList = list
            notifyDataSetChanged()
        }
    }


    class HomeItemClassificationViewHolder(binding: ItemClassificationLayoutBinding) :
        RecyclerView.ViewHolder(binding.root) {
        var itemBinding: ItemClassificationLayoutBinding

        init {
            itemBinding = binding
        }
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): HomeItemClassificationViewHolder {

        return HomeItemClassificationViewHolder(
            ItemClassificationLayoutBinding.inflate(
                LayoutInflater.from(parent.context),
                parent, false
            )
        )
    }

    override fun getItemCount(): Int {
        return dataList.size

    }

    override fun onBindViewHolder(holder: HomeItemClassificationViewHolder, position: Int) {
//        Glide.with(context).load(dataList[position].imageUrl).into(holder.itemBinding.imageView)

        loadImage(dataList[position].imageUrl, holder.itemBinding.imageView)
        holder.itemBinding.item = dataList[position]


    }
}

7.item_classification_layout

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="item"
            type="edu.stu.mall.repository.data.ItemModel" />
    </data>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="@dimen/dp_12">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="47dp"
            android:layout_height="@dimen/dp_45"
            android:layout_gravity="center" />

        <TextView
            android:id="@+id/textView"
            style="@style/TextView_Description"
            android:layout_width="@dimen/dp_45"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@{item.title}" />

    </LinearLayout>
</layout>

  • 10
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值