Android實現(xiàn)滾動刻度尺效果
緣起
最近在幫人做一個計步器,其中涉及到身高、體重等信息的采集;我參考了眾多app的實現(xiàn),覺得"樂動力"中滑動刻度的方式比較優(yōu)雅。于是乎,反編譯了該app,結(jié)果發(fā)現(xiàn)它是采用圖片的方式實現(xiàn)的,即ScrollView內(nèi)嵌了一張帶刻度的圖片。
個人覺得該方式太不靈活,且對美工的依賴較大,于是便想自定義一個刻度尺控件。
需求分析
- 繪制刻度,區(qū)分整值刻度和普通刻度
- 紅色指針始終在刻度尺的中間,表示當(dāng)前的刻度
- 刻度的最大值和最小值可動態(tài)設(shè)置
- 刻度尺的高度或?qū)挾瓤稍O(shè)置,設(shè)置后中間刻度不變
- 可滑動,滑動后當(dāng)前刻度隨之改變
涉及的知識點(diǎn)
- View的機(jī)制
- canvas繪圖
- Scroller工具類的使用
- 自定義View的屬性
- 點(diǎn)擊、滑動事件的處理
最終效果
由于簡書上無法嵌入gif,為不影響效果,請移步github查看,如果覺得不錯,幫忙給個star ^_^https://github.com/LichFaker/ScaleView
實現(xiàn)過程
1、新建一個class:HorizontalScaleScrollView, 繼承自View
2、在構(gòu)造方法中獲取自定義屬性:
protected void init(AttributeSet attrs) { // 獲取自定義屬性 TypedArray ta = getContext().obtainStyledAttributes(attrs, ATTR); mMin = ta.getInteger(LF_SCALE_MIN, 0); mMax = ta.getInteger(LF_SCALE_MAX, 200); mScaleMargin = ta.getDimensionPixelOffset(LF_SCALE_MARGIN, 15); mScaleHeight = ta.getDimensionPixelOffset(LF_SCALE_HEIGHT, 20); ta.recycle(); mScroller = new Scroller(getContext()); }
3、重寫onMeasure,計算中間刻度
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height=MeasureSpec.makeMeasureSpec(mRectHeight, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, height); mScaleScrollViewRange = getMeasuredWidth(); mTempScale = mScaleScrollViewRange / mScaleMargin / 2 + mMin; mMidCountScale = mScaleScrollViewRange / mScaleMargin / 2 + mMin; }
4、重寫onDraw,繪制刻度和指針
protected void onDrawScale(Canvas canvas, Paint paint) { paint.setTextSize(mRectHeight / 4); for (int i = 0, k = mMin; i <= mMax - mMin; i++) { if (i % 10 == 0) { //整值 canvas.drawLine(i * mScaleMargin, mRectHeight, i * mScaleMargin, mRectHeight - mScaleMaxHeight, paint); //整值文字 canvas.drawText(String.valueOf(k), i * mScaleMargin, mRectHeight - mScaleMaxHeight - 20, paint); k += 10; } else { canvas.drawLine(i * mScaleMargin, mRectHeight, i * mScaleMargin, mRectHeight - mScaleHeight, paint); } } }
protected void onDrawPointer(Canvas canvas, Paint paint) { paint.setColor(Color.RED); //每一屏幕刻度的個數(shù)/2 int countScale = mScaleScrollViewRange / mScaleMargin / 2; //根據(jù)滑動的距離,計算指針的位置【指針始終位于屏幕中間】 int finalX = mScroller.getFinalX(); //滑動的刻度 int tmpCountScale = (int) Math.rint((double) finalX / (double) mScaleMargin);//四舍五入取整 //總刻度 mCountScale = tmpCountScale + countScale + mMin; if (mScrollListener != null) { //回調(diào)方法 mScrollListener.onScaleScroll(mCountScale); } canvas.drawLine(countScale * mScaleMargin + finalX, mRectHeight, countScale * mScaleMargin + finalX, mRectHeight - mScaleMaxHeight - mScaleHeight, paint); }
處理滑動事件
- 在手指按下時,記錄當(dāng)前的x坐標(biāo)(針對水平刻度尺)。
- 在手指滑動過程中,判斷當(dāng)前指針?biāo)傅目潭仁欠褚呀?jīng)超出了邊界,如果超出,則禁止滑動,同時刷新當(dāng)前界面。
- 在手指抬起時,校正當(dāng)前的刻度。
@Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (mScroller != null && !mScroller.isFinished()) { mScroller.abortAnimation(); } mScrollLastX = x; return true; case MotionEvent.ACTION_MOVE: int dataX = mScrollLastX - x; if (mCountScale - mTempScale < 0) { //向右邊滑動 if (mCountScale <= mMin && dataX <= 0) //禁止繼續(xù)向右滑動 return super.onTouchEvent(event); } else if (mCountScale - mTempScale > 0) { //向左邊滑動 if (mCountScale >= mMax && dataX >= 0) //禁止繼續(xù)向左滑動 return super.onTouchEvent(event); } smoothScrollBy(dataX, 0); mScrollLastX = x; postInvalidate(); mTempScale = mCountScale; return true; case MotionEvent.ACTION_UP: if (mCountScale < mMin) mCountScale = mMin; if (mCountScale > mMax) mCountScale = mMax; int finalX = (mCountScale - mMidCountScale) * mScaleMargin; mScroller.setFinalX(finalX); //糾正指針位置 postInvalidate(); return true; } return super.onTouchEvent(event); }
最后的說明
以上只是針對水平滑動刻度的實現(xiàn),垂直滑動原理一致,在源碼中已經(jīng)實現(xiàn),其中也有許多不夠完善的地方,如:
- 第一次快速滑動時,可以超出邊界,之后則不會;
- 開放的自定義屬性不夠(根據(jù)具體情況);
- 可以考慮將水平和垂直的實現(xiàn),在一個類中完成,因為在實現(xiàn)過程中發(fā)現(xiàn)其實有很多代碼都是類似的,只是個別參數(shù)屬性的不同,在坐標(biāo)系中,垂直可以看成是水平旋轉(zhuǎn)了90°,之后有時間可以朝這個方向嘗試下。
相關(guān)文章
Android使用BroadcastReceiver監(jiān)聽網(wǎng)絡(luò)連接狀態(tài)的改變
這篇文章主要為大家詳細(xì)介紹了Android使用BroadcastReceiver監(jiān)聽網(wǎng)絡(luò)連接狀態(tài)的改變,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05Android冷啟動實現(xiàn)app秒開的實現(xiàn)代碼
本篇文章主要介紹了Android冷啟動實現(xiàn)app秒開的實現(xiàn)代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08Android GPS室內(nèi)定位問題的解決方法(location為null)
這篇文章主要為大家詳細(xì)介紹了Android GPS室內(nèi)定位問題的解決方法,location為null,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02Android自定義FloatingActionButton滑動行為只隱藏不出現(xiàn)的問題小結(jié)
這篇文章主要介紹了Android自定義FloatingActionButton滑動行為只隱藏不出現(xiàn)的問題小結(jié),需要的朋友可以參考下2017-01-01android FragmentTabhost實現(xiàn)導(dǎo)航分頁
這篇文章主要為大家詳細(xì)介紹了android FragmentTabhost實現(xiàn)導(dǎo)航分頁,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-08-08Android自定義DigitalClock控件實現(xiàn)商品倒計時
這篇文章主要為大家詳細(xì)介紹了Android DigitalClock實現(xiàn)商品倒計時,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02Android實現(xiàn)后臺開啟服務(wù)默默拍照功能
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)后臺開啟服務(wù)默默拍照功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06