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

前言

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

源码下载

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Kotlin如何直接使用控件ID原理详析

    前言 最近断断续续地把项目的界面部分的代码由JAva改成了Kotlin编写,并且如果应用了kotlin-android-extensions插件,一个显而易见的好处是再也不用写 findViewById()来实例化你的控件对象了,直接操作你在布局文件里的id即可,这一点我感觉比butterknife做的还简洁友好. Activity import android.support.v7.app.AppCompatActivity import android.os.Bundle import ko

  • 自定义View系列之kotlin绘制手势设置温度控件的方法

    引言 最近公司接了一个车联网的项目,主要是新能源汽车的一些控制功能,其中涉及到一个是温度的调节功能,产品的意思是做一个手势滑动调节温度,大概意思我是明白的.就是要手势调节呗,没办法,谁让我是搬砖的呢,人为刀俎,我为鱼肉,只有搞了: 最后搞出来的效果大概如下,不过还没确定, 思路 在这里我先说下自己的实现思路,这个控件的难点主要是手势控制,其他的都很简单,没有什么好说的,控制的一些具体的数值我是写死的,没有做自定义拓展,主要是闲麻烦,如果有需要可以自己的实现: 具体的实现步奏 首先绘制圆盘,刻度,

  • Kotlin自定义菜单控件

    本文实例为大家分享了Kotlin自定义菜单控件的具体代码,供大家参考,具体内容如下 首先贴一下效果图 思路:菜单控件分两部分,一是点击的子按钮(RecordButton),二是包裹着子按钮的容器(RecordMenu). 子按钮负责显示文字及背景颜色和点击事件,父容器主要控制子控件的位置和动画显示. 实现: 子按钮,先贴代码 class RecordButton : RelativeLayout { /** 控件显示的文本*/ lateinit var textValue: String /**

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

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

  • Android自定义View之简约风歌词控件实战指南

    目录 前言 一. 歌词解析 1.歌词实体类LrcBean 2. 解析歌词工具类LrcUtil 二.歌词绘制 1.设置自定View属性,在代码中设置默认值 2. 初始化两支画笔 3. 重复执行onDraw方法 1.获得控件的测量后的宽高 2. 得到当前歌词的位置 4. 歌词同步滑动 5.不断重绘 三 .使用 总结 前言 最近重构了之前的音乐播放器,添加了许多功能,比如歌词,下载功能等.这篇文章就让我们聊聊歌词控件的实现,先上效果图,如果感觉海星,就继续瞧下去! 看到这里,估计你对这个控件还有点感兴

  • Android自定义View实现随手势滑动控件

    本文控件为大家分享了Android随手势滑动控件的具体代码,供大家参考,具体内容如下 1.新建自定义控件类:MyView public class MyView extends Button{ //记录上次滑动后的坐标值 private int lastX; private int lastY; public MyView(Context context) { super(context); // TODO Auto-generated constructor stub } public MyV

  • Android自定义view实现水波进度条控件

    通过自定义view实现了一个水滴滴落到水波面,溅起水花并且水波流动上涨的进度条控件.之前看到过好多水波流动的进度条,感觉欠缺些东西,就想到了水滴到水平面,溅起水花然后水流动上涨的进度条效果,于是自己动手写了出来.效果如下,视频录制有些卡顿,实际会流畅很多. 一.用法 1.布局文件中添加WaveProgressView,circleColor属性为圆环颜色,waterColor属性为水波水滴的颜色,progress属性为初始的进度 <com.yhongm.wave_progress_view.Wa

  • Android拆轮子系列之写验证码控件的方法

    前言 先看看效果 怎么样不错吧?别急下面我就一步一步的教你实现. 用到的知识点总结: 1.Canvas和pint的使用,我们用它画点,线,字 2.View的基本用法 其实做这个东西还是很简单的,总体思路步骤如下: 1.准备一个Canvas. 2.向Canvas里面画几条斜杠. 3.向canvas里面画100个小点. 4.随机生成4个数字,然后画在canvas里面. 其实就是这么简单,没什么深奥的. 开始写编码 1.首先我们要重写View 既然我们要画验证码,那么我们就需要准备画笔(paint)和

  • Android自定义ViewGroup实现朋友圈九宫格控件

    目录 一.简介 1.1.效果图如下 1.2.主要功能如下 二.使用 2.1.自定义属性如下 2.2.布局中使用自定义NineImageLayout 2.3.Adapter方式绑定数据和UI 2.4.列表里面使用 三.源码地址 四.总结 一.简介 最近项目里有个类似微信朋友圈的九图控件的需求,Github找了一下,发现都不太满足需求,我需要单张图片的时候可以按照图片宽高比列在一定范围内自适应,而大多开源项目单张图片也是一个小正方形,所以,干脆自己动手写一个 1.1.效果图如下 1.2.主要功能如下

  • 解决iView中时间控件选择的时间总是少一天的问题

    今天在用iview做前端页面开发的时候,遇到一个奇葩问题(也许自己主要做后台开发,当时我还纳闷了),我在时间控件中明明选择的是2017-10-19,但是当通过vue的调试器查看的时候,竟然是这样的,如下图: 当时我还纳闷了,这iview的时间控件真神奇,我还百度谷歌了好半天,后来查看官方文档,是我获取时间的方式不是很对,我当时用的是l来v-mode绑定的,这样绑定后获取的时间好像叫utc时间,如果要想获取正确的时间,通过@on-change事件来绑定即可,(注意,用@on-change来获取时间

  • layui时间控件选择时间范围的实现方法

    解决layui时间控件清空之后无法正常使用的问题,以及时间范围的选择 共有两种解决方式: 方式一(layui 1.x): html代码: <div class="layui-inline"> <div class="layui-input-inline"> <input type="text" name="start_time" class="layui-input" id=&

  • Android 使用Kotlin自定义View的方法教程

    前言 随着google宣布kotlin作为官方开发语言,在Android中使用kotlin的趋势也越来越明显,最近被kotlin的文章轰炸了,所以决定上手试一下,试过之后,感觉靠它灵简直有魔性.特别是一句话写出一个复杂的循环的时候,简直被惊呆.而且使用AS,Java代码可以直接转成Kotlin. 效果图如下: 首先是这次自定义View的效果图,是一张饼图.如果是用java写的话也就几十行,觉得换成Kotlin的话可能会更少. 示例代码 主要的功能是可以任设定数据的个数,我这里是4个数据,可以任意

随机推荐