代碼地址以下:<br>http://www.demodashi.com/demo/13502.htmlhtml
簡介
主要提供一個漂亮而強大的自定義SeekBar,進度變化由提示牌 (sign)展現,具備強大的屬性設置,支持設置section(節點)、mark(標記)、track(軌跡)、thumb(拖動塊)、progress(進度)、sign(提示框)等功能java
主要功能
- 強大的track(軌跡)和second track (選中軌跡)的最小值、最大值、軌跡粗細,顏色等設置;
- 靈活的數字顯示,支持設置進度展現、節點文本展現採用整數仍是浮點數;
- 支持設置進度單位,例如 10s,15km/h、平方,對數等;
- 支持手柄拖動塊thumb半徑、顏色、陰影、透明度等;
- 支持節點個數、文字大小、顏色設置;
- 支持指示牌寬高、顏色、圓角半徑、三角arrow指示、border邊框、跟隨thumb移動等;
- 支持設置拖動進度監聽回掉;
- 支持格式化進度數字,徹底自定義進度樣式
- ......
演示
xml
<com.zhouyou.view.seekbar.SignSeekBar android:id="@+id/seek_bar" android:layout_width="match_parent" android:layout_height="16dp" app:ssb_section_text_position="bottom_sides" app:ssb_show_progress_in_float="false" app:ssb_show_section_mark="false" app:ssb_show_section_text="true" app:ssb_show_sign="true" app:ssb_show_thumb_text="false" app:ssb_sign_arrow_height="5dp" app:ssb_sign_arrow_width="10dp" app:ssb_sign_border_color="@color/color_red" app:ssb_sign_border_size="1dp" app:ssb_sign_color="@color/color_gray" app:ssb_sign_show_border="true"/>
java
signSeekBar.getConfigBuilder() .min(0) .max(4) .progress(2) .sectionCount(4) .trackColor(ContextCompat.getColor(getContext(), R.color.color_gray)) .secondTrackColor(ContextCompat.getColor(getContext(), R.color.color_blue)) .thumbColor(ContextCompat.getColor(getContext(), R.color.color_blue)) .sectionTextColor(ContextCompat.getColor(getContext(), R.color.colorPrimary)) .sectionTextSize(16) .thumbTextColor(ContextCompat.getColor(getContext(), R.color.color_red)) .thumbTextSize(18) .signColor(ContextCompat.getColor(getContext(), R.color.color_green)) .signTextSize(18) .autoAdjustSectionMark() .sectionTextPosition(SignSeekBar.TextPosition.BELOW_SECTION_MARK) .build();
回調
signSeekBar.setOnProgressChangedListener(new SignSeekBar.OnProgressChangedListener() { @Override public void onProgressChanged(SignSeekBar signSeekBar, int progress, float progressFloat,boolean fromUser) { //fromUser 表示是不是用戶觸發 是不是用戶touch事件產生 String s = String.format(Locale.CHINA, "onChanged int:%d, float:%.1f", progress, progressFloat); progressText.setText(s); } @Override public void getProgressOnActionUp(SignSeekBar signSeekBar, int progress, float progressFloat) { String s = String.format(Locale.CHINA, "onActionUp int:%d, float:%.1f", progress, progressFloat); progressText.setText(s); } @Override public void getProgressOnFinally(SignSeekBar signSeekBar, int progress, float progressFloat,boolean fromUser) { String s = String.format(Locale.CHINA, "onFinally int:%d, float:%.1f", progress, progressFloat); progressText.setText(s + getContext().getResources().getStringArray(R.array.labels)[progress]); } });
Attributes
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="SignSeekBar"> <attr name="ssb_min" format="float|reference"/> <!--min < max, default: 0.0f--> <attr name="ssb_max" format="float|reference"/> <!--min < max, default: 100.0f--> <attr name="ssb_progress" format="float|reference"/> <!--real time progress value, default: min--> <attr name="ssb_is_float_type" format="boolean"/> <!--support for float type--> <attr name="ssb_track_size" format="dimension|reference"/> <!--height of right-track(on the right of thumb), default: 2dp--> <!--height of left-track(on the left of thumb), default: 2dp higher than right-track's height--> <attr name="ssb_second_track_size" format="dimension|reference"/> <attr name="ssb_thumb_radius" format="dimension|reference"/> <!--radius of thumb, default: 2dp higher than left-track's height--> <!--radius of thumb when be dragging, default: 2 times of left-track's height--> <attr name="ssb_thumb_radius_on_dragging" format="dimension|reference"/> <attr name="ssb_track_color" format="color|reference"/> <!--color of right-track, default: R.color.colorPrimary--> <attr name="ssb_second_track_color" format="color|reference"/> <!--color of left-track, default: R.color.colorAccent--> <attr name="ssb_thumb_color" format="color|reference"/> <!--color of thumb, default: same as left-track's color--> <attr name="ssb_section_count" format="integer|reference"/> <!--shares of whole progress(max - min), default: 10--> <attr name="ssb_show_section_mark" format="boolean"/> <!--show demarcation points or not, default: false--> <attr name="ssb_auto_adjust_section_mark" format="boolean"/> <!--auto scroll to the nearest section_mark or not, default: false--> <attr name="ssb_show_section_text" format="boolean"/> <!--show section-text or not, default: false--> <attr name="ssb_section_text_size" format="dimension|reference"/> <!--text size of section-text, default: 14sp--> <attr name="ssb_section_text_color" format="color|reference"/> <!--text color of section-text, default: same as right-track's color--> <!--text position of section-text relative to track, sides, bottom_sides, below_section_mark, default: sides--> <attr name="ssb_section_text_position"> <enum name="sides" value="0"/> <enum name="bottom_sides" value="1"/> <enum name="below_section_mark" value="2"/> </attr> <attr name="ssb_section_text_interval" format="integer"/> <!--the interval of two section-text, default: 1--> <attr name="ssb_show_thumb_text" format="boolean"/> <!--show real time progress-text under thumb or not, default: false--> <attr name="ssb_thumb_text_size" format="dimension|reference"/> <!--text size of progress-text, default: 14sp--> <attr name="ssb_thumb_text_color" format="color|reference"/> <!--text color of progress-text, default: same as left-track's color--> <attr name="ssb_show_progress_in_float" format="boolean"/> <!--show Sign-progress in float or not, default: false--> <attr name="ssb_touch_to_seek" format="boolean"/> <!--touch anywhere on track to quickly seek, default: false--> <attr name="ssb_seek_by_section" format="boolean"/> <!--seek by section, the progress may not be linear, default: false--> <attr name="ssb_sign_color" format="color|reference"/> <!--color of sign, default: same as left-track's color--> <attr name="ssb_sign_border_color" format="color|reference"/> <!--color of sign, border--> <attr name="ssb_sign_show_border" format="boolean"/> <!--color of sign, default: same as left-track's color--> <attr name="ssb_sign_text_size" format="dimension|reference"/> <!--text size of sign-progress, default: 14sp--> <attr name="ssb_sign_border_size" format="dimension|reference"/> <!--border size, default: 1dp--> <attr name="ssb_sign_text_color" format="color|reference"/> <!--text color of sign-progress, default: #ffffffff--> <attr name="ssb_anim_duration" format="integer"/> <!--duration of animation, default: 200ms--> <attr name="ssb_show_sign" format="boolean"/> <!--hide sign, default: false--> <attr name="ssb_text_space" format="dimension|reference"/><!--default:2dp--> <attr name="ssb_sides_labels" format="reference"/><!--default:null--> <attr name="ssb_thumb_bg_alpha" format="float|reference"/> <!--0.0f-1.0f, default: 0.2f--> <attr name="ssb_thumb_ratio" format="float|reference"/> <!--0.0f-1.0f, default: 0.7f--> <attr name="ssb_show_thumb_shadow" format="boolean"/> <!--0.0f-1.0f, default: false--> <attr name="ssb_sign_arrow_autofloat" format="boolean"/> <!--sign arrow auto float, default: true--> <attr name="ssb_sign_height" format="dimension|reference"/> <!--sign height,default:22dp--> <attr name="ssb_sign_width" format="dimension|reference"/> <!--sign_width ,default:72dp--> <attr name="ssb_sign_arrow_height" format="dimension|reference"/> <!--sign arrow height ,default:3dp--> <attr name="ssb_sign_arrow_width" format="dimension|reference"/> <!--sign arrow width, default:5dp--> <attr name="ssb_sign_round" format="dimension|reference"/> <!--sign round, default:3dp--> </declare-styleable> </resources>
主要實現思路介紹
概況
本庫自定義控件主要是用了Canvas相關的drawXXX系列方法、一些簡單的算法和動畫來完成的。好比拖動軌跡、滑塊thumb拖動、放大、自動滾動最近節點、指示牌、區段節點標記、進度單位顯示等。接下來會講解下主要的實現思路,對於自定義View的其它基本流程,屬性獲取和設置、onMeasure的重寫等都不重點介紹,想了解完整流程請看源碼。android
track(軌道)繪製
畫軌道比較簡單,主要實現方式就是畫兩條不一樣顏色的線條(其實畫的是一條分爲左右兩部分,銜接的地方是有個thumb遮擋着),主要是要求出滑動thumb的中心點mThumbCenterX,mThumbCenterX的計算很是重要,本庫的不少計算都是圍繞mThumbCenterX,mThumbCenterX是經過onTouchEvent事件MotionEvent 根據down、move事件實時計算出中心點x座標。git
// draw track mPaint.setColor(mSecondTrackColor); mPaint.setStrokeWidth(mSecondTrackSize); canvas.drawLine(xLeft, yTop, mThumbCenterX, yTop, mPaint); // draw second track mPaint.setColor(mTrackColor); mPaint.setStrokeWidth(mTrackSize); canvas.drawLine(mThumbCenterX, yTop, xRight, yTop, mPaint);
track(軌道)接觸有效計算
MotionEvent的getX()和getY()得到的永遠是相對view的觸摸位置座標,getRawX()和getRawY()得到的是相對屏幕的位置,軌道計算用的是getX,getY 相對於容器的位置座標x,y,計算x,y座標是否在軌道的矩形方框內,從而判斷是否在軌道上。github
private boolean isTrackTouched(MotionEvent event) { return isEnabled() && event.getX() >= getPaddingLeft() && event.getX() <= getMeasuredWidth() - getPaddingRight() && event.getY() >= getPaddingTop() && event.getY() <= getMeasuredHeight() - getPaddingBottom(); }
thumb(滑塊)接觸有效計算
thumb就是軌道上的圓形滑塊,如何判斷手指拖動的區域是否在滑塊上呢,使用圓的標準方程(x-a)²+(y-b)²=r²來判斷算法
private boolean isThumbTouched(MotionEvent event) { if (!isEnabled()) return false; float mCircleR = isThumbOnDragging ? mThumbRadiusOnDragging : mThumbRadius; float x = mTrackLength / mDelta * (mProgress - mMin) + mLeft; float y = getMeasuredHeight() / 2f; return (event.getX() - x) * (event.getX() - x) + (event.getY() - y) * (event.getY() - y) <= (mLeft + mCircleR) * (mLeft + mCircleR); }
thumb(滑塊)透明度實現
滑塊的透明度,是將滑塊的顏色值進行計算加上alpha,求出一個新的顏色值,主要是使用Color這個工具類的方法,你們常常用到的是Color的parseColor(@Size(min=1) String colorString)方法,庫中主要用的是Color的另外一些方法alpha(int color)、red(int color)、green(int color)、blue(int color)方法分別求出argb值,求出的透明度通過計算修改後,再用 Color.argb(alpha, r, g, b)組合得出一個新的顏色值。canvas
/** * 計算新的透明度顏色 * * @param color 舊顏色 * @param ratio 透明度係數 */ public int getColorWithAlpha(int color, float ratio) { int newColor = 0; int alpha = Math.round(Color.alpha(color) * ratio); int r = Color.red(color); int g = Color.green(color); int b = Color.blue(color); newColor = Color.argb(alpha, r, g, b); return newColor; }
thumb(滑塊) 最近節點位置計算方法
根據節點個數mSectionCount和兩個節點之間的間隔mSectionOffset,與滑塊當前位置mThumbCenterX的比較,求出最近一個節點的位置。app
//計算最近節點位置,mSectionCount:節點個數,mSectionOffset:兩個節點間隔距離,mThumbCenterX:滑塊中心點位置 float x = 0; for (i = 0; i <= mSectionCount; i++) { x = i * mSectionOffset + mLeft; if (x <= mThumbCenterX && mThumbCenterX - x <= mSectionOffset) { break; } }
thumb(滑塊) 滾動最近節點動畫效果實現
滑塊自動滾動到最近節點增長了動畫移動效果,使用ValueAnimator實現動畫,Property Animation提供了Animator.AnimatorListener和Animator.AnimatorUpdateListener兩個監聽器用於動畫在播放過程當中的重要動畫事件。其中AnimatorUpdateListener監聽中onAnimationUpdate() 方法,動畫每播放一幀時調用,在動畫過程當中,可偵聽此事件來獲取並使用 ValueAnimator 計算出來的屬性值。利用傳入事件的 ValueAnimator 對象,調用其 getAnimatedValue() 方法便可獲取當前的屬性值,就是修改後滑塊的位置mThumbCenterX。此動畫還配合有Interpolator,動畫播放採用LinearInterpolator線性插值的方式執行動畫。插值器它定義了動畫變化過程當中的屬性變化規則,它根據時間比例因子計算出一個插值因子,用於設定目標對象的動畫執行是否爲線性變化、非線性變化或先加速後減速等等。Android系統自己內置了一些通用的Interpolator(插值器),以下:ide
類或接口名 | 說明 |
---|---|
AccelerateDecelerateInterpolator | 在動畫開始與結束的地方速率改變比較慢,在中間的時候加速 |
AccelerateInterpolator | 在動畫開始的地方速率改變比較慢,而後開始加速 |
AnticipateInterpolator | 開始的時候向後而後向前甩 |
AnticipateOvershootInterpolator | 開始的時候向後而後向前甩必定值後返回最後的值 |
BounceInterpolator | 動畫結束的時候彈起 |
CycleInterpolator | 動畫循環播放特定的次數,速率改變沿着正弦曲線 |
DecelerateInterpolator | 在動畫開始的地方快而後慢 |
LinearInterpolator | 以常量速率改變 |
OvershootInterpolator | 向前甩必定值後再回到原來位置 |
完整源碼:工具
private void autoAdjustSection() { int i; //計算最近節點位置,mSectionCount:節點個數,mSectionOffset:兩個節點間隔距離,mThumbCenterX:滑塊中心點位置 float x = 0; for (i = 0; i <= mSectionCount; i++) { x = i * mSectionOffset + mLeft; if (x <= mThumbCenterX && mThumbCenterX - x <= mSectionOffset) { break; } } BigDecimal bigDecimal = BigDecimal.valueOf(mThumbCenterX); //BigDecimal setScale保留1位小數,四捨五入,2.35變成2.4 float x_ = bigDecimal.setScale(1, BigDecimal.ROUND_HALF_UP).floatValue(); boolean onSection = x_ == x; // 就在section處,不做valueAnim,優化性能 AnimatorSet animatorSet = new AnimatorSet(); ValueAnimator valueAnim = null; if (!onSection) { if (mThumbCenterX - x <= mSectionOffset / 2f) { valueAnim = ValueAnimator.ofFloat(mThumbCenterX, x); } else { valueAnim = ValueAnimator.ofFloat(mThumbCenterX, (i + 1) * mSectionOffset + mLeft); } valueAnim.setInterpolator(new LinearInterpolator()); valueAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mThumbCenterX = (float) animation.getAnimatedValue(); mProgress = (mThumbCenterX - mLeft) * mDelta / mTrackLength + mMin; invalidate(); if (mProgressListener != null) { mProgressListener.onProgressChanged(SignSeekBar.this, getProgress(), getProgressFloat(),true); } } }); } if (!onSection) { animatorSet.setDuration(mAnimDuration).playTogether(valueAnim); } animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mProgress = (mThumbCenterX - mLeft) * mDelta / mTrackLength + mMin; isThumbOnDragging = false; isTouchToSeekAnimEnd = true; invalidate(); if (mProgressListener != null) { mProgressListener.getProgressOnFinally(SignSeekBar.this, getProgress(), getProgressFloat(),true); } } @Override public void onAnimationCancel(Animator animation) { mProgress = (mThumbCenterX - mLeft) * mDelta / mTrackLength + mMin; isThumbOnDragging = false; isTouchToSeekAnimEnd = true; invalidate(); } }); animatorSet.start(); }
採用BigDecimal處理小數
代碼中的小數採用BigDecimal來處理,只介紹setScale相關方法,其它更多方法能夠本身去學習,這裏只是拋磚引玉。 BigDecimal.setScale()方法用於格式化小數點 setScale(1)表示保留一位小數,默認用四捨五入方式 setScale(1,BigDecimal.ROUND_DOWN)直接刪除多餘的小數位,如2.35會變成2.3 setScale(1,BigDecimal.ROUND_UP)進位處理,2.35變成2.4 setScale(1,BigDecimal.ROUND_HALF_UP)四捨五入,2.35變成2.4 setScaler(1,BigDecimal.ROUND_HALF_DOWN)四捨五入,2.35變成2.3,若是是5則向下舍
Sign 提示框--三角形邊框繪製
單純的進度提示框實現比較簡單,主要是由矩形框+三角形組成,可是加邊框繪製的時候比較麻煩一點,須要留出矩形和三角形交接的地方不能畫線,這裏作了假象交接的地方其實額外繪製了三角形的底邊,顏色採用的是矩形庫填充的顏色。三角形邊框繪製以下:
private void drawTriangleBoder(Canvas canvas, Point point1, Point point2, Point point3, Paint paint) { triangleboderPath.reset(); triangleboderPath.moveTo(point1.x, point1.y); triangleboderPath.lineTo(point2.x, point2.y); paint.setColor(signPaint.getColor()); float value = mSignBorderSize / 6; paint.setStrokeWidth(mSignBorderSize + 1f); canvas.drawPath(triangleboderPath, paint); triangleboderPath.reset(); paint.setStrokeWidth(mSignBorderSize); triangleboderPath.moveTo(point1.x - value, point1.y - value); triangleboderPath.lineTo(point3.x, point3.y); triangleboderPath.lineTo(point2.x + value, point2.y - value); paint.setColor(mSignBorderColor); canvas.drawPath(triangleboderPath, paint); }
Sign 提示框--進度單位unit實現方式
進度單位不少需求也是須要的,不是單純的用canvas.drawText來繪製。這裏採用的是StaticLayout。使用Canvas的drawText繪製文本是不會自動換行的,即便一個很長很長的字符串,drawText也只顯示一行,超出部分被隱藏在屏幕以外。能夠逐個計算每一個字符的寬度,經過必定的算法將字符串分割成多個部分,而後分別調用drawText一部分一部分的顯示, 可是這種顯示效率會很低。StaticLayout是android中處理文字換行的一個工具類,StaticLayout已經實現了文本繪製換行處理,也支持標籤屬性<small>,m/s<sup>2</sup>,μmol/l,μ/l
從而實現強大靈活的單位設置。
private void createValueTextLayout() { String value = isShowProgressInFloat ? String.valueOf(getProgressFloat()) : String.valueOf(getProgress()); if (value != null && unit != null && !unit.isEmpty()) value += String.format(" <small>%s</small>", unit); Spanned spanned = Html.fromHtml(value); valueTextLayout = new StaticLayout(spanned, valueTextPaint, mSignWidth, Layout.Alignment.ALIGN_CENTER, 1, 0, false); }
圓圈中心繪製文本
圓圈中心繪製文字,高度是比較難控制的,特別是中文,不能簡單的經過bounds.height()來獲取高度的方式計算,須要先求出baseline這種方式來處理,求baseline的方式是固定的。下面提供一個通用的方法:
/** * 精確畫圓圈中心文字(通用方法),其中文字的高度是最難計算適配的,採用此方法,能夠完美解決 * * @param canvas 畫板 * @param paint 畫筆panit * @param centerX 圓圈中心X座標 * @param centerY 圓圈中心Y座標 * @param radius 半徑 * @param text 顯示的文本 */ private void drawCircleText(Canvas canvas, Paint paint, float centerX, float centerY, float radius, String text) { paint.setTextAlign(Paint.Align.LEFT); Rect bounds = new Rect(); paint.getTextBounds(text, 0, text.length(), bounds); Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); float baseline = centerY - radius + (2 * radius - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top; canvas.drawText(text, centerX - radius + radius - bounds.width() / 2, baseline, paint); }
項目結構目錄截圖
一個漂亮而強大的自定義view
代碼地址以下:<br>http://www.demodashi.com/demo/13502.html
注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權