Android實(shí)現(xiàn)可拖拽帶有坐標(biāo)尺進(jìn)度條的示例代碼
拿到下面的UI效果圖,給我的第一印象就是這實(shí)現(xiàn)起來(lái)也太簡(jiǎn)單了吧,SeekBar輕輕松松就搞定了,換個(gè)thumb,加個(gè)漸變不就完成了,說(shuō)搞就搞,搞著搞著就抑郁了,底部坐標(biāo)尺還能搞,等比例分割后,在SeekBar下面多設(shè)置幾個(gè)TextView就行了,中間的等比例小分割線怎么搞?而且滑動(dòng)前滑動(dòng)后都需要有,并且,左右的分割線還要留出一小段間距,漸變顏色要跟著滑動(dòng)的距離進(jìn)行展示,而不是整個(gè)寬度展示,在多種條件下,SeekBar就很難滿足這個(gè)需求了,怎么辦?只能自定義了。
還是按照慣例,粗略的列一個(gè)大綱:
1、分析要素,確定實(shí)現(xiàn)方案
2、主要代碼進(jìn)行刨析
3、開(kāi)源地址及使用方式
4、總結(jié)
一、分析要素,確定實(shí)現(xiàn)方案
Canvas繪制這樣的一個(gè)可拖拽坐標(biāo)尺,基本上可以拆分出四部分,第一部分就是背景和默認(rèn)的離散間隔,第二部分是移動(dòng)的背景和離散間隔,第三部分是移動(dòng)的圖片也就是thumb,最后一部分是底部的文字坐標(biāo)。
四部分基本上就繪制出來(lái)了,但是除了繪制之外,還需要考慮一下其他的因素,比如高度,比如手指的移動(dòng)事件等。
1、設(shè)置默認(rèn)高度
設(shè)置默認(rèn)高度的原因,是為了讓View更好的展示一個(gè)合適的尺寸,不至于設(shè)置wrap_content時(shí)不展示,具體的設(shè)置可以根據(jù)當(dāng)前設(shè)置的模式來(lái)控制,關(guān)于模式呢,有三種,這個(gè)在之前的文章中介紹過(guò),這里就不詳細(xì)介紹了,當(dāng)控件設(shè)置wrap_content時(shí),此時(shí)的模式為MeasureSpec.AT_MOST,在這個(gè)模式下,我們就要給一個(gè)默認(rèn)的高度。
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()//默認(rèn)的高度 } setMeasuredDimension(widthMeasureSpec, windowHeight) }
2、拖動(dòng)事件
實(shí)現(xiàn)拖動(dòng)效果,我們就需要監(jiān)聽(tīng)用戶的手指移動(dòng)事件了,也就是在自定義View中我們要重寫onTouchEvent方法,在這個(gè)方法里,需要針對(duì)手指的按下、抬起、移動(dòng)做相應(yīng)的處理。
在onTouchEvent里我做了如下處理,一是直接返回,不執(zhí)行事件的消費(fèi),目的是讓自定義View可實(shí)現(xiàn)靜態(tài)展示和動(dòng)態(tài)展示兩種效果,通過(guò)一個(gè)變量mProgressIsIntercept來(lái)控制;第二個(gè)是解決與父View的滑動(dòng)沖突事件,在有橫向或者縱向滑動(dòng)事件時(shí),在拖動(dòng)的時(shí)候,難免會(huì)有沖突,那么就需要通知父View不要消費(fèi)事件,也就是執(zhí)行requestDisallowInterceptTouchEvent方法。
所有的拖拽效果,都是在move事件,不斷的改變坐標(biāo)執(zhí)行更新UI的方式實(shí)現(xiàn)的,mMoveProgress就是手指移動(dòng)的坐標(biāo)。
onTouchEvent(event: MotionEvent?): Boolean { super.onTouchEvent(event) //如果為true直接返回,不進(jìn)行拖拽 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 -> { //移動(dòng) var moveX = getChangeX(event.x) //滑動(dòng)至最右邊 //計(jì)算最后邊的坐標(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 }
二、主要代碼進(jìn)行刨析
1、繪制背景
背景沒(méi)什么好說(shuō)的,就是一個(gè)簡(jiǎn)單的圓角矩形,使用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ù)量,動(dòng)態(tài)的計(jì)算每個(gè)間隔的位置,可以使用drawLine繪制一個(gè)小小的豎線,豎線也需要確定距離上下的距離和自身的寬度;特殊情況下,離散間隔,在滑動(dòng)前后的顏色是不一樣的,所以這里也做了一個(gè)動(dòng)態(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、繪制移動(dòng)thumb
關(guān)于thumb,首先要確定的就是大小,如果設(shè)置了寬高,那么就需要使用Bitmap重新設(shè)置高度,改變thumb的坐標(biāo),只需要不斷的改變圖片的left坐標(biāo)點(diǎn)即可,也就是通過(guò)上述的Move事件的中移動(dòng)坐標(biāo)來(lái)設(shè)置。
/** * AUTHOR:AbnerMing * INTRODUCE:繪制移動(dòng)的圖標(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 // 計(jì)算縮放比例 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、繪制移動(dòng)進(jìn)度
移動(dòng)的進(jìn)度,和背景的繪制是一樣的,只不過(guò)需要按照手指的坐標(biāo)一點(diǎn)一點(diǎn)的移動(dòng)距離,也就是不斷的改變右邊的坐標(biāo)值,同樣的,也是通過(guò)Move事件中的mMoveProgress進(jìn)度來(lái)動(dòng)態(tài)的計(jì)算。進(jìn)度的漸變比較簡(jiǎn)單,使用的是畫筆的shader屬性,當(dāng)前使用的是橫向的線性漸變LinearGradient。
/** * AUTHOR:AbnerMing * INTRODUCE:繪制進(jìn)度 */ 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時(shí) val rect = RectF() rect.left = mProgressMarginLeftRight rect.top = mProgressMarginTopBottom rect.right = mMoveProgress + mProgressMarginLeftRight rect.bottom = mProgressHeight + mProgressMarginTopBottom canvas.drawRoundRect(rect, mProgressRadius, mProgressRadius, mProgressPaint!!) //計(jì)算比例 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、繪制文字刻度
其實(shí)大家可以發(fā)現(xiàn),離散間隔和底部的坐標(biāo)文字刻度,其實(shí)是一一對(duì)應(yīng)的,既然是相互關(guān)聯(lián),我們直接放到一起就可以,也就是在遍歷離散間隔的時(shí)候,我們直接繪制底部的坐標(biāo)尺刻度。
坐標(biāo)刻度,有四種效果,第一種是不要刻度值,第二種是只要開(kāi)始和結(jié)尾刻度值,第三種是展示所有的刻度值,第四種是刻度值是從0還是從1開(kāi)始。
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開(kāi)始記錄 if (graduation == 0 && !mGraduationSectionZero) { graduation = 1 } //如果移動(dòng)到了,改變顏色 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!! ) } } }
三、開(kāi)源地址及使用方式
目前已經(jīng)上傳到了Github,本身就一個(gè)簡(jiǎn)單的類,沒(méi)多少東西,需要的鐵子,可以直接查看源碼即可。
地址:github.com/AbnerMing888/MoveProgress
如果懶得下載源碼,想直接使用,沒(méi)得問(wèn)題,我已經(jīng)上傳到了遠(yuǎn)程Maven,大家可以依賴使用。
1、在你的根項(xiàng)目下的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 | 進(jìn)度條的高度 |
ms_progress_thumb | reference | 進(jìn)度條的Icon |
ms_progress_margin_top_bottom | dimension | 進(jìn)度條距離icon的上下距離 |
ms_progress_margin_left_right | dimension | 進(jìn)度條距離左右的邊距 |
ms_progress_radius | dimension | 進(jìn)度條的圓角 |
ms_progress_background | color | 進(jìn)度條的背景顏色 |
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 | 定義的移動(dòng)顏色 |
ms_progress_max | integer | 最大進(jìn)度 |
ms_progress_default | integer | 默認(rèn)進(jìn)度 |
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 | 刻度值段從零開(kāi)始 |
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 | 無(wú)參 | 返回當(dāng)前進(jìn)度 |
changeProgress | Int | 改變當(dāng)前進(jìn)度 |
getMoveProgress | 返回Int | 回調(diào)函數(shù) |
setProgressIsIntercept | Boolean | 設(shè)置是否進(jìn)行攔截 |
四、總結(jié)
關(guān)于漸變,需要注意,漸變的范圍不是默認(rèn)的從左到右固定的距離,而是從左到手指滑動(dòng)的距離,這一點(diǎn)需要注意,也就是在設(shè)置漸變的時(shí)候,終止的X坐標(biāo)需要根據(jù)手勢(shì)的左邊動(dòng)態(tài)設(shè)置。
從這個(gè)簡(jiǎn)單的拖拽進(jìn)度條,我們可以了解到,canvas繪制線,圓角矩形,圖片以及和手勢(shì)結(jié)合的相關(guān)知識(shí)點(diǎn),本身并沒(méi)有難點(diǎn)。
以上就是Android實(shí)現(xiàn)可拖拽帶有坐標(biāo)尺進(jìn)度條的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Android可拖拽進(jìn)度條的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實(shí)現(xiàn)懸浮可拖拽的Button
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)懸浮可拖拽的Button,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06基于android實(shí)現(xiàn)五子棋開(kāi)發(fā)
這篇文章主要為大家詳細(xì)介紹了基于android實(shí)現(xiàn)五子棋開(kāi)發(fā),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02Android Flutter實(shí)現(xiàn)原理淺析
這篇文章主要介紹了Android Flutter的實(shí)現(xiàn)原理是怎么樣的,flutter可以說(shuō)是當(dāng)下最流行的跨平臺(tái)技術(shù)了,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08Android模仿知乎的回答詳情頁(yè)的動(dòng)畫效果
這篇文章主要介紹了Android模仿“知乎”的回答詳情頁(yè)的動(dòng)畫效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02使用IntelliJ IDEA 配置安卓(Android)開(kāi)發(fā)環(huán)境的教程詳解(新手必看)
這篇文章主要介紹了使用IntelliJ IDEA 配置安卓(Android)開(kāi)發(fā)環(huán)境的教程詳解(新手必看),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Android編譯出現(xiàn)Warning:Mapping?new?ns?to?old?ns報(bào)錯(cuò)的解決方案
android在編譯的過(guò)程中難免會(huì)出現(xiàn)些錯(cuò)誤,下面這篇文章主要給大家介紹了關(guān)于Android編譯出現(xiàn)Warning:Mapping?new?ns?to?old?ns報(bào)錯(cuò)的解決方案,需要的朋友可以參考下2023-02-02Android小程序?qū)崿F(xiàn)訪問(wèn)聯(lián)系人
這篇文章主要為大家詳細(xì)介紹了Android小程序?qū)崿F(xiàn)訪問(wèn)聯(lián)系人,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一2020-05-05