Android カスタム ビューのスライド選択の高さ、重さの制御 - MyRulerView

 1. レンダリング

2. ビューをカスタマイズする基本手順

onMeasure() 測定: ビューのサイズを決定します。

onLayout() レイアウト: ViewGroup 内のビューの位置を決定します。

onDraw() 描画: このビューを描画することを決定し、ビューを描画するために onDraw メソッドを書き換えてから、ビューのdraw() メソッドを呼び出して特定の描画作業を実行します。

3、完全なコード付き

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>

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>

3. 必要に応じて、dialog_height_ruler.xml の完全なコードが MyRulerView のアプリに渡されます: 対応する属性

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

    }
}

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

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 カスタム定規コントロール(身長、体重など選択) - ショートブック

ソースコードのダウンロード

おすすめ

転載: juejin.im/post/7229615985808572473