Android滾動刻度尺實現(xiàn)

緣起

最近在幫人做一個計步器,其中涉及到身高、體重等信息的采集;我參考了眾多app的實現(xiàn),覺得"樂動力"中滑動刻度的方式比較優(yōu)雅。于是乎,反編譯了該app,結(jié)果發(fā)現(xiàn)它是采用圖片的方式實現(xiàn)的,即ScrollView內(nèi)嵌了一張帶刻度的圖片。
個人覺得該方式太不靈活,且對美工的依賴較大,于是便想自定義一個刻度尺控件。

需求分析

  1. 繪制刻度,區(qū)分整值刻度和普通刻度
  • 紅色指針始終在刻度尺的中間,表示當前的刻度
  • 刻度的最大值和最小值可動態(tài)設(shè)置
  • 刻度尺的高度或?qū)挾瓤稍O(shè)置,設(shè)置后中間刻度不變
  • 可滑動,滑動后當前刻度隨之改變

涉及的知識點

  1. View的機制
  • canvas繪圖
  • Scroller工具類的使用
  • 自定義View的屬性
  • 點擊、滑動事件的處理

最終效果

由于簡書上無法嵌入gif,為不影響效果,請移步github查看,如果覺得不錯,幫忙給個star _
https://github.com/LichFaker/ScaleView

實現(xiàn)過程

  1. 新建一個class:HorizontalScaleScrollView, 繼承自View
  • 在構(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());    
}
  • 重寫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;
}
  • 重寫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);
}
  • 處理滑動事件
    1. 在手指按下時,記錄當前的x坐標(針對水平刻度尺)。
    • 在手指滑動過程中,判斷當前指針所指的刻度是否已經(jī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),其中也有許多不夠完善的地方,如:

  1. 第一次快速滑動時,可以超出邊界,之后則不會;
  • 開放的自定義屬性不夠(根據(jù)具體情況);
  • 可以考慮將水平和垂直的實現(xiàn),在一個類中完成,因為在實現(xiàn)過程中發(fā)現(xiàn)其實有很多代碼都是類似的,只是個別參數(shù)屬性的不同,在坐標系中,垂直可以看成是水平旋轉(zhuǎn)了90°,之后有時間可以朝這個方向嘗試下。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容