Android—滾動(dòng)選擇器

在進(jìn)行自定義控件的學(xué)習(xí)和使用時(shí)遮精,由于Android原生的日期選擇器界面固定,偶爾會(huì)遇到自定義日期選擇器的時(shí)候,比如滾動(dòng)的時(shí)間選擇器,這個(gè)時(shí)候就需要進(jìn)行自定義控件來(lái)實(shí)現(xiàn)客们。

public class PickerView extends View {

    public static final String TAG = "PickerView";
    /**
     * text之間間距和minTextSize之比
     */
    public static final float MARGIN_ALPHA = 2.8f;
    /**
     * 自動(dòng)回滾到中間的速度
     */
    public static final float SPEED = 2;

    private List<Pickers> mDataList;
    /**
     * 選中的位置,這個(gè)位置是mDataList的中心位置彰触,一直不變
     */
    private int mCurrentSelected;
    private Paint mPaint;

    private float mMaxTextSize = 20;
    private float mMinTextSize = 10;

    private float mMaxTextAlpha = 255;
    private float mMinTextAlpha = 120;

    private int mColorText = 0x333333;

    private int mViewHeight;
    private int mViewWidth;

    private float mLastDownY;
    /**
     * 滑動(dòng)的距離
     */
    private float mMoveLen = 0;
    private boolean isInit = false;
    private onSelectListener mSelectListener;
    private Timer timer;
    private MyTimerTask mTask;

    private PickerView mPickerView;

    private Calendar mCalendar;
    public static final int YEAR_PICKER = 0;
    public static final int MONTH_PICKER = 1;
    public static final int DAY_PICKER = 2;
    private int type;


    Handler updateHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            if (Math.abs(mMoveLen) < SPEED) {
                mMoveLen = 0;
                if (mTask != null) {
                    mTask.cancel();
                    mTask = null;
                    performSelect();
                }
            } else
                // 這里mMoveLen / Math.abs(mMoveLen)是為了保有mMoveLen的正負(fù)號(hào)都许,以實(shí)現(xiàn)上滾或下滾
            {
                mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * SPEED;
            }
            invalidate();
        }

    };

    public PickerView(Context context) {
        super(context);
        init();
    }

    public PickerView(Context context, int type) {
        super(context);
        this.type = type;
        init();
    }

    public PickerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }



    public void setOnSelectListener(onSelectListener listener) {
        mSelectListener = listener;
    }

    private void performSelect() {
        if (mSelectListener != null) {
            mSelectListener.onSelect(mDataList.get(mCurrentSelected));
        }
    }

    public void setData(List<Pickers> datas) {
        mDataList = datas;
        mCurrentSelected = datas.size() / 2;
    }

    /**
     * 選擇選中的item的index
     *
     * @param selected
     */
    public void setSelected(int selected) {
        mCurrentSelected = selected;
        int distance = mDataList.size() / 2 - mCurrentSelected;
        if (distance < 0) {
            for (int i = 0; i < -distance; i++) {
                moveHeadToTail();
                mCurrentSelected--;
            }
        } else if (distance > 0) {
            for (int i = 0; i < distance; i++) {
                moveTailToHead();
                mCurrentSelected++;
            }
        }
        invalidate();
    }

    /**
     * 選擇選中的內(nèi)容
     *
     * @param mSelectItem
     */
    public void setSelected(String mSelectItem) {
        for (int i = 0; i < mDataList.size(); i++) {
            if (mDataList.get(i).getId ().equals(mSelectItem)) {
                setSelected(i);
                break;
            }
        }
    }

    private void moveHeadToTail() {
        Pickers head = mDataList.get(0);
        mDataList.remove(0);
        mDataList.add(head);
    }

    private void moveTailToHead() {
        Pickers tail = mDataList.get(mDataList.size() - 1);
        mDataList.remove(mDataList.size() - 1);
        mDataList.add(0, tail);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mViewHeight = getMeasuredHeight();
        mViewWidth = getMeasuredWidth();
        // 按照View的高度計(jì)算字體大小
        mMaxTextSize = mViewHeight / 8.0f;
        mMinTextSize = mMaxTextSize / 2f;
        isInit = true;
        invalidate();
    }

    private void init() {
        timer = new Timer();
        mCalendar = Calendar.getInstance ();
        mDataList = new ArrayList<>();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Style.FILL);
        mPaint.setTextAlign(Align.CENTER);
        mPaint.setColor(mColorText);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 根據(jù)index繪制view
        if (isInit) {
            drawData(canvas);
        }
    }

    private void drawData(Canvas canvas) {
        // 先繪制選中的text再往上往下繪制其余的text
        float scale = parabola(mViewHeight / 4.0f, mMoveLen);
        float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
        mPaint.setTextSize(size);
        mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));
        // text居中繪制,注意baseline的計(jì)算才能達(dá)到居中押桃,y值是text中心坐標(biāo)
        float x = (float) (mViewWidth / 2.0);
        float y = (float) (mViewHeight / 2.0 + mMoveLen);
        FontMetricsInt fmi = mPaint.getFontMetricsInt();
        float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));

        int indexs = mCurrentSelected;
        String textData = mDataList.get(indexs).getShowConetnt();
        canvas.drawText(textData, x, baseline, mPaint);

        // 繪制上方data
        for (int i = 1; (mCurrentSelected - i) >= 0; i++) {
            drawOtherText(canvas, i, -1);
        }
        // 繪制下方data
        for (int i = 1; (mCurrentSelected + i) < mDataList.size(); i++) {
            drawOtherText(canvas, i, 1);
        }
    }

    /**
     * @param canvas
     * @param position
     *            距離mCurrentSelected的差值
     * @param type
     *            1表示向下繪制葵萎,-1表示向上繪制
     */
    private void drawOtherText(Canvas canvas, int position, int type) {
        float d = (float) (MARGIN_ALPHA * mMinTextSize * position + type
                * mMoveLen);
        float scale = parabola(mViewHeight / 4.0f, d);
        float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
        mPaint.setTextSize(size);
        mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));
        float y = (float) (mViewHeight / 2.0 + type * d);
        FontMetricsInt fmi = mPaint.getFontMetricsInt();
        float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));

        int indexs = mCurrentSelected + type * position;
        String textData = mDataList.get(indexs).getShowConetnt();
        /**
         * 居中繪制
         */
        canvas.drawText(textData, (float) (mViewWidth / 2.0), baseline, mPaint);
    }

    /**
     * 拋物線(xiàn)
     *
     * @param zero
     *            零點(diǎn)坐標(biāo)
     * @param x
     *            偏移量
     * @return scale
     */
    private float parabola(float zero, float x) {
        float f = (float) (1 - Math.pow(x / zero, 2));
        return f < 0 ? 0 : f;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                doDown(event);
                break;
            case MotionEvent.ACTION_MOVE:
                doMove(event);
                break;
            case MotionEvent.ACTION_UP:
                doUp(event);
                break;
        }
        return true;
    }

    private void doDown(MotionEvent event) {
        if (mTask != null) {
            mTask.cancel();
            mTask = null;
        }
        mLastDownY = event.getY();
    }

    private void doMove(MotionEvent event) {

        mMoveLen += (event.getY() - mLastDownY);

        if (mMoveLen > MARGIN_ALPHA * mMinTextSize / 2) {
            // 往下滑超過(guò)離開(kāi)距離
            moveTailToHead();
            mMoveLen = mMoveLen - MARGIN_ALPHA * mMinTextSize;
        } else if (mMoveLen < -MARGIN_ALPHA * mMinTextSize / 2) {
            // 往上滑超過(guò)離開(kāi)距離
            moveHeadToTail();
            mMoveLen = mMoveLen + MARGIN_ALPHA * mMinTextSize;
        }

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

    private void doUp(MotionEvent event) {
        // 抬起手后mCurrentSelected的位置由當(dāng)前位置move到中間選中位置
        if (Math.abs(mMoveLen) < 0.0001) {
            mMoveLen = 0;
            return;
        }
        if (mTask != null) {
            mTask.cancel();
            mTask = null;
        }
        mTask = new MyTimerTask(updateHandler);
        timer.schedule(mTask, 0, 10);
    }

    class MyTimerTask extends TimerTask {
        Handler handler;

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

        @Override
        public void run() {
            handler.sendMessage(handler.obtainMessage());
        }

    }

    public interface onSelectListener {
        void onSelect(Pickers pickers);
    }

    public static class Pickers implements Serializable {
        private String content;
        private String id;

        public String getShowConetnt() {
            return content;
        }

        public String getId() {
            return id;
        }

        public Pickers(String content, String id) {
            super();
            this.content = content;
            this.id = id;
        }
        public Pickers(int data) {
            super();
            this.content = String.valueOf (data);
            this.id = String.valueOf (data);
        }
    }
}

使用方法

    @BindView (R2.id.picker_view_year)
    PickerView pickerViewYear;
    @BindView (R2.id.picker_view_month)
    PickerView pickerViewMonth;
    @BindView (R2.id.picker_view_day)
    PickerView pickerViewDay;
        pickerViewYear.setData (yearList);
        pickerViewYear.setSelected (String.valueOf (mYear));
        pickerViewMonth.setData (monthList);
        pickerViewMonth.setSelected (mMonth);
        pickerViewDay.setData (dayList);
        pickerViewDay.setSelected (mDay - 1);

        pickerViewYear.setOnSelectListener (new PickerView.onSelectListener () {
            @Override
            public void onSelect(PickerView.Pickers pickers) {
                setYear = pickers.getShowConetnt ();
                mHandler.sendEmptyMessage (0);
            }
        });
···
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市唱凯,隨后出現(xiàn)的幾起案子羡忘,更是在濱河造成了極大的恐慌,老刑警劉巖磕昼,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卷雕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡票从,警方通過(guò)查閱死者的電腦和手機(jī)漫雕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)滨嘱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蝎亚,你說(shuō)我怎么就攤上這事九孩。” “怎么了发框?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵躺彬,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我梅惯,道長(zhǎng)宪拥,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任铣减,我火速辦了婚禮她君,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘葫哗。我一直安慰自己缔刹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布劣针。 她就那樣靜靜地躺著校镐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捺典。 梳的紋絲不亂的頭發(fā)上鸟廓,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音襟己,去河邊找鬼引谜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛擎浴,可吹牛的內(nèi)容都是我干的员咽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼贮预,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贝室!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起萌狂,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤档玻,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后茫藏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體误趴,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年务傲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凉当。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枣申。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖看杭,靈堂內(nèi)的尸體忽然破棺而出忠藤,到底是詐尸還是另有隱情,我是刑警寧澤楼雹,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布模孩,位于F島的核電站,受9級(jí)特大地震影響贮缅,放射性物質(zhì)發(fā)生泄漏榨咐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一谴供、第九天 我趴在偏房一處隱蔽的房頂上張望块茁。 院中可真熱鬧,春花似錦桂肌、人聲如沸数焊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)佩耳。三九已至,卻和暖如春照雁,著一層夾襖步出監(jiān)牢的瞬間蚕愤,已是汗流浹背答恶。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工饺蚊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悬嗓。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓污呼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親包竹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子燕酷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,077評(píng)論 25 707
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料周瞎? 從這篇文章中你...
    hw1212閱讀 12,714評(píng)論 2 59
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 32,930評(píng)論 6 472
  • 1苗缩、通過(guò)CocoaPods安裝項(xiàng)目名稱(chēng)項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明先生_X自主閱讀 15,979評(píng)論 3 119
  • 都說(shuō)女人是比較感性化的,我也不例外声诸,雖然我自認(rèn)是理性大于感性酱讶,偶爾也會(huì)矯情,某個(gè)感動(dòng)的時(shí)刻會(huì)哭彼乌,但哭的很悶騷泻肯。 我...
    林四月閱讀 161評(píng)論 4 1