简体   繁体   English

如何以编程方式为按钮创建选择器

[英]How to create a selector for a Button programmatically

How to create a selector programmatically (Kotlin) for an ImageButton with the following requirements.如何以编程方式 (Kotlin) 为具有以下要求的 ImageButton 创建选择器。

  • Only the background scales by 25% while the image remains the same size while the button is at the Pressed state.只有背景缩放 25%,而图像在按钮处于Pressed状态时保持相同大小。
  • The background colour of the button at the Pressed state is different from that of the normal (default) state. Pressed状态下按钮的背景颜色与正常(默认)状态下的不同。
  • able to set the background colour programmatically for the button so that the same button can be reused.能够以编程方式为按钮设置背景颜色,以便可以重复使用相同的按钮。

Thank you for all the help in advance感谢您提前提供的所有帮助

You will need to recreate the following drawable programmatically:您将需要以编程方式重新创建以下可绘制对象:

<selector>
    <item android:drawable="@android:color/holo_blue_light" android:state_pressed="true" />
    <item>
        <inset android:drawable="@android:color/holo_red_light" android:inset="12.5%" />
    </item>
</selector>

This StateListDrawable uses an InsetDrawable to set the background color to something smaller than the view width and height.StateListDrawable使用InsetDrawable将背景颜色设置为小于视图宽度和高度的值。

In the following layout在以下布局中

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/darker_gray">

    <View
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@android:color/holo_green_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageButton
        android:id="@+id/imageButton"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@drawable/selector_drawable"
        android:src="@drawable/ic_launcher_foreground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

the background behaves as follows:背景的行为如下:

在此处输入图像描述

The green square is there just to show the true extent of the ImageButton .绿色方块只是为了显示ImageButton的真实范围。

Here is the code to create the StateListDrawable programmatically and to assign it to the ImageButton :这是以编程方式创建 StateListDrawable 并将其分配给ImageButton的代码:

val blue = ContextCompat.getColor(requireContext(), android.R.color.holo_blue_light)  
val red = ContextCompat.getColor(requireContext(), android.R.color.holo_red_light)  
  
// Create the inset drawable that is inset 12.5% on each side. This will be the default state.  
val colorDrawable = ColorDrawable(red)  
val insetDrawable = InsetDrawable(colorDrawable, 0.12f)  
  
// Create the drawable that will be the pressed state background.  
val pressedStateDrawable = ColorDrawable(blue)  
val bg = StateListDrawable()  
bg.addState(intArrayOf(android.R.attr.state_pressed), pressedStateDrawable)  
bg.addState(intArrayOf(), insetDrawable)  
binding.imageButton.background = bg


If you want to animate the background, you can use a ScaleDrawable with a ValueAnimator .如果要为背景设置动画,可以使用ScaleDrawableValueAnimator Here is some sample code:这是一些示例代码:

val baseLevel = 7500
val topLevel = 10000
val animationDuration = 250L
val redColor = ContextCompat.getColor(requireContext(), android.R.color.holo_red_light)
val colorDrawable = ColorDrawable(redColor)
val blueColor = ContextCompat.getColor(requireContext(), android.R.color.holo_blue_light)
val scaleDrawable = ScaleDrawable(colorDrawable, Gravity.CENTER, 1f, 1f)
scaleDrawable.level = baseLevel
binding.imageButton.background = scaleDrawable
binding.imageButton.setOnClickListener {
    ValueAnimator.ofInt(baseLevel, topLevel).apply {
        duration = animationDuration
        doOnStart {
            (scaleDrawable.drawable as ColorDrawable).color = blueColor
            scaleDrawable.level = baseLevel
        }
        addUpdateListener {
            scaleDrawable.level = it.animatedValue as Int
            binding.imageButton.invalidate()
        }
        doOnEnd {
            (scaleDrawable.drawable as ColorDrawable).color = redColor
            scaleDrawable.level = baseLevel
        }
        start()
    }
}

在此处输入图像描述



A still better way (IMO) is to encapsulate the expanding drawable as a custom drawable.更好的方法(IMO)是将扩展的drawable封装为自定义drawable。

ExpandingBackgroundDrawable.kt ExpandingBackgroundDrawable.kt

class ExpandingBackgroundDrawable(
    matchStates: IntArray,
    activeColor: Int,
    insetPercentage: Float,
    animationDuration: Long
) : Drawable(), Animatable, TimeAnimator.TimeListener {

    private val mMatchStates = matchStates
    private val mInsetPercentage = insetPercentage
    private val mAnimDuration = animationDuration

    private var mRunning = false
    private var mStartTime = 0L
    private val mTimeAnimator: TimeAnimator = TimeAnimator().also {
        it.setTimeListener(this)
    }

    private var mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = activeColor
        style = Paint.Style.FILL
    }

    private val mRectMin = RectF()
    private val mRectCurrent = RectF()

    // Notify this Drawable when the View's state changes.
    override fun isStateful() = true

    override fun onStateChange(states: IntArray): Boolean {
        return if (StateSet.stateSetMatches(mMatchStates, states)) {
            start()
            true
        } else {
            false
        }
    }

    override fun onBoundsChange(bounds: Rect) {
        super.onBoundsChange(bounds)
        mRectMin.set(
            0f, 0f, bounds.right - mInsetPercentage * bounds.right * 2,
            bounds.bottom - mInsetPercentage * bounds.bottom * 2
        )
    }

    override fun draw(canvas: Canvas) {
        canvas.withTranslation(
            (bounds.width() - mRectCurrent.right) / 2,
            (bounds.height() - mRectCurrent.bottom) / 2
        ) {
            canvas.drawRect(mRectCurrent, mPaint)
        }
    }

    override fun setAlpha(alpha: Int) {
        mPaint.alpha = alpha
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        mPaint.colorFilter = colorFilter
    }

    override fun getOpacity() = PixelFormat.TRANSLUCENT

    override fun start() {
        if (isRunning) return
        mRunning = true
        mStartTime = SystemClock.uptimeMillis()
        invalidateSelf()
        mTimeAnimator.duration = mAnimDuration
        mTimeAnimator.start()
    }

    override fun stop() {
        if (!isRunning) return
        mTimeAnimator.cancel()
        mRunning = false
        invalidateSelf()
    }

    override fun isRunning() = mRunning

    override fun onTimeUpdate(animation: TimeAnimator, totalTime: Long, deltaTime: Long) {
        val progress = totalTime.toFloat() / animation.duration
        mRectCurrent.right = mRectMin.right + (bounds.right - mRectMin.right) * progress
        mRectCurrent.bottom = mRectMin.bottom + (bounds.bottom - mRectMin.bottom) * progress
        if (progress >= 1F) {
            stop()
        } else {
            invalidateSelf()
        }
    }

    fun setColor(color: Int) {
        mPaint.color = color
        invalidateSelf()
    }
}

The custom drawable would be created as follows:自定义可绘制对象将按如下方式创建:

imageButton.background = getBackground(context)

private fun getBackground(view: View): Drawable {
    val context = view.context

    // Create the inset drawable that is inset 12.5% on each side.
    // This will be the default state.
    val red = ContextCompat.getColor(context, android.R.color.holo_red_light)
    val colorDrawable = ColorDrawable(red)
    val insetDrawable = InsetDrawable(colorDrawable, 0.125f)

    // Get the expanding background drawable that is the pressed state drawable.
    val expandingStates = intArrayOf(
        android.R.attr.state_pressed,
        android.R.attr.state_accelerated
    )
    val mExpandingBackground = ExpandingBackgroundDrawable(
        expandingStates,
        ContextCompat.getColor(context, R.color.accent),
        0.125f,
        context.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
    )

    return StateListDrawable().apply {
        addState(expandingStates, mExpandingBackground)
        addState(StateSet.WILD_CARD, insetDrawable)
    }
}

While the state of the ImageButton is "pressed", the background will start as the size of the InsetDrawable and expand to the size of the outer square.ImageButton的状态为“按下”时,背景会以InsetDrawable的大小开始,然后扩展到外部正方形的大小。 When the "pressed" state is removed, the drawable will disappear.当“按下”状态被移除时,drawable 将消失。

在您的 xml 代码中尝试

android:foreground="?selectableItemBackground"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM