Android實現(xiàn)可拖拽帶有坐標尺進度條的示例代碼
拿到下面的UI效果圖,給我的第一印象就是這實現(xiàn)起來也太簡單了吧,SeekBar輕輕松松就搞定了,換個thumb,加個漸變不就完成了,說搞就搞,搞著搞著就抑郁了,底部坐標尺還能搞,等比例分割后,在SeekBar下面多設置幾個TextView就行了,中間的等比例小分割線怎么搞?而且滑動前滑動后都需要有,并且,左右的分割線還要留出一小段間距,漸變顏色要跟著滑動的距離進行展示,而不是整個寬度展示,在多種條件下,SeekBar就很難滿足這個需求了,怎么辦?只能自定義了。
還是按照慣例,粗略的列一個大綱:
1、分析要素,確定實現(xiàn)方案
2、主要代碼進行刨析
3、開源地址及使用方式
4、總結
一、分析要素,確定實現(xiàn)方案
Canvas繪制這樣的一個可拖拽坐標尺,基本上可以拆分出四部分,第一部分就是背景和默認的離散間隔,第二部分是移動的背景和離散間隔,第三部分是移動的圖片也就是thumb,最后一部分是底部的文字坐標。
四部分基本上就繪制出來了,但是除了繪制之外,還需要考慮一下其他的因素,比如高度,比如手指的移動事件等。
1、設置默認高度
設置默認高度的原因,是為了讓View更好的展示一個合適的尺寸,不至于設置wrap_content時不展示,具體的設置可以根據(jù)當前設置的模式來控制,關于模式呢,有三種,這個在之前的文章中介紹過,這里就不詳細介紹了,當控件設置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方法,在這個方法里,需要針對手指的按下、抬起、移動做相應的處理。
在onTouchEvent里我做了如下處理,一是直接返回,不執(zhí)行事件的消費,目的是讓自定義View可實現(xiàn)靜態(tài)展示和動態(tài)展示兩種效果,通過一個變量mProgressIsIntercept來控制;第二個是解決與父View的滑動沖突事件,在有橫向或者縱向滑動事件時,在拖動的時候,難免會有沖突,那么就需要通知父View不要消費事件,也就是執(zhí)行requestDisallowInterceptTouchEvent方法。
所有的拖拽效果,都是在move事件,不斷的改變坐標執(zhí)行更新UI的方式實現(xiàn)的,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、繪制離散間隔
離散間隔,需要確定,間隔數(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
關于thumb,首先要確定的就是大小,如果設置了寬高,那么就需要使用Bitmap重新設置高度,改變thumb的坐標,只需要不斷的改變圖片的left坐標點即可,也就是通過上述的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參數(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、繪制移動進度
移動的進度,和背景的繪制是一樣的,只不過需要按照手指的坐標一點一點的移動距離,也就是不斷的改變右邊的坐標值,同樣的,也是通過Move事件中的mMoveProgress進度來動態(tài)的計算。進度的漸變比較簡單,使用的是畫筆的shader屬性,當前使用的是橫向的線性漸變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),離散間隔和底部的坐標文字刻度,其實是一一對應的,既然是相互關聯(lián),我們直接放到一起就可以,也就是在遍歷離散間隔的時候,我們直接繪制底部的坐標尺刻度。
坐標刻度,有四種效果,第一種是不要刻度值,第二種是只要開始和結尾刻度值,第三種是展示所有的刻度值,第四種是刻度值是從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" />
相關屬性
屬性 | 類型 | 概述 |
---|---|---|
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 | 是否禁止拖拽 |
相關方法
方法 | 參數(shù) | 概述 |
---|---|---|
getProgress | 無參 | 返回當前進度 |
changeProgress | Int | 改變當前進度 |
getMoveProgress | 返回Int | 回調(diào)函數(shù) |
setProgressIsIntercept | Boolean | 設置是否進行攔截 |
四、總結
關于漸變,需要注意,漸變的范圍不是默認的從左到右固定的距離,而是從左到手指滑動的距離,這一點需要注意,也就是在設置漸變的時候,終止的X坐標需要根據(jù)手勢的左邊動態(tài)設置。
從這個簡單的拖拽進度條,我們可以了解到,canvas繪制線,圓角矩形,圖片以及和手勢結合的相關知識點,本身并沒有難點。
以上就是Android實現(xiàn)可拖拽帶有坐標尺進度條的示例代碼的詳細內(nèi)容,更多關于Android可拖拽進度條的資料請關注腳本之家其它相關文章!
相關文章
使用IntelliJ IDEA 配置安卓(Android)開發(fā)環(huán)境的教程詳解(新手必看)
這篇文章主要介紹了使用IntelliJ IDEA 配置安卓(Android)開發(fā)環(huán)境的教程詳解(新手必看),本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09Android編譯出現(xiàn)Warning:Mapping?new?ns?to?old?ns報錯的解決方案
android在編譯的過程中難免會出現(xiàn)些錯誤,下面這篇文章主要給大家介紹了關于Android編譯出現(xiàn)Warning:Mapping?new?ns?to?old?ns報錯的解決方案,需要的朋友可以參考下2023-02-02