簡體   English   中英

如何以編程方式為按鈕創建選擇器

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

如何以編程方式 (Kotlin) 為具有以下要求的 ImageButton 創建選擇器。

  • 只有背景縮放 25%,而圖像在按鈕處於Pressed狀態時保持相同大小。
  • Pressed狀態下按鈕的背景顏色與正常(默認)狀態下的不同。
  • 能夠以編程方式為按鈕設置背景顏色,以便可以重復使用相同的按鈕。

感謝您提前提供的所有幫助

您將需要以編程方式重新創建以下可繪制對象:

<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>

StateListDrawable使用InsetDrawable將背景顏色設置為小於視圖寬度和高度的值。

在以下布局中

<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>

背景的行為如下:

在此處輸入圖像描述

綠色方塊只是為了顯示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


如果要為背景設置動畫,可以使用ScaleDrawableValueAnimator 這是一些示例代碼:

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()
    }
}

在此處輸入圖像描述



更好的方法(IMO)是將擴展的drawable封裝為自定義drawable。

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()
    }
}

自定義可繪制對象將按如下方式創建:

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)
    }
}

ImageButton的狀態為“按下”時,背景會以InsetDrawable的大小開始,然后擴展到外部正方形的大小。 當“按下”狀態被移除時,drawable 將消失。

在您的 xml 代碼中嘗試

android:foreground="?selectableItemBackground"

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM