从零开始学习自定义view【2】com.youth.banner:banner库下的自定义Indicator指示器

在Android开发中,Indicator(指示器)是一个常见的UI元素,用于指示当前页面或内容的位置。它通常被用于轮播图、导航栏、引导页面等场景。Indicator通过视觉上的变化来表示当前所处的位置,为用户提供了导航和反馈的服务。

日常开发时,我们会经常遇到编写轮播图代码的需求,而且会对轮播图下面的小点点,也就是指示器进行各种各样的调整,位置上的,视觉上的等等,下面我们就来一探究竟🍗🍗。

1.🌹添加依赖

首先,在目标module的build.gradle文件中添加com.youth.banner:banner库的依赖项。

dependencies {
	implementation 'com.youth.banner:banner:2.1.0'
}

添加比较流行的youth banner库,可以比较快速构建我们的轮播图。

2. 😲在布局文件中添加Banner组件

在我们的布局文件中,添加Banner组件。它是一个自定义的ViewPager,负责显示轮播图和管理Indicator

<com.youth.banner.Banner
    android:id="@+id/banner"
    android:layout_width="match_parent"
    android:layout_height="200dp" />

根据我们的需求设置适当的布局宽度和高度。

3.👀准备轮播图数据

在代码中,准备轮播图数据。通常,它是一个包含图片URL或资源ID的列表。

val images = listOf(
    "https://example.com/image1.jpg",
    "https://example.com/image2.jpg",
    "https://example.com/image3.jpg"
)

4. 🙉设置Banner和Indicator

在代码中,找到Banner组件的实例,并设置轮播图数据、Indicator以及其他相关属性。

viewBinding.banner.adapter = object : BannerImageAdapter<String>(data.titleBanner) {
    override fun onBindView(
        holder: BannerImageHolder?,
        data: String?,
        position: Int,
        size: Int
    ) {
        holder?.let {
            Glide.with(holder.itemView)
                .load(data)
                .apply(RequestOptions.bitmapTransform(RoundedCorners(20)))
                .into(holder.imageView)
        }
    }
}

上面这段代码的作用是在轮播图中显示一组图片,通过Glide库加载图片并应用圆角效果,然后将其绑定到每个项的视图上。

适配器的作用是将数据源中的数据绑定到轮播图的每个项上。在这个例子中,适配器的泛型参数为String,表示数据源中的每个项是字符串类型。也就是说banner接收的数据就是我们上面定义的images列表,当然这个我们也可以自定义,可以是任意类型的,只要后面在glide图片加载那里写好逻辑就好了。(毕竟glide图片加载框架可以加载本地的图片,也可以加载网络的图片嘛)

下面开始设置一些参数:这里不配置指示器的话默认使用自带的指示器。

viewBinding.banner.apply {

    // 添加生命周期管理,确保在适当的生命周期内开始和停止轮播
    addBannerLifecycleObserver(owner)
    
    setLoopTime(3000)
    
    // 设置轮播图的点击事件监听器,点了哪张图执行自定义逻辑
    setOnBannerListener { data, _ ->
        when (data) {
            "https://example.com/image1.jpg" -> {
                
            }
            "https://example.com/image2.jpg" -> {

            }
            "https://example.com/image3.jpg" -> {

            }
        }
    }
}

5. 💎自定义Indicator

com.youth.banner:banner库提供了一些默认的Indicator样式,例如圆点指示器。如果我们想要自定义Indicator的样式,可以使用setIndicator()方法来设置。可以设置圆点的大小、间距、颜色等属性,以满足我们的设计需求。
自定义MyCircleIndicator : 其实就是com.youth.banner:banner库下的CircleIndicator的kotlin版本,只不过我们可以在此基础上进行自定义,但前提是我们得先看懂youth作者写的代码

class MyCircleIndicator @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : BaseIndicator(context, attrs, defStyleAttr) {

    private var mNormalRadius = 0
    private var mSelectedRadius = 0
    private var maxRadius = 0

    init {
        mNormalRadius = config.normalWidth / 2
        mSelectedRadius = config.selectedWidth / 2
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val count = config.indicatorSize
        if (count <= 1) {
            return
        }
        mNormalRadius = config.normalWidth / 2
        mSelectedRadius = config.selectedWidth / 2
        //考虑当 选中和默认 的大小不一样的情况
        maxRadius = Math.max(mSelectedRadius, mNormalRadius)
        //间距*(总数-1)+选中宽度+默认宽度*(总数-1)
        val width =
            (count - 1) * config.indicatorSpace + config.selectedWidth + config.normalWidth * (count - 1)
        setMeasuredDimension(width, Math.max(config.normalWidth, config.selectedWidth))
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val count = config.indicatorSize
        if (count <= 1) {
            return
        }
        var left = 0f
        for (i in 0 until count) {
            mPaint.apply {
                color = if (config.currentPosition == i) config.selectedColor else config.normalColor
                alpha = if (config.currentPosition == i) (0.2 * 256).toInt() else (0.5 * 256).toInt()
            }
            val indicatorWidth =
                if (config.currentPosition == i) config.selectedWidth else config.normalWidth
            val radius = if (config.currentPosition == i) mSelectedRadius else mNormalRadius
            canvas.drawCircle(left + radius, maxRadius.toFloat(), radius.toFloat(), mPaint)
            left += (indicatorWidth + config.indicatorSpace).toFloat()
        }
    }

}

在自定义 View 中重写 onMeasure() 方法的主要目的是测量视图的宽度和高度。在 MyCircleIndicator 类中,onMeasure() 方法用于计算并设置指示器视图的尺寸。

具体来说,onMeasure() 方法中的计算逻辑如下:

  1. 首先,获取指示器的数量 (config.indicatorSize)。如果数量小于等于1,表示没有需要绘制的指示器,直接返回,不进行后续计算。

  2. 然后,根据配置对象中的默认宽度和选中宽度计算出默认半径和选中半径 (mNormalRadius 和 mSelectedRadius)。这里假设宽度值是直径,通过除以 2 可以得到半径。

  3. 接下来,计算默认半径和选中半径中的最大值 (maxRadius),以便在绘制时可以确定绘制圆形的中心位置。

  4. 计算整个指示器视图的宽度 (width)。通过间距和宽度参数的组合,以及指示器数量来计算宽度。公式为:间距 * (总数-1) + 选中宽度 + 默认宽度 * (总数-1)。这样可以确保所有指示器都能适应在视图中显示。

  5. 最后,调用 setMeasuredDimension() 方法设置测量的视图宽度和高度。宽度为计算出的 width,高度为默认宽度和选中宽度中的最大值 (Math.max(config.normalWidth, config.selectedWidth))。

通过执行这些计算,onMeasure() 方法确保了指示器视图在布局过程中得到正确的测量尺寸,从而在绘制时能够正确地显示和布局指示器的位置。

onDraw 方法就不展开说了,无非就是用画笔画几个圆点点。canvas.drawCircle

布局好我们要放置的指示器的位置:

<com.youth.banner.Banner
        android:id="@+id/banner"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <com.example.view.MyCircleIndicator
        android:id="@+id/indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="10dp"
        android:layout_marginBottom="9dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="@id/banner" />

这里再来解释说明一下MyCircleIndicator的onMeasure方法:因为我们提前不知道banner具体有多少内容,也就不知道有多少个点,从而也无法判断MyCircleIndicator的具体的宽高。当我们设置宽度为wrap_content的时候就表示说,我们的view(MyCircleIndicator)根据子view(onDraw画的)来判定大小,也就是根据我们在MyCircleIndicator的onDraw方法里画的大小来判断,所以,如果我们不调用onMeasure对MyCircleIndicator进行测量,那么就好造成显示不全,布局错位等千奇百怪的问题。

因此通过重写 onMeasure() 方法,可以自定义指示器视图的测量逻辑,确保其宽度正确适应父视图的布局要求。

tips⚡:override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
在 Android 中,widthMeasureSpec 和 heightMeasureSpec 是由系统传递给 onMeasure() 方法的参数,用于指定视图的宽度和高度的测量规格。

widthMeasureSpec 和 heightMeasureSpec 都是由测量规格和测量模式组成的整数值。

测量规格 (MeasureSpec):测量规格是一个 32 位整数,其中高 2 位表示测量模式,低 30 位表示测量的尺寸大小。

测量模式 (MeasureSpec Mode):测量模式定义了测量尺寸的规则。它有三种可能的值:

MeasureSpec.EXACTLY:精确测量模式,表示视图的尺寸已经明确指定,通常是固定的数值(android:layout_height=“100dp”)或 match_parent。
MeasureSpec.AT_MOST:最大值测量模式,表示视图的尺寸可以根据需要进行调整,但不能超过指定的最大值,通常是 wrap_content。
MeasureSpec.UNSPECIFIED:未指定测量模式,表示视图的尺寸可以任意大小,常用于特殊情况下的测量,不常用。

最后代码里设置一下参数:
这样MyCircleIndicator所继承的BaseIndicator才有config.normalWidth和config.selectedWidth的值,这些我们都可以自定义的。

viewBinding.banner.apply {

    // 添加生命周期管理,确保在适当的生命周期内开始和停止轮播
    addBannerLifecycleObserver(owner)
    
    // 设置指示器,false 代表我们可以将指示器通过布局放在任何位置
    // 这里的Indiacator就是我们自定义的指示器了
    setIndicator(viewBinding.indicator, false)
    
    setIndicatorWidth(10.dpToPx(), 10.dpToPx())
    setIndicatorNormalColor(Color.parseColor("#FFFFFF"))
    setIndicatorSelectedColor(Color.parseColor("#000000"))
    setIndicatorSpace(10.dpToPx())
    setLoopTime(3000)
    
    // 设置轮播图的点击事件监听器,点了哪张图执行自定义逻辑
    setOnBannerListener { data, _ ->
        when (data) {
            "https://example.com/image1.jpg" -> {
                
            }
            "https://example.com/image2.jpg" -> {

            }
            "https://example.com/image3.jpg" -> {

            }
        }
    }
}

都自定义view了,想加的逻辑和样式,码上代码就ok啦。如果想自己写出banner控件,那还得好好沉淀呀💪💪。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiet_h

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值