【原創(chuàng)】也仿薄荷健康的刻度尺效果——帶慣性滑動(dòng)效果(二)

一撬码、先看效果

scroll-v.gif

二、分析

上一篇博客 我們繪制了薄荷健康的直尺效果缘揪,可以說只是簡(jiǎn)單的繪制耍群,并沒有交互的操作,例如手勢(shì)滑動(dòng)找筝,數(shù)值回調(diào)蹈垢。這一篇我們來完善一下。

首先是手勢(shì)滑動(dòng)袖裕,如果還用上一篇的寫法曹抬,不好處理,慣性滑動(dòng)的話我們想到的是 Scroller 這個(gè)輔助類以及速度追蹤器急鳄。 Scroller 很熟悉谤民,自定義 View 的滑動(dòng)經(jīng)常用到堰酿,就是計(jì)算一系列的數(shù)值,然后調(diào)用 scrollTo() 這個(gè)方法將 View 滾動(dòng)到確定的位置张足,寫法都是固定的触创,參考百度。

重點(diǎn)說說速度追蹤器 VelocityTracker为牍,這個(gè)類干嘛的哼绑?我也不清楚,找了 一篇博客 觀察一下碉咆。用法很詳細(xì)抖韩,大致了解了一下,但是博客里有幾個(gè)重要的參數(shù)沒有說明疫铜,后面重點(diǎn)提茂浮。

上面的 gif 圖中間有一條綠線,這個(gè)綠線認(rèn)為是基準(zhǔn)線壳咕,代碼里用偏移量表示席揽。

三、代碼

相比較上一篇的代碼囱井,我們需要修改幾個(gè)地方驹尼,首先是初始化:

    private void init(Context context, AttributeSet attrs) {
        mContext = context;
        centerLinePaint = new Paint();
        centerLinePaint.setAntiAlias(true);
        centerLinePaint.setColor(Color.parseColor("#49BA72"));
        centerLinePaint.setStrokeWidth(5);

        grayLinePaint = new Paint();
        grayLinePaint.setAntiAlias(true);
        grayLinePaint.setColor(Color.parseColor("#66666666"));
        grayLinePaint.setStrokeWidth(5);

        txtPaint = new Paint();
        txtPaint.setAntiAlias(true);
        txtPaint.setColor(Color.parseColor("#333333"));
        txtPaint.setTextSize(50);
        
        // 新增部分
        ViewConfiguration viewConfiguration = ViewConfiguration.get(mContext);
        // 最小響應(yīng)距離
        touchSlop = viewConfiguration.getScaledTouchSlop();
        mScroller = new Scroller(mContext);
        // 慣性滑動(dòng)最低速度要求 低于這個(gè)速度認(rèn)為是觸摸
        mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
        // 慣性滑動(dòng)的最大速度  觸摸速度不會(huì)超過這個(gè)值 
        mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
    }

新增的部分標(biāo)注出來了。然后是觸摸部分 onTouchEvent():

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        obtainVelocityTracker();
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mLastX = event.getX();
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                isFastScroll = false;
                float moveX = event.getX();
                currentOffset = (int) (moveX - mLastX);
                scrollTo(getScrollX() - currentOffset, 0);
                computeAndCallback(getScrollX());
                mLastX = moveX;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialVelocity = (int) mVelocityTracker.getXVelocity();
                if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                    isFastScroll = true;
                    flingX(-initialVelocity);
                } else {
                    int x = getScrollX();
                    if (x % space != 0) {
                        x -= x % space;
                    }
                    if (x < -BASELINE_OFFSET) {
                        x = -BASELINE_OFFSET + BASELINE_OFFSET % space;
                    } else if (x > (endValue - startValue) * space * 10 - BASELINE_OFFSET) {
                        x = (endValue - startValue) * space * 10 - BASELINE_OFFSET + BASELINE_OFFSET % space;
                    }
                    scrollTo(x, 0);
                    computeAndCallback(x);
                }
                releaseVelocityTracker();
                break;
        }
        //對(duì)每一個(gè)Event都需要交給速度追蹤器
        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(event);
        }
        return true;
    }

很長(zhǎng)庞呕,一行行分析新翎。

    case MotionEvent.ACTION_DOWN:
         mLastX = event.getX();
         if (!mScroller.isFinished()) {
             mScroller.abortAnimation();
         }
         break;
         

記錄按下的位置,然后如果上一次的動(dòng)畫還在繼續(xù)住练,立即停止地啰。
再看 MOVE 里面:

    case MotionEvent.ACTION_MOVE:
         isFastScroll = false;
         float moveX = event.getX();
         currentOffset = (int) (moveX - mLastX);
         scrollTo(getScrollX() - currentOffset, 0);
         computeAndCallback(getScrollX());
         mLastX = moveX;
         break;

第一個(gè)布爾值是標(biāo)記是否正在慣性滑動(dòng),在后面會(huì)用到讲逛。為什么在這里置為false亏吝?因?yàn)橛|摸的時(shí)候不可能在慣性滑動(dòng)。然后計(jì)算每一次觸摸的偏移盏混,調(diào)用 scrollTo() 不斷的讓自己(View 本身)滾動(dòng)蔚鸥。
后面的 computeAndCallback() 方法暫時(shí)可以不看。最后還要記下每一次 MOVE 的坐標(biāo)许赃,因?yàn)槭怯?jì)算每一次的偏移的止喷,不是總的偏移。

最后看 UP 和 CANCEL 事件:

     case MotionEvent.ACTION_CANCEL:
         mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
         int initialVelocity = (int) mVelocityTracker.getXVelocity();
         if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
             isFastScroll = true;
             flingX(-initialVelocity);
         } else {
             int x = getScrollX();
             if (x % space != 0) {
                 x -= x % space;
             }
             if (x < -BASELINE_OFFSET) {
                 x = -BASELINE_OFFSET + BASELINE_OFFSET % space;
             } else if (x > (endValue - startValue) * space * 10 - BASELINE_OFFSET) {
                 x = (endValue - startValue) * space * 10 - BASELINE_OFFSET + BASELINE_OFFSET % space;
             }
             scrollTo(x, 0);
             computeAndCallback(x);
         }
         releaseVelocityTracker();
         break;

第1行 computeCurrentVelocity() 方法是手指離開屏幕的瞬間去計(jì)算 View 在手機(jī) x-y 方向的速度值混聊;
第2行 getXVelocity() 方法獲得 X 軸方向的速度值 initialVelocity弹谁;
第3行 判斷速度是否大于最低 mMinimumVelocity 要求,滿足的話,認(rèn)為需要慣性滑動(dòng)预愤,調(diào)用方法 flingX():

    /**
     * 慣性滑動(dòng)
     *
     * @param velocityX
     */
    public void flingX(int velocityX) {
        mScroller.fling(getScrollX(), getScrollY(), velocityX, 0, -BASELINE_OFFSET, (endValue - startValue) * space * 10 - BASELINE_OFFSET, 0, 0);
        awakenScrollBars(mScroller.getDuration());
        invalidate();
    }
    

上面這個(gè)方法就是慣性滑動(dòng)的重點(diǎn)所在沟于。有了初速度,調(diào)用 mScroller.fling() 方法交給 Scroller 處理植康。這里要注意 fling() 方法的8個(gè)參數(shù)分別代表什么旷太,12參數(shù)代表滾動(dòng)開始的位置,34參數(shù)代表這個(gè)方向上的初速度销睁,56參數(shù)代表X滾動(dòng)的范圍泳秀,78參數(shù)代表Y滾動(dòng)的范圍。

第6行 也就是 else 不滿足最小滾動(dòng)速度的時(shí)候榄攀,認(rèn)為是觸摸事件的抬起,這個(gè)時(shí)候我們需要手動(dòng)的將 View 的刻度線滾動(dòng)到基準(zhǔn)線的位置金句,因?yàn)闈L動(dòng)的時(shí)候可能基準(zhǔn)線位于兩根刻度線之間檩赢,這個(gè)時(shí)候需要校準(zhǔn):

    int x = getScrollX();
    if (x % space != 0) {
        x -= x % space;
    }
    if (x < -BASELINE_OFFSET) {
        x = -BASELINE_OFFSET + BASELINE_OFFSET % space;
    } else if (x > (endValue - startValue) * space * 10 - BASELINE_OFFSET) {
        x = (endValue - startValue) * space * 10 - BASELINE_OFFSET + BASELINE_OFFSET % space;
    }
    scrollTo(x, 0);
    computeAndCallback(x);

首先獲取滾動(dòng)的長(zhǎng)度,如果對(duì) space 取余有余违寞,說明基準(zhǔn)線在兩個(gè)刻度之間贞瞒,需要減去這個(gè)余數(shù)。得到 space 的整倍數(shù)的偏移之后趁曼,
還要判斷邊界军浆,如果 x 在基準(zhǔn)線右邊,說明滾動(dòng)過頭了挡闰,需要回滾到基準(zhǔn)線上乒融。由于基準(zhǔn)線是偏移過的,所以 scrollTo 的時(shí)候需要補(bǔ)上這個(gè)偏移摄悯;
如果 x 在基準(zhǔn)線左邊赞季,說明向左滾過頭了,也需要回滾到基準(zhǔn)線上奢驯,同理申钩,后面也要加上偏移的量。最后調(diào)用 scrollTo() 就可以回到基準(zhǔn)線上瘪阁。

再來看下 onDraw() 方法:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = startValue * 10; i < endValue * 10 + 1; i++) {
            int lineHeight = 80;
            if (i % 5 == 0) {
                if (i % 10 == 0) {
                    lineHeight = 120;
                    int x = (i - startValue * 10) * space;
                    if (x > 0 || x < width) {
                        canvas.drawText(String.valueOf(i / 10), x, lineHeight + 50, txtPaint);
                    }
                }
            } else {
                lineHeight = 50;
            }
            int startX = (i - startValue * 10) * space;
            if (startX > 0 || startX < width) {
                canvas.drawLine(startX, 0, startX, lineHeight, grayLinePaint);
            }
        }
        int startX = BASELINE_OFFSET + getScrollX() - BASELINE_OFFSET % space;
        canvas.drawLine(startX, 0, startX, 180, centerLinePaint);
    }

這個(gè)方法相比較第一個(gè)撒遣,有所改動(dòng),繪制刻度線的時(shí)候不需要再加上偏移量了管跺,直接從 View 的起始開始繪制义黎,滾動(dòng)就交給 scrollTo() 方法處理了。
這里繪制基準(zhǔn)線的時(shí)候同樣需要注意伙菜,除了加上基準(zhǔn)偏移轩缤,還要扣除余數(shù),否則,基準(zhǔn)線對(duì)不準(zhǔn)刻度線火的。

滾動(dòng)還必須要覆蓋的一個(gè)方法是 computeScroll():

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            int x = mScroller.getCurrX();
            scrollTo(x, 0);
            computeAndCallback(x);
            postInvalidate();
        } else {
            if (isFastScroll) {
                int x = mScroller.getCurrX() + BASELINE_OFFSET % space;
                if (x % space != 0) {
                    x -= x % space;
                }
                scrollTo(x, 0);
                computeAndCallback(x);
                postInvalidate();
            }
        }
    }

這里的處理也是要注意細(xì)節(jié)壶愤,if 下面的代碼沒的說,但是 else 下面的代碼是只有快速慣性滾動(dòng)才能去判斷馏鹤,否則征椒,手指觸摸的時(shí)候也會(huì)去計(jì)算位置,導(dǎo)致移不動(dòng)湃累,
這個(gè)時(shí)候上面的 isFastScroll 就有用處了勃救。另外,這里也要加上基準(zhǔn)線扣除的余數(shù)治力,同時(shí)還要對(duì)space取余數(shù)蒙秒。

我們發(fā)現(xiàn)只要滾動(dòng),后面都會(huì)執(zhí)行 computeAndCallback() 方法:

    /**
     * 計(jì)算并回調(diào)位置信息
     *
     * @param scrollX
     */
    private void computeAndCallback(int scrollX) {
        if (mListener != null) {
            int finalX = BASELINE_OFFSET + scrollX;
            if (finalX % space != 0) {
                finalX -= finalX % space;
            }
            mListener.onRulerSelected((endValue - startValue) * 10, startValue * 10 + finalX / space);
        }
    }

就是一個(gè)回調(diào)宵统,返回當(dāng)前刻度下的值晕讲,這個(gè)值需要我們計(jì)算。
我們首先拿到基準(zhǔn)線對(duì)準(zhǔn)的 finalX 值马澈,這個(gè)值確定下來瓢省,減去對(duì) space 的取余數(shù),就能得到對(duì)應(yīng)的刻度個(gè)數(shù) finalX / space痊班,只要加上 startValue 就好了勤婚。
onRulerSelected 方法第一個(gè)參數(shù)是長(zhǎng)度,沒有特別用處涤伐。

到這馒胆,全部結(jié)束了。

附上 Github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末废亭,一起剝皮案震驚了整個(gè)濱河市国章,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌豆村,老刑警劉巖液兽,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異掌动,居然都是意外死亡四啰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門粗恢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柑晒,“玉大人,你說我怎么就攤上這事眷射〕自蓿” “怎么了佛掖?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)涌庭。 經(jīng)常有香客問我芥被,道長(zhǎng),這世上最難降的妖魔是什么坐榆? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任拴魄,我火速辦了婚禮,結(jié)果婚禮上席镀,老公的妹妹穿的比我還像新娘匹中。我一直安慰自己,他們只是感情好豪诲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布顶捷。 她就那樣靜靜地躺著,像睡著了一般屎篱。 火紅的嫁衣襯著肌膚如雪焊切。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天芳室,我揣著相機(jī)與錄音,去河邊找鬼刹勃。 笑死堪侯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荔仁。 我是一名探鬼主播伍宦,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼乏梁!你這毒婦竟也來了次洼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤遇骑,失蹤者是張志新(化名)和其女友劉穎卖毁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體落萎,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亥啦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了练链。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翔脱。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖媒鼓,靈堂內(nèi)的尸體忽然破棺而出届吁,到底是詐尸還是另有隱情错妖,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布疚沐,位于F島的核電站暂氯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏濒旦。R本人自食惡果不足惜株旷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尔邓。 院中可真熱鬧晾剖,春花似錦、人聲如沸梯嗽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灯节。三九已至循头,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炎疆,已是汗流浹背卡骂。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留形入,地道東北人全跨。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像亿遂,于是被迫代替她去往敵國(guó)和親浓若。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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