Android 自定義 View -- 雙向范圍選擇器淘太,新手踏坑

風(fēng)起

最近的項目需要用到一個雙向范圍選擇器,遂自己操刀并做下記錄

介紹

范圍選擇器要實現(xiàn)的功能就是進行范圍選擇规丽,并提供接口向調(diào)用者暴露所選最小最大值蒲牧,由于項目只是需要一個普通的范圍選擇器,所以并沒有其他的花哨的動畫特效 duang ~(為自己的技窮找一個借口)

實現(xiàn)

  1. 確定范圍選擇器需要哪些自定義屬性赌莺,并在 res/values 目錄下新建一個資源文件 attrs.xml (隨意) 來聲明我們這些屬性
    <resources>
    <declare-styleable name="LcRangeBar">
    <attr name="minMark" format="integer" />
    <attr name="maxMark" format="integer" />
    <attr name="markBallRadius" format="dimension" />
    <attr name="markBallColor" format="color" />
    <attr name="unMarkLineSize" format="dimension" />
    <attr name="markLineSize" format="dimension" />
    <attr name="unMarkLineColor" format="color" />
    <attr name="markLineColor" format="color" />
    </declare-styleable>
    </resources>

  2. 接下來接是創(chuàng)建范圍選擇器冰抢,LcRangeView 繼承自 View ,并實現(xiàn) LcRangeView 的三個構(gòu)造方法
    public LcRangeBar(Context context) {
    super(context);
    initAttrs(null);
    }
    public LcRangeBar(Context context, AttributeSet attrs) {
    super(context, attrs);
    initAttrs(attrs);
    }
    public LcRangeBar(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    initAttrs(attrs);
    }

  3. 之前我們定義選擇器所需要的屬性艘狭,那么現(xiàn)在我就要在 View 中拿到這些屬性的賦值并處理挎扰,當然為了避免調(diào)用者沒有給這之中的哪個屬性賦值而產(chǎn)生繪圖顯示異常,我們也默認得給這些屬性默認值
    private void initAttrs(AttributeSet attrs) {
    if (attrs != null) {
    TypedArray ta = getContext().obtainStyledAttributes(attrs,
    R.styleable.LcRangeBar, 0, 0);
    minMark = ta.getInt(R.styleable.LcRangeBar_minMark,
    DEFAULT_MIN_MARK);
    maxMark = ta.getInt(R.styleable.LcRangeBar_maxMark,
    DEFAULT_MAX_MARK);
    markBallColor = ta.getColor(R.styleable.LcRangeBar_markBallColor,
    DEFAULT_MARK_BALL_COLOR);
    markLineColor = ta.getColor(R.styleable.LcRangeBar_markLineColor,
    DEFAULT_MARK_LINE_COLOR);
    unMarkLineColor = ta.getColor(
    R.styleable.LcRangeBar_unMarkLineColor,
    DEFAULT_UNMARK_LINE_COLOR);
    markBallRadius = (int) ta.getDimension(
    R.styleable.LcRangeBar_markBallRadius,
    dp2px(DEFAULT_MARK_BALL_RADIUS));
    markLineSize = (int) ta.getDimension(
    R.styleable.LcRangeBar_markLineSize,
    dp2px(DEFAULT_MARK_LINE_SIZE));
    unMarkLineSize = (int) ta.getDimension(
    R.styleable.LcRangeBar_unMarkLineSize,
    dp2px(DEFAULT_UNMARK_LINE_SIZE));
    ta.recycle();
    }
    markRange = maxMark - minMark;
    }

  4. 拿到了繪圖所需要的數(shù)據(jù)巢音,接下來就是測量選擇器的大小遵倦,重寫 onMeasure() 方法。首先試想一下官撼,自適應(yīng)情況控件的寬高應(yīng)該是多大梧躺,寬的話我們就填充完屏幕,高呢傲绣,選擇球的高度掠哥,外部大小決定好了就該考慮一下內(nèi)部的測量巩踏,標刻線應(yīng)該為控件正中間位置,即兩個球心的連接線续搀,寬則為控件左右邊各空出一個球的半徑位置以保證球在最左或最右顯示不完整塞琼。
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int expectedWidth = dp2px(200);
    int expectedHeight = dp2px(30);
    int finalWidth = expectedWidth;
    int finalHeight = expectedHeight;

         if (widthMode == MeasureSpec.EXACTLY) {
             finalWidth = widthSize; 
         } else if (widthMode == MeasureSpec.AT_MOST) {
             finalWidth = expectedWidth;
         }
         if (heightMode == MeasureSpec.EXACTLY) {
             finalHeight = heightSize;
         } else if (heightMode == MeasureSpec.AT_MOST) {
             finalHeight = markBallRadius;
         }
         
         mLineLength = (finalWidth - markBallRadius * 2);
         mMidY = finalHeight / 2;
         Log.d("測試", "看看y"+mMidY);
         mLineStartX = markBallRadius;
         mLineEndX = mLineLength + markBallRadius;
         mMinPosition = mLineStartX;
         mMaxPosition = mLineEndX;
     }
    
  5. 測量好了就該繪圖了,重寫 onDraw() 方法目代,我們要明確的畫圖的順序屈梁,標準刻度線 -> 選擇刻度線 -> 選擇球,想好了怎么畫就該準備筆 (paint) 和 (canvas) ,繪制所需的參數(shù)在前面已經(jīng)定義過了榛了,形狀一出立馬感覺成功了一半在讶,

     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
         drawUnMarkLine(canvas);
         drawMarkLine(canvas);
         drawMarkBalls(canvas);  
     }
    
     private void drawMarkBalls(Canvas canvas) {
         mPaint.setColor(markBallColor);
         canvas.drawCircle(mMinPosition, mMidY, markBallRadius, mPaint);
         canvas.drawCircle(mMaxPosition, mMidY, markBallRadius, mPaint);
     }
    
     private void drawMarkLine(Canvas canvas) {
         mPaint.setColor(markLineColor);
         mPaint.setStrokeWidth(markLineSize);
         canvas.drawLine(mMinPosition, mMidY, mMaxPosition, mMidY, mPaint);
     }
    
     private void drawUnMarkLine(Canvas canvas) {
         mPaint.setColor(unMarkLineColor);
         mPaint.setStrokeWidth(unMarkLineSize);
         canvas.drawLine(mLineStartX, mMidY, mLineEndX, mMidY, mPaint);
     }
    
  6. 圖形已經(jīng)出現(xiàn),我們目前要操作的是兩個球霜大,那么我們就得判斷球是否被觸摸到构哺,我這里觸摸的范圍是剛好裝下球的正方形,你也適當?shù)迷龃笥|控面積(如果你的球需要繪制很小的話)

     private boolean isTouchingMaxBall(MotionEvent event) {
         return event.getX() > mMaxPosition - markBallRadius
                 && event.getX() < mMaxPosition + markBallRadius
                 && event.getY() > mMidY - markBallRadius
                 && event.getY() < mMidY + markBallRadius;
     }
    
     private boolean isTouchingMinBall(MotionEvent event) {
         return event.getX() > mMinPosition - markBallRadius
                 && event.getX() < mMinPosition + markBallRadius
                 && event.getY() > mMidY - markBallRadius
                 && event.getY() < mMidY + markBallRadius;
     }
    
  7. 寫好了判斷战坤,接下來就是實現(xiàn)拖動效果了曙强,當手指按下時,就判斷是否觸摸到了球途茫,觸摸了那個球碟嘴,記錄下狀態(tài);當手指抬起時囊卜,都將觸摸狀態(tài)置為 false 娜扇;當手指滑動時,根據(jù)觸摸狀態(tài)執(zhí)行相應(yīng)的的滑動

     @Override
     public boolean onTouchEvent(MotionEvent event) {
         switch (event.getAction()) {
         case MotionEvent.ACTION_DOWN:
             if (isTouchingMinBall(event)) {
                 isOnMinBall = true;
             } else if (isTouchingMaxBall(event)) {
                 isOnMaxBall = true;
             }
             break;
         case MotionEvent.ACTION_MOVE:
             if (isOnMinBall) {
                 jumpToMin(event);
             }
             if (isOnMaxBall) {
                 jumpToMax(event);
             }
             break;
    
         case MotionEvent.ACTION_UP:
             if (isOnMinBall) {
                 isOnMinBall = false;
             }
             if (isOnMaxBall) {
                 isOnMaxBall = false;
             }
             break;
         }
    
         return true;
     }
    
  8. 繼續(xù)來處理滑動邏輯栅组,我們要先知道球的滑動范圍雀瓢,
    minBall 的滑動范圍為 標準線的起點 -- maxBall 的球心位置,
    maxBall 的滑動位置為 maxBall 的球心位置 -- 標準線的終點玉掸。
    (如果需要讓兩個球不重疊刃麸,可以邊界增加一個球的寬度)
    確定球的新位置后,調(diào)用 invalidate() 進行重繪
    當確定為正在移動球的時候司浪,即使脫離本控件的的范圍一樣可以更新視圖

     private void moveToMinPosition(MotionEvent event) {
         if (event.getX() < mMaxPosition && event.getX() >= mLineStartX) {
             mMinPosition = (int) event.getX();
             invalidate();
             /** 配合 10 一起看泊业,這個必須判斷是否為空,如果調(diào)用者不監(jiān)聽會導(dǎo)致空指針異常
             if (mRangeChangeListener != null) {
                 mRangeChangeListener.onMinChange(Math
                         .round((float) (mMinPosition - mLineStartX)
                                 / mLineLength * markRange));
             }
             **/
         }
     }
    
     private void moveToMaxPosition(MotionEvent event) {
         if (event.getX() > mMinPosition && event.getX() <= mLineEndX) {
             mMaxPosition = (int) event.getX();
             invalidate();
             /** 配合 10 一起看
             if (mRangeChangeListener != null) {
                 mRangeChangeListener.onMaxChange(Math
                         .round((float) (mMaxPosition - mLineStartX)
                                 / mLineLength * markRange));
             }
             **/
         }
     }
    
  9. 現(xiàn)在界面的雛形已經(jīng)出現(xiàn)了啊易,接下來我們要根據(jù)滑動來實時更新我們的范圍值脱吱,一開始我們就拿到了總范圍值,然后根據(jù)滑動比例獲取范圍值

    計算公式

    • min 值:(minBall 位置 - 標準線起點)/ 標準線長度 * 總范圍值

    • max 值:(maxBall 位置 - 標準線起點)/ 標準線長度 * 總范圍值

  10. 范圍值我們拿到了认罩,最后一步結(jié)束范圍值提供給調(diào)用者,這個部分大家都很熟悉了续捂,直接貼

    public interface RangeChangeListener {
        void onMinChange(int minValue);
        void onMaxChange(int maxValue);
    }
    
    public void setRangeChangeListener(RangeChangeListener rangeChangeListener) {
        mRangeChangeListener = rangeChangeListener;
    }
    

總結(jié)

到此為止一個簡單的范圍選擇器就完成了垦垂,由于最近還在趕其他項目宦搬,所以目前先這么簡陋的吧,如果有其他需要還可以更加完善劫拗,如多點操作间校,在標準線上點擊實現(xiàn)球的位置跳轉(zhuǎn),變化動畫等页慷。沒什么技術(shù)含量憔足,純粹寫寫文記錄開發(fā)經(jīng)歷而已。(有空補上源碼圖片)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酒繁,一起剝皮案震驚了整個濱河市滓彰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌州袒,老刑警劉巖揭绑,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異郎哭,居然都是意外死亡他匪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門夸研,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邦蜜,“玉大人,你說我怎么就攤上這事亥至〉可颍” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵抬闯,是天一觀的道長井辆。 經(jīng)常有香客問我,道長溶握,這世上最難降的妖魔是什么杯缺? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮睡榆,結(jié)果婚禮上萍肆,老公的妹妹穿的比我還像新娘。我一直安慰自己胀屿,他們只是感情好塘揣,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宿崭,像睡著了一般亲铡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天奖蔓,我揣著相機與錄音赞草,去河邊找鬼。 笑死吆鹤,一個胖子當著我的面吹牛厨疙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疑务,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼沾凄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了知允?” 一聲冷哼從身側(cè)響起撒蟀,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎廊镜,沒想到半個月后牙肝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡嗤朴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年配椭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雹姊。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡股缸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吱雏,到底是詐尸還是另有隱情敦姻,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布歧杏,位于F島的核電站镰惦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏犬绒。R本人自食惡果不足惜旺入,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凯力。 院中可真熱鬧茵瘾,春花似錦、人聲如沸咐鹤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祈惶。三九已至雕旨,卻和暖如春扮匠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奸腺。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工餐禁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人突照。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像氧吐,于是被迫代替她去往敵國和親讹蘑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容