Android實現(xiàn)可拖拽帶有坐標(biāo)尺進度條的示例代碼
拿到下面的UI效果圖,給我的第一印象就是這實現(xiàn)起來也太簡單了吧,SeekBar輕輕松松就搞定了,換個thumb,加個漸變不就完成了,說搞就搞,搞著搞著就抑郁了,底部坐標(biāo)尺還能搞,等比例分割后,在SeekBar下面多設(shè)置幾個TextView就行了,中間的等比例小分割線怎么搞?而且滑動前滑動后都需要有,并且,左右的分割線還要留出一小段間距,漸變顏色要跟著滑動的距離進行展示,而不是整個寬度展示,在多種條件下,SeekBar就很難滿足這個需求了,怎么辦?只能自定義了。

還是按照慣例,粗略的列一個大綱:
1、分析要素,確定實現(xiàn)方案
2、主要代碼進行刨析
3、開源地址及使用方式
4、總結(jié)
一、分析要素,確定實現(xiàn)方案
Canvas繪制這樣的一個可拖拽坐標(biāo)尺,基本上可以拆分出四部分,第一部分就是背景和默認的離散間隔,第二部分是移動的背景和離散間隔,第三部分是移動的圖片也就是thumb,最后一部分是底部的文字坐標(biāo)。
四部分基本上就繪制出來了,但是除了繪制之外,還需要考慮一下其他的因素,比如高度,比如手指的移動事件等。
1、設(shè)置默認高度
設(shè)置默認高度的原因,是為了讓View更好的展示一個合適的尺寸,不至于設(shè)置wrap_content時不展示,具體的設(shè)置可以根據(jù)當(dāng)前設(shè)置的模式來控制,關(guān)于模式呢,有三種,這個在之前的文章中介紹過,這里就不詳細介紹了,當(dāng)控件設(shè)置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、拖動事件
實現(xiàn)拖動效果,我們就需要監(jiān)聽用戶的手指移動事件了,也就是在自定義View中我們要重寫onTouchEvent方法,在這個方法里,需要針對手指的按下、抬起、移動做相應(yīng)的處理。
在onTouchEvent里我做了如下處理,一是直接返回,不執(zhí)行事件的消費,目的是讓自定義View可實現(xiàn)靜態(tài)展示和動態(tài)展示兩種效果,通過一個變量mProgressIsIntercept來控制;第二個是解決與父View的滑動沖突事件,在有橫向或者縱向滑動事件時,在拖動的時候,難免會有沖突,那么就需要通知父View不要消費事件,也就是執(zhí)行requestDisallowInterceptTouchEvent方法。
所有的拖拽效果,都是在move事件,不斷的改變坐標(biāo)執(zhí)行更新UI的方式實現(xiàn)的,mMoveProgress就是手指移動的坐標(biāo)。
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)
//滑動至最右邊
//計算最后邊的坐標(biāo)
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、繪制離散間隔
離散間隔,需要確定,間隔數(shù),然后根據(jù)間隔數(shù)量,動態(tài)的計算每個間隔的位置,可以使用drawLine繪制一個小小的豎線,豎線也需要確定距離上下的距離和自身的寬度;特殊情況下,離散間隔,在滑動前后的顏色是不一樣的,所以這里也做了一個動態(tài)改變顏色的判斷。
/**
* 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、繪制移動thumb
關(guān)于thumb,首先要確定的就是大小,如果設(shè)置了寬高,那么就需要使用Bitmap重新設(shè)置高度,改變thumb的坐標(biāo),只需要不斷的改變圖片的left坐標(biāo)點即可,也就是通過上述的Move事件的中移動坐標(biāo)來設(shè)置。
/**
* AUTHOR:AbnerMing
* INTRODUCE:繪制移動的圖標(biāo)
*/
private fun canvasMoveIcon(canvas: Canvas) {
mProgressThumb?.let {
var decodeResource = BitmapFactory.decodeResource(resources, it)
mProgressThumbWidth = decodeResource.width
if (mThumbWidth != 0f) {
val height: Int = decodeResource.height
// 設(shè)置想要的大小
val newWidth = mThumbWidth
val newHeight = mThumbHeight
// 計算縮放比例
val scaleWidth = newWidth / width
val scaleHeight = newHeight / height
// 取得想要縮放的matrix參數(shù)
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、繪制移動進度
移動的進度,和背景的繪制是一樣的,只不過需要按照手指的坐標(biāo)一點一點的移動距離,也就是不斷的改變右邊的坐標(biāo)值,同樣的,也是通過Move事件中的mMoveProgress進度來動態(tài)的計算。進度的漸變比較簡單,使用的是畫筆的shader屬性,當(dāng)前使用的是橫向的線性漸變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、繪制文字刻度
其實大家可以發(fā)現(xiàn),離散間隔和底部的坐標(biāo)文字刻度,其實是一一對應(yīng)的,既然是相互關(guān)聯(lián),我們直接放到一起就可以,也就是在遍歷離散間隔的時候,我們直接繪制底部的坐標(biāo)尺刻度。
坐標(biāo)刻度,有四種效果,第一種是不要刻度值,第二種是只要開始和結(jié)尾刻度值,第三種是展示所有的刻度值,第四種是刻度值是從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!!
)
}
}
}三、開源地址及使用方式
目前已經(jīng)上傳到了Github,本身就一個簡單的類,沒多少東西,需要的鐵子,可以直接查看源碼即可。
地址:github.com/AbnerMing888/MoveProgress
如果懶得下載源碼,想直接使用,沒得問題,我已經(jīng)上傳到了遠程Maven,大家可以依賴使用。
1、在你的根項目下的build.gradle文件下,引入maven。
allprojects {
repositories {
maven { url "https://gitee.com/AbnerAndroid/almighty/raw/master" }
}
}2、在你需要使用的Module中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" />相關(guān)屬性
| 屬性 | 類型 | 概述 |
|---|---|---|
| ms_height | dimension | View視圖的高度 |
| ms_progress_height | dimension | 進度條的高度 |
| ms_progress_thumb | reference | 進度條的Icon |
| ms_progress_margin_top_bottom | dimension | 進度條距離icon的上下距離 |
| ms_progress_margin_left_right | dimension | 進度條距離左右的邊距 |
| ms_progress_radius | dimension | 進度條的圓角 |
| ms_progress_background | color | 進度條的背景顏色 |
| ms_interval_color | color | 間隔線顏色 |
| ms_interval_select_color | color | 間隔線選中顏色 |
| ms_interval_parent_margin_left_right | dimension | 間隔線距離父左右 |
| ms_interval_size | integer | 間隔線數(shù)量 |
| ms_interval_width | dimension | 間隔線寬度 |
| ms_interval_margin_top_bottom | dimension | 間隔線上下邊距 |
| ms_progress_move_color | reference | 定義的移動顏色 |
| ms_progress_max | integer | 最大進度 |
| ms_progress_default | integer | 默認進度 |
| ms_is_graduation | boolean | 是否顯示刻度尺 |
| ms_graduation_text_size | dimension | 刻度尺文字大小 |
| ms_graduation_text_color | color | 刻度尺文字顏色 |
| ms_graduation_select_text_color | color | 刻度尺文字選中顏色 |
| ms_graduation_section | integer | 刻度值段 |
| ms_graduation_section_zero | boolean | 刻度值段從零開始 |
| ms_graduation_hide_center | boolean | 刻度值段中間是否隱藏 |
| ms_graduation_margin_top | dimension | 刻度值距離上邊的距離 |
| ms_progress_thumb_width | dimension | icon的寬 |
| ms_progress_thumb_height | dimension | icon的高 |
| ms_progress_thumb_margin_top | dimension | icon距離上邊的高度 |
| ms_progress_thumb_spacing | dimension | icon的內(nèi)邊距 |
| ms_progress_disallow_intercept | boolean | 是否攔截 |
| ms_progress_is_intercept | boolean | 是否禁止拖拽 |
相關(guān)方法
| 方法 | 參數(shù) | 概述 |
|---|---|---|
| getProgress | 無參 | 返回當(dāng)前進度 |
| changeProgress | Int | 改變當(dāng)前進度 |
| getMoveProgress | 返回Int | 回調(diào)函數(shù) |
| setProgressIsIntercept | Boolean | 設(shè)置是否進行攔截 |
四、總結(jié)
關(guān)于漸變,需要注意,漸變的范圍不是默認的從左到右固定的距離,而是從左到手指滑動的距離,這一點需要注意,也就是在設(shè)置漸變的時候,終止的X坐標(biāo)需要根據(jù)手勢的左邊動態(tài)設(shè)置。
從這個簡單的拖拽進度條,我們可以了解到,canvas繪制線,圓角矩形,圖片以及和手勢結(jié)合的相關(guān)知識點,本身并沒有難點。
以上就是Android實現(xiàn)可拖拽帶有坐標(biāo)尺進度條的示例代碼的詳細內(nèi)容,更多關(guān)于Android可拖拽進度條的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用IntelliJ IDEA 配置安卓(Android)開發(fā)環(huán)境的教程詳解(新手必看)
這篇文章主要介紹了使用IntelliJ IDEA 配置安卓(Android)開發(fā)環(huán)境的教程詳解(新手必看),本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
Android編譯出現(xiàn)Warning:Mapping?new?ns?to?old?ns報錯的解決方案
android在編譯的過程中難免會出現(xiàn)些錯誤,下面這篇文章主要給大家介紹了關(guān)于Android編譯出現(xiàn)Warning:Mapping?new?ns?to?old?ns報錯的解決方案,需要的朋友可以參考下2023-02-02
Android小程序?qū)崿F(xiàn)訪問聯(lián)系人
這篇文章主要為大家詳細介紹了Android小程序?qū)崿F(xiàn)訪問聯(lián)系人,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一2020-05-05

