Android.CustomWidget.系统的Slider不合你胃口?教你实现一个美观的Slider
- 1. 效果
- 2. 如何实现
- 3. 如何使用
- 4. 仓库地址
Welcome来到自定义控件
1. 效果
- 正常的实现
- 还可以组合BubbleText实现一些效果
2. 如何实现
最简单的方法就是依赖
- 依赖博主的库
- Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
- Add the dependency
dependencies {
implementation 'com.github.mozhimen.SwiftKit:uicorek:1.1.3'
}
- 自己实现
Crazy Code
- menu
- ISliderListener
interface ISliderListener {
fun onScrollStart()
fun onScrolling(currentValue: Float)
fun onScrollEnd(currentValue: Float)
}
- LayoutKSliderParser
internal object LayoutKSliderParser {
//const
val DEFAULT_PADDING_VERTICAL = 4f.dp2px()
val SLIDER_WIDTH = 100f.dp2px().toFloat()
val SLIDER_HEIGHT = 10f.dp2px().toFloat()
val SLIDER_HEIGHT_INSIDE = 16f.dp2px().toFloat()
val SLIDER_ROD_LEFT_COLOR = UtilKRes.getColor(R.color.blue_normal)
val SLIDER_ROD_RIGHT_COLOR = UtilKRes.getColor(R.color.gray_light)
val ROD_COLOR = Color.WHITE
val ROD_COLOR_INSIDE = UtilKRes.getColor(R.color.blue_dark)
val ROD_IS_INSIDE = false
val ROD_MIN_VAL = 0f
val ROD_MAX_VAL = 0f
fun parseAttrs(context: Context, attrs: AttributeSet?): LayoutKSliderAttrs {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LayoutKSlider)
val rodIsInside: Boolean =
typedArray.getBoolean(R.styleable.LayoutKSlider_layoutKSlider_rodIsInside, ROD_IS_INSIDE)
val sliderHeight: Float =
typedArray.getDimension(R.styleable.LayoutKSlider_layoutKSlider_sliderHeight, if (!rodIsInside) SLIDER_HEIGHT else SLIDER_HEIGHT_INSIDE)
val sliderRodLeftColor: Int =
typedArray.getColor(R.styleable.LayoutKSlider_layoutKSlider_sliderRodLeftColor, SLIDER_ROD_LEFT_COLOR)
val sliderRodRightColor: Int =
typedArray.getColor(R.styleable.LayoutKSlider_layoutKSlider_sliderRodRightColor, SLIDER_ROD_RIGHT_COLOR)
val rodColor: Int =
typedArray.getColor(R.styleable.LayoutKSlider_layoutKSlider_rodColor, ROD_COLOR)
val rodColorInside: Int =
typedArray.getColor(R.styleable.LayoutKSlider_layoutKSlider_rodColorInside, ROD_COLOR_INSIDE)
val rodRadius: Float =
typedArray.getDimension(R.styleable.LayoutKSlider_layoutKSlider_rodRadius, if (!rodIsInside) sliderHeight / 2f * 2f else sliderHeight / 2f * 0.7f)
val rodRadiusInside: Float =
typedArray.getDimension(R.styleable.LayoutKSlider_layoutKSlider_rodRadiusInside, if (!rodIsInside) rodRadius * 0.7f else rodRadius * 0.5f)
val rodMinVal =
typedArray.getFloat(R.styleable.LayoutKSlider_layoutKSlider_rodMinVal, ROD_MIN_VAL)
val rodMaxVal =
typedArray.getFloat(R.styleable.LayoutKSlider_layoutKSlider_rodMaxVal, ROD_MAX_VAL)
typedArray.recycle()
return LayoutKSliderAttrs(
sliderHeight,
sliderRodLeftColor,
sliderRodRightColor,
rodColor,
rodColorInside,
rodIsInside,
rodRadius,
rodRadiusInside,
rodMinVal,
rodMaxVal
)
}
}
- LayoutKSliderAttrs
data class LayoutKSliderAttrs(
var sliderHeight: Float,
var sliderRodLeftColor: Int,
var sliderRodRightColor: Int,
var rodColor: Int,
var rodColorInside: Int,
var rodIsInside: Boolean,
var rodRadius: Float,
var rodRadiusInside: Float,
var rodMinVal: Float,
var rodMaxVal: Float,
)
- Rod
class Rod {
private val TAG = "Rod>>>>>"
var minX: Float = 0f
var maxX: Float = 0f
val intervalX: Float
get() = maxX - minX
var centerY: Float = 0f
var isInsideSlider: Boolean = false
var radius: Float = 0f
var radiusInside: Float = 0f
set(value) {
field = value.normalize(0f to radius)
}
var currentX: Float = 0f
set(value) {
field = value.normalize(minX to maxX)
_sliderListener?.onScrolling(currentVal)
}
var minVal: Float = 0f
var maxVal: Float = 100f
val intervalVal: Float
get() = maxVal - minVal
var currentVal: Float = 0f
get() = currentX.percent(minX to maxX) * intervalVal
private var _sliderListener: ISliderListener? = null
constructor()
constructor(minX: Float, maxX: Float, centerY: Float, isInsideSlider: Boolean, radius: Float, radiusInside: Float, _sliderListener: ISliderListener?) {
this.minX = minX
this.maxX = maxX
this.currentX = minX
this.centerY = centerY
this.isInsideSlider = isInsideSlider
this.radius = radius
this.radiusInside = radiusInside
_sliderListener?.let { this._sliderListener = it }
}
}
- Slider
class Slider {
var width: Float = 0f
set(value) {
field = if (value <= 0) LayoutKSliderParser.SLIDER_WIDTH else value
refreshX()
}
var height: Float = 0f
set(value) {
field = if (value <= 0) LayoutKSliderParser.SLIDER_HEIGHT else value
refreshY()
}
var leftX: Float = 0f
set(value) {
field = value
refreshX()
}
var topY: Float = 0f
set(value) {
field = value
refreshY()
}
var rodLeftColor = Color.BLACK
var rodRightColor = Color.LTGRAY
var widthHalf: Float = 0f
var centerX: Float = 0f
var rightX: Float = 0f
var heightHalf: Float = 0f
var centerY: Float = 0f
var bottomY: Float = 0f
constructor()
constructor(width: Float, height: Float, leftX: Float, topY: Float) {
this.width = width
this.height = height
this.leftX = leftX
this.topY = topY
}
fun refreshX() {
widthHalf = width / 2f
centerX = leftX + widthHalf
rightX = leftX + width
}
fun refreshY() {
heightHalf = height / 2f
centerY = topY + heightHalf
bottomY = topY + height
}
}
- LayoutKSliderProxy
/**
* @ClassName LayoutKSliderProxy
* @Description
*
* minLabel maxLabel LabelTop
* minVal currentVal maxVal ValTop
*
* rod section
* |----------<======>----||-----| Slider
* ___/\___
* | tip | Bubble
*
* minLabel maxLabel LabelBottom
* minVal currentVal maxVal ValBottom
*
* @Author Kolin Zhao / Mozhimen
* @Date 2022/9/8 14:33
* @Version 1.0
*/
class LayoutKSliderProxy(
private val _context: Context,
) :
BaseViewCallback() {
//region # variate
private lateinit var _layoutKSlider: LayoutKSlider
private lateinit var _attrs: LayoutKSliderAttrs
private var _slider: Slider = Slider()
private var _rod: Rod = Rod()
private var _scrollableParentView: ViewGroup? = null
private var _rodIsScrolling = false
private lateinit var _paintSlider: Paint
private lateinit var _paintRod: Paint
private var _sliderListener: ISliderListener? = null
//endregion
//region # public func
fun init(layoutKSlider: LayoutKSlider) {
_layoutKSlider = layoutKSlider
}
fun getRod(): Rod {
return _rod
}
fun getSlider(): Slider {
return _slider
}
fun setSliderListener(sliderListener: ISliderListener) {
_sliderListener = sliderListener
}
fun getHeightMeasureSpec(): Int {
var height = 0
height += if (_attrs.rodIsInside) _attrs.sliderHeight.toInt() else _attrs.rodRadius.toInt() * 2
height += LayoutKSliderParser.DEFAULT_PADDING_VERTICAL * 2
return height
}
fun attachScrollableParentView(scrollableParentView: ViewGroup?) {
_scrollableParentView = scrollableParentView
}
//endregion
override fun initAttrs(attrs: AttributeSet?, defStyleAttr: Int) {
attrs ?: return
_attrs = LayoutKSliderParser.parseAttrs(_context, attrs)
}
override fun initPaint() {
_paintRod = Paint()
_paintRod.isAntiAlias = true
_paintRod.strokeWidth = 2f
_paintRod.color = _attrs.rodColor
_paintSlider = Paint()
_paintSlider.isAntiAlias = true
_paintSlider.strokeWidth = 2f
_paintSlider.color = _attrs.sliderRodRightColor
}
override fun initView() {
initSlider()
initRod()
_layoutKSlider.postInvalidate()
}
private fun initRod() {
val distance = if (!_attrs.rodIsInside) 0f else _slider.heightHalf - _attrs.rodRadius
_rod = Rod(
_layoutKSlider.paddingStart.toFloat() + _attrs.rodRadius + distance,
_layoutKSlider.width - _layoutKSlider.paddingEnd.toFloat() - _attrs.rodRadius - distance,
_slider.centerY,
_attrs.rodIsInside,
_attrs.rodRadius,
_attrs.rodRadiusInside,
_sliderListener
)
}
private fun initSlider() {
_slider = Slider(
_layoutKSlider.width - _layoutKSlider.paddingStart.toFloat() - _layoutKSlider.paddingEnd - _attrs.sliderHeight,
_attrs.sliderHeight,
_layoutKSlider.paddingStart.toFloat() + _attrs.sliderHeight / 2f,
(if (_attrs.rodIsInside) 0f else _attrs.rodRadius - (_attrs.sliderHeight / 2f)) + LayoutKSliderParser.DEFAULT_PADDING_VERTICAL
)
_slider.rodLeftColor = _attrs.sliderRodLeftColor
_slider.rodRightColor = _attrs.sliderRodRightColor
}
fun onDraw(canvas: Canvas) {
canvas.save()
//slider
drawSlider(canvas)
//rod
drawRod(canvas)
canvas.restore()
}
private fun drawSlider(canvas: Canvas) {
//rightSlider
_paintSlider.color = _slider.rodRightColor
canvas.drawCircle(
_slider.leftX,
_slider.centerY,
_slider.heightHalf,
_paintSlider
)
canvas.drawCircle(
_slider.rightX,
_slider.centerY,
_slider.heightHalf,
_paintSlider
)
canvas.drawRect(
_slider.leftX,
_slider.topY,
_slider.rightX,
_slider.bottomY,
_paintSlider
)
//leftSlider
_paintSlider.color = _slider.rodLeftColor
if (_rod.currentVal > _rod.minVal) {
val distance = if (!_rod.isInsideSlider) 0f else _slider.heightHalf - _rod.radius
canvas.drawCircle(
_slider.leftX,
_slider.centerY,
if (!_rod.isInsideSlider) _slider.heightHalf else _rod.radius,
_paintSlider
)
canvas.drawRect(
_slider.leftX,
_slider.topY + distance,
_rod.currentX,
_slider.bottomY - distance,
_paintSlider
)
}
}
private fun drawRod(canvas: Canvas) {
val color = _attrs.rodColor
canvas.drawCircle(_rod.currentX, _rod.centerY, _rod.radius, _paintRod)//外圆
_paintRod.color = _attrs.rodColorInside
canvas.drawCircle(_rod.currentX, _rod.centerY, _rod.radiusInside, _paintRod)//内圆
_paintRod.color = color
}
fun onTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
_scrollableParentView?.requestDisallowInterceptTouchEvent(false)
_sliderListener?.onScrollEnd(_rod.currentVal)
_rodIsScrolling = false
}
MotionEvent.ACTION_DOWN -> {
_rodIsScrolling = if (!UtilKGesture.isTapInArea(
event,
_slider.leftX,
_slider.rightX,
_slider.topY - LayoutKSliderParser.DEFAULT_PADDING_VERTICAL,
_slider.bottomY + LayoutKSliderParser.DEFAULT_PADDING_VERTICAL
)
) {
return true
} else {
_sliderListener?.onScrollStart()
true
}
_scrollableParentView?.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_MOVE -> {
if (_rodIsScrolling) {
_rod.currentX = event.x
_layoutKSlider.postInvalidate()
//update()
}
}
}
return true
}
}
- LayoutKSlider
class LayoutKSlider @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) :
BaseKLayoutFrame(context, attrs, defStyleAttr, defStyleRes) {
private var _layoutKSliderProxy: LayoutKSliderProxy = LayoutKSliderProxy(context)
init {
setWillNotDraw(false)
_layoutKSliderProxy.init(this)
initAttrs(attrs, defStyleAttr)
initPaint()
minimumHeight = _layoutKSliderProxy.getHeightMeasureSpec()
}
val rod: Rod
get() = _layoutKSliderProxy.getRod()
val slider:Slider
get() = _layoutKSliderProxy.getSlider()
fun setSliderListener(sliderListener: ISliderListener) {
_layoutKSliderProxy.setSliderListener(sliderListener)
}
override fun initAttrs(attrs: AttributeSet?, defStyleAttr: Int) {
_layoutKSliderProxy.initAttrs(attrs, defStyleAttr)
}
private fun initPaint() {
_layoutKSliderProxy.initPaint()
}
override fun initView() {
_layoutKSliderProxy.initView()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(_layoutKSliderProxy.getHeightMeasureSpec(), MeasureSpec.EXACTLY)
super.onMeasure(widthMeasureSpec, newHeightMeasureSpec)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
initView()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
_layoutKSliderProxy.onDraw(canvas)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
return _layoutKSliderProxy.onTouchEvent(event)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
_layoutKSliderProxy.attachScrollableParentView(UtilKView.getParentViewMatch(this, ScrollView::class.java, NestedScrollView::class.java, RecyclerView::class.java) as ViewGroup?)
}
}
- 部分依赖代码可以看Github: SwiftKit.uicorek.layoutk.slider
3. 如何使用
xml样式
<com.mozhimen.uicorek.layoutk.slider.LayoutKSlider
android:id="@+id/layoutk_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp" />
<com.mozhimen.uicorek.layoutk.slider.LayoutKSlider
android:id="@+id/layoutk_slider_inside"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:layoutKSlider_rodIsInside="true" />
- kotlin代码
vb.layoutkSliderInside.setSliderListener(object : ISliderListener {
override fun onScrollStart() {
}
override fun onScrolling(currentValue: Float) {
vb.layoutkSliderTxt.text = currentValue.toString()
}
override fun onScrollEnd(currentValue: Float) {
val sliderInside = vb.layoutkSliderInside
val centerX = sliderInside.left + (sliderInside.width) / 2f - sliderInside.slider.leftX
val currentX = sliderInside.left +
currentValue.percent(sliderInside.rod.minVal to sliderInside.rod.maxVal) * (sliderInside.width - 2 * sliderInside.slider.leftX)
genPopwinKBubbleText(
sliderInside,
currentValue.toInt().toString(),
xOffset = (currentX - centerX).toInt(),
yOffset = -(8f).dp2px()
)
}
})
- 完整demo地址可见:uicorek.layoutk.LayoutKActivity.kt
4. 仓库地址
github仓库地址: https://github.com/mozhimen/SwiftKit