Android自定义View之滑动选择身高、体重控件——MyRulerView

一、效果图

Android自定义View之滑动选择身高、体重控件——MyRulerView

Android自定义View之滑动选择身高、体重控件——MyRulerView

二、自定义View基本步骤

onMeasure()测量:决议View的巨细

onLayout()布局:决议View在ViewGroup中的方位

onDraw()制作:决议制作这个View,重写onDraw这个办法里对视图进行制作,然后经过调用View的draw()办法来履行详细的制作作业。

三、附完好代码

1、身高标尺需用到多个特点,res/values下创立一个attrs.xml文件,添加View需要用到的自定义特点,attrs.xml内容如下

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyRulerView">
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="lineColor" format="color" />
        <attr name="lineSpaceWidth" format="dimension" />
        <attr name="lineWidth" format="dimension" />
        <attr name="lineMaxHeight" format="dimension" />
        <attr name="lineMidHeight" format="dimension" />
        <attr name="lineMinHeight" format="dimension" />
        <attr name="textMarginTop" format="dimension" />
        <attr name="minValue" format="float"/>
        <attr name="maxValue" format="float"/>
        <attr name="selectorValue" format="float"/>
        <attr name="perValue" format="float"/>
    </declare-styleable>
</resources>

Android自定义View之滑动选择身高、体重控件——MyRulerView 2、在drawble中新建bg_dialog.xml,用于设置标尺弹窗的布景。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke
        android:width="0.5dp"
        android:color="#FFFFFFFF" />
    <corners
        android:topLeftRadius="8dp"
        android:topRightRadius="8dp" />
    <solid android:color="#FFFFFFFF" />
</shape>

Android自定义View之滑动选择身高、体重控件——MyRulerView 3、dialog_height_ruler.xml完好代码,根据需要传入MyRulerView中app:对应的特点

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:background="@drawable/bg_dialog"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:visibility="visible">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="left"
            android:layout_marginLeft="20dp"
            android:layout_marginTop="21dp"
            android:layout_marginRight="20dp">
            <ImageView
                android:id="@+id/close_image"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="left"
                android:src="@mipmap/icon_close" />
            <TextView
                android:id="@+id/ruler_title"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="right|center_vertical"
                android:text="挑选身高"
                android:textColor="#333333"
                android:textSize="18sp"
                android:textStyle="bold" />
            <TextView
                android:id="@+id/required"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="right|center_vertical"
                android:text="确认"
                android:textColor="#FF00C0C5"
                android:textSize="16sp" />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/discirble"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="16dp">
            <TextView
                android:id="@+id/tv_register_info_height_value"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="right"
                android:includeFontPadding="false"
                android:text="165"
                android:textColor="#FF00C0C5"
                android:textSize="32dp"
                android:textStyle="bold" />
            <TextView
                android:id="@+id/danwei"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center_vertical|left"
                android:includeFontPadding="false"
                android:text="  厘米"
                android:textColor="#FF00C0C5"
                android:textSize="16dp" />
        </LinearLayout>
        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="116dp"
            android:layout_marginTop="24dp"
            android:background="#FFF5F6F7">
            <View
                android:layout_width="2dp"
                android:layout_height="95dp"
                android:layout_centerHorizontal="true"
                android:layout_marginBottom="19dp"
                android:background="#FF00C0C5" />
            <com.example.myrulerview.MyRulerView
                android:id="@+id/ruler_height"
                android:layout_width="match_parent"
                android:layout_height="116dp"
                android:background="@color/transparent"
                app:lineColor="#801d2129"
                app:lineMaxHeight="40dp"
                app:lineMidHeight="30dp"
                app:lineMinHeight="20dp"
                app:lineSpaceWidth="10dp"
                app:lineWidth="1dp"
                app:maxValue="250.0"
                app:minValue="80.0"
                app:perValue="1"
                app:selectorValue="165.0"
                app:textColor="@color/black" />
        </RelativeLayout>
    </LinearLayout>
</LinearLayout>

Android自定义View之滑动选择身高、体重控件——MyRulerView 4、HeightDialog布景弹窗,使用dimAmount特点来调整变暗的程度(1.0不通明,0.0完全通明)。

package com.example.myrulerview
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.Display
import android.view.Gravity
import android.view.WindowManager
import com.example.myrulerview.databinding.DialogHeightRulerBinding
@Suppress("DEPRECATION")
class HeightDialog(context: Context) : Dialog(context, R.style.UIAlertViewStyle) {
    private lateinit var mBinding: DialogHeightRulerBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DialogHeightRulerBinding.inflate(layoutInflater)
        setContentView(mBinding.root)
        //点击弹窗外侧封闭弹窗
        setCanceledOnTouchOutside(true)
        setCancelable(true)
        val windowManager: WindowManager? = window?.windowManager
        val lp: WindowManager.LayoutParams? = window?.attributes
        //一切在这个window之后的会变暗,使用dimAmount特点来控制变暗的程度(1.0不通明,0.0完全通明)
        lp?.alpha = 1f
        lp?.dimAmount = 0.5f
        window?.attributes = lp
        window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
        //设置窗口的占比
        val display: Display? = windowManager?.defaultDisplay
        (display?.height)?.div(2.2)
            ?.let { window?.setLayout(WindowManager.LayoutParams.MATCH_PARENT, it.toInt()) }
        //设置弹窗方位于屏幕底部
        window?.attributes?.gravity = Gravity.BOTTOM
        mBinding.rulerHeight.setTextChangedListener {
            //得到身高的最终值
            mBinding.tvRegisterInfoHeightValue.text = it.toString()
        }
        //封闭
        mBinding.closeImage.setOnClickListener {
            this.dismiss()
        }
        //确认
        mBinding.required.setOnClickListener {
            this.dismiss()
        }
    }
}

Android自定义View之滑动选择身高、体重控件——MyRulerView 5、创立MyRulerView类,继承View,在init得到自定义特点,重写onDraw办法制作刻度线以及下方数字。

package com.example.myrulerview
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface
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
@SuppressLint("CustomViewStyleable")
class MyRulerView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private var call:((String)->Unit)? = null
    private var mLinePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)    //刻度画笔
    private var mTextPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)    //文字画笔
    private var mWidth: Int = 0
    private var mHeight: Int = 0
    //标尺
    private var mMaxValue = 250f           //最大值
    private var mMinValue = 80f            //最小值
    private var mPerValue = 1f             //最小刻度值,最小单位
    private var mLineSpace = 5f            //两条刻度之间的间隔距离
    private var mTotalLine = 0             //计算mMaxValue-mMinValue之间一共有多少条刻度线
    private var mMaxOffset = 0             //一切刻度共有多长 (mTotalLine-1)* mLineSpaceWidth
    private var mOffset = 0f               // 默许状态下,mSelectorValue所在的方位  位于尺子总刻度的方位
    private var mLastX: Int = 0
    private var mMove: Int = 0
    //刻度线
    private var mLineMaxLength = 40f       //三种不同长度(如刻度80cm-250cm),最长的那根线(80,90,100,...)时的线高度
    private var mLineMidLength = 30f       //中等长度(85,95,105,...)时的线高度
    private var mLineMinLength = 20f       //最短长度(81,82,83,...)时的线高度
    private var mLineWidth = 1f            //刻度线的粗细
    private var mLineColor = context.getColor(R.color.white6)    //刻度线色彩
    private var mSelectorValue = 100.0f                          // 未挑选时 默许的值 指针指向的默许值
    //标尺下方文字
    private var mTextColor = context.getColor(R.color.black)       //文字色彩
    private var mTextSize = 35f                                    //文字巨细
    private var mTextMarginTop = 10f                               //文字与上方的距离
    private var mTextHeight = 40f                                  //尺子刻度下方数字的高度
    private var mMinVelocity = 0
    private var mScroller: Scroller? = null
    private var mVelocityTracker: VelocityTracker? = null
    init {
        this.mLineSpace = myFloat(mLineSpace)
        this.mLineWidth = myFloat(mLineWidth)
        this.mLineMidLength = myFloat(mLineMidLength)
        this.mLineMinLength = myFloat(mLineMinLength)
        this.mTextHeight = myFloat(mTextHeight)
        mScroller = Scroller(context)
        val styleable = context.obtainStyledAttributes(attrs, R.styleable.MyRulerView)
        mMaxValue = styleable.getFloat(R.styleable.MyRulerView_maxValue, mMaxValue)
        mMinValue = styleable.getFloat(R.styleable.MyRulerView_minValue, mMinValue)
        mPerValue = styleable.getFloat(R.styleable.MyRulerView_perValue, mPerValue)
        mLineSpace = styleable.getDimension(R.styleable.MyRulerView_lineSpaceWidth, mLineSpace)
        mSelectorValue = styleable.getFloat(R.styleable.MyRulerView_selectorValue, 0f)
        mLineMaxLength = styleable.getDimension(R.styleable.MyRulerView_lineMaxHeight, mLineMaxLength)
        mLineMidLength =  styleable.getDimension(R.styleable.MyRulerView_lineMidHeight, mLineMidLength)
        mLineMinLength = styleable.getDimension(R.styleable.MyRulerView_lineMinHeight, mLineMinLength)
        mLineWidth = styleable.getDimension(R.styleable.MyRulerView_lineWidth, mLineWidth)
        mLineColor = styleable.getColor(R.styleable.MyRulerView_lineColor, mLineColor)
        mTextColor = styleable.getColor(R.styleable.MyRulerView_textColor, mTextColor)
        mTextSize = styleable.getDimension(R.styleable.MyRulerView_textSize, mTextSize)
        mTextMarginTop = styleable.getDimension(R.styleable.MyRulerView_textMarginTop, mTextMarginTop)
        styleable.recycle()
        mMinVelocity = ViewConfiguration.get(getContext()).scaledMinimumFlingVelocity
        initPaint()
        setRulerValue(mSelectorValue, mMaxValue,mMinValue , mPerValue)
    }
    fun myFloat(paramFloat: Float) = 0.5f + paramFloat * 1.0f
    /**
     * 初始化刻度线画笔、标尺下方文字画笔
     */
    private fun initPaint() {
        mTextPaint.color = mTextColor
        mTextPaint.textSize = mTextSize
        mTextPaint.typeface = Typeface.DEFAULT_BOLD
        mLinePaint.color = mLineColor
        mLinePaint.strokeWidth = mLineWidth
    }
    /**
     * 设置标尺的值
     */
    private fun setRulerValue(
        selectorValue: Float,
        maxValue: Float,
        minValue: Float,
        preValue: Float
    ) {
        Log.d("mSelectorValue---", mSelectorValue.toString())
        mSelectorValue = selectorValue
        mMaxValue = maxValue
        mMinValue = minValue
        mPerValue = preValue * 10f
        mTotalLine =
            ((mMaxValue * 10 - mMinValue * 10) / mPerValue).toInt() + 1  //需要画 mTotalLine 条刻度线
        mMaxOffset =
            (-(mTotalLine - 1) * mLineSpace).toInt()         //mTotalLine条刻度线之间有 mTotalLine-1 个距离
        mOffset = (mMinValue - mSelectorValue) / mPerValue * mLineSpace * 10
        invalidate()
        visibility = VISIBLE
    }
    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?) {
        var left: Float
        var value: String
        var height: Float
        val srcPointX = mWidth / 2
        super.onDraw(canvas)
        for (i in 0 until mTotalLine) {
            left = srcPointX + mOffset + i * mLineSpace
            if (left < 0 || left > width) {
                continue
            }
            //整10时,更改制作时线的高度
            if (i % 10 == 0) {
                height = mLineMaxLength
                value = (mMinValue + i * mPerValue / 10).toInt().toString()
                mLinePaint.color = context.getColor(R.color.white6)
                //制作刻度线下方数字
                canvas?.drawText(value, left - mTextPaint.measureText(value) / 2, height + mTextMarginTop + mTextHeight, mTextPaint)
            } else if (i % 5 == 0) {
                height = mLineMidLength
                mLinePaint.color = context.getColor(R.color.white5)
            } else {
                height = mLineMinLength
                mLinePaint.color = context.getColor(R.color.white5)
            }
            //画刻度线
            canvas?.drawLine(left, 0f, left, height, mLinePaint)
        }
    }
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        var xPosition = event?.x?.toInt()
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain()
        }
        mVelocityTracker?.addMovement(event)
        when (event?.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
            }
        }
        mLastX = xPosition!!
        return true
    }
    /**
     * 滑动完成后如果指针落在两条刻度中间,则指向接近的那条指针
     */
    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 / mLineSpace) * mPerValue / 10.0f
        mOffset = (mMinValue - mSelectorValue) * 10.0f / mPerValue * mLineSpace
        call?.invoke(mSelectorValue.toInt().toString())
        postInvalidate()
    }
    private fun countVelocityTracker() {
        mVelocityTracker?.computeCurrentVelocity(1000) //初始化速率的单位
        val xVelocity = mVelocityTracker!!.getXVelocity() //当时的速度
        if (Math.abs(xVelocity) > mMinVelocity) {
            mScroller!!.fling(0, 0, xVelocity.toInt(), 0, Int.MIN_VALUE, Int.MAX_VALUE, 0, 0)
        }
    }
    /**
     * 滑动后的操作
     */
    private fun changeMoveAndValue() {
        mOffset -= mMove.toFloat()
        if (mOffset <= mMaxOffset) {
            mOffset = mMaxOffset.toFloat()
            mMove = 0
            mScroller!!.forceFinished(true)
        } else if (mOffset >= 0) {
            mOffset = 0f
            mMove = 0
            mScroller!!.forceFinished(true)
        }
        mSelectorValue =
            mMinValue + Math.round(Math.abs(mOffset) * 1.0f / mLineSpace) * mPerValue / 10.0f
        call?.invoke(mSelectorValue.toInt().toString())
        postInvalidate()
    }
    override fun 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
            }
        }
    }
    fun setTextChangedListener(call: (String) -> Unit) {
        this.call = call
    }
}

Android自定义View之滑动选择身高、体重控件——MyRulerView

6、MainActivty中调用身高弹窗。

package com.example.myrulerview
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.myrulerview.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
    private lateinit var mBinding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mBinding.root)
        mBinding.button.setOnClickListener {
            HeightDialog(this).show()
        }
    }
}

Android自定义View之滑动选择身高、体重控件——MyRulerView 参考资料:Android自定义标尺控件(挑选身高、体重等) – 简书

源码下载