上の 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. まとめ
勾配に関しては、勾配の範囲がデフォルトの左から右への固定距離ではなく、左から指をスライドさせた距離までとなることに注意が必要です。終端の座標は、左側のジェスチャの動的設定に従っている必要があります。
この単純なドラッグ アンド ドロップの進行状況バーから、キャンバスに線、角丸四角形、画像、およびジェスチャと組み合わせて関連する知識ポイントを描画すること自体は難しくないことがわかります。