Android は座標ルーラーを備えたドラッグ可能なプログレス バーを実装します

上の UI エフェクトの画像を見て、第一印象は「実装が簡単すぎる」ということです。SeekBar は簡単にできます。サムを変更して、グラデーションを追加して完了です。とにかく実行してください。やっているのですが、一番下の座標定規はそのまま使えます 等分割した後、SeekBarの下にTextViewをいくつか追加するだけです 真ん中の等分割の小分割線はどうでしょうか?さらに、スライドの前後で利用可能である必要があり、左右の分割線は少し距離を空ける必要があり、グラデーションカラーは幅全体ではなく、スライド距離に応じて表示される必要があります。いろいろな条件がありますが、この要望、どうすればいいでしょうか?カスタマイズのみ可能です。

通常の慣例に従って、大まかな概要を作成します。

1. 要素を分析し、実装計画を決定する

2. メインコードを分析する

3. オープンソースのアドレスと使用法

4. まとめ

1. 要素を分析し、実装計画を決定する

Canvas はドラッグ可能な座標定規を描画しますが、基本的に 4 つの部分に分割でき、最初の部分は背景とデフォルトの離散区間、2 番目の部分は移動する背景と離散区間、3 番目の部分は動画です。最後の部分は下部のテキスト座標です。

基本的に 4 つの部分を描画しますが、描画に加えて、高さ、指の動きのイベントなど、他の要素も考慮する必要があります。

1. デフォルトの高さを設定します

デフォルトの高さを設定する理由は、wrap_content が設定されているときに表示されないよう、ビューが適切なサイズで表示されるようにするためです。特定の設定は、現在設定されているモードに従って制御できます。モードは 3 つあります。は前回のものです。記事で紹介したので、ここでは詳しく紹介しません。コントロールがwrap_contentを設定するとき、このときのモードはMeasureSpec.AT_MOSTです。このモードでは、デフォルトの高さを与える必要があります。

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        var windowHeight = heightMeasureSpec
        if (heightMode == MeasureSpec.AT_MOST) {
            windowHeight = mDefaultHeight.toInt()//默认的高度
        }
        setMeasuredDimension(widthMeasureSpec, windowHeight)
    }

2. ドラッグイベント

ドラッグ効果を実現するには、ユーザーの指の動きイベントを監視する必要があり、カスタム View の onTouchEvent メソッドを書き換える必要があり、このメソッド内で、オブジェクトを押す、持ち上げる、移動することに対応する処理を行う必要があります。指が

onTouchEventでは以下の処理を行いました 1つはイベント消費を行わずに直接返すこと 目的はカスタムViewの静的表示と動的表示を実現させること 変数mProgressIsInterceptで制御すること 2つ目は問題解決親ビューのスライディング競合イベントについては、水平または垂直のスライディングイベントがある場合、ドラッグ時に必然的に競合が発生するため、イベントを消費しないように親ビューに通知する、つまり requestDisallowInterceptTouchEvent を実行する必要があります方法。

すべてのドラッグ効果は移動イベントで実現され、常に座標を変更して UI を更新します。mMoveProgress は指の動きの座標です。

onTouchEvent(event: MotionEvent?): Boolean {
        super.onTouchEvent(event)
        //如果为true直接返回,不进行拖拽
        if (mProgressIsIntercept) {
            return mProgressIsIntercept
        }
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                parent.requestDisallowInterceptTouchEvent(mDisallowIntercept)
                val downX = getChangeX(event.x)
                val startX = mMoveOldX - mProgressMarginLeftRight
                val endX = mMoveOldX + mProgressMarginLeftRight
                return downX in startX..endX
            }
            MotionEvent.ACTION_MOVE -> {
                //移动
                var moveX = getChangeX(event.x)
                //滑动至最右边
                //计算最后边的坐标
                val viewWidth = getViewWidth()

                if (moveX >= viewWidth) {
                    moveX = viewWidth
                }

                mMoveProgress = moveX

                invalidate()
            }
            MotionEvent.ACTION_UP -> {
                //手指谈起
                mMoveOldX = getChangeX(event.x)

                val viewWidth = getViewWidth()

                if (mMoveOldX >= viewWidth) {
                    mMoveOldX = viewWidth
                }
            }
        }
        return true
    }

次に、分析用のメインコード

1.背景を描く

背景については言うことはありませんが、drawRoundRectで描画できる単純な角丸長方形であり、決める必要があるのは左上と右下の間隔です。

 /**
     * AUTHOR:AbnerMing
     * INTRODUCE:绘制背景
     */
    private fun canvasBackground(canvas: Canvas) {
        mPaint!!.color = mProgressBackground
        val rect = RectF().apply {
            left = mProgressMarginLeftRight
            top = mProgressMarginTopBottom
            right = width.toFloat() - mProgressMarginLeftRight
            bottom = mProgressHeight + mProgressMarginTopBottom
        }
        canvas.drawRoundRect(rect, mProgressRadius, mProgressRadius, mPaint!!)
    }

2. 離散的な間隔を描く

離散間隔の場合は、間隔の数を決定し、間隔の数に応じて各間隔の位置を動的に計算する必要があります。drawLine を使用して、小さな垂直線を描画できます。垂直線は、からの距離も決定する必要があります。上から下と独自の幅; 特殊な場合 次に離散区間ですが、スライド前後で色が違うので、こちらも動的に色を変える判定です。

 /**
     * AUTHOR:AbnerMing
     * INTRODUCE:绘制离散间隔
     */
    private fun canvasIntervalLine(canvas: Canvas, isCanvas: Boolean) {
        val rect =
            (width - mProgressMarginLeftRight * 2 - mIntervalParentLeftRight * 2) / mIntervalSize
        if (isCanvas) {
            mPaint!!.color = mIntervalSelectColor
        } else {
            mPaint!!.color = mIntervalColor
        }

        mPaint!!.strokeWidth = mIntervalWidth
        for (a in 0..mIntervalSize) {
            val x = (rect * a) + mProgressMarginLeftRight + mIntervalParentLeftRight
            val y = mIntervalMarginTopBottom + mProgressMarginTopBottom
            canvas.drawLine(
                x,
                y,
                x,
                mProgressHeight + mProgressMarginTopBottom - mIntervalMarginTopBottom,
                mPaint!!
            )
        }
    }

3. 動く親指を描く

サムについては、最初にサイズを決定します。幅と高さが設定されている場合は、ビットマップを使用して高さをリセットし、サムの座標を変更する必要があります。左の座標点を連続的に変更するだけで済みます。画像、つまり上記の Move イベントを介して移動座標を に設定します。

 /**
     * AUTHOR:AbnerMing
     * INTRODUCE:绘制移动的图标
     */
    private fun canvasMoveIcon(canvas: Canvas) {
        mProgressThumb?.let {
            var decodeResource = BitmapFactory.decodeResource(resources, it)
            mProgressThumbWidth = decodeResource.width
            if (mThumbWidth != 0f) {
                val height: Int = decodeResource.height
                // 设置想要的大小
                val newWidth = mThumbWidth
                val newHeight = mThumbHeight
                // 计算缩放比例
                val scaleWidth = newWidth / width
                val scaleHeight = newHeight / height
                // 取得想要缩放的matrix参数
                val matrix = Matrix()
                matrix.postScale(scaleWidth, scaleHeight)
                // 得到新的图片
                decodeResource =
                    Bitmap.createBitmap(decodeResource, 0, 0, width, height, matrix, true)

            }

            var mThumpLeft = mMoveProgress
            if (mThumpLeft < (mProgressThumbWidth / 2 - mIntervalParentLeftRight + mProgressThumbSpacing)) {
                mThumpLeft =
                    mProgressThumbWidth / 2 - mIntervalParentLeftRight + mProgressThumbSpacing
            }

            if (mThumpLeft > (getViewWidth() - mIntervalParentLeftRight + mProgressThumbSpacing)) {
                mThumpLeft = getViewWidth() - mIntervalParentLeftRight + mProgressThumbSpacing
            }

            canvas.drawBitmap(
                decodeResource, mThumpLeft, mThumbMarginTop, mIconPaint!!
            )
        }
    }

4. 動きの経過を描く

動きの進み方は背景の描画と同じですが、指の座標に合わせて少しずつ距離を移動する、つまり右側の座標値を常に変化させる必要があります。また、Move イベントの mMoveProgress の進行を通じて動的に計算されます。進行状況のグラデーションは比較的単純で、ブラシのシェーダー プロパティを使用し、現在は水平方向の線形グラデーション LinearGradient を使用しています。

/**
     * AUTHOR:AbnerMing
     * INTRODUCE:绘制进度
     */
    private fun canvasMoveProgress(canvas: Canvas) {
        //为空
        if (mColorArray.isEmpty()) {
            mColorArray = intArrayOf(
                ContextCompat.getColor(context, R.color.text_ff3e3e93),
                ContextCompat.getColor(context, R.color.text_ff8548d2),
            )
        }
        val linearShader = LinearGradient(
            0f,
            0f,
            mMoveProgress + mProgressMarginLeftRight,
            mProgressHeight,
            mColorArray,
            floatArrayOf(0f, 1f),
            Shader.TileMode.CLAMP
        )
        mProgressPaint!!.shader = linearShader


        //等于0时
        val rect = RectF()
        rect.left = mProgressMarginLeftRight
        rect.top = mProgressMarginTopBottom
        rect.right = mMoveProgress + mProgressMarginLeftRight
        rect.bottom = mProgressHeight + mProgressMarginTopBottom
        canvas.drawRoundRect(rect, mProgressRadius, mProgressRadius, mProgressPaint!!)


        //计算比例
          
        mGraduationResult =
            ((mMoveProgress / getViewWidth()) * mMaxProgress).roundToInt()//(endProgress * mMaxProgress).roundToInt()

        if (mGraduationResult < 1) {
            mGraduationResult = if (mGraduationSectionZero) {
                0
            } else {
                1
            }
        }
        if (mGraduationResult >= mMaxProgress) {
            mGraduationResult = mMaxProgress
        }

        mMoveProgressCallback?.invoke(mGraduationResult)

    }

5. テキストスケールを描画する

実際、離散間隔と下部の座標テキスト スケールは実際には 1 対 1 に対応していることがわかります。これらは相互に関連しているため、離散間隔を横断するときに、これらを直接まとめることができます。間隔では、一番下のスケールに座標スケールを直接描画します。

座標スケールには 4 つの効果があります。1 つ目はスケール値なし、2 つ目は開始スケール値と終了スケール値のみ、3 つ目はすべてのスケール値を表示する、4 つ目はスケール値が 0 から始まるか 1 から始まるかです。

mIsGraduation はスケール値が必要かどうかを決定するために使用される変数です。true の場合は描画する必要があり、そうでない場合は描画されません。つまり、スケール値は必要ありません。mHideGraduationSectionCenter は中間スケールを非表示にする変数です。非表示に設定すると true、それ以外の場合は非表示になりません。具体的なコードは次のとおりです。

 /**
     * AUTHOR:AbnerMing
     * INTRODUCE:绘制离散间隔
     */
    private fun canvasIntervalLine(canvas: Canvas, isCanvas: Boolean) {
        val rect =
            (width - mProgressMarginLeftRight * 2 - mIntervalParentLeftRight * 2) / mIntervalSize
        if (isCanvas) {
            mPaint!!.color = mIntervalSelectColor
        } else {
            mPaint!!.color = mIntervalColor
        }

        mPaint!!.strokeWidth = mIntervalWidth
        for (a in 0..mIntervalSize) {
            val x = (rect * a) + mProgressMarginLeftRight + mIntervalParentLeftRight
            val y = mIntervalMarginTopBottom + mProgressMarginTopBottom
            canvas.drawLine(
                x,
                y,
                x,
                mProgressHeight + mProgressMarginTopBottom - mIntervalMarginTopBottom,
                mPaint!!
            )
            //绘制刻度值
            if (mIsGraduation && isCanvas) {

                if (mHideGraduationSectionCenter && (a != 0 && a != mIntervalSize)) {
                    //隐藏中间
                    continue
                }

                var graduation = a * mGraduationSection
                //是否从0开始记录
                if (graduation == 0 && !mGraduationSectionZero) {
                    graduation = 1
                }

                //如果移动到了,改变颜色


                if (mGraduationResult >= graduation && mGraduationResult < graduation + mGraduationSection) {
                    mGraduationPaint?.color = mGraduationSelectTextColor
                } else {
                    mGraduationPaint?.color = mGraduationTextColor
                }


                val text = graduation.toString()
                val rectText = Rect()
                mGraduationPaint!!.getTextBounds(text, 0, text.length, rectText)
                val textWidth = rectText.width()
                val textHeight = rectText.height()
                canvas.drawText(
                    text,
                    x - textWidth / 2,
                    mProgressHeight + mProgressMarginTopBottom * 2 + textHeight + mGraduationMarginTop,
                    mGraduationPaint!!
                )

            }

        }
    }

3. オープンソースのアドレスと使用法

現在、Github にアップロードされていますが、クラス自体は単純なもので、それほど多くはありませんが、必要な場合はソースコードを直接見ることができます。

アドレス: https://github.com/AbnerMing888/MoveProgress

ソースコードをダウンロードするのが面倒で、直接使用したい場合は、リモートの Maven にアップロードしておきますので、問題ありません。

1. ルート プロジェクトの build.gradle ファイルで、maven をインポートします。

allprojects {
    repositories {
        maven { url "https://gitee.com/AbnerAndroid/almighty/raw/master" }
    }
}

2. 使用する必要があるモジュールの build.gradle ファイルの下に、依存関係を導入します。

dependencies {
    implementation 'com.vip:moveprogress:1.0.0'
}

3.XMLをインポートするだけ

 <com.vip.moveprogress.MoveProgress
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:ms_graduation_hide_center="true" />

関連する属性

属性

タイプ

概要

ms_height

寸法

View ビューの高さ

ms_progress_height

寸法

プログレスバーの高さ

ms_progress_thumb

参照

プログレスバーのアイコン

ms_progress_margin_top_bottom

寸法

アイコンからのプログレスバーの上下の距離

ms_progress_margin_left_right

寸法

プログレスバーの左右のマージンからの距離

ms_progress_radius

寸法

プログレスバーの角が丸い

ms_progress_background

プログレスバーの背景色

ms_interval_color

スペーサーの色

ms_interval_select_color

間隔選択色

ms_interval_parent_margin_left_right

寸法

左右の親からのスペーサー距離

ms_interval_size

整数

間隔線の数

ms_interval_width

寸法

スペーサー幅

ms_interval_margin_top_bottom

寸法

スペーサーの上下マージン

ms_progress_move_color

参照

定義されたモバイルカラー

ms_progress_max

整数

最大限の進歩

ms_progress_default

整数

デフォルトの進行状況

ms_is_卒業

ブール値

スケールを表示するかどうか

ms_graduation_text_size

寸法

テキストサイズの拡大縮小

ms_graduation_text_color

スケールのテキストの色

ms_graduation_select_text_color

スケールテキスト選択の色

ms_graduation_section

整数

スケール値セグメント

ms_graduation_section_zero

ブール値

スケール値セグメントはゼロから始まります

ms_graduation_hide_center

ブール値

スケール値セグメントの中央を非表示にするかどうか

ms_graduation_margin_top

寸法

スケール値から上端までの距離

ms_progress_thumb_width

寸法

アイコンの幅

ms_progress_thumb_height

寸法

アイコンの高さ

ms_progress_thumb_margin_top

寸法

アイコンの上からの高さ

ms_progress_thumb_spacing

寸法

アイコンのパディング

ms_progress_disallow_intercept

ブール値

インターセプトするかどうか

ms_progress_is_intercept

ブール値

ドラッグ禁止かどうか

関連するメソッド

方法

パラメータ

概要

進行状況を取得する

参考文献なし

現在の進行状況を返す

変更の進捗状況

内部

現在の進行状況を変更する

getMoveProgress

整数を返します

折り返し電話

setProgressIsIntercept

ブール値

インターセプトするかどうかを設定する

4. まとめ

勾配に関しては、勾配の範囲がデフォルトの左から右への固定距離ではなく、左から指をスライドさせた距離までとなることに注意が必要です。終端の座標は、左側のジェスチャの動的設定に従っている必要があります。

この単純なドラッグ アンド ドロップの進行状況バーから、キャンバスに線、角丸四角形、画像、およびジェスチャと組み合わせて関連する知識ポイントを描画すること自体は難しくないことがわかります。

おすすめ

転載: blog.csdn.net/ming_147/article/details/131251506