android 時間控件概述


android的自帶時間選擇控件,是一個讓用戶既能輸入的又能選擇的樣子。這本來沒有太大的問題了。 但是,坑爹的android是開源的。自帶的時間控件在某些機型上,早已經是面目全非了,在用以一個普通用戶來說,蘋果的時間滾輪的控件就是爽點。 要寫滾軸控件,無非是要用好,寫好wheelview這個類,對於wheelview這個類,我這里要做詳細的分析。在iOS里面有一種控件------滾筒控件(Wheel View),這通常用於設置時間/日期,非常方便,但Android SDK並沒有提供類似的控件。這里介紹一下如何Android實現WheelView。

我們從網上找到了一個開源的代碼,它也實現了這樣的效果,而且效果也不錯,大家可以用SVN來checkout:

http://android-wheel.googlecode.com/svn/trunk

 它這個Demo最本質是自己寫布局,好像是利用一個LinearLayout來布局child,然后調用LinearLayout.draw(canvas)方法,把child繪制在指定的can1vas上面。它同時還提供了類似AdapterView的訪問方式,用戶可以設置Adapter來提供數據。我在這里主要不是講解這個Demo的結構,如果大家感興趣,可以自己下載代碼研究。

 我們進入WheelView類,看一下他是如何構建,首先,WheelView繼承了View類。代碼的22行到45行是導入的所需要的類。從54行到135行是聲明一些變量和類:

/** 滾動的間隔 */   
    private static final int SCROLLING_DURATION = 400;   
   
    /** 最小滾動的距離*/   
    private static final int MIN_DELTA_FOR_SCROLLING = 1;   
   
    /** 當前顏色*/   
    private static final int VALUE_TEXT_COLOR = 0xF0000000;   
   
    /** 文本的顏色*/   
    private static final int ITEMS_TEXT_COLOR = 0xFF000000;   
   
    /** 頂部和底部的顏色 */   
    private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111,   
            0x00AAAAAA, 0x00AAAAAA };   
   
    /** 附加的高度*/   
    private static final int ADDITIONAL_ITEM_HEIGHT = 15;   
   
    /** 文字的大小*/   
    private static final int TEXT_SIZE = 24;   
   
    /** 頂部和底部的距離*/   
    private static final int ITEM_OFFSET = TEXT_SIZE / 5;   
   
    /** 附加的寬度 */   
    private static final int ADDITIONAL_ITEMS_SPACE = 10;   
   
    /** 文本的距離*/   
    private static final int LABEL_OFFSET = 8;   
   
    /** 左右的距離 */   
    private static final int PADDING = 10;   
   
    /** 默認顯示的項目*/   
    private static final int DEF_VISIBLE_ITEMS = 5;   
   
    //滾輪的值    
    private WheelAdapter adapter = null;   
    //所選擇的當前的項目 
    private int currentItem = 0;   
       
    // 相應的寬度   
    private int itemsWidth = 0;   
    private int labelWidth = 0;   
   
    //顯示的項目數
    private int visibleItems = DEF_VISIBLE_ITEMS;   
       
    // 項目的高度  
    private int itemHeight = 0;   
   
    // 條目的筆刷 
    private TextPaint itemsPaint;   
    private TextPaint valuePaint;   
   
    // 相應的布局控件    
    private StaticLayout itemsLayout;   
    private StaticLayout labelLayout;   
    private StaticLayout valueLayout;   
   
    // 標簽和文本    
    private String label;   
    private Drawable centerDrawable;   
   
    // 陰影的材質  
    private GradientDrawable topShadow;   
    private GradientDrawable bottomShadow;   
   
    // Scrolling    
    private boolean isScrollingPerformed;    
    private int scrollingOffset;   
   
    // 滾輪動畫的堅挺着    
    private GestureDetector gestureDetector;   
    private Scroller scroller;   
    private int lastScrollY;   
   
    // 是不是循環   
    boolean isCyclic = false;   
       
    // 滾動變化的監聽着
    private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>();   
    private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>();  

在這里面,使用到了StaticLayout,在開發文檔中找一下這個類:

StaticLayout is a Layout for text that will not be edited after it is laid out. Use DynamicLayout for text that may change.  
  
This is used by widgets to control text layout. You should not need to use this class directly unless you are implementing your own widget or custom display object, or would be tempted to call Canvas.drawText
staticLayout被創建以后就不能被修改了,通常被用於控制文本組件布局。

還使用到了Drawable、Text'Paint、GradientDrawable、GestureDetector、Scroller類,在開發文檔中,GradientDrawable的概述:

A Drawable with a color gradient for buttons, backgrounds, etc.  
  
It can be defined in an XML file with the <shape> element. For more information, see the guide to Drawable Resources

就是說這個類可以為按鈕或者背景等提供漸變顏色的繪制。

TextPaint的概述:

TextPaint is an extension of Paint that leaves room for some extra data used during text measuring and drawing.

TextPaint是Paint類的一個擴展,主要是用於文本在繪制的過程中為附件的數據留出空間。

GestureDetector:手勢檢測,看下開發文檔中關於該類的概述:

Detects various gestures and events using the supplied MotionEvents. The GestureDetector.OnGestureListener callback will notify users when a particular motion event has occurred. This class should only be used with MotionEvents reported via touch (don't use for trackball events). 


為各種手勢和事件提供MotionEvents。當一個具體的事件發生時會調用回調函數GestureDetector.OnGestureListener。這個類應該只適用於MotionEvents通過觸摸觸發的事件(不要使用追蹤事件)。

140行到156行是構造方法,175到183行是set和getAdapter。在193行,setInterpolator()方法,設置interPolator這個動畫接口,我們看下這個接口的概述:

An interpolator defines the rate of change of an animation. This allows the basic animation effects (alpha, scale, translate, rotate) to be accelerated, decelerated, repeated, etc.

定義了一種基於變率的一個動畫。這使得基本的動畫效果(alpha, scale, translate, rotate)是加速,減慢,重復等。這個方法在隨機數這個例子中被使用。

203行到213行設置顯示的item條數。在setVisibleItems()方法里面調用了View的invalidate()方法,看下文檔中對該方法的介紹:

Invalidate the whole view. If the view is visible, onDraw(Android.graphics.Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().

使全部視圖失效,如果View視圖是可見的,會在UI線程里面從新調用onDraw()方法。

223行到233行是設置Label,既后面圖片中的hours

245行到296行是設置監聽,在上面已經簡單的說了一下,這里不在累述。

307行到349行是設置正被選中item,就是在那個陰影條框下的那個部分,比較簡單。里面主要調用了scroll這個方法:

相應的源代碼如下:

private void initResourcesIfNecessary() {  
        if (itemsPaint == null) {  
            itemsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG  
                    | Paint.FAKE_BOLD_TEXT_FLAG);  
            //itemsPaint.density = getResources().getDisplayMetrics().density;   
            itemsPaint.setTextSize(TEXT_SIZE);  
        }  
  
        if (valuePaint == null) {  
            valuePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG  
                    | Paint.FAKE_BOLD_TEXT_FLAG | Paint.DITHER_FLAG);  
            //valuePaint.density = getResources().getDisplayMetrics().density;   
            valuePaint.setTextSize(TEXT_SIZE);  
            valuePaint.setShadowLayer(0.1f, 0, 0.1f, 0xFFC0C0C0);  
        }  
  
        if (centerDrawable == null) {  
            centerDrawable = getContext().getResources().getDrawable(R.drawable.wheel_val);  
        }  
  
        if (topShadow == null) {  
            topShadow = new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS);  
        }  
  
        if (bottomShadow == null) {  
            bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS);  
        }  
  
        setBackgroundResource(R.drawable.wheel_bg);  
    } 

這個方法就是初始化在532行calculateLayoutWidth()方法中調用了這個方法,同時調用了487行的getMaxTextLength()這個方法。

471行getTextItem(int index)通過一個索引獲取該item的文本。

這是第一部分,沒有多少有太多意思的地方,重點的地方在以后532行到940行的內容,

首先,先把代碼貼出來:

/**  
     * =進行文本的寬度和布局文件的計算  
     * @param widthSize the input layout width  
     * @param mode the layout mode  
     * @return the calculated control width  
     */   
    private int calculateLayoutWidth(int widthSize, int mode) {   
        initResourcesIfNecessary();   
   
        int width = widthSize;   
        int maxLength = getMaxTextLength();   
        if (maxLength > 0) {   
            float textWidth = FloatMath.ceil(Layout.getDesiredWidth("0", itemsPaint));   
            itemsWidth = (int) (maxLength * textWidth);   
        } else {   
            itemsWidth = 0;   
        }   
        itemsWidth += ADDITIONAL_ITEMS_SPACE; // make it some more    
   
        labelWidth = 0;   
        if (label != null && label.length() > 0) {   
            labelWidth = (int) FloatMath.ceil(Layout.getDesiredWidth(label, valuePaint));   
        }   
   
        boolean recalculate = false;   
        if (mode == MeasureSpec.EXACTLY) {   
            width = widthSize;   
            recalculate = true;   
        } else {   
            width = itemsWidth + labelWidth + 2 * PADDING;   
            if (labelWidth > 0) {   
                width += LABEL_OFFSET;   
            }   
   
            // Check against our minimum width    
            width = Math.max(width, getSuggestedMinimumWidth());   
   
            if (mode == MeasureSpec.AT_MOST && widthSize < width) {   
                width = widthSize;   
                recalculate = true;   
            }   
        }   
   
        if (recalculate) {   
            // recalculate width    
            int pureWidth = width - LABEL_OFFSET - 2 * PADDING;   
            if (pureWidth <= 0) {   
                itemsWidth = labelWidth = 0;   
            }   
            if (labelWidth > 0) {   
                double newWidthItems = (double) itemsWidth * pureWidth   
                        / (itemsWidth + labelWidth);   
                itemsWidth = (int) newWidthItems;   
                labelWidth = pureWidth - itemsWidth;   
            } else {   
                itemsWidth = pureWidth + LABEL_OFFSET; // no label    
            }   
        }   
   
        if (itemsWidth > 0) {   
            createLayouts(itemsWidth, labelWidth);   
        }   
   
        return width;   
    }   
   
    /**  
     * 創建相應的布局文件
     * @param widthItems width of items layout  
     * @param widthLabel width of label layout  
     */   
    private void createLayouts(int widthItems, int widthLabel) {   
        if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {   
            itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,   
                    widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,   
                    1, ADDITIONAL_ITEM_HEIGHT, false);   
        } else {   
            itemsLayout.increaseWidthTo(widthItems);   
        }   
   
        if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {   
            String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;   
            valueLayout = new StaticLayout(text != null ? text : "",   
                    valuePaint, widthItems, widthLabel > 0 ?   
                            Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,   
                            1, ADDITIONAL_ITEM_HEIGHT, false);   
        } else if (isScrollingPerformed) {   
            valueLayout = null;   
        } else {   
            valueLayout.increaseWidthTo(widthItems);   
        }   
   
        if (widthLabel > 0) {   
            if (labelLayout == null || labelLayout.getWidth() > widthLabel) {   
                labelLayout = new StaticLayout(label, valuePaint,   
                        widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,   
                        ADDITIONAL_ITEM_HEIGHT, false);   
            } else {   
                labelLayout.increaseWidthTo(widthLabel);   
            }   
        }   
    }   
   
   /**
    *重寫了次存改變的事件
    */
    @Override   
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);   
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);   
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);   
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);   
   
        int width = calculateLayoutWidth(widthSize, widthMode);   
   
        int height;   
        if (heightMode == MeasureSpec.EXACTLY) {   
            height = heightSize;   
        } else {   
            height = getDesiredHeight(itemsLayout);   
   
            if (heightMode == MeasureSpec.AT_MOST) {   
                height = Math.min(height, heightSize);   
            }   
        }   
   
        setMeasuredDimension(width, height);   
    }   
   
    @Override   
    protected void onDraw(Canvas canvas) {   
        super.onDraw(canvas);   
           
        if (itemsLayout == null) {   
            if (itemsWidth == 0) {   
                calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);   
            } else {   
                createLayouts(itemsWidth, labelWidth);   
            }   
        }   
   
        if (itemsWidth > 0) {   
            canvas.save();   
            // Skip padding space and hide a part of top and bottom items    
            canvas.translate(PADDING, -ITEM_OFFSET);   
            drawItems(canvas);   
            drawValue(canvas);   
            canvas.restore();   
        }   
   
        drawCenterRect(canvas);   
        drawShadows(canvas);   
    }   
   
    /**  
     *畫出頂部和底部的控件
     * @param canvas the canvas for drawing  
     */   
    private void drawShadows(Canvas canvas) {   
        topShadow.setBounds(0, 0, getWidth(), getHeight() / visibleItems);   
        topShadow.draw(canvas);   
   
        bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems,   
                getWidth(), getHeight());   
        bottomShadow.draw(canvas);   
    }   
   
    /**  
     * Draws value and label layout  
     * @param canvas the canvas for drawing  
     */   
    private void drawValue(Canvas canvas) {   
        valuePaint.setColor(VALUE_TEXT_COLOR);   
        valuePaint.drawableState = getDrawableState();   
   
        Rect bounds = new Rect();   
        itemsLayout.getLineBounds(visibleItems / 2, bounds);   
   
        // draw label    
        if (labelLayout != null) {   
            canvas.save();   
            canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top);   
            labelLayout.draw(canvas);   
            canvas.restore();   
        }   
   
        // draw current value    
        if (valueLayout != null) {   
            canvas.save();   
            canvas.translate(0, bounds.top + scrollingOffset);   
            valueLayout.draw(canvas);   
            canvas.restore();   
        }   
    }   
   
    /**  
     * Draws items  
     * @param canvas the canvas for drawing  
     */   
    private void drawItems(Canvas canvas) {   
        canvas.save();   
           
        int top = itemsLayout.getLineTop(1);   
        canvas.translate(0, - top + scrollingOffset);   
           
        itemsPaint.setColor(ITEMS_TEXT_COLOR);   
        itemsPaint.drawableState = getDrawableState();   
        itemsLayout.draw(canvas);   
           
        canvas.restore();   
    }   
   
    /**  
     * Draws rect for current value  
     * @param canvas the canvas for drawing  
     */   
    private void drawCenterRect(Canvas canvas) {   
        int center = getHeight() / 2;   
        int offset = getItemHeight() / 2;   
        centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);   
        centerDrawable.draw(canvas);   
    }   
   
    @Override   
    public boolean onTouchEvent(MotionEvent event) {   
        WheelAdapter adapter = getAdapter();   
        if (adapter == null) {   
            return true;   
        }   
           
            if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {   
            justify();   
        }   
        return true;   
    }   
       
    /**  
     * 滾動滾輪的方法
     * @param delta the scrolling value  
     */   
    private void doScroll(int delta) {   
        scrollingOffset += delta;   
           
        int count = scrollingOffset / getItemHeight();   
        int pos = currentItem - count;   
        if (isCyclic && adapter.getItemsCount() > 0) {   
            // fix position by rotating    
            while (pos < 0) {   
                pos += adapter.getItemsCount();   
            }   
            pos %= adapter.getItemsCount();   
        } else if (isScrollingPerformed) {   
            //     
            if (pos < 0) {   
                count = currentItem;   
                pos = 0;   
            } else if (pos >= adapter.getItemsCount()) {   
                count = currentItem - adapter.getItemsCount() + 1;   
                pos = adapter.getItemsCount() - 1;   
            }   
        } else {   
            // fix position    
            pos = Math.max(pos, 0);   
            pos = Math.min(pos, adapter.getItemsCount() - 1);   
        }   
           
        int offset = scrollingOffset;   
        if (pos != currentItem) {   
            setCurrentItem(pos, false);   
        } else {   
            invalidate();   
        }   
           
        // update offset    
        scrollingOffset = offset - count * getItemHeight();   
        if (scrollingOffset > getHeight()) {   
            scrollingOffset = scrollingOffset % getHeight() + getHeight();   
        }   
    }   
       
    // 手勢的監聽者
    private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {   
        public boolean onDown(MotionEvent e) {   
            if (isScrollingPerformed) {   
                scroller.forceFinished(true);   
                clearMessages();   
                return true;   
            }   
            return false;   
        }   
           
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {   
            startScrolling();   
            doScroll((int)-distanceY);   
            return true;   
        }   
           
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {   
            lastScrollY = currentItem * getItemHeight() + scrollingOffset;   
            int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();   
            int minY = isCyclic ? -maxY : 0;   
            scroller.fling(0, lastScrollY, 0, (int) -velocityY / 2, 0, 0, minY, maxY);   
            setNextMessage(MESSAGE_SCROLL);   
            return true;   
        }   
    };   
   
    // 消息的本事
    private final int MESSAGE_SCROLL = 0;   
    private final int MESSAGE_JUSTIFY = 1;   
       
    /**  
     * 請出消息隊列的消息 防治下一條消息
     *   
     * @param message the message to set  
     */   
    private void setNextMessage(int message) {   
        clearMessages();   
        animationHandler.sendEmptyMessage(message);   
    }   
   
    /**  
     * Clears messages from queue  
     */   
    private void clearMessages() {   
        animationHandler.removeMessages(MESSAGE_SCROLL);   
        animationHandler.removeMessages(MESSAGE_JUSTIFY);   
    }   
       
    //動畫的handler
    private Handler animationHandler = new Handler() {   
        public void handleMessage(Message msg) {   
            scroller.computeScrollOffset();   
            int currY = scroller.getCurrY();   
            int delta = lastScrollY - currY;   
            lastScrollY = currY;   
            if (delta != 0) {   
                doScroll(delta);   
            }   
               
            // scrolling is not finished when it comes to final Y    
            // so, finish it manually     
            if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {   
                currY = scroller.getFinalY();   
                scroller.forceFinished(true);   
            }   
            if (!scroller.isFinished()) {   
                animationHandler.sendEmptyMessage(msg.what);   
            } else if (msg.what == MESSAGE_SCROLL) {   
                justify();   
            } else {   
                finishScrolling();   
            }   
        }   
    };   
       
    /**  
     *調整滾輪的方法
     */   
    private void justify() {   
        if (adapter == null) {   
            return;   
        }   
           
        lastScrollY = 0;   
        int offset = scrollingOffset;   
        int itemHeight = getItemHeight();   
        boolean needToIncrease = offset > 0 ? currentItem < adapter.getItemsCount() : currentItem > 0;    
        if ((isCyclic || needToIncrease) && Math.abs((float) offset) > (float) itemHeight / 2) {   
            if (offset < 0)   
                offset += itemHeight + MIN_DELTA_FOR_SCROLLING;   
            else   
                offset -= itemHeight + MIN_DELTA_FOR_SCROLLING;   
        }   
        if (Math.abs(offset) > MIN_DELTA_FOR_SCROLLING) {   
            scroller.startScroll(0, 0, 0, offset, SCROLLING_DURATION);   
            setNextMessage(MESSAGE_JUSTIFY);   
        } else {   
            finishScrolling();   
        }   
    }   
       
    /**  
     * 開始滾動
     */   
    private void startScrolling() {   
        if (!isScrollingPerformed) {   
            isScrollingPerformed = true;   
            notifyScrollingListenersAboutStart();   
        }   
    }   
   
    /**  
     * 結束滾動
     */   
    void finishScrolling() {   
        if (isScrollingPerformed) {   
            notifyScrollingListenersAboutEnd();   
            isScrollingPerformed = false;   
        }   
        invalidateLayouts();   
        invalidate();   
    }   
           
    /**  
     * 滾動滾輪
     * @param itemsToSkip items to scroll  
     * @param time scrolling duration  
     */   
    public void scroll(int itemsToScroll, int time) {   
        scroller.forceFinished(true);   
        lastScrollY = scrollingOffset;   
        int offset = itemsToScroll * getItemHeight();          
        scroller.startScroll(0, lastScrollY, 0, offset - lastScrollY, time);   
        setNextMessage(MESSAGE_SCROLL);        
        startScrolling();   
    }  

在629行到744行的代碼是繪制圖形,747行onTouchEvent()里面主要是調用了882行的justify()方法,用於調整畫面

@Override   
    public boolean onTouchEvent(MotionEvent event) {   
        WheelAdapter adapter = getAdapter();   
        if (adapter == null) {   
            return true;   
        }   
           
            if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {   
            justify();   
        }   
        return true;   
    }  
/**  
     * Justifies wheel  
     */   
    private void justify() {   
        if (adapter == null) {   
            return;   
        }   
           
        lastScrollY = 0;   
        int offset = scrollingOffset;   
        int itemHeight = getItemHeight();   
        boolean needToIncrease = offset > 0 ? currentItem < adapter.getItemsCount() : currentItem > 0;    
        if ((isCyclic || needToIncrease) && Math.abs((float) offset) > (float) itemHeight / 2) {   
            if (offset < 0)   
                offset += itemHeight + MIN_DELTA_FOR_SCROLLING;   
            else   
                offset -= itemHeight + MIN_DELTA_FOR_SCROLLING;   
        }   
        if (Math.abs(offset) > MIN_DELTA_FOR_SCROLLING) {   
            scroller.startScroll(0, 0, 0, offset, SCROLLING_DURATION);   
            setNextMessage(MESSAGE_JUSTIFY);   
        } else {   
            finishScrolling();   
        }   
    }  

我們看下重寫的系統回調函數onMeasure()(用於測量各個控件距離,父子控件空間大小等):

 

@Override   
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);   
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);   
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);   
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);   
   
        int width = calculateLayoutWidth(widthSize, widthMode);   
   
        int height;   
        if (heightMode == MeasureSpec.EXACTLY) {   
            height = heightSize;   
        } else {   
            height = getDesiredHeight(itemsLayout);   
   
            if (heightMode == MeasureSpec.AT_MOST) {   
                height = Math.min(height, heightSize);   
            }   
        }   
   
        setMeasuredDimension(width, height);   
    }  
里面用到了532行calculateLayoutWidth()的方法,就是計算Layout的寬度,在calculateLayoutWidth()這個方法里面調用了

/**  
     * Creates layouts  
     * @param widthItems width of items layout  
     * @param widthLabel width of label layout  
     */   
    private void createLayouts(int widthItems, int widthLabel) {   
        if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {   
            itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,   
                    widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,   
                    1, ADDITIONAL_ITEM_HEIGHT, false);   
        } else {   
            itemsLayout.increaseWidthTo(widthItems);   
        }   
   
        if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {   
            String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;   
            valueLayout = new StaticLayout(text != null ? text : "",   
                    valuePaint, widthItems, widthLabel > 0 ?   
                            Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,   
                            1, ADDITIONAL_ITEM_HEIGHT, false);   
        } else if (isScrollingPerformed) {   
            valueLayout = null;   
        } else {   
            valueLayout.increaseWidthTo(widthItems);   
        }   
   
        if (widthLabel > 0) {   
            if (labelLayout == null || labelLayout.getWidth() > widthLabel) {   
                labelLayout = new StaticLayout(label, valuePaint,   
                        widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,   
                        ADDITIONAL_ITEM_HEIGHT, false);   
            } else {   
                labelLayout.increaseWidthTo(widthLabel);   
            }   
        }   
    }  

 

然后我們接着看onDraw()方法:

@Override   
    protected void onDraw(Canvas canvas) {   
        super.onDraw(canvas);   
           
        if (itemsLayout == null) {   
            if (itemsWidth == 0) {   
                calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);   
            } else {   
                createLayouts(itemsWidth, labelWidth);   
            }   
        }   
   
        if (itemsWidth > 0) {   
            canvas.save();   
            // Skip padding space and hide a part of top and bottom items    
            canvas.translate(PADDING, -ITEM_OFFSET);   
            drawItems(canvas);   
            drawValue(canvas);   
            canvas.restore();   
        }   
   
        drawCenterRect(canvas);   
        drawShadows(canvas);   
    }  

在onDraw方法中,也調用了CreateLayout()方法,然后在后面調用drawCenterRect()、drawItems()、drawValue()、繪制陰影drawShadows()兩個方法:

**  
     * Draws shadows on top and bottom of control  
     * @param canvas the canvas for drawing  
     */   
    private void drawShadows(Canvas canvas) {   
        topShadow.setBounds(0, 0, getWidth(), getHeight() / visibleItems);   
        topShadow.draw(canvas);   
   
   
        bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems,   
                getWidth(), getHeight());   
        bottomShadow.draw(canvas);   
    }   
   
   
    /**  
     * Draws value and label layout  
     * @param canvas the canvas for drawing  
     */   
    private void drawValue(Canvas canvas) {   
        valuePaint.setColor(VALUE_TEXT_COLOR);   
        valuePaint.drawableState = getDrawableState();   
   
   
        Rect bounds = new Rect();   
        itemsLayout.getLineBounds(visibleItems / 2, bounds);   
   
   
        // draw label    
        if (labelLayout != null) {   
            canvas.save();   
            canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top);   
            labelLayout.draw(canvas);   
            canvas.restore();   
        }   
   
   
        // draw current value    
        if (valueLayout != null) {   
            canvas.save();   
            canvas.translate(0, bounds.top + scrollingOffset);   
            valueLayout.draw(canvas);   
            canvas.restore();   
        }   
    }   
   
   
    /**  
     * Draws items  
     * @param canvas the canvas for drawing  
     */   
    private void drawItems(Canvas canvas) {   
        canvas.save();   
           
        int top = itemsLayout.getLineTop(1);   
        canvas.translate(0, - top + scrollingOffset);   
           
        itemsPaint.setColor(ITEMS_TEXT_COLOR);   
        itemsPaint.drawableState = getDrawableState();   
        itemsLayout.draw(canvas);   
           
        canvas.restore();   
    }   
   
   
    /**  
     * Draws rect for current value  
     * @param canvas the canvas for drawing  
     */   
    private void drawCenterRect(Canvas canvas) {   
        int center = getHeight() / 2;   
        int offset = getItemHeight() / 2;   
        centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);   
        centerDrawable.draw(canvas);   
    }  

主要就是通過canvas類進行圖形的繪制。

最后,我們看下840行定義的手勢監聽:

// gesture listener    
    private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {   
        public boolean onDown(MotionEvent e) {   
            if (isScrollingPerformed) {   
                scroller.forceFinished(true);   
                clearMessages();   
                return true;   
            }   
            return false;   
        }   
           
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {   
            startScrolling();   
            doScroll((int)-distanceY);   
            return true;   
        }   
           
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {   
            lastScrollY = currentItem * getItemHeight() + scrollingOffset;   
            int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();   
            int minY = isCyclic ? -maxY : 0;   
            scroller.fling(0, lastScrollY, 0, (int) -velocityY / 2, 0, 0, minY, maxY);   
            setNextMessage(MESSAGE_SCROLL);   
            return true;   
        }   
    };  

里面主要調用的方法:clearMessages()、startScrolling()、doScroll()、setNextMessage(),先看下中間的兩個方法開始滑動和滑動

/**  
     * Scrolls the wheel  
     * @param delta the scrolling value  
     */   
    private void doScroll(int delta) {   
        scrollingOffset += delta;          
        int count = scrollingOffset / getItemHeight();   
        int pos = currentItem - count;   
        if (isCyclic && adapter.getItemsCount() > 0) {   
            // fix position by rotating    
            while (pos < 0) {   
                pos += adapter.getItemsCount();   
            }   
            pos %= adapter.getItemsCount();   
        } else if (isScrollingPerformed) {   
            //     
            if (pos < 0) {   
                count = currentItem;   
                pos = 0;   
            } else if (pos >= adapter.getItemsCount()) {   
                count = currentItem - adapter.getItemsCount() + 1;   
                pos = adapter.getItemsCount() - 1;   
            }   
        } else {   
            // fix position    
            pos = Math.max(pos, 0);   
            pos = Math.min(pos, adapter.getItemsCount() - 1);   
        }   
           
        int offset = scrollingOffset;   
        if (pos != currentItem) {   
            setCurrentItem(pos, false);   
        } else {   
            invalidate();   
        }   
           
        // update offset    
        scrollingOffset = offset - count * getItemHeight();   
        if (scrollingOffset > getHeight()) {   
            scrollingOffset = scrollingOffset % getHeight() + getHeight();   
        }   
    }  
 

/**  
     * Starts scrolling  
     */   
    private void startScrolling() {   
        if (!isScrollingPerformed) {   
            isScrollingPerformed = true;   
            notifyScrollingListenersAboutStart();   
        }   
    }  

在startScrolling方法里面有287行的notifyScrollingListenersAboutStart函數。

再看clearMessages()、setMessageNext()

private void setNextMessage(int message) {   
        clearMessages();   
        animationHandler.sendEmptyMessage(message);   
    }   
   
   
    /**  
     * Clears messages from queue  
     */   
    private void clearMessages() {   
        animationHandler.removeMessages(MESSAGE_SCROLL);   
        animationHandler.removeMessages(MESSAGE_JUSTIFY);   
    }  

里面使用到了animationHandler,用來傳遞動畫有段的操作:

animation handler    
    private Handler animationHandler = new Handler() {   
        public void handleMessage(Message msg) {   
            scroller.computeScrollOffset();   
            int currY = scroller.getCurrY();   
            int delta = lastScrollY - currY;   
            lastScrollY = currY;   
            if (delta != 0) {   
                doScroll(delta);   
            }   
               
            // scrolling is not finished when it comes to final Y    
            // so, finish it manually     
            if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {   
                currY = scroller.getFinalY();   
                scroller.forceFinished(true);   
            }   
            if (!scroller.isFinished()) {   
                animationHandler.sendEmptyMessage(msg.what);   
            } else if (msg.what == MESSAGE_SCROLL) {   
                justify();   
            } else {   
                finishScrolling();   
            }   
        }   
    };  

里面調用了finishScrolling()

/**  
     * Finishes scrolling  
     */   
    void finishScrolling() {   
        if (isScrollingPerformed) {   
            notifyScrollingListenersAboutEnd();   
            isScrollingPerformed = false;   
        }   
        invalidateLayouts();   
        invalidate();   
    }  

over

這就是對wheelview的控件的分析,有了它,時間滾輪控件,so-easy。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM