1. 程式人生 > >自制日期選擇器(datepicker)

自制日期選擇器(datepicker)

在網上看過很多關於datepicker的列子,效果就是我想要的,但介面卻和我的專案不同,因此在網上的例子上做做手腳,改變下外觀,先說下改動的地方,再將程式碼放上去。

1、主要改動關鍵是在WheelView類:protected void onDraw(Canvas canvas)方法中,drawCenterRect(canvas)的位置放置

@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		
		if (itemsLayout == null) {
			if (itemsWidth == 0) {
				calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);
			} else {
				createLayouts(itemsWidth, labelWidth);
			}
		}

		drawCenterRect(canvas); // 放在此處,字在背景色上
		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);
	}

2、在wheel_val.xml檔案中設定選中項背景色

<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#FF00b5ef"       // 選中項的上部分
android:centerColor="#FF00b5ef"    // 選中項的中間部分
android:endColor="#FF00b5ef"        // 選中項的下部分
android:angle="90" />


<stroke android:width="1dp" android:color="#FFFFFFFF" /> //  年月日間的間隔顏色以及寬度

</shape>

現在看下效果圖:


下面公佈下程式碼:

NumericWheelAdapter類:

/*
 *  Copyright 2010 Yuri Kanivets
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.lakala.pay.easycashregisterphone2.view.datepicker;


/**
 * Numeric Wheel adapter.
 */
public class NumericWheelAdapter implements WheelAdapter {
	
	/** The default min value */
	public static final int DEFAULT_MAX_VALUE = 9;

	/** The default max value */
	private static final int DEFAULT_MIN_VALUE = 0;
	
	// Values
	private int minValue;
	private int maxValue;
	
	// format
	private String format;
	
	/**
	 * Default constructor
	 */
	public NumericWheelAdapter() {
		this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE);
	}

	/**
	 * Constructor
	 * @param minValue the wheel min value
	 * @param maxValue the wheel max value
	 */
	public NumericWheelAdapter(int minValue, int maxValue) {
		this(minValue, maxValue, null);
	}

	/**
	 * Constructor
	 * @param minValue the wheel min value
	 * @param maxValue the wheel max value
	 * @param format the format string
	 */
	public NumericWheelAdapter(int minValue, int maxValue, String format) {
		this.minValue = minValue;
		this.maxValue = maxValue;
		this.format = format;
	}

	
	public String getItem(int index) {
		if (index >= 0 && index < getItemsCount()) {
			int value = minValue + index;
			return format != null ? String.format(format, value) : Integer.toString(value);
		}
		return null;
	}

	
	public int getItemsCount() {
		return maxValue - minValue + 1;
	}
	
	
	public int getMaximumLength() {
		int max = Math.max(Math.abs(maxValue), Math.abs(minValue));
		int maxLen = Integer.toString(max).length();
		if (minValue < 0) {
			maxLen++;
		}
		return maxLen;
	}
}

OnWheelChangedListener類:
/*
 *  Copyright 2010 Yuri Kanivets
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.lakala.pay.easycashregisterphone2.view.datepicker;

/**
 * Wheel changed listener interface.
 * <p>The currentItemChanged() method is called whenever current wheel positions is changed:
 * <li> New Wheel position is set
 * <li> Wheel view is scrolled
 */
public interface OnWheelChangedListener {
	/**
	 * Callback method to be invoked when current item changed
	 * @param wheel the wheel view whose state has changed
	 * @param oldValue the old value of current item
	 * @param newValue the new value of current item
	 */
	void onChanged(WheelView wheel, int oldValue, int newValue);
}

OnWheelScrollListener類:
/*
 *  Copyright 2010 Yuri Kanivets
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.lakala.pay.easycashregisterphone2.view.datepicker;

/**
 * Wheel scrolled listener interface.
 */
public interface OnWheelScrollListener {
	/**
	 * Callback method to be invoked when scrolling started.
	 * @param wheel the wheel view whose state has changed.
	 */
	void onScrollingStarted(WheelView wheel);
	
	/**
	 * Callback method to be invoked when scrolling ended.
	 * @param wheel the wheel view whose state has changed.
	 */
	void onScrollingFinished(WheelView wheel);
}

WheelAdapter類:
/*
 *  Copyright 2010 Yuri Kanivets
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.lakala.pay.easycashregisterphone2.view.datepicker;

public interface WheelAdapter {
	/**
	 * Gets items count
	 * @return the count of wheel items
	 */
	public int getItemsCount();
	
	/**
	 * Gets a wheel item by index.
	 * 
	 * @param index the item index
	 * @return the wheel item text or null
	 */
	public String getItem(int index);
	
	/**
	 * Gets maximum item length. It is used to determine the wheel width. 
	 * If -1 is returned there will be used the default wheel width.
	 * 
	 * @return the maximum item length or -1
	 */
	public int getMaximumLength();
	
	
	
	
}

WheelView類:
/*
 *  Android Wheel Control.
 *  https://code.google.com/p/android-wheel/
 *  
 *  Copyright 2010 Yuri Kanivets
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package kankan.wheel.widget;

import java.util.LinkedList;
import java.util.List;

import org.unism.wang.R;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.GradientDrawable.Orientation;
import android.os.Handler;
import android.os.Message;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.Scroller;

/**
 * Numeric wheel view.
 * 
 * @author Yuri Kanivets
 */
public class WheelView extends View {
	/** 
	 * Scrolling duration 
	 * 
	 *  </br>
	 * 滾動持續時間(毫秒) 
	 */
	private static final int SCROLLING_DURATION = 400;

	/** 
	 * Minimum delta for scrolling 
	 * 
	 *  </br>
	 * 滾動的最小差值 
	 */
	private static final int MIN_DELTA_FOR_SCROLLING = 1;

	/** 
	 * Current value & label text color 
	 * 
	 *  </br>
	 * 當前選中項  的 文字 的 顏色 
	 */
	private static final int VALUE_TEXT_COLOR = 0xFFFFFFFF;

	/** 
	 * Items text color 
	 * 
	 *  </br>
	 * 選項 的 文字 的 顏色 
	 */
	private static final int ITEMS_TEXT_COLOR = 0xFF000000;

	/** 
	 * Top and bottom shadows colors 
	 * 
	 *  </br>
	 * 頂部和底部陰影 的 顏色   </br>
	 * 選擇器 頂部和底部顏色是漸變的,這裡指定一個漸變色的陣列
	 */
	private static final int[] SHADOWS_COLORS = new int[] { 0xFFFFFFFF,
			0x00EEEED5, 0x00EEEED5 };

	/** 
	 * Additional items height (is added to standard text item height) 
	 * 
	 *  </br>
	 * 附加項的高度項的高度  (單位應該是dp) </br>
	 * 從後面getDesiredHeight() 函式看出,這個值應該是平分給每一個選項的。 </br>
	 * 類似於設定行距吧,不過這是一個總和,也就是有5個可見項,那麼每個可見項的附加高就是 ADDITIONAL_ITEM_HEIGHT/5
	 */
	private static final int ADDITIONAL_ITEM_HEIGHT = 15;

	/** 
	 * Text size 
	 * 
	 *  </br>
	 * 字號
	 */
	private static final int TEXT_SIZE = 24;

	/** 
	 * Top and bottom items offset (to hide that) 
	 * 
	 *  </br>
	 * 這個是選項在頂部和底部被抵消的值。 </br>
	 * 怎麼解釋呢~ 其實試一下就知道了, </br>
	 *   比如說在picker中顯示的五項(中間那個是選中的),剩餘4是沒選中的。 </br>
	 *   在沒選中的這4項中,位於頂部和底部的項,會用陰影遮擋(遮擋一部分,這樣使用者就明白是需要滑動操作了) </br>
	 *   這裡設定的值,就是指定遮擋的size,這裡的預設值 TEXT_SIZE / 5 是遮擋了1/5的字號 (那麼單位也應該是sp吧)
	 */
	private static final int ITEM_OFFSET = TEXT_SIZE / 5;

	/** 
	 * Additional width for items layout 
	 * 
	 *  </br>
	 * 附加項空間? 不理解~~還是試試吧 </br>
	 * 應該是項的寬,這個屬性應該是受制於外邊區域的, 設定的太寬裡面的文字顯示會出問題 </br>
	 * 具體影響 有待進一步實驗證明
	 */
	private static final int ADDITIONAL_ITEMS_SPACE = 10;

	/** 
	 * Label offset
	 * 
	 *  </br>
	 * 標籤抵消 (作用未知) 用1,8,和800 三個值實驗(8是預設值) 效果是一樣的。 
	 */
	private static final int LABEL_OFFSET = 8;

	/** 
	 * Left and right padding value
	 * 
	 *  </br>
	 * 填充  </br>
	 * 這個選項的內部填充,如果選項是個TextView的話,那這裡設定的即是TextView的填充 </br>
	 * =。=!後面程式碼還米有看,item是啥我也不知道
	 */
	private static final int PADDING = 10;

	/** 
	 * Default count of visible items 
	 * 
	 *  </br>
	 * 預設可見項的數目。就是picker中顯示幾項
	 */
	private static final int DEF_VISIBLE_ITEMS = 5;

	// Wheel Values
	/**
	 * Wheel Values
	 * 
	 *  </br>
	 * 介面卡,items就通過介面卡來提供的吧
	 */
	private WheelAdapter adapter = null;
	/**
	 * Wheel Values
	 * 
	 *  </br>
	 * 當前項
	 */
	private int currentItem = 0;
	
	// Widths
	/**
	 * Widths
	 * 
	 *  </br>
	 * 做了實驗 把值設為100 沒有任何變化
	 */
	private int itemsWidth = 0;
	/**
	 * Widths
	 * 
	 *  </br>
	 * 也做了實驗 把值設為100 沒有任何變化
	 */
	private int labelWidth = 0;

	// Count of visible items
	/**
	 * Count of visible items
	 * 
	 *  </br>
	 * 可見專案的條數
	 */
	private int visibleItems = DEF_VISIBLE_ITEMS;
	
	// Item height
	/**
	 * Item height
	 * 
	 *  </br>
	 * 是item的高
	 */
	private int itemHeight = 0;

	// Text paints
	/**
	 * Text paints
	 * 
	 *  </br>
	 * 選中文字的顏色
	 */
	private TextPaint itemsPaint;
	/**
	 * Text paints
	 * 
	 *  </br>
	 * 未選中文字的顏色
	 */
	private TextPaint valuePaint;

	// Layouts
	/**
	 * Layouts
	 * 
	 *  </br>
	 * 選項  的 佈局
	 */
	private StaticLayout itemsLayout;
	/**
	 * Layouts
	 * 
	 *  </br>
	 * 標籤 的 佈局
	 */
	private StaticLayout labelLayout;
	/**
	 * Layouts
	 * 
	 *  </br>
	 * 選中項 的 佈局
	 */
	private StaticLayout valueLayout;

	// Label & background
	/**
	 * Label & background
	 * 
	 *  </br>
	 * 標籤
	 */
	private String label;
	/**
	 * Label & background
	 * 中間的幾何體 </br>
	 * 選中區域的背景
	 */
	private Drawable centerDrawable;

	// Shadows drawables
	/**
	 * Shadows drawables
	 * 
	 *  </br>
	 * 上邊 和 底部 的陰影部分的背景資源
	 */
	private GradientDrawable topShadow;
	private GradientDrawable bottomShadow;

	// Scrolling
	/**
	 * Scrolling
	 * 
	 *  </br>
	 * 執行滾動?
	 */
	private boolean isScrollingPerformed; 
	/**
	 * Scrolling
	 * 
	 *  </br>
	 * 滾動抵消?
	 */
	private int scrollingOffset;

	// Scrolling animation
	/**
	 * Scrolling animation
	 * 
	 *  </br>
	 * 手勢檢測器
	 */
	private GestureDetector gestureDetector;
	/**
	 * Scrolling animation
	 * 
	 *  </br>
	 * 卷軸
	 */
	private Scroller scroller;
	/**
	 * Scrolling animation
	 * 
	 *  </br>
	 * 最後的 卷軸Y
	 */
	private int lastScrollY;

	// Cyclic
	/**
	 * Cyclic
	 * 
	 *  </br>
	 * 是否迴圈
	 */
	boolean isCyclic = false;
	
	// Listeners
	/**
	 * Listeners
	 * 
	 *  </br>
	 * 控制元件改變監聽器的 集合
	 */
	private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>();
	/**
	 * Listeners
	 * 
	 *  </br>
	 * 控制元件滾動監聽器的 集合
	 */
	private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>();

	/**
	 * Constructor
	 *
	 * </br>
	 * 構造器 並例項了手勢檢測器 和 卷軸
	 */
	public WheelView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initData(context);
	}

	/**
	 * Constructor
	 * 
	 * </br>
	 * 構造器 並例項了手勢檢測器 和 卷軸
	 */
	public WheelView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initData(context);
	}

	/**
	 * Constructor
	 *
	 * </br>
	 * 構造器 並例項了手勢檢測器 和 卷軸
	 */
	public WheelView(Context context) {
		super(context);
		initData(context);
	}
	
	/**
	 * Initializes class data
	 * @param context the context
	 *
	 * </br>
	 * 資料初始化 </br>
	 * 就是把手勢檢測器 和 卷軸類 例項了 </br>
	 * 這個方法在所有的構造器中都被呼叫了
	 */
	private void initData(Context context) {
		gestureDetector = new GestureDetector(context, gestureListener);
		gestureDetector.setIsLongpressEnabled(false); //這個沒看出來有什麼用,不設定,或設定成true都不影響效果
		
		scroller = new Scroller(context);
	}
	
	/**
	 * Gets wheel adapter
	 * @return the adapter
	 *
	 * </br>
	 * 獲得介面卡
	 */
	public WheelAdapter getAdapter() {
		return adapter;
	}
	
	/**
	 * Sets wheel adapter
	 * @param adapter the new wheel adapter
	 *
	 * </br>
	 * 設定介面卡 </br>
	 * 還會介面進行了重繪
	 */
	public void setAdapter(WheelAdapter adapter) {
		this.adapter = adapter;
		invalidateLayouts();
		invalidate();
	}
	
	/**
	 * Set the the specified scrolling interpolator
	 * @param interpolator the interpolator
	 *
	 * </br>
	 * 作用也是設定 卷軸的吧,是通過動畫插補器來例項 卷軸物件
	 */
	public void setInterpolator(Interpolator interpolator) {
		scroller.forceFinished(true);
		scroller = new Scroller(getContext(), interpolator);
	}
	
	/**
	 * Gets count of visible items
	 * 
	 * @return the count of visible items
	 *
	 * </br>
	 * 獲得可見項的條數
	 */
	public int getVisibleItems() {
		return visibleItems;
	}

	/**
	 * Sets count of visible items
	 * 
	 * @param count
	 *            the new count
	 *
	 * </br>
	 * 設定可見項的條數 並重繪view
	 */
	public void setVisibleItems(int count) {
		visibleItems = count;
		invalidate();
	}

	/**
	 * Gets label
	 * 
	 * @return the label
	 *
	 * </br>
	 * 獲得標籤
	 */
	public String getLabel() {
		return label;
	}

	/**
	 * Sets label
	 * 
	 * @param newLabel
	 *            the label to set
	 *
	 * </br>
	 * 設定標籤
	 */
	public void setLabel(String newLabel) {
		if (label == null || !label.equals(newLabel)) {
			label = newLabel;
			labelLayout = null;
			invalidate();
		}
	}
	
	/**
	 * Adds wheel changing listener
	 * @param listener the listener 
	 *
	 * </br>
	 * 新增控制元件改變監聽器
	 */
	public void addChangingListener(OnWheelChangedListener listener) {
		changingListeners.add(listener);
	}

	/**
	 * Removes wheel changing listener
	 * @param listener the listener
	 *
	 * </br>
	 * 移除控制元件改變監聽器
	 */
	public void removeChangingListener(OnWheelChangedListener listener) {
		changingListeners.remove(listener);
	}
	
	/**
	 * Notifies changing listeners
	 * @param oldValue the old wheel value
	 * @param newValue the new wheel value
	 *
	 * </br>
	 * 通知 改變監聽器, </br>
	 * 迴圈  控制元件改變監聽器集合, 並依次呼叫它們的onChenge方法
	 */
	protected void notifyChangingListeners(int oldValue, int newValue) {
		for (OnWheelChangedListener listener : changingListeners) {
			listener.onChanged(this, oldValue, newValue);
		}
	}

	/**
	 * Adds wheel scrolling listener
	 * @param listener the listener 
	 *
	 * </br>
	 * 新增控制元件滾動監聽器
	 */
	public void addScrollingListener(OnWheelScrollListener listener) {
		scrollingListeners.add(listener);
	}

	/**
	 * Removes wheel scrolling listener
	 * @param listener the listener
	 *
	 * </br>
	 * 移除控制元件滾動監聽器
	 */
	public void removeScrollingListener(OnWheelScrollListener listener) {
		scrollingListeners.remove(listener);
	}
	
	/**
	 * Notifies listeners about starting scrolling
	 *
	 * </br>
	 * 通知控制元件滾動監聽器呼叫開始滾動的方法
	 */
	protected void notifyScrollingListenersAboutStart() {
		for (OnWheelScrollListener listener : scrollingListeners) {
			listener.onScrollingStarted(this);
		}
	}

	/**
	 * Notifies listeners about ending scrolling
	 *
	 * </br>
	 * 通知控制元件滾動監聽器呼叫結束滾動的方法
	 */
	protected void notifyScrollingListenersAboutEnd() {
		for (OnWheelScrollListener listener : scrollingListeners) {
			listener.onScrollingFinished(this);
		}
	}

	/**
	 * Gets current value
	 * 
	 * @return the current value
	 *
	 * </br>
	 * 返回當前項的索引
	 */
	public int getCurrentItem() {
		return currentItem;
	}

	/**
	 * Sets the current item. Does nothing when index is wrong.
	 * 
	 * @param index the item index
	 * @param animated the animation flag
	 *
	 * </br>
	 * 設定當前項 如果輸入錯誤的索引,則控制元件什麼都不會做喲 </br>
	 * 第二個引數是設定是否使用滾動動畫的
	 */
	public void setCurrentItem(int index, boolean animated) {
		if (adapter == null || adapter.getItemsCount() == 0) {
			return; // throw?
		}
		if (index < 0 || index >= adapter.getItemsCount()) {
			if (isCyclic) {
				while (index < 0) {
					index += adapter.getItemsCount();
				}
				index %= adapter.getItemsCount();
			} else{
				return; // throw?
			}
		}
		if (index != currentItem) {
			if (animated) {
				scroll(index - currentItem, SCROLLING_DURATION);
			} else {
				invalidateLayouts();
			
				int old = currentItem;
				currentItem = index;
			
				notifyChangingListeners(old, currentItem);
			
				invalidate();
			}
		}
	}

	/**
	 * Sets the current item w/o animation. Does nothing when index is wrong.
	 * 
	 * @param index the item index
	 *
	 * </br>
	 * 設定當前選中項,預設是不啟動動畫的
	 */
	public void setCurrentItem(int index) {
		setCurrentItem(index, false);
	}	
	
	/**
	 * Tests if wheel is cyclic. That means before the 1st item there is shown the last one
	 * @return true if wheel is cyclic
	 *
	 * </br>
	 * 是否迴圈顯示
	 */
	public boolean isCyclic() {
		return isCyclic;
	}

	/**
	 * Set wheel cyclic flag
	 * @param isCyclic the flag to set
	 *
	 * </br>
	 * 設定是否迴圈顯示
	 */
	public void setCyclic(boolean isCyclic) {
		this.isCyclic = isCyclic;
		
		invalidate();
		invalidateLayouts();
	}

	/**
	 * Invalidates layouts
	 * 
	 *  </br>
	 * 重繪佈局 </br>
	 * 方法將 選項佈局itemsLayout 和 選中項佈局 valueLayout 賦值為null </br>
	 * 同事將 滾動抵消?scrollingOffset 設定為0 </br>
	 * (這個scrollingOffset 還沒搞明白做什麼用)
	 */
	private void invalidateLayouts() {
		itemsLayout = null;
		valueLayout = null;
		scrollingOffset = 0;
	}

	/**
	 * Initializes resources
	 *
	 * </br>
	 * 初始化源 </br>
	 * 這個方法是這樣的, 判斷前面定義的畫筆、背景資源等私有屬性的值,如果是null就重新從靜態常量中取值,並付給響應的屬性。 </br>
	 * 如果這些必要屬性為空的話,這個函式應該起到了初始化的作用
	 */
	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);
	}

	/**
	 * Calculates desired height for layout
	 * 
	 * @param layout
	 *            the source layout
	 * @return the desired layout height
	 * 
	 * </br>
	 * 獲得理想的控制元件高度,並保證其不低於建議的最小高度
	 */
	private int getDesiredHeight(Layout layout) {
		if (layout == null) {
			return 0;
		}

		int desired = getItemHeight() * visibleItems - ITEM_OFFSET * 2
				- ADDITIONAL_ITEM_HEIGHT;

		// Check against our minimum height
		desired = Math.max(desired, getSuggestedMinimumHeight());

		return desired;
	}

	/**
	 * Returns text item by index
	 * @param index the item index
	 * @return the item or null
	 * 
	 * </br> 
	 * 指定索引,獲得選項的文字值(Sring) </br>
	 * 如果索引超出範圍,控制元件又不是迴圈的(isCyclic),則返回null </br>
	 * 如果是迴圈的情況,方法內部進行了處理, </br>
	 * 為負數則+count, 然會取餘
	 */
	private String getTextItem(int index) {
		if (adapter == null || adapter.getItemsCount() == 0) {
			return null;
		}
		int count = adapter.getItemsCount();
		if ((index < 0 || index >= count) && !isCyclic) {
			return null;
		} else {
			while (index < 0) {
				index = count + index;
			}
		}
		
		index %= count;
		return adapter.getItem(index);
	}
	
	/**
	 * Builds text depending on current value
	 * 
	 * @param useCurrentValue
	 * @return the text
	 * 
	 *  </br>
	 * 依賴當前項 構建文字 </br>
	 * 獲得一個字串,如果當前專案的索引為5, 可見專案數為3  </br>
	 * 則字串的值為 getTextItem(3).append("\n").getTextItem(4).append("\n").getTextItem(5).append("\n").getTextItem(6).append("\n").getTextItem(7) </br>
	 * 如果 引數useCurrentValue為false </br>
	 * 則返回的字串為 getTextItem(3).append("\n").getTextItem(4).append("\n").getTextItem(6).append("\n").getTextItem(7) </br>
	 * 如果getTextItem(i)返回null, 則不會向返回值中追加
	 */
	private String buildText(boolean useCurrentValue) {
		StringBuilder itemsText = new StringBuilder();
		int addItems = visibleItems / 2 + 1;

		for (int i = currentItem - addItems; i <= currentItem + addItems; i++) {
			if (useCurrentValue || i != currentItem) {
				String text = getTextItem(i);
				if (text != null) {
					itemsText.append(text);
				}
			}
			if (i < currentItem + addItems) {
				itemsText.append("\n");
			}
		}
		
		return itemsText.toString();
	}

	/**
	 * Returns the max item length that can be present
	 * @return the max length
	 * 
	 *  </br>
	 *  獲得最大的文字長度</br>
	 *  後面計算控制元件寬度用的
	 */
	private int getMaxTextLength() {
		WheelAdapter adapter = getAdapter();
		if (adapter == null) {
			return 0;
		}
		
		int adapterLength = adapter.getMaximumLength();
		if (adapterLength > 0) {
			return adapterLength;
		}

		String maxText = null;
		int addItems = visibleItems / 2;
		for (int i = Math.max(currentItem - addItems, 0);
				i < Math.min(currentItem + visibleItems, adapter.getItemsCount()); i++) {
			String text = adapter.getItem(i);
			if (text != null && (maxText == null || maxText.length() < text.length())) {
				maxText = text;
			}
		}//這個迴圈的範圍沒看明白呀,起始值不考慮迴圈嗎? 上限為什麼是 當前項索引+可見專案數?

		return maxText != null ? maxText.length() : 0;
	}

	/**
	 * Returns height of wheel item
	 * @return the item height
	 * 
	 * </br>
	 * 獲得選項高
	 */
	private int getItemHeight() {
		if (itemHeight != 0) {
			return itemHeight;
		} else if (itemsLayout != null && itemsLayout.getLineCount() > 2) {
			itemHeight = itemsLayout.getLineTop(2) - itemsLayout.getLineTop(1);
			return itemHeight;
		}//如果是itemlayout 為什麼要用 第三行的top減第一行的top呢,不是應該返回layout的高嘛?沒看明白
		
		return getHeight() / visibleItems;
	}

	/**
	 * Calculates control width and creates text layouts
	 * @param widthSize the input layout width
	 * @param mode the layout mode
	 * @return the calculated control width
	 * 
	 * </br>
	 * 計算佈局寬
	 */
	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;
	}

	/**
	 * Creates layouts
	 * @param widthItems width of items layout
	 * @param widthLabel width of label layout
	 * 
	 * </br>
	 * 建立佈局</br>
	 */
	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);
			}
		}
	}

	/**
	 * 重寫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);
	}

	/**
	 * 繪製
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		
		if (itemsLayout == null) {
			if (itemsWidth == 0) {
				calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);
			} else {
				createLayouts(itemsWidth, labelWidth);
			}
		}

		drawCenterRect(canvas); // 放在此處,字在背景色上
		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);
	}

	/**
	 * Draws shadows on top and bottom of control
	 * @param canvas the canvas for drawing
	 * 
	 * </br>
	 * 繪製控制元件頂端 和底部的 陰影區域 </br>
	 * 需要傳入畫布物件
	 */
	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
	 * 
	 * </br>
	 * 繪製選中項 和 標籤
	 */
	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
	 * 
	 * </br>
	 * 繪製選項
	 */
	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
	 * 
	 * </br>
	 * 繪製中間的矩形區域
	 */
	private void drawCenterRect(Canvas canvas) {
		int center = getHeight() / 2;
		int offset = getItemHeight() / 2;
		centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);
		centerDrawable.draw(canvas);
	}

	/**
	 * 觸控事件的 回撥方法</br>
	 * 看到了吧 如果介面卡 adapter是null 的話,這裡是什麼都不會做的。</br>
	 * 如果介面卡不為空,將MotionEvent傳遞給手勢識別器。 並判斷是否已ACTION_UP </br>
	 * 如果是說明操作已結束 呼叫justify()方法。</br>
	 * return true. 不會洩露
	 */
	@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;
	}
	
	/**
	 * Scrolls the wheel
	 * @param delta the scrolling value
	 * 
	 * </br>
	 * 滾動</br>
	 * 好像只是重新定義了 scrollingOffset的值,</br>
	 * 執行滾動的操作是這裡嗎?</br>
	 * 先往後看吧。
	 */
	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();
		}
	}
	
	// gesture listener
	/**
	 * gesture listener
	 * 
	 * </br>
	 * 手勢監聽器</br>
	 * 這裡是響應使用者fling 以及 scroll操作的程式碼
	 */
	private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {
		/**
		 * 這個是按下時停止滾動的操作
		 */
		public boolean onDown(MotionEvent e) {
			if (isScrollingPerformed) {
				scroller.forceFinished(true);
				clearMessages();
				return true;
			}
			return false;
		}
		
		/**
		 * scroll
		 */
		public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
			startScrolling();
			doScroll((int)-distanceY);
			return true;
		}
		
		/**
		 * fling
		 */
		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;
		}
	};

	// Messages
	/**
	 * Messages
	 * 
	 * </br>
	 * 向動畫處理器傳送的訊息 -滾動
	 */
	private final int MESSAGE_SCROLL = 0;
	/**
	 * Messages
	 * 
	 * </br>
	 * 向動畫處理器傳送的訊息 -證明
	 */
	private final int MESSAGE_JUSTIFY = 1;
	
	/**
	 * Set next message to queue. Clears queue before.
	 * 
	 * @param message the message to set
	 * 
	 * </br>
	 * 清楚動畫處理器animationHandler中的原有訊息,併發送新訊息
	 */
	private void setNextMessage(int message) {
		clearMessages();
		animationHandler.sendEmptyMessage(message);
	}

	/**
	 * Clears messages from queue
	 * 
	 * </br>
	 * 清楚動畫處理器中原有的訊息
	 */
	private void clearMessages() {
		animationHandler.removeMessages(MESSAGE_SCROLL);
		animationHandler.removeMessages(MESSAGE_JUSTIFY);
	}
	
	// animation handler
	/**
	 * animation handler
	 * 
	 * </br>
	 * 動畫處理器
	 */
	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();
			}
		}
	};
	
	/**
	 * Justifies wheel
	 * 
	 * </br>
	 * 驗證輪子
	 */
	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();
		}
	}
	
	/**
	 * Starts scrolling
	 * 
	 * </br>
	 * 開始滾動</br>
	 * 並通知開始滾動監聽器
	 */
	private void startScrolling() {
		if (!isScrollingPerformed) {
			isScrollingPerformed = true;
			notifyScrollingListenersAboutStart();
		}
	}

	/**
	 * Finishes scrolling
	 * 
	 * </br>
	 * 結束滾動</br>
	 * 並通知結束滾動監聽器
	 */
	void finishScrolling() {
		if (isScrollingPerformed) {
			notifyScrollingListenersAboutEnd();
			isScrollingPerformed = false;
		}
		invalidateLayouts();
		invalidate();
	}
	
	
	/**
	 * Scroll the wheel
	 * @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();
	}

}

MyDatePicker類:
package com.lakala.pay.easycashregisterphone2.view.datepicker;

import java.util.Calendar;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;

/**
 * DatePicker
 */
public class MyDatePicker extends LinearLayout {
	
	private Calendar calendar = Calendar.getInstance(); //日曆類
	private WheelView years, months, days; //Wheel picker
	private OnChangeListener onChangeListener; //onChangeListener
	
	//Constructors
	public MyDatePicker(Context context) {
		super(context);
		init(context);
	}
	
	public MyDatePicker(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}

	/**
	 * 初始化
	 * @param context
	 */
	private void init(Context context){
		years = new WheelView(context);
		LinearLayout.LayoutParams yearParams = new LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
				android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
		yearParams.weight = 1;
		years.setLayoutParams(yearParams);
		
		months = new WheelView(context);
		LinearLayout.LayoutParams monthParams = new LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
				android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
		monthParams.weight = 1;
		months.setLayoutParams(monthParams);
		
		days = new WheelView(context);
		LinearLayout.LayoutParams dayParams = new LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
				android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
		dayParams.weight = 1;
		days.setLayoutParams(dayParams);
		
		NumericWheelAdapter numericWheelAdapter = new NumericWheelAdapter(1900, 9999);
		years.setAdapter(numericWheelAdapter);
//		year.setLabel("年");
//		years.setCurrentItem(2005-1801);
		
		months.setAdapter(new NumericWheelAdapter(1, 12, "%02d"));
//		month.setLabel("月");
		months.setCyclic(true);
		int maxday_of_month = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
		days.setAdapter(new NumericWheelAdapter(1, maxday_of_month, "%02d"));
//		day.setLabel("日");
		days.setCyclic(true);
		
		years.addChangingListener(onYearsChangedListener);
		months.addChangingListener(onMonthsChangedListener);
		days.addChangingListener(onDaysChangedListener);
		
		addView(years);
		addView(months);
		addView(days);
	}
	
	
	
	//listeners
	private OnWheelChangedListener onYearsChangedListener = new OnWheelChangedListener(){
		@Override
		public void onChanged(WheelView hours, int oldValue, int newValue) {
			calendar.set(Calendar.YEAR, 1900 + newValue);	
			onChangeListener.onChange(getYear(), getMonth(), getDay(), getDayOfWeek());	
			setMonth(getMonth());
			setDay(getDay());
			days.setAdapter(new NumericWheelAdapter(1, calendar.getActualMaximum(Calendar.DAY_OF_MONTH), "%02d"));
		}
	};
	private OnWheelChangedListener onMonthsChangedListener = new OnWheelChangedListener(){
		@Override
		public void onChanged(WheelView mins, int oldValue, int newValue) {
			calendar.set(Calendar.MONTH, 1 + newValue - 1);
			onChangeListener.onChange(getYear(), getMonth(), getDay(), getDayOfWeek());		
			setMonth(getMonth());
			setDay(getDay());
			days.setAdapter(new NumericWheelAdapter(1, calendar.getActualMaximum(Calendar.DAY_OF_MONTH), "%02d"));
		}
	};
	private OnWheelChangedListener onDaysChangedListener = new OnWheelChangedListener(){
		@Override
		public void onChanged(WheelView mins, int oldValue, int newValue) {
			calendar.set(Calendar.DAY_OF_MONTH, newValue + 1);
			onChangeListener.onChange(getYear(), getMonth(), getDay(), getDayOfWeek());
			days.setAdapter(new NumericWheelAdapter(1, calendar.getActualMaximum(Calendar.DAY_OF_MONTH), "%02d"));
		}
	};
	
	
	/**
	 * 定義了監聽時間改變的監聽器藉口
	 */
	public interface OnChangeListener {
		void onChange(int year, int month, int day, int day_of_week);
	}
	
	/**
	 * 設定監聽器的方法
	 * @param onChangeListener
	 */
	public void setOnChangeListener(OnChangeListener onChangeListener){
		this.onChangeListener = onChangeListener;
	}
	
	/**
	 * 設定年
	 * @param year
	 */
	public void setYear(int year){
		years.setCurrentItem(year-1900);
	}
	
	/**
	 * 獲得年
	 * @return
	 */
	public int getYear(){
		return calendar.get(Calendar.YEAR);
	}
	
	/**
	 * 設定月
	 */
	public void setMonth(int month){
		months.setCurrentItem(month - 1);
	}
	
	/**
	 * 獲得月
	 * @return
	 */
	public int getMonth(){
		return calendar.get(Calendar.MONTH)+1;
	}
	
	/**
	 * 設定日
	 * @param day
	 */
	public void setDay(int day){
		days.setCurrentItem(day - 1);
	}
	
	/**
	 * 獲得日
	 * @return
	 */
	public int getDay(){
		return calendar.get(Calendar.DAY_OF_MONTH);
	}
	
	/**
	 * 獲得星期
	 * @return
	 */
	public int getDayOfWeek(){
		return calendar.get(Calendar.DAY_OF_WEEK);
	}
	
	/**
	 * 獲得星期
	 * @return
	 */
	public static String getDayOfWeekCN(int day_of_week){
		String result = null;
		switch(day_of_week){
		case 1:
			result = "日";
			break;
		case 2:
			result = "一";
			break;
		case 3:
			result = "二";
			break;
		case 4:
			result = "三";
			break;
		case 5:
			result = "四";
			break;
		case 6:
			result = "五";
			break;
		case 7:
			result = "六";
			break;	
		default:
			break;
		}
		return result;
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		//預設設定為系統時間
		setYear(getYear());
		setMonth(getMonth());
		setDay(getDay());
	}
}

呼叫方法:
/***
	 * 時間控制元件
	 * @param text
	 */
	protected void datePickerShow(final TextView textView) {
//		DatePickerDialog picker = new DatePickerDialog(this,
//			new OnDateSetListener() {
//				@Override
//				public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
//					if (monthOfYear < 9 && dayOfMonth < 10) {
//						textView.setText(year + "-0" + (monthOfYear + 1) + "-0" + dayOfMonth);
//					} else if (monthOfYear >= 9 && dayOfMonth < 10) {
//						textView.setText(year + "-" + (monthOfYear + 1) + "-0" + dayOfMonth);
//					} else if (monthOfYear < 9 && dayOfMonth >= 10) {
//						textView.setText(year + "-0" + (monthOfYear + 1) + "-" + dayOfMonth);
//					} else {
//						textView.setText(year + "-" + (monthOfYear + 1) + "-" + dayOfMonth);
//					}
//				}
//			}, cd.get(Calendar.YEAR), cd.get(Calendar.MONTH), cd.get(Calendar.DAY_OF_MONTH));
//		picker.show();
		
		final Dialog dialog = new Dialog(this, R.style.Theme_ShareDialog);  
        dialog.setContentView(R.layout.datepickerdialog);  
        
        MyDatePicker dpicker = (MyDatePicker) dialog.findViewById(R.id.datepicker_layout);
        final TextView txDateAndWeekDay = (TextView) dialog.findViewById(R.id.datepicker_date_and_weekday);
        Button btBeDown = (Button) dialog.findViewById(R.id.datepicker_btsure);
        Button btCancel = (Button) dialog.findViewById(R.id.datepicker_btcancel);
		dpicker.setOnChangeListener(new MyDatePicker.OnChangeListener() {
		@Override
			public void onChange(int year, int month, int day, int day_of_week) {
//				txDateAndWeekDay.setText(year + "年" + month + "月" + day + "日  星期" + MyDatePicker.getDayOfWeekCN(day_of_week));
				dateYear = year;
				dateMonth = month;
				dateDay = day;
				
				if (dateMonth < 10 && dateDay < 10) {
					txDateAndWeekDay.setText(dateYear + "-0" + dateMonth + "-0" + dateDay+ " 星期" + MyDatePicker.getDayOfWeekCN(day_of_week));
				} else if (dateMonth >= 10 && dateDay < 10) {
					txDateAndWeekDay.setText(dateYear + "-" + dateMonth + "-0" + dateDay+ " 星期" + MyDatePicker.getDayOfWeekCN(day_of_week));
				} else if (dateMonth < 10 && dateDay >= 10) {
					txDateAndWeekDay.setText(dateYear + "-0" + dateMonth + "-" + dateDay+ " 星期" + MyDatePicker.getDayOfWeekCN(day_of_week));
				} else {
					txDateAndWeekDay.setText(dateYear + "-" + dateMonth + "-" + dateDay+ " 星期" + MyDatePicker.getDayOfWeekCN(day_of_week));
				}
			}
        });
        
  
        btBeDown.setOnClickListener(new Button.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				if (dateMonth < 10 && dateDay < 10) {
					textView.setText(dateYear + "-0" + dateMonth  + "-0" + dateDay);
				} else if (dateMonth >= 10 && dateDay < 10) {
					textView.setText(dateYear + "-" + dateMonth  + "-0" + dateDay);
				} else if (dateMonth < 10 && dateDay >= 10) {
					textView.setText(dateYear + "-0" + dateMonth + "-" + dateDay);
				} else {
					textView.setText(dateYear + "-" + dateMonth  + "-" + dateDay);
				}
//				textView.setText(dateYear + "-" + dateMonth + "-" + dateDay);
				dialog.dismiss();
			}
		});
        
        btCancel.setOnClickListener(new Button.OnClickListener() {  
            public void onClick(View view) {  
            	dialog.dismiss();
            }  
        }); 
        dialog.setCancelable(false);
        dialog.setOnKeyListener(new android.content.DialogInterface.OnKeyListener() {
			@Override
			public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
				switch (keyCode) {
					case KeyEvent.KEYCODE_BACK:
						return true;
				}
				return false;
			}
		});
        dialog.show();
        
	}

涉及到的xml檔案:

layout檔案下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/dialog_bg"
    android:layout_gravity="center"
    android:orientation="vertical" >
    <TextView 
        android:id="@+id/datepicker_date_and_weekday"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:textColor="@color/black"
        android:textSize="@dimen/main_bottom_more_size"
        android:ellipsize="middle"/>
    <View 
        android:layout_width="fill_parent"
        android:layout_height="1dp"
        android:layout_marginTop="5dp"
        android:background="@color/grayblack"/>
    <com.lakala.pay.easycashregisterphone2.view.datepicker.MyDatePicker 
        android:id="@+id/datepicker_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        
    </com.lakala.pay.easycashregisterphone2.view.datepicker.MyDatePicker>
    
    <View 
        android:layout_width="fill_parent"
        android:layout_height="0.5dp"
        android:background="@color/grayblack"/>
    <LinearLayout 
        android:id="@+id/datepicker_button_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:gravity="center"
        android:orientation="horizontal">
        <Button 
            android:id="@+id/datepicker_btsure"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="5dp"
            android:textColor="@color/white"
            android:text="@string/bedown"
            android:background="@drawable/btn1_normal"/>
        <Button 
            android:id="@+id/datepicker_btcancel"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="10dp"
            android:textColor="@color/white"
            android:text="@string/cact_cancel"
            android:background="@drawable/btn1_normal"/>
    </LinearLayout>

</LinearLayout>

drawable檔案下:

layout_bg.xml:

<?xml version="1.0" encoding="utf-8"?>

<!-- 
    Android Wheel Control.
    http://android-devblog.blogspot.com/2010/05/wheel-ui-contol.html

    Copyright 2010 Yuri Kanivets
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->

<shape xmlns:android="http://schemas.android.com/apk/res/android">
	<gradient
		android:startColor="#FF000000"
		android:centerColor="#FF000000"
		android:endColor="#FF777777"
		android:angle="90" />
</shape>

wheel_bg.xml:
<?xml version="1.0" encoding="utf-8"?>

<!-- 
    Android Wheel Control.
    http://android-devblog.blogspot.com/2010/05/wheel-ui-contol.html
   
    Copyright 2010 Yuri Kanivets
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
	<item>
		<shape android:shape="rectangle">
			<gradient
				android:startColor="#FFFFFF"
				android:centerColor="#FFFFFF"
				android:endColor="#FFFFFF"
				android:angle="90" />

		</shape>
	</item>
</layer-list>

wheel_var.xml:
<?xml version="1.0" encoding="utf-8"?>

<!-- 
    Android Wheel Control.
    http://android-devblog.blogspot.com/2010/05/wheel-ui-contol.html
   
    Copyright 2010 Yuri Kanivets
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->

<shape xmlns:android="http://schemas.android.com/apk/res/android">
	<gradient
		android:startColor="#FF00b5ef"
		android:centerColor="#FF00b5ef"
		android:endColor="#FF00b5ef"
		android:angle="90" />

	<stroke android:width="1dp" android:color="#FFFFFFFF" /> 
</shape>


相關推薦

自制日期選擇datepicker

在網上看過很多關於datepicker的列子,效果就是我想要的,但介面卻和我的專案不同,因此在網上的例子上做做手腳,改變下外觀,先說下改動的地方,再將程式碼放上去。 1、主要改動關鍵是在WheelView類:protected void onDraw(Canvas canv

jQuery UI 日期選擇Datepicker

jquery ui next ext style cti cto log href region 設置JqueryUI DatePicker默認語言為中文 <!doctype html><html lang="en"> <head&g

jQuery UI 實例 - 日期選擇Datepicker

for cal 周四 radi panel alternate 可能 max 輸入 默認功能 日期選擇器(Datepicker)綁定到一個標準的表單 input 字段上。把焦點移到 input 上(點擊或者使用 tab 鍵),在一個小的覆蓋層上打開一個交互日歷。選擇一個日期

java 日期選擇帶時間

import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayo

第三方開源庫:日期時間選擇TimePickerDialog+ 省市區三級聯動(CityPicker) + 一級滾動

TimePickerDialog Android時間選擇器,支援年月日時分,年月日,年月,月日時分,時分格式,可以設定最小時間和最大時間(精確到分)。 效果圖 gradle compile 'com.jzxiang.pickervie

CSS3學習系列之選擇

計算 選擇器 sky :focus ddr gree for 指定元素 學習 first-child選擇器和last-child選擇器 first-child指定第一個元素。last-child指定最後一個子元素。 例如: <!DOCTYPE html>

css3新特性選擇補充

last inpu child 一行 標簽 after 第一個 ren 得到 1.選擇p標簽中的第一個字符 p:first-letter{ color:red; font-size:25px; } 2.選擇p標簽中的第一行 p:first-line{   color:red

css選擇基礎

掌握 class 就是 content tle 版本 語法 tex 人物 CSS選擇器: 一個樣式的語法是由選擇器+屬性+屬性值三部分組成; 到底什麽是選擇器呢? 答:個人直白的理解為:選擇一種要對它進行操作的標簽的方法就叫做選擇器。也就是說選擇器就

CSS(CSS3)選擇1

cti str 插入 link 規則 padding 不可 情況 可能 這篇文章主要用於存儲CSS以及CSS3的選擇器部分知識,以便日後查閱及記憶. 該內容分為兩部分,第一部分為css選擇器的一些基本知識。第二部分為CSS3新增加的選擇器。 在開始之前,先簡單介紹一下選擇器

CSS(CSS3)選擇2

for 字符 tutorials pty disable post input purple enabled 該部分主要為CSS3新增的選擇器 接上一篇 CSS(CSS3)選擇器(1) 一.通用兄弟選擇器: 24:E ~

CSS選擇之兄弟選擇~和+

spa 例子 inf 代碼 但是 info 發現 效果 說話   今天在改以以前人家寫的網頁的樣式的時候,碰到這個選擇器,‘~’,當時我是懵逼的,傻傻分不清 ‘+’ 跟 ‘~’的區別,雖然我知道他們都是

CSS樣式選擇1

class 是按照 同類型 來歸類HTML的各種元素的,要把某些元素歸為一類,一般會考慮到下面這些因素: 它們具有同樣的樣式,比如不管他們的標籤是什麼,h1 或者是 div,但是字型大小都是 16px; 它們是一種具有同樣意義的東西,比如我們做網站,一般首頁的 LOGO

css選擇基本選擇

基本選擇器 1、通用元素選擇器 *表示應用到所有的標籤。   *{    padding:0px;    margin:0px;   } 2、元素/標籤選擇器 匹配所有p標籤的元素   p{    color:red;    background:yellow;   } 3、類選擇器

第6章 征服CSS3選擇

屬性選擇器 在HTML中,通過各種各樣的屬性可以給元素增加很多附加的資訊。例如,通過id屬性可以將不同div元素進行區分。 在CSS2中引入了一些屬性選擇器,而CSS3在CSS2的基礎上對屬性選擇器進行了擴充套件,新增了3個屬性選擇器,使得屬性選擇器有了萬用字元的概念,這三個屬性選擇器與CSS2的屬性選擇器

第7章 征服CSS3選擇

:enabled選擇器 在Web的表單中,有些表單元素有可用(“:enabled”)和不可用(“:disabled”)狀態,比如輸入框,密碼框,複選框等。在預設情況之下,這些表單元素都處在可用狀態。那麼我們可以通過偽選擇器“:enabled”對這些表單元素設定樣式。 示例演示 通過“:enabled”選擇器

前端基礎學習筆記 選擇高階

組合選擇器的優先順序 比較優先順序時,保證精準控制到了元素 比較id選擇器,如果id多,那優先順序高 id選擇器一樣多時,比較class選擇器,class多的則優先順序高 class如果相等,比較標籤選擇器,標籤選擇器多則優先順序高 偽類選擇器,如hover,優先順

CSS3選擇

CSS選擇器複習 通用選擇器:* 選擇到所有的元素 選擇子元素:> 選擇到元素的直接後代(第一級子元素) 相鄰兄弟選擇器:+ 選擇到緊隨目標元素後的第一個元素 普通兄弟選擇器:~ 選擇

Scrapy 小技巧:選擇Selectors怎麼寫

一、引言 最近剛好在學 Scrapy 框架。Scrapy 毋容置疑的強大。 但是有一點,就是它的選擇器語法實在是太難讓人上手了。畢竟在接觸 Scrapy 之前,我都是用 BeautifulSoup 進行選擇解析的,一下子讓我接觸 xpath 和 css 兩種

CSS 派生選擇

  派生選擇器 通過依據元素在其位置的上下文關係來定義樣式,你可以使標記更加簡潔。 在 CSS1 中,通過這種方式來應用規則的選擇器被稱為上下文選擇器 (contextual selectors),這是由於他們依賴於上下文關係來應用或者避免某項規則。在 CSS2 中,它們稱

兄弟選擇+和~

1. + 選擇器  如果需要選擇緊接在另一個元素後的元素,而且二者有相同的父元素,可以使用相鄰兄弟選擇器。      比如:<styletype="text/css"> h1 + p { margin-top:50px;