Android custom View sliding selection height, weight control - MyRulerView

 1. Rendering

2. Basic steps to customize View

onMeasure() measurement: determine the size of the View

onLayout() layout: determine the position of the View in the ViewGroup

onDraw() drawing: decide to draw this View, rewrite the onDraw method to draw the view, and then perform specific drawing work by calling the View's draw() method.

Three, with complete code

1. The height scale needs to use multiple attributes. Create an attrs.xml file under res/values, and add the custom attributes needed for View. The content of attrs.xml is as follows

<?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>

2. Create a new bg_dialog.xml in drawble to set the background of the ruler pop-up window.

<?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>

3. The complete code of dialog_height_ruler.xml is passed to the app in MyRulerView as needed: the corresponding attribute

<?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>

4. For the HeightDialog background pop-up window, use the dimAmount attribute to adjust the degree of dimming (1.0 is opaque, 0.0 is completely transparent).

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()
        }

    }
}

5. Create the MyRulerView class, inherit View, get custom attributes in init, and rewrite the onDraw method to draw the scale line and the numbers below.

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
    }
}

6. Call the height pop-up window in 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()
        }
    }
}

Reference materials: Android custom ruler control (select height, weight, etc.) - Short Book

Source code download

Guess you like

Origin juejin.im/post/7229615985808572473