介面無小事(四):來寫個滾動選擇器吧!

Sorrower發表於2018-07-16

介面無小事(一): RecyclerView+CardView瞭解一下

介面無小事(二): 讓RecyclerView展示更多不同檢視

介面無小事(三):用RecyclerView + Toolbar做個檔案選擇器

介面無小事(四):來寫個滾動選擇器吧!

介面無小事(五):自定義TextView

介面無小事(六):來做個好看得側拉選單!

去github看原始碼


目錄

  • 效果圖
  • 前言
  • Paint類
  • 計時器
  • 基線baseline
  • 滾動選擇器實現
  • 最後

效果圖

不廢話, 先上效果圖. 覺得有趣再往下看吧. 去github看原始碼

效果圖


前言

在pc時代, 輸入一般都依靠鍵盤. 對於像選時間這種操作, win一般會列出全部日期, 然後讓你點選選擇. 說句實話, 土爆了. 當然了, 滾動選時間也土爆了(手動尷尬), 但是比win的操作方式已經有趣不少了. 而且滾動選擇器我覺得還是有很多不錯的應用場景的, 所以這次就寫一個分享給大家.


Paint類

官方文件 Paint還是很值得熟悉的一個類, 大部分函式都是set方法, 去文件看就好了. 本文有兩個Paint例項, 一個是繪製文字用, 一個是繪線.

mPaint = new Paint();
// 設定抗鋸齒
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
// 設定文字對齊方式
mPaint.setTextAlign(Paint.Align.CENTER);
// 設定畫筆顏色
mPaint.setColor(UIUtil.getColor(R.color.colorText));

mLinePaint = new Paint();
mLinePaint.setColor(UIUtil.getColor(R.color.colorPrimaryTrans));
複製程式碼

後續程式碼中, Paint例項會依據曲線設定文字字號以及透明度. 所以, 我們需要自己設定最小最大字號, 最小最大透明度, 這樣就可以在範圍內依據函式曲線變化. 差不多就是下圖, 但是y是大於0的.

變化曲線

// 依據曲線設定字號
float scale = gradient(mMax, mMoveLen);
float size = (mMax - mMin) * scale + mMin;
mPaint.setTextSize(size);

// 依據曲線設定透明度
mPaint.setAlpha((int) ((mMaxAlpha - mMinAlpha) * scale + mMinAlpha));
複製程式碼

計時器

計時器是經常用到的, Android裡面會用Timer, TimerTask, Handler三個組合使用. 思路就是Timer例項使用schedule函式, 傳入TimerTask例項以及時間引數. 然後在TimerTask例項的run方法中讓Handler例項呼叫sendMessage方法傳送訊息. 最後在handleMessage方法中處理. 程式碼如下:

mTask = new MyTimerTask(mHandler);
mTimer.schedule(mTask, 0, 10);
複製程式碼
private class MyTimerTask extends TimerTask {
    Handler handler;

    public MyTimerTask(Handler handler) {
        this.handler = handler;
    }

    @Override
    public void run() {
        handler.sendMessage(handler.obtainMessage());
    }
}
複製程式碼
Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // 逐步回滾, 直到小於指定值, 選中目標
        if (Math.abs(mMoveLen) < BACK_SPEED) {
            // 選中
            mMoveLen = 0;
            if (mTask != null) {
                mTask.cancel();
                mTask = null;
                select();
            }
        } else {
            // 滾動
            mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * BACK_SPEED;
        }
        invalidate();
    }
};
複製程式碼

基線baseline

最後來談談玄學, 基線. Android的繪製是基於基線的. 那什麼是基線, 來看兩張圖片:

玄學圖1

玄學圖2

也就是說, 想要把某個文字垂直居中, 除了要獲取View的高度, 還要獲取文字的高度. 這裡就需要Paint.FontMetrics類了, 裡面有我們要的引數. 官方文件 這裡有兩種思路, 依靠top和bottom算出文字高度, 或者依靠ascent和descent. 你對上面哪張圖更理解, 就用哪個. 還有一點要說的就是, 所有引數都是相對於baseline的, 比方說top就可能是-100, bottom就會是30.

float baseline = y - (fmi.top + fmi.bottom) / 2.0f;
float baseline = y - (fmi.ascent + fmi.descent) / 2.0f;
複製程式碼

滾動選擇器實現

要想實現滾動選擇器, 肯定還是要處理觸控操作的. 如果對自定義檢視不熟悉的, 可以看看我之前的文章. 或者google一下. 要點就是抬手時候開啟計時器, 點下記錄位置, 移動重繪. 然後為了頭尾銜接, 需要在到頂和到底的時候處理下List中的內容.

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN: {
            if (mTask != null) {
                mTask.cancel();
                mTask = null;
            }
            mLastDownY = event.getY();
        }
        break;

        case MotionEvent.ACTION_MOVE: {
            mMoveLen += (event.getY() - mLastDownY);

            if (mMoveLen > DIS * mMin / 2) {
                tailToHead();
                mMoveLen = mMoveLen - DIS * mMin;
            } else if (mMoveLen < -DIS * mMin / 2) {
                headToTail();
                mMoveLen = mMoveLen + DIS * mMin;
            }

            mLastDownY = event.getY();
            invalidate();
        }
        break;

        case MotionEvent.ACTION_UP: {
            // 移動過小就不移動
            if (Math.abs(mMoveLen) < 0.001) {
                mMoveLen = 0;
                break;
            }
            if (mTask != null) {
                mTask.cancel();
                mTask = null;
            }

            mTask = new MyTimerTask(mHandler);
            mTimer.schedule(mTask, 0, 10);
        }
        break;
    }
    return true;
}
複製程式碼
private void headToTail() {
    String head = mData.get(0);
    mData.remove(0);
    mData.add(head);
}

private void tailToHead() {
    String tail = mData.get(mData.size() - 1);
    mData.remove(mData.size() - 1);
    mData.add(0, tail);
}
複製程式碼

最後

有段時間沒寫文章了, 也是忙一些亂七八糟的事情去了. 寫得有點找不到感覺, 之後會努力寫出有趣的分享文章來的. 喜歡可以點贊或者關注我哦~


相關文章