Kotlin自定义View系列教程之标尺控件(选择身高、体重等)的实现

作者:Adan0520 时间:2022-06-26 00:35:40 

前言

本篇文章讲的是Kotlin 自定义view之实现标尺控件Ruler,以选择身高、体重等。开发中,当我们需要获取用户的身高和体重等信息时,如果直接让他们输入,显然体验不够好。像类似于唯品会、好轻等APP都是使用了类似于刻度尺的控件让用户滑动选择身高体重,觉得很棒。网上已有人使用Java语言实现这样的功能,但不影响我对其的学习。和往常一样,主要还是想总结一下自定义view之实现标尺控件的开发过程以及一些需要注意的地方。

按照惯例,我们先来看看效果图

Kotlin自定义View系列教程之标尺控件(选择身高、体重等)的实现

一、先总结下自定义View的步骤:

1、自定义View的属性

2、在View的构造方法中获得我们自定义的属性

3、重写onMesure

4、重写onDraw

其中onMesure方法不一定要重写,但大部分情况下还是需要重写的

二、View 的几个构造函数

1、constructor(mContext: Context)

—>java代码直接new一个RulerView实例的时候,会调用这个只有一个参数的构造函数;

2、constructor(mContext: Context, attrs: AttributeSet)

—>在默认的XML布局文件中创建的时候调用这个有两个参数的构造函数。AttributeSet类型的参数负责把XML布局文件中所自定义的属性通过AttributeSet带入到View内;

3、constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int)

—>构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或者Activity所用的Theme中的默认Style,且只有在明确调用的时候才会调用

4、constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int,defStyleRes:Int)

—>该构造函数是在API21的时候才添加上的

三、下面我们就开始来看看代码啦

1、自定义View的属性,首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的需要用到的属性以及声明相对应属性的取值类型


<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
 android:id="@+id/tv_weight_tip"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="体重"
 android:textColor="@android:color/black"
 android:textSize="14dp"
 app:layout_constraintBottom_toBottomOf="parent"
 app:layout_constraintLeft_toLeftOf="parent"
 app:layout_constraintRight_toRightOf="parent"
 app:layout_constraintTop_toTopOf="parent"
 app:layout_constraintVertical_bias="0.132" />
<RelativeLayout
 android:id="@+id/rl_weight_ruler"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 app:layout_constraintTop_toBottomOf="@+id/tv_weight_tip"
 app:layout_constraintLeft_toLeftOf="parent"
 app:layout_constraintRight_toRightOf="parent">

<per.lijuan.rulerdome.RulerView
  android:id="@+id/ruler_weight"
  android:layout_width="match_parent"
  android:layout_height="58dp"
  android:layout_marginTop="24dp"
  app:alphaEnable="true"
  app:lineColor="@android:color/darker_gray"
  app:lineMaxHeight="40dp"
  app:lineMidHeight="30dp"
  app:lineMinHeight="20dp"
  app:lineSpaceWidth="10dp"
  app:lineWidth="2.5dp"
  app:textColor="@android:color/black"
  app:minValue="20"
  app:maxValue="200"
  app:perValue="0.1"
  app:selectorValue="55"/>

<ImageView
  android:layout_width="14dp"
  android:layout_height="46dp"
  android:layout_centerHorizontal="true"
  android:layout_marginTop="6dp"
  android:scaleType="fitXY"
  android:src="@mipmap/ic_arrow"/>
</RelativeLayout>

<TextView
 android:id="@+id/tv_weight"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_marginTop="11dp"
 android:maxHeight="30sp"
 android:textColor="@color/colorPrimary"
 android:textSize="24sp"
 app:layout_constraintTop_toBottomOf="@+id/rl_weight_ruler"
 app:layout_constraintEnd_toEndOf="parent"
 app:layout_constraintStart_toStartOf="parent"/>
</android.support.constraint.ConstraintLayout>

一定要引入xmlns:app=”http://schemas.android.com/apk/res-auto” ,Android Studio中我们可以使用res-atuo命名空间,就不用在添加自定义View全类名。

3、在View的构造方法中,获得我们的自定义的样式


private var mMinVelocity:Int = 0
private var mScroller: Scroller? = null//Scroller是一个专门用于处理滚动效果的工具类 用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动
private var mVelocityTracker: VelocityTracker?=null//主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率。
private var mWidth:Int = 0
private var mHeight:Int = 0

private var mSelectorValue=50f  // 未选择时 默认的值 滑动后表示当前中间指针正在指着的值
private var mMaxValue=200f   // 最大数值
private var mMinValue=100f   //最小的数值
private var mPerValue=1f   //最小单位(如 1:表示每2条刻度差为1;0.1:表示每2条刻度差为0.1

private var mLineSpaceWidth = 5f // 尺子刻度2条线之间的距离
private var mLineWidth = 4f   // 尺子刻度的宽度
private var mLineMaxHeight = 420f // 尺子刻度分为3中不同的高度。 mLineMaxHeight表示最长的那根(也就是 10的倍数时的高度)
private var mLineMidHeight = 30f // mLineMidHeight 表示中间的高度(也就是 5 15 25 等时的高度)
private var mLineMinHeight = 17f // mLineMinHeight 表示最短的那个高度(也就是 1 2 3 4 等时的高度)

private var mTextMarginTop = 10f
private var mTextSize = 30f   //尺子刻度下方数字的大小
private var mAlphaEnable=false  // 尺子 最左边 最后边是否需要透明 `(透明效果更好点)
private var mTextHeight = 0.toFloat()//尺子刻度下方数字的高度
private var mTextPaint: Paint?=null // 尺子刻度下方数字(也就是每隔10个出现的数值)画笔
private var mLinePaint: Paint?=null // 尺子刻度线的画笔

private var mTotalLine:Int = 0  //共有多少条 刻度
private var mMaxOffset:Int = 0  //所有刻度 共有多长
private var mOffset:Float = 0.toFloat()// 默认状态下,mSelectorValue所在的位置 位于尺子总刻度的位置
private var mLastX:Int = 0
private var mMove: Int = 0
private lateinit var mListener: OnValueChangeListener// 滑动后数值回调

private var mLineColor:Int= Color.GRAY //刻度的颜色
private var mTextColor:Int= Color.BLACK//文字的颜色

constructor(mContext: Context) : super(mContext,null)

constructor(mContext: Context, attrs: AttributeSet) : super(mContext, attrs,0)

constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int) : super(mContext, attrs,defStyleAttr) {
 init(mContext, attrs)
}

fun init(context: Context, attrs: AttributeSet){
 Log.d(TAG, "init")
 mScroller= Scroller(context)

this.mLineSpaceWidth=myfloat(25.0f)
 this.mLineWidth=myfloat(2.0f)
 this.mLineMaxHeight=myfloat(100.0f)
 this.mLineMidHeight=myfloat(60.0f)
 this.mLineMinHeight=myfloat(40.0f)
 this.mTextHeight=myfloat(40.0f)

val typedArray: TypedArray =context.obtainStyledAttributes(attrs,
   R.styleable.RulerView)

mAlphaEnable= typedArray.getBoolean(R.styleable.RulerView_alphaEnable, mAlphaEnable)

mLineSpaceWidth = typedArray.getDimension(R.styleable.RulerView_lineSpaceWidth, mLineSpaceWidth)
 mLineWidth = typedArray.getDimension(R.styleable.RulerView_lineWidth, mLineWidth)
 mLineMaxHeight = typedArray.getDimension(R.styleable.RulerView_lineMaxHeight, mLineMaxHeight)
 mLineMidHeight = typedArray.getDimension(R.styleable.RulerView_lineMidHeight, mLineMidHeight)
 mLineMinHeight = typedArray.getDimension(R.styleable.RulerView_lineMinHeight, mLineMinHeight)
 mLineColor = typedArray.getColor(R.styleable.RulerView_lineColor, mLineColor)

mTextSize = typedArray.getDimension(R.styleable.RulerView_textSize, mTextSize)
 mTextColor = typedArray.getColor(R.styleable.RulerView_textColor, mTextColor)
 mTextMarginTop = typedArray.getDimension(R.styleable.RulerView_textMarginTop, mTextMarginTop)

mSelectorValue = typedArray.getFloat(R.styleable.RulerView_selectorValue, 0.0f)
 mMinValue = typedArray.getFloat(R.styleable.RulerView_minValue, 0.0f)
 mMaxValue = typedArray.getFloat(R.styleable.RulerView_maxValue, 100.0f)
 mPerValue = typedArray.getFloat(R.styleable.RulerView_perValue, 0.1f)

mMinVelocity= ViewConfiguration.get(getContext()).scaledMinimumFlingVelocity

mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
 mTextPaint!!.textSize = mTextSize
 mTextPaint!!.color = mTextColor
 mTextHeight = getFontHeight(mTextPaint!!)

mLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
 mLinePaint!!.strokeWidth = mLineWidth
 mLinePaint!!.color = mLineColor
}

我们重写了3个构造方法,在上面的构造方法中说过默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造方法调用三个参数的构造方法,然后在三个参数的构造方法中获得自定义属性。
一开始一个参数的构造方法和两个参数的构造方法是这样的:


constructor(mContext: Context) : super (mContext)

constructor(mContext: Context, attrs: AttributeSet?) : super(mContext, attrs)

有一点要注意的是super应该改成this,然后让一个参数的构造方法引用两个参数的构造方法,两个参数的构造方法引用三个参数的构造方法,代码如下:


constructor(mContext: Context) : this(mContext,null)

constructor(mContext: Context, attrs: AttributeSet?) : this(mContext, attrs!!,0)

constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int) : super(mContext, attrs,defStyleAttr) {
 init(mContext, attrs)
}

4、重写onDraw方法


override fun onDraw(canvas: Canvas) {
 super.onDraw(canvas)
 var left: Float
 var height: Float
 var value: String
 var alpha = 0
 var scale: Float
 val srcPointX = mWidth / 2
 for (i in 0 until mTotalLine) {
  left = srcPointX.toFloat() + mOffset + i * mLineSpaceWidth

if (left < 0 || left > mWidth) {
   continue //先画默认值在正中间,左右各一半的view。多余部分暂时不画(也就是从默认值在中间,画旁边左右的刻度线)
  }

if (i % 10 == 0) {
   height = mLineMaxHeight
  } else if (i % 5 == 0) {
   height = mLineMidHeight
  } else {
   height = mLineMinHeight
  }
  if (mAlphaEnable) {
   scale = 1 - Math.abs(left - srcPointX) / srcPointX
   alpha = (255f * scale * scale).toInt()

mLinePaint!!.setAlpha(alpha)
  }
  canvas.drawLine(left, 0f, left, height, mLinePaint)

if (i % 10 == 0) {
   value = (mMinValue + i * mPerValue / 10).toInt().toString()
   if (mAlphaEnable) {
    mTextPaint!!.alpha = alpha
   }
   canvas.drawText(value, left - mTextPaint!!.measureText(value) / 2,
     height + mTextMarginTop + mTextHeight, mTextPaint) // 在为整数时,画 数值
  }
 }
}

View的绘制流程是从ViewRoot的performTravarsals方法开始的,经过measure、layout和draw三个过程才能最终将一个View绘制出来,其中:

测量——onMeasure():用来测量View的宽和高来决定View的大小
布局——onLayout():用来确定View在父容器ViewGroup中的放置位置
绘制——onDraw():负责将View绘制在屏幕上

5、重写onTouchEvent方法

onTouchEvent()是View自带的接口,Android系统提供了默认的实现,用于处理触摸事件。当我们对标尺控件向左向右滑动时,此方法就会被调用。


override fun onTouchEvent(event: MotionEvent): Boolean {
   Log.d(TAG, "onTouchEvent")

val action = event.action
   val xPosition = event.x.toInt()

if (mVelocityTracker == null) {
     mVelocityTracker = VelocityTracker.obtain()
   }
   mVelocityTracker!!.addMovement(event)

when (action) {
     MotionEvent.ACTION_DOWN -> {
       mScroller!!.forceFinished(true)
       mLastX = xPosition
       mMove = 0
     }
     MotionEvent.ACTION_MOVE -> {
       mMove = mLastX - xPosition
       changeMoveAndValue()
     }
     MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
       countMoveEnd()
       countVelocityTracker()
       return false
     }
     else -> {
     }
   }

mLastX = xPosition
   return true
 }

现在我把完整的代码贴出来


package per.lijuan.rulerdome

import android.content.Context
import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import android.widget.Scroller

/**
* Created by juan on 2018/5/11.
*/
class RulerView: View {
 private val TAG : String = "RulerView"

private var mMinVelocity:Int = 0
 private var mScroller: Scroller? = null//Scroller是一个专门用于处理滚动效果的工具类  用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动
 private var mVelocityTracker: VelocityTracker?=null//主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率。
 private var mWidth:Int = 0
 private var mHeight:Int = 0

private var mSelectorValue=50f   // 未选择时 默认的值 滑动后表示当前中间指针正在指着的值
 private var mMaxValue=200f     // 最大数值
 private var mMinValue=100f     //最小的数值
 private var mPerValue=1f      //最小单位(如 1:表示每2条刻度差为1;0.1:表示每2条刻度差为0.1

private var mLineSpaceWidth = 5f  // 尺子刻度2条线之间的距离
 private var mLineWidth = 4f     // 尺子刻度的宽度
 private var mLineMaxHeight = 420f  // 尺子刻度分为3中不同的高度。 mLineMaxHeight表示最长的那根(也就是 10的倍数时的高度)
 private var mLineMidHeight = 30f  // mLineMidHeight 表示中间的高度(也就是 5 15 25 等时的高度)
 private var mLineMinHeight = 17f  // mLineMinHeight 表示最短的那个高度(也就是 1 2 3 4 等时的高度)

private var mTextMarginTop = 10f
 private var mTextSize = 30f     //尺子刻度下方数字的大小
 private var mAlphaEnable=false    // 尺子 最左边 最后边是否需要透明 `(透明效果更好点)
 private var mTextHeight = 0.toFloat()//尺子刻度下方数字的高度
 private var mTextPaint: Paint?=null  // 尺子刻度下方数字(也就是每隔10个出现的数值)画笔
 private var mLinePaint: Paint?=null  // 尺子刻度线的画笔

private var mTotalLine:Int = 0    //共有多少条 刻度
 private var mMaxOffset:Int = 0    //所有刻度 共有多长
 private var mOffset:Float = 0.toFloat()// 默认状态下,mSelectorValue所在的位置 位于尺子总刻度的位置
 private var mLastX:Int = 0
 private var mMove: Int = 0
 private lateinit var mListener: OnValueChangeListener// 滑动后数值回调

private var mLineColor:Int= Color.GRAY //刻度的颜色
 private var mTextColor:Int= Color.BLACK//文字的颜色

constructor(mContext: Context) : this(mContext,null)

constructor(mContext: Context, attrs: AttributeSet?) : this(mContext, attrs!!,0)

constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int) : super(mContext, attrs,defStyleAttr) {
   init(mContext, attrs)
 }

fun init(context: Context, attrs: AttributeSet){
   Log.d(TAG, "init")
   mScroller= Scroller(context)

this.mLineSpaceWidth=myfloat(25.0f)
   this.mLineWidth=myfloat(2.0f)
   this.mLineMaxHeight=myfloat(100.0f)
   this.mLineMidHeight=myfloat(60.0f)
   this.mLineMinHeight=myfloat(40.0f)
   this.mTextHeight=myfloat(40.0f)

val typedArray: TypedArray =context.obtainStyledAttributes(attrs,
       R.styleable.RulerView)

mAlphaEnable= typedArray.getBoolean(R.styleable.RulerView_alphaEnable, mAlphaEnable)

mLineSpaceWidth = typedArray.getDimension(R.styleable.RulerView_lineSpaceWidth, mLineSpaceWidth)
   mLineWidth = typedArray.getDimension(R.styleable.RulerView_lineWidth, mLineWidth)
   mLineMaxHeight = typedArray.getDimension(R.styleable.RulerView_lineMaxHeight, mLineMaxHeight)
   mLineMidHeight = typedArray.getDimension(R.styleable.RulerView_lineMidHeight, mLineMidHeight)
   mLineMinHeight = typedArray.getDimension(R.styleable.RulerView_lineMinHeight, mLineMinHeight)
   mLineColor = typedArray.getColor(R.styleable.RulerView_lineColor, mLineColor)

mTextSize = typedArray.getDimension(R.styleable.RulerView_textSize, mTextSize)
   mTextColor = typedArray.getColor(R.styleable.RulerView_textColor, mTextColor)
   mTextMarginTop = typedArray.getDimension(R.styleable.RulerView_textMarginTop, mTextMarginTop)

mSelectorValue = typedArray.getFloat(R.styleable.RulerView_selectorValue, 0.0f)
   mMinValue = typedArray.getFloat(R.styleable.RulerView_minValue, 0.0f)
   mMaxValue = typedArray.getFloat(R.styleable.RulerView_maxValue, 100.0f)
   mPerValue = typedArray.getFloat(R.styleable.RulerView_perValue, 0.1f)

mMinVelocity= ViewConfiguration.get(getContext()).scaledMinimumFlingVelocity

mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
   mTextPaint!!.textSize = mTextSize
   mTextPaint!!.color = mTextColor
   mTextHeight = getFontHeight(mTextPaint!!)

mLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
   mLinePaint!!.strokeWidth = mLineWidth
   mLinePaint!!.color = mLineColor
 }

private fun myfloat(paramFloat:Float):Float{
   return 0.5f+paramFloat*1.0f
 }

private fun getFontHeight(paint: Paint):Float{
   val fm = paint.fontMetrics
   return fm.descent - fm.ascent
 }

/**
  * 设置默认的参数
  * @param selectorValue 未选择时 默认的值 滑动后表示当前中间指针正在指着的值
  * @param minValue  最大数值
  * @param maxValue  最小的数值
  * @param per  最小单位(如1:表示每2条刻度差为1;0.1:表示每2条刻度差为0.1;其中身高mPerValue为1,体重mPerValue 为0.1)
  */
 fun setValue(selectorValue: Float, minValue: Float, maxValue: Float, per: Float) {
   this.mSelectorValue = selectorValue
   this.mMaxValue = maxValue
   this.mMinValue = minValue
   this.mPerValue = per * 10.0f
   this.mTotalLine = ((mMaxValue * 10 - mMinValue * 10) / mPerValue).toInt() + 1

mMaxOffset = (-(mTotalLine - 1) * mLineSpaceWidth).toInt()
   mOffset = (mMinValue - mSelectorValue) / mPerValue * mLineSpaceWidth * 10f
   Log.d(TAG, "mOffset:" + mOffset + ",mMaxOffset:" + mMaxOffset
       + ",mTotalLine:" + mTotalLine)
   invalidate()
   visibility = View.VISIBLE
 }

fun setOnValueChangeListener(listener: OnValueChangeListener) {
   mListener = listener
 }

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {

super.onSizeChanged(w, h, oldw, oldh)
   if (w > 0 && h > 0) {
     mWidth = w
     mHeight = h
   }
 }

override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   var left: Float
   var height: Float
   var value: String
   var alpha = 0
   var scale: Float
   val srcPointX = mWidth / 2
   for (i in 0 until mTotalLine) {
     left = srcPointX.toFloat() + mOffset + i * mLineSpaceWidth

if (left < 0 || left > mWidth) {
       continue //先画默认值在正中间,左右各一半的view。多余部分暂时不画(也就是从默认值在中间,画旁边左右的刻度线)
     }

if (i % 10 == 0) {
       height = mLineMaxHeight
     } else if (i % 5 == 0) {
       height = mLineMidHeight
     } else {
       height = mLineMinHeight
     }
     if (mAlphaEnable) {
       scale = 1 - Math.abs(left - srcPointX) / srcPointX
       alpha = (255f * scale * scale).toInt()

mLinePaint!!.setAlpha(alpha)
     }
     canvas.drawLine(left, 0f, left, height, mLinePaint)

if (i % 10 == 0) {
       value = (mMinValue + i * mPerValue / 10).toInt().toString()
       if (mAlphaEnable) {
         mTextPaint!!.alpha = alpha
       }
       canvas.drawText(value, left - mTextPaint!!.measureText(value) / 2,
           height + mTextMarginTop + mTextHeight, mTextPaint)  // 在为整数时,画 数值
     }
   }
 }

override fun onTouchEvent(event: MotionEvent): Boolean {
   Log.d(TAG, "onTouchEvent")

val action = event.action
   val xPosition = event.x.toInt()

if (mVelocityTracker == null) {
     mVelocityTracker = VelocityTracker.obtain()
   }
   mVelocityTracker!!.addMovement(event)

when (action) {
     MotionEvent.ACTION_DOWN -> {
       mScroller!!.forceFinished(true)
       mLastX = xPosition
       mMove = 0
     }
     MotionEvent.ACTION_MOVE -> {
       mMove = mLastX - xPosition
       changeMoveAndValue()
     }
     MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
       countMoveEnd()
       countVelocityTracker()
       return false
     }
     else -> {
     }
   }

mLastX = xPosition
   return true
 }

private fun countVelocityTracker() {
   Log.d(TAG, "countVelocityTracker")
   mVelocityTracker!!.computeCurrentVelocity(1000) //初始化速率的单位
   val xVelocity = mVelocityTracker!!.xVelocity //当前的速度
   if (Math.abs(xVelocity) > mMinVelocity) {
     mScroller!!.fling(0, 0, xVelocity.toInt(), 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0)
   }
 }

/**
  * 滑动结束后,若是指针在2条刻度之间时,改变mOffset 让指针正好在刻度上。
  */
 private fun countMoveEnd() {
   mOffset -= mMove.toFloat()
   if (mOffset <= mMaxOffset) {
     mOffset = mMaxOffset.toFloat()
   } else if (mOffset >= 0) {
     mOffset = 0f
   }

mLastX = 0
   mMove = 0

mSelectorValue = mMinValue + Math.round(Math.abs(mOffset) * 1.0f / mLineSpaceWidth) * mPerValue / 10.0f
   mOffset = (mMinValue - mSelectorValue) * 10.0f / mPerValue * mLineSpaceWidth

notifyValueChange()
   postInvalidate()
 }

/**
  * 滑动后的操作
  */
 private fun changeMoveAndValue() {
   mOffset -= mMove.toFloat()

if (mOffset <= mMaxOffset) {
     mOffset = mMaxOffset.toFloat()
     mMove = 0
     mScroller!!.forceFinished(true)
   } else if (mOffset >= 0) {
     mMove = 0
     mScroller!!.forceFinished(true)
   }
   mSelectorValue = mMinValue + Math.round(Math.abs(mOffset) * 1.0f / mLineSpaceWidth) * mPerValue / 10.0f

notifyValueChange()
   postInvalidate()
 }

private fun notifyValueChange() {
   if (null != mListener) {
     mListener.onValueChange(mSelectorValue)
   }
 }

/**
  * 滑动后的回调
  */
 interface OnValueChangeListener{
   fun onValueChange(value: Float)
 }

override fun computeScroll() {
   Log.d(TAG, "computeScroll")
   super.computeScroll()
   if (mScroller!!.computeScrollOffset()) {//mScroller.computeScrollOffset()返回true表示滑动还没有结束
     if (mScroller!!.currX == mScroller!!.finalX) {
       countMoveEnd()
     } else {
       val xPosition = mScroller!!.currX
       mMove = mLastX - xPosition
       changeMoveAndValue()
       mLastX = xPosition
     }
   }
 }
}

在页面中,我们要给自定义的标尺设置默认的参数:未选择时默认的值、最大数值、最小的数值以及最小单位


//体重的view
   mWeightRuler!!.setOnValueChangeListener(object : RulerView.OnValueChangeListener {
     override fun onValueChange(value: Float) {
       weight = value
       mTvWeight!!.text = weight.toString() + "kg"
     }
   })
   mWeightRuler!!.setValue(55f, 20f, 200f, 0.1f)

参考资料:

https://github.com/panacena/RuleView

源码下载

来源:https://blog.csdn.net/qq_20785431/article/details/80297962

标签:kotlin,自定义view,标尺控件
0
投稿

猜你喜欢

  • Java常用工具类库——Hutool的使用简介

    2022-09-27 18:24:45
  • 一文带你搞懂Java中的递归

    2022-10-08 07:34:04
  • java的主要特性学习总结

    2021-11-16 05:46:19
  • C#观察者模式(Observer Pattern)实例教程

    2021-07-13 02:53:39
  • java web中图片验证码功能的简单实现方法

    2023-06-07 13:30:53
  • Android Studio中Run按钮是灰色的快速解决方法

    2023-12-27 03:06:18
  • SpringBoot日志配置操作全面介绍

    2023-03-08 14:37:54
  • SpringMvc框架的简介与执行流程详解

    2022-10-15 18:49:00
  • Java中Collections.sort的使用

    2022-08-20 04:31:49
  • 区分C# 中的 Struct 和 Class

    2022-09-10 19:51:25
  • 解决springboot环境切换失效的问题

    2023-11-11 20:40:35
  • java 使用Scanner类接收从控制台输入的数据方式

    2023-10-23 08:54:57
  • Flutter有无状态类与State及生命周期详细介绍

    2022-12-27 15:48:53
  • Android Intent调用 Uri的方法总结

    2021-11-13 22:46:53
  • C# 指针内存控制Marshal内存数据存储原理分析

    2021-09-22 04:05:00
  • 将替代ListView的RecyclerView 的使用详解(一)

    2023-12-05 07:57:00
  • Android显示网络图片实例

    2022-11-02 14:13:31
  • springboot+thymeleaf 文件上传功能的实现代码

    2023-11-25 05:08:59
  • C#常见应用函数实例小结

    2022-10-17 02:35:46
  • C#异常处理中try和catch语句及finally语句的用法示例

    2022-05-27 12:20:43
  • asp之家 软件编程 m.aspxhome.com