Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android自定義實現循環滾輪控件WheelView

Android自定義實現循環滾輪控件WheelView

編輯:關於android開發

Android自定義實現循環滾輪控件WheelView


首先呈上效果圖

\

現在很多地方都用到了滾輪布局WheelView,比如在選擇生日的時候,風格類似系統提供的DatePickerDialog,開源的控件也有很多,不過大部分都是根據當前項目的需求繪制的界面,因此我就自己寫了一款比較符合自己項目的WheelView。

首先這個控件有以下的需求:

1、能夠循環滾動,當向上或者向下滑動到臨界值的時候,則循環開始滾動

2、中間的一塊有一塊半透明的選擇區,滑動結束時,哪一塊在這個選擇區,就選擇這快。

3、繼承自View進行繪制

然後進行一些關鍵點的講解:

1、整體控件繼承自View,在onDraw中進行繪制。整體包含三個模塊,整個View、每一塊的條目、中間選擇區的條目(額外繪制一塊灰色區域)。

2、通過動態設置或者默認設置的可顯示條目數,在最上和最下再各加入一塊,意思就是一共繪制showCount+2個條目。

3、當最上面的條目數滑動超過條目高度的一半時,進行動態條目更新:將最下面的條目刪除加入第一個條目、將第一個條目刪除加入最下面的條目。

4、外界可設置條目顯示數、字體大小、顏色、選擇區提示文字(圖中那個年字)、默認選擇項、padding補白等等。

5、在onTouchEvent中,得到手指滑動的漸變值,動態更新當前所有的條目。

6、在onMeasure中動態計算寬度,所有條目的寬度、高度、起始Y坐標等等。

7、通過當前條目和被選擇條目的坐標,超過一半則視為被選擇,並且滑動到對應的位置。

下面的是WheelView代碼,主要是計算初始值、得到外面設置的值:

package cc.wxf.view.wheel;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by ccwxf on 2016/3/31.
 */
public class WheelView extends View {

    public static final int FONT_COLOR = Color.BLACK;
    public static final int FONT_SIZE = 30;
    public static final int PADDING = 10;
    public static final int SHOW_COUNT = 3;
    public static final int SELECT = 0;
    //總體寬度、高度、Item的高度
    private int width;
    private int height;
    private int itemHeight;
    //需要顯示的行數
    private int showCount = SHOW_COUNT;
    //當前默認選擇的位置
    private int select = SELECT;
    //字體顏色、大小、補白
    private int fontColor = FONT_COLOR;
    private int fontSize = FONT_SIZE;
    private int padding = PADDING;
    //文本列表
    private List lists;
    //選中項的輔助文本,可為空
    private String selectTip;
    //每一項Item和選中項
    private List wheelItems = new ArrayList();
    private WheelSelect wheelSelect = null;
    //手點擊的Y坐標
    private float mTouchY;
    //監聽器
    private OnWheelViewItemSelectListener listener;

    public WheelView(Context context) {
        super(context);
    }

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

    public WheelView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 設置字體的顏色,不設置的話默認為黑色
     * @param fontColor
     * @return
     */
    public WheelView fontColor(int fontColor){
        this.fontColor = fontColor;
        return this;
    }

    /**
     * 設置字體的大小,不設置的話默認為30
     * @param fontSize
     * @return
     */
    public WheelView fontSize(int fontSize){
        this.fontSize = fontSize;
        return this;
    }

    /**
     * 設置文本到上下兩邊的補白,不合適的話默認為10
     * @param padding
     * @return
     */
    public WheelView padding(int padding){
        this.padding = padding;
        return this;
    }

    /**
     * 設置選中項的復制文本,可以不設置
     * @param selectTip
     * @return
     */
    public WheelView selectTip(String selectTip){
        this.selectTip = selectTip;
        return this;
    }

    /**
     * 設置文本列表,必須且必須在build方法之前設置
     * @param lists
     * @return
     */
    public WheelView lists(List lists){
        this.lists = lists;
        return this;
    }

    /**
     * 設置顯示行數,不設置的話默認為3
     * @param showCount
     * @return
     */
    public WheelView showCount(int showCount){
        if(showCount % 2 == 0){
            throw new IllegalStateException("the showCount must be odd");
        }
        this.showCount = showCount;
        return this;
    }

    /**
     * 設置默認選中的文本的索引,不設置默認為0
     * @param select
     * @return
     */
    public WheelView select(int select){
        this.select = select;
        return this;
    }

    /**
     * 最後調用的方法,判斷是否有必要函數沒有被調用
     * @return
     */
    public WheelView build(){
        if(lists == null){
            throw new IllegalStateException("this method must invoke after the method [lists]");
        }
        return this;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //得到總體寬度
        width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        // 得到每一個Item的高度
        Paint mPaint = new Paint();
        mPaint.setTextSize(fontSize);
        Paint.FontMetrics metrics =  mPaint.getFontMetrics();
        itemHeight = (int) (metrics.bottom - metrics.top) + 2 * padding;
        //初始化每一個WheelItem
        initWheelItems(width, itemHeight);
        //初始化WheelSelect
        wheelSelect = new WheelSelect(showCount / 2 * itemHeight, width, itemHeight, selectTip, fontColor, fontSize, padding);
        //得到所有的高度
        height = itemHeight * showCount;
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    }

    /**
     * 創建顯示個數+2個WheelItem
     * @param width
     * @param itemHeight
     */
    private void initWheelItems(int width, int itemHeight) {
        wheelItems.clear();
        for(int i = 0; i < showCount + 2; i++){
            int startY = itemHeight * (i - 1);
            int stringIndex = select - showCount / 2 - 1 + i;
            if(stringIndex < 0){
                stringIndex = lists.size() + stringIndex;
            }
            wheelItems.add(new WheelItem(startY, width, itemHeight, fontColor, fontSize, lists.get(stringIndex)));
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mTouchY = event.getY();
                return true;
            case MotionEvent.ACTION_MOVE:
                float dy = event.getY() - mTouchY;
                mTouchY = event.getY();
                handleMove(dy);
                break;
            case MotionEvent.ACTION_UP:
                handleUp();
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 處理移動操作
     * @param dy
     */
    private void handleMove(float dy) {
        //調整坐標
        for(WheelItem item : wheelItems){
            item.adjust(dy);
        }
        invalidate();
        //調整
        adjust();
    }

    /**
     * 處理抬起操作
     */
    private void handleUp(){
        int index = -1;
        //得到應該選擇的那一項
        for(int i = 0; i < wheelItems.size(); i++){
            WheelItem item = wheelItems.get(i);
            //如果startY在selectItem的中點上面,則將該項作為選擇項
            if(item.getStartY() > wheelSelect.getStartY() && item.getStartY() < (wheelSelect.getStartY() + itemHeight / 2)){
                index = i;
                break;
            }
            //如果startY在selectItem的中點下面,則將上一項作為選擇項
            if(item.getStartY() >= (wheelSelect.getStartY() + itemHeight / 2) && item.getStartY() < (wheelSelect.getStartY() + itemHeight)){
                index = i - 1;
                break;
            }
        }
        //如果沒找到或者其他因素,直接返回
        if(index == -1){
            return;
        }
        //得到偏移的位移
        float dy = wheelSelect.getStartY() - wheelItems.get(index).getStartY();
        //調整坐標
        for(WheelItem item : wheelItems){
            item.adjust(dy);
        }
        invalidate();
        // 調整
        adjust();
        //設置選擇項
        int stringIndex = lists.indexOf(wheelItems.get(index).getText());
        if(stringIndex != -1){
            select = stringIndex;
            if(listener != null){
                listener.onItemSelect(select);
            }
        }
    }

    /**
     * 調整Item移動和循環顯示
     */
    private void adjust(){
        //如果向下滑動超出半個Item的高度,則調整容器
        if(wheelItems.get(0).getStartY() >= -itemHeight / 2 ){
            //移除最後一個Item重用
            WheelItem item = wheelItems.remove(wheelItems.size() - 1);
            //設置起點Y坐標
            item.setStartY(wheelItems.get(0).getStartY() - itemHeight);
            //得到文本在容器中的索引
            int index = lists.indexOf(wheelItems.get(0).getText());
            if(index == -1){
                return;
            }
            index -= 1;
            if(index < 0){
                index = lists.size() + index;
            }
            //設置文本
            item.setText(lists.get(index));
            //添加到最開始
            wheelItems.add(0, item);
            invalidate();
            return;
        }
        //如果向上滑超出半個Item的高度,則調整容器
        if(wheelItems.get(0).getStartY() <= (-itemHeight / 2 - itemHeight)){
            //移除第一個Item重用
            WheelItem item = wheelItems.remove(0);
            //設置起點Y坐標
            item.setStartY(wheelItems.get(wheelItems.size() - 1).getStartY() + itemHeight);
            //得到文本在容器中的索引
            int index = lists.indexOf(wheelItems.get(wheelItems.size() - 1).getText());
            if(index == -1){
                return;
            }
            index += 1;
            if(index >= lists.size()){
                index = 0;
            }
            //設置文本
            item.setText(lists.get(index));
            //添加到最後面
            wheelItems.add(item);
            invalidate();
            return;
        }
    }

    /**
     * 得到當前的選擇項
     */
    public int getSelectItem(){
        return select;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //繪制每一項Item
        for(WheelItem item : wheelItems){
            item.onDraw(canvas);
        }
        //繪制陰影
        if(wheelSelect != null){
            wheelSelect.onDraw(canvas);
        }
    }

    /**
     * 設置監聽器
     * @param listener
     * @return
     */
    public WheelView listener(OnWheelViewItemSelectListener listener){
        this.listener = listener;
        return this;
    }

    public interface OnWheelViewItemSelectListener{
        void onItemSelect(int index);
    }
}
然後是每一個條目類,根據當前的坐標進行繪制,根據漸變值改變坐標等:
package cc.wxf.view.wheel;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;

/**
 * Created by ccwxf on 2016/3/31.
 */
public class WheelItem {
    // 起點Y坐標、寬度、高度
    private float startY;
    private int width;
    private int height;
    //四點坐標
    private RectF rect = new RectF();
    //字體大小、顏色
    private int fontColor;
    private int fontSize;
    private String text;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public WheelItem(float startY, int width, int height, int fontColor, int fontSize, String text) {
        this.startY = startY;
        this.width = width;
        this.height = height;
        this.fontColor = fontColor;
        this.fontSize = fontSize;
        this.text = text;
        adjust(0);
    }

    /**
     * 根據Y坐標的變化值,調整四點坐標值
     * @param dy
     */
    public void adjust(float dy){
        startY += dy;
        rect.left = 0;
        rect.top = startY;
        rect.right = width;
        rect.bottom = startY + height;
    }

    public float getStartY() {
        return startY;
    }

    /**
     * 直接設置Y坐標屬性,調整四點坐標屬性
     * @param startY
     */
    public void setStartY(float startY) {
        this.startY = startY;
        rect.left = 0;
        rect.top = startY;
        rect.right = width;
        rect.bottom = startY + height;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public void onDraw(Canvas mCanvas){
        //設置鋼筆屬性
        mPaint.setTextSize(fontSize);
        mPaint.setColor(fontColor);
        //得到字體的寬度
        int textWidth = (int)mPaint.measureText(text);
        //drawText的繪制起點是左下角,y軸起點為baseLine
        Paint.FontMetrics metrics =  mPaint.getFontMetrics();
        int baseLine = (int)(rect.centerY() + (metrics.bottom - metrics.top) / 2 - metrics.bottom);
        //居中繪制
        mCanvas.drawText(text, rect.centerX() - textWidth / 2, baseLine, mPaint);
    }
}

最後是選擇項,就是額外得在中間區域繪制一塊灰色區域:
package cc.wxf.view.wheel;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;

/**
 * Created by ccwxf on 2016/4/1.
 */
public class WheelSelect {
    //黑框背景顏色
    public static final int COLOR_BACKGROUND = Color.parseColor("#77777777");
    //黑框的Y坐標起點、寬度、高度
    private int startY;
    private int width;
    private int height;
    //四點坐標
    private Rect rect = new Rect();
    //需要選擇文本的顏色、大小、補白
    private String selectText;
    private int fontColor;
    private int fontSize;
    private int padding;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public WheelSelect(int startY, int width, int height, String selectText, int fontColor, int fontSize, int padding) {
        this.startY = startY;
        this.width = width;
        this.height = height;
        this.selectText = selectText;
        this.fontColor = fontColor;
        this.fontSize = fontSize;
        this.padding = padding;
        rect.left = 0;
        rect.top = startY;
        rect.right = width;
        rect.bottom = startY + height;
    }

    public int getStartY() {
        return startY;
    }

    public void setStartY(int startY) {
        this.startY = startY;
    }

    public void onDraw(Canvas mCanvas) {
        //繪制背景
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(COLOR_BACKGROUND);
        mCanvas.drawRect(rect, mPaint);
        //繪制提醒文字
        if(selectText != null){
            //設置鋼筆屬性
            mPaint.setTextSize(fontSize);
            mPaint.setColor(fontColor);
            //得到字體的寬度
            int textWidth = (int)mPaint.measureText(selectText);
            //drawText的繪制起點是左下角,y軸起點為baseLine
            Paint.FontMetrics metrics =  mPaint.getFontMetrics();
            int baseLine = (int)(rect.centerY() + (metrics.bottom - metrics.top) / 2 - metrics.bottom);
            //在靠右邊繪制文本
            mCanvas.drawText(selectText, rect.right - padding - textWidth, baseLine, mPaint);
        }
    }
}


源代碼就三個文件,很簡單,注釋也很詳細,接下來就是使用文件了:

 

 

        final WheelView wheelView = (WheelView) findViewById(R.id.wheelView);
        final List lists = new ArrayList<>();
        for(int i = 0; i < 20; i++){
            lists.add("test:" + i);
        }
        wheelView.lists(lists).fontSize(35).showCount(5).selectTip("年").select(0).listener(new WheelView.OnWheelViewItemSelectListener() {
            @Override
            public void onItemSelect(int index) {
                Log.d("cc", "current select:" + wheelView.getSelectItem() + " index :" + index + ",result=" + lists.get(index));
            }
        }).build();

這個控件說簡單也簡單,說復雜也挺復雜,從最基礎的onDraw實現,可以非常高靈活度地定制各自的需求。

demo工程就不提供了,使用非常簡單。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved