package com.xw.common.custom
|
|
import android.content.Context
|
import android.graphics.*
|
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.Drawable
|
import android.util.AttributeSet
|
import android.view.MotionEvent
|
import android.view.View
|
import android.view.animation.DecelerateInterpolator
|
import android.widget.Scroller
|
import androidx.core.content.ContextCompat
|
import com.xw.common.R
|
import kotlin.math.max
|
import kotlin.math.min
|
|
/**
|
* 滑动按钮自定义 View
|
*/
|
class SlideButtonView @JvmOverloads constructor(
|
context: Context,
|
attrs: AttributeSet? = null,
|
defStyleAttr: Int = 0
|
) : View(context, attrs, defStyleAttr) {
|
|
companion object {
|
const val DIRECTION_LEFT = 0
|
const val DIRECTION_RIGHT = 1
|
const val SHAPE_CIRCLE = 0
|
const val SHAPE_SQUARE = 1
|
const val SHAPE_ROUND_RECT = 2
|
}
|
|
// 固定间距 2dp
|
private val fixedMargin = dpToPx(3f)
|
|
private var slideDirection: Int = DIRECTION_RIGHT
|
private var backgroundColor: Int = Color.parseColor("#2196F3")
|
private var backgroundCornerRadius: Float = 0f
|
private var text: String = "滑动"
|
private var textColor: Int = Color.WHITE
|
private var textSize: Float = 48f
|
|
private var thumbDrawableRes: Int = -1
|
private var thumbBitmap: Bitmap? = null
|
private var thumbWidth: Float = 0f
|
private var thumbHeight: Float = 0f
|
private var thumbShape: Int = SHAPE_ROUND_RECT
|
private var thumbCornerRadius: Float = 0f
|
|
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
private val thumbPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
private var thumbX: Float = 0f
|
private var maxThumbX: Float = 0f
|
private var isDragging = false
|
private var slideThreshold: Float = 0.7f
|
//是否能够使用
|
var enable: Boolean = true
|
set(value) {
|
field = value
|
invalidate()
|
}
|
|
private val scroller = Scroller(context, DecelerateInterpolator())
|
private var onSlideCompleteListener: (() -> Unit)? = null
|
|
init {
|
context.theme.obtainStyledAttributes(
|
attrs,
|
R.styleable.SlideButtonView,
|
defStyleAttr,
|
0
|
).apply {
|
try {
|
slideDirection = getInt(R.styleable.SlideButtonView_slideDirection, DIRECTION_RIGHT)
|
backgroundColor = getColor(R.styleable.SlideButtonView_slideBackgroundColor, backgroundColor)
|
backgroundCornerRadius = getDimension(R.styleable.SlideButtonView_backgroundCornerRadius, 0f)
|
text = getString(R.styleable.SlideButtonView_slideText) ?: text
|
textColor = getColor(R.styleable.SlideButtonView_slideTextColor, textColor)
|
textSize = getDimension(R.styleable.SlideButtonView_slideTextSize, textSize)
|
thumbDrawableRes = getResourceId(R.styleable.SlideButtonView_thumbDrawable, -1)
|
thumbShape = getInt(R.styleable.SlideButtonView_thumbShape, SHAPE_ROUND_RECT)
|
thumbCornerRadius = getDimension(R.styleable.SlideButtonView_thumbCornerRadius, 0f)
|
slideThreshold = getFloat(R.styleable.SlideButtonView_slideThreshold, 0.7f)
|
enable = getBoolean(R.styleable.SlideButtonView_enable, enable)
|
} finally {
|
recycle()
|
}
|
}
|
|
initPaints()
|
loadThumbDrawable()
|
}
|
|
private fun initPaints() {
|
backgroundPaint.color = backgroundColor
|
textPaint.apply {
|
color = textColor
|
textSize = this@SlideButtonView.textSize
|
textAlign = Paint.Align.CENTER
|
isFakeBoldText = true // 粗体效果
|
// typeface = Typeface.DEFAULT_BOLD
|
}
|
thumbPaint.apply {
|
isFilterBitmap = true
|
isAntiAlias = true
|
}
|
}
|
|
private fun loadThumbDrawable() {
|
if (thumbDrawableRes != -1) {
|
val drawable = ContextCompat.getDrawable(context, thumbDrawableRes)
|
drawable?.let {
|
thumbBitmap = if (it is BitmapDrawable) {
|
it.bitmap
|
} else {
|
drawableToBitmap(it)
|
}
|
}
|
}
|
}
|
|
private fun drawableToBitmap(drawable: Drawable): Bitmap {
|
val width = drawable.intrinsicWidth.coerceAtLeast(1)
|
val height = drawable.intrinsicHeight.coerceAtLeast(1)
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
val canvas = Canvas(bitmap)
|
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
drawable.draw(canvas)
|
return bitmap
|
}
|
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
if (backgroundCornerRadius == 0f) {
|
backgroundCornerRadius = h / 2f
|
}
|
|
// 滑块高度 = 总高度 - 上下间距(各 5dp)
|
thumbHeight = h - fixedMargin * 2
|
thumbWidth = if (thumbBitmap != null) {
|
val ratio = thumbBitmap!!.width.toFloat() / thumbBitmap!!.height
|
thumbHeight * ratio
|
} else {
|
thumbHeight
|
}
|
|
maxThumbX = w - thumbWidth - fixedMargin * 2
|
resetThumbPosition()
|
}
|
|
private fun resetThumbPosition() {
|
thumbX = when (slideDirection) {
|
DIRECTION_LEFT -> maxThumbX
|
else -> 0f
|
}
|
}
|
|
override fun onDraw(canvas: Canvas) {
|
super.onDraw(canvas)
|
|
val width = width.toFloat()
|
val height = height.toFloat()
|
|
// 绘制背景(全填充,无内边距)
|
val backgroundRect = RectF(0f, 0f, width, height)
|
canvas.drawRoundRect(backgroundRect, backgroundCornerRadius, backgroundCornerRadius, backgroundPaint)
|
|
// 绘制文字
|
val textX = width / 2
|
val textY = height / 2 + textPaint.textSize / 3
|
canvas.drawText(text, textX, textY, textPaint)
|
|
// 绘制滑块(距离边缘 5dp)
|
thumbBitmap?.let { bitmap ->
|
val left = fixedMargin + thumbX
|
val top = fixedMargin
|
val right = left + thumbWidth
|
val bottom = top + thumbHeight
|
|
when (thumbShape) {
|
SHAPE_CIRCLE -> {
|
val path = Path().apply {
|
val centerX = (left + right) / 2
|
val centerY = (top + bottom) / 2
|
val radius = min(thumbWidth, thumbHeight) / 2
|
addCircle(centerX, centerY, radius, Path.Direction.CW)
|
}
|
canvas.save()
|
canvas.clipPath(path)
|
canvas.drawBitmap(bitmap, null, RectF(left, top, right, bottom), thumbPaint)
|
canvas.restore()
|
}
|
SHAPE_ROUND_RECT -> {
|
val radius = if (thumbCornerRadius == 0f) {
|
min(thumbWidth, thumbHeight) / 2
|
} else {
|
thumbCornerRadius
|
}
|
val path = Path().apply {
|
addRoundRect(
|
RectF(left, top, right, bottom),
|
radius, radius,
|
Path.Direction.CW
|
)
|
}
|
canvas.save()
|
canvas.clipPath(path)
|
canvas.drawBitmap(bitmap, null, RectF(left, top, right, bottom), thumbPaint)
|
canvas.restore()
|
}
|
else -> {
|
canvas.drawBitmap(bitmap, null, RectF(left, top, right, bottom), thumbPaint)
|
}
|
}
|
}
|
}
|
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
if (!enable) {
|
return super.onTouchEvent(event)
|
}
|
when (event.action) {
|
MotionEvent.ACTION_DOWN -> {
|
val touchX = event.x
|
val thumbLeft = fixedMargin + thumbX
|
val thumbRight = thumbLeft + thumbWidth
|
if (touchX in thumbLeft..thumbRight) {
|
isDragging = true
|
parent.requestDisallowInterceptTouchEvent(true)
|
return true
|
}
|
}
|
|
MotionEvent.ACTION_MOVE -> {
|
if (isDragging) {
|
val rawX = event.x - thumbWidth / 2 - fixedMargin
|
thumbX = min(max(rawX, 0f), maxThumbX)
|
invalidate()
|
return true
|
}
|
}
|
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
if (isDragging) {
|
isDragging = false
|
parent.requestDisallowInterceptTouchEvent(false)
|
|
val progress = if (slideDirection == DIRECTION_LEFT) {
|
1 - (thumbX / maxThumbX)
|
} else {
|
thumbX / maxThumbX
|
}
|
|
if (progress >= slideThreshold) {
|
onSlideCompleteListener?.invoke()
|
} else {
|
animateReset()
|
}
|
return true
|
}
|
}
|
}
|
return super.onTouchEvent(event)
|
}
|
|
fun animateReset() {
|
val startX = thumbX.toInt()
|
val endX = when (slideDirection) {
|
DIRECTION_LEFT -> maxThumbX.toInt()
|
else -> 0
|
}
|
scroller.startScroll(startX, 0, endX - startX, 0, 300)
|
invalidate()
|
}
|
|
override fun computeScroll() {
|
if (scroller.computeScrollOffset()) {
|
thumbX = scroller.currX.toFloat()
|
invalidate()
|
if (!scroller.isFinished) {
|
postInvalidateOnAnimation()
|
}
|
}
|
}
|
|
private fun dpToPx(dp: Float): Float {
|
return dp * resources.displayMetrics.density
|
}
|
|
// ==================== 公共 API ====================
|
|
fun setOnSlideCompleteListener(listener: () -> Unit) {
|
onSlideCompleteListener = listener
|
}
|
|
fun setSlideDirection(direction: Int) {
|
slideDirection = direction
|
resetThumbPosition()
|
invalidate()
|
}
|
|
fun getSlideDirection() = slideDirection
|
|
fun setSlideBackgroundColor(color: Int) {
|
backgroundColor = color
|
backgroundPaint.color = color
|
invalidate()
|
}
|
|
fun setBackgroundCornerRadius(radius: Float) {
|
backgroundCornerRadius = radius
|
invalidate()
|
}
|
|
fun setSlideText(text: String) {
|
this.text = text
|
invalidate()
|
}
|
|
fun setSlideTextColor(color: Int) {
|
textColor = color
|
textPaint.color = color
|
invalidate()
|
}
|
|
fun setSlideTextSize(size: Float) {
|
textSize = size
|
textPaint.textSize = size
|
invalidate()
|
}
|
|
fun setThumbDrawable(resId: Int) {
|
thumbDrawableRes = resId
|
loadThumbDrawable()
|
requestLayout()
|
invalidate()
|
}
|
|
fun setThumbBitmap(bitmap: Bitmap?) {
|
thumbBitmap = bitmap
|
requestLayout()
|
invalidate()
|
}
|
|
fun setThumbShape(shape: Int) {
|
thumbShape = shape
|
invalidate()
|
}
|
|
fun setThumbCornerRadius(radius: Float) {
|
thumbCornerRadius = radius
|
invalidate()
|
}
|
|
fun setSlideThreshold(threshold: Float) {
|
slideThreshold = threshold.coerceIn(0f, 1f)
|
}
|
}
|