日期选择器 datepicker WheelView

自制日期选择器(datepicker)

2019-01-08
6次浏览

在网上看过很多关于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文件中设置选中项背景色



下面公布下代码:


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.
?*The?currentItemChanged()?method?is?called?whenever?current?wheel?positions?is?changed:
?*New?Wheel?position?is?set
?*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?
	?*?
	?*??
	?*?滚动持续时间(毫秒)?
	?*/
	private?static?final?int?SCROLLING_DURATION?=?400;

	/**?
	?*?Minimum?delta?for?scrolling?
	?*?
	?*??
	?*?滚动的最小差值?
	?*/
	private?static?final?int?MIN_DELTA_FOR_SCROLLING?=?1;

	/**?
	?*?Current?value?&?label?text?color?
	?*?
	?*??
	?*?当前选中项??的?文字?的?颜色?
	?*/
	private?static?final?int?VALUE_TEXT_COLOR?=?0xFFFFFFFF;

	/**?
	?*?Items?text?color?
	?*?
	?*??
	?*?选项?的?文字?的?颜色?
	?*/
	private?static?final?int?ITEMS_TEXT_COLOR?=?0xFF000000;

	/**?
	?*?Top?and?bottom?shadows?colors?
	?*?
	?*??
	?*?顶部和底部阴影?的?颜色???
	?*?选择器?顶部和底部颜色是渐变的,这里指定一个渐变色的数组
	?*/
	private?static?final?int[]?SHADOWS_COLORS?=?new?int[]?{?0xFFFFFFFF,
			0x00EEEED5,?0x00EEEED5?};

	/**?
	?*?Additional?items?height?(is?added?to?standard?text?item?height)?
	?*?
	?*??
	?*?附加项的高度项的高度??(单位应该是dp)?
	?*?从后面getDesiredHeight()?函数看出,这个值应该是平分给每一个选项的。?
	?*?类似于设置行距吧,不过这是一个总和,也就是有5个可见项,那么每个可见项的附加高就是?ADDITIONAL_ITEM_HEIGHT/5
	?*/
	private?static?final?int?ADDITIONAL_ITEM_HEIGHT?=?15;

	/**?
	?*?Text?size?
	?*?
	?*??
	?*?字号
	?*/
	private?static?final?int?TEXT_SIZE?=?24;

	/**?
	?*?Top?and?bottom?items?offset?(to?hide?that)?
	?*?
	?*??
	?*?这个是选项在顶部和底部被抵消的值。?
	?*?怎么解释呢~?其实试一下就知道了,?
	?*???比如说在picker中显示的五项(中间那个是选中的),剩余4是没选中的。?
	?*???在没选中的这4项中,位于顶部和底部的项,会用阴影遮挡(遮挡一部分,这样用户就明白是需要滑动操作了)?
	?*???这里设定的值,就是指定遮挡的size,这里的默认值?TEXT_SIZE?/?5?是遮挡了1/5的字号?(那么单位也应该是sp吧)
	?*/
	private?static?final?int?ITEM_OFFSET?=?TEXT_SIZE?/?5;

	/**?
	?*?Additional?width?for?items?layout?
	?*?
	?*??
	?*?附加项空间??不理解~~还是试试吧?
	?*?应该是项的宽,这个属性应该是受制于外边区域的,?设置的太宽里面的文字显示会出问题?
	?*?具体影响?有待进一步实验证明
	?*/
	private?static?final?int?ADDITIONAL_ITEMS_SPACE?=?10;

	/**?
	?*?Label?offset
	?*?
	?*??
	?*?标签抵消?(作用未知)?用1,8,和800?三个值实验(8是默认值)?效果是一样的。?
	?*/
	private?static?final?int?LABEL_OFFSET?=?8;

	/**?
	?*?Left?and?right?padding?value
	?*?
	?*??
	?*?填充??
	?*?这个选项的内部填充,如果选项是个TextView的话,那这里设置的即是TextView的填充?
	?*?=。=!后面代码还米有看,item是啥我也不知道
	?*/
	private?static?final?int?PADDING?=?10;

	/**?
	?*?Default?count?of?visible?items?
	?*?
	?*??
	?*?默认可见项的数目。就是picker中显示几项
	?*/
	private?static?final?int?DEF_VISIBLE_ITEMS?=?5;

	//?Wheel?Values
	/**
	?*?Wheel?Values
	?*?
	?*??
	?*?适配器,items就通过适配器来提供的吧
	?*/
	private?WheelAdapter?adapter?=?null;
	/**
	?*?Wheel?Values
	?*?
	?*??
	?*?当前项
	?*/
	private?int?currentItem?=?0;
	
	//?Widths
	/**
	?*?Widths
	?*?
	?*??
	?*?做了实验?把值设为100?没有任何变化
	?*/
	private?int?itemsWidth?=?0;
	/**
	?*?Widths
	?*?
	?*??
	?*?也做了实验?把值设为100?没有任何变化
	?*/
	private?int?labelWidth?=?0;

	//?Count?of?visible?items
	/**
	?*?Count?of?visible?items
	?*?
	?*??
	?*?可见项目的条数
	?*/
	private?int?visibleItems?=?DEF_VISIBLE_ITEMS;
	
	//?Item?height
	/**
	?*?Item?height
	?*?
	?*??
	?*?是item的高
	?*/
	private?int?itemHeight?=?0;

	//?Text?paints
	/**
	?*?Text?paints
	?*?
	?*??
	?*?选中文本的颜色
	?*/
	private?TextPaint?itemsPaint;
	/**
	?*?Text?paints
	?*?
	?*??
	?*?未选中文本的颜色
	?*/
	private?TextPaint?valuePaint;

	//?Layouts
	/**
	?*?Layouts
	?*?
	?*??
	?*?选项??的?布局
	?*/
	private?StaticLayout?itemsLayout;
	/**
	?*?Layouts
	?*?
	?*??
	?*?标签?的?布局
	?*/
	private?StaticLayout?labelLayout;
	/**
	?*?Layouts
	?*?
	?*??
	?*?选中项?的?布局
	?*/
	private?StaticLayout?valueLayout;

	//?Label?&?background
	/**
	?*?Label?&?background
	?*?
	?*??
	?*?标签
	?*/
	private?String?label;
	/**
	?*?Label?&?background
	?*?中间的几何体?
	?*?选中区域的背景
	?*/
	private?Drawable?centerDrawable;

	//?Shadows?drawables
	/**
	?*?Shadows?drawables
	?*?
	?*??
	?*?上边?和?底部?的阴影部分的背景资源
	?*/
	private?GradientDrawable?topShadow;
	private?GradientDrawable?bottomShadow;

	//?Scrolling
	/**
	?*?Scrolling
	?*?
	?*??
	?*?执行滚动?
	?*/
	private?boolean?isScrollingPerformed;?
	/**
	?*?Scrolling
	?*?
	?*??
	?*?滚动抵消?
	?*/
	private?int?scrollingOffset;

	//?Scrolling?animation
	/**
	?*?Scrolling?animation
	?*?
	?*??
	?*?手势检测器
	?*/
	private?GestureDetector?gestureDetector;
	/**
	?*?Scrolling?animation
	?*?
	?*??
	?*?卷轴
	?*/
	private?Scroller?scroller;
	/**
	?*?Scrolling?animation
	?*?
	?*??
	?*?最后的?卷轴Y
	?*/
	private?int?lastScrollY;

	//?Cyclic
	/**
	?*?Cyclic
	?*?
	?*??
	?*?是否循环
	?*/
	boolean?isCyclic?=?false;
	
	//?Listeners
	/**
	?*?Listeners
	?*?
	?*??
	?*?控件改变监听器的?集合
	?*/
	private?ListchangingListeners?=?new?LinkedList();
	/**
	?*?Listeners
	?*?
	?*??
	?*?控件滚动监听器的?集合
	?*/
	private?ListscrollingListeners?=?new?LinkedList();

	/**
	?*?Constructor
	?*
	?*?
	?*?构造器?并实例了手势检测器?和?卷轴
	?*/
	public?WheelView(Context?context,?AttributeSet?attrs,?int?defStyle)?{
		super(context,?attrs,?defStyle);
		initData(context);
	}

	/**
	?*?Constructor
	?*?
	?*?
	?*?构造器?并实例了手势检测器?和?卷轴
	?*/
	public?WheelView(Context?context,?AttributeSet?attrs)?{
		super(context,?attrs);
		initData(context);
	}

	/**
	?*?Constructor
	?*
	?*?
	?*?构造器?并实例了手势检测器?和?卷轴
	?*/
	public?WheelView(Context?context)?{
		super(context);
		initData(context);
	}
	
	/**
	?*?Initializes?class?data
	?*?@param?context?the?context
	?*
	?*?
	?*?数据初始化?
	?*?就是把手势检测器?和?卷轴类?实例了?
	?*?这个方法在所有的构造器中都被调用了
	?*/
	private?void?initData(Context?context)?{
		gestureDetector?=?new?GestureDetector(context,?gestureListener);
		gestureDetector.setIsLongpressEnabled(false);?//这个没看出来有什么用,不设置,或设置成true都不影响效果
		
		scroller?=?new?Scroller(context);
	}
	
	/**
	?*?Gets?wheel?adapter
	?*?@return?the?adapter
	?*
	?*?
	?*?获得适配器
	?*/
	public?WheelAdapter?getAdapter()?{
		return?adapter;
	}
	
	/**
	?*?Sets?wheel?adapter
	?*?@param?adapter?the?new?wheel?adapter
	?*
	?*?
	?*?设置适配器?
	?*?还会界面进行了重绘
	?*/
	public?void?setAdapter(WheelAdapter?adapter)?{
		this.adapter?=?adapter;
		invalidateLayouts();
		invalidate();
	}
	
	/**
	?*?Set?the?the?specified?scrolling?interpolator
	?*?@param?interpolator?the?interpolator
	?*
	?*?
	?*?作用也是设置?卷轴的吧,是通过动画插补器来实例?卷轴对象
	?*/
	public?void?setInterpolator(Interpolator?interpolator)?{
		scroller.forceFinished(true);
		scroller?=?new?Scroller(getContext(),?interpolator);
	}
	
	/**
	?*?Gets?count?of?visible?items
	?*?
	?*?@return?the?count?of?visible?items
	?*
	?*?
	?*?获得可见项的条数
	?*/
	public?int?getVisibleItems()?{
		return?visibleItems;
	}

	/**
	?*?Sets?count?of?visible?items
	?*?
	?*?@param?count
	?*????????????the?new?count
	?*
	?*?
	?*?设置可见项的条数?并重绘view
	?*/
	public?void?setVisibleItems(int?count)?{
		visibleItems?=?count;
		invalidate();
	}

	/**
	?*?Gets?label
	?*?
	?*?@return?the?label
	?*
	?*?
	?*?获得标签
	?*/
	public?String?getLabel()?{
		return?label;
	}

	/**
	?*?Sets?label
	?*?
	?*?@param?newLabel
	?*????????????the?label?to?set
	?*
	?*?
	?*?设置标签
	?*/
	public?void?setLabel(String?newLabel)?{
		if?(label?==?null?||?!label.equals(newLabel))?{
			label?=?newLabel;
			labelLayout?=?null;
			invalidate();
		}
	}
	
	/**
	?*?Adds?wheel?changing?listener
	?*?@param?listener?the?listener?
	?*
	?*?
	?*?添加控件改变监听器
	?*/
	public?void?addChangingListener(OnWheelChangedListener?listener)?{
		changingListeners.add(listener);
	}

	/**
	?*?Removes?wheel?changing?listener
	?*?@param?listener?the?listener
	?*
	?*?
	?*?移除控件改变监听器
	?*/
	public?void?removeChangingListener(OnWheelChangedListener?listener)?{
		changingListeners.remove(listener);
	}
	
	/**
	?*?Notifies?changing?listeners
	?*?@param?oldValue?the?old?wheel?value
	?*?@param?newValue?the?new?wheel?value
	?*
	?*?
	?*?通知?改变监听器,?
	?*?循环??控件改变监听器集合,?并依次调用它们的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?
	?*
	?*?
	?*?添加控件滚动监听器
	?*/
	public?void?addScrollingListener(OnWheelScrollListener?listener)?{
		scrollingListeners.add(listener);
	}

	/**
	?*?Removes?wheel?scrolling?listener
	?*?@param?listener?the?listener
	?*
	?*?
	?*?移除控件滚动监听器
	?*/
	public?void?removeScrollingListener(OnWheelScrollListener?listener)?{
		scrollingListeners.remove(listener);
	}
	
	/**
	?*?Notifies?listeners?about?starting?scrolling
	?*
	?*?
	?*?通知控件滚动监听器调用开始滚动的方法
	?*/
	protected?void?notifyScrollingListenersAboutStart()?{
		for?(OnWheelScrollListener?listener?:?scrollingListeners)?{
			listener.onScrollingStarted(this);
		}
	}

	/**
	?*?Notifies?listeners?about?ending?scrolling
	?*
	?*?
	?*?通知控件滚动监听器调用结束滚动的方法
	?*/
	protected?void?notifyScrollingListenersAboutEnd()?{
		for?(OnWheelScrollListener?listener?:?scrollingListeners)?{
			listener.onScrollingFinished(this);
		}
	}

	/**
	?*?Gets?current?value
	?*?
	?*?@return?the?current?value
	?*
	?*?
	?*?返回当前项的索引
	?*/
	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
	?*
	?*?
	?*?设置当前项?如果输入错误的索引,则控件什么都不会做哟?
	?*?第二个参数是设置是否使用滚动动画的
	?*/
	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
	?*
	?*?
	?*?设置当前选中项,默认是不启动动画的
	?*/
	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
	?*
	?*?
	?*?是否循环显示
	?*/
	public?boolean?isCyclic()?{
		return?isCyclic;
	}

	/**
	?*?Set?wheel?cyclic?flag
	?*?@param?isCyclic?the?flag?to?set
	?*
	?*?
	?*?设置是否循环显示
	?*/
	public?void?setCyclic(boolean?isCyclic)?{
		this.isCyclic?=?isCyclic;
		
		invalidate();
		invalidateLayouts();
	}

	/**
	?*?Invalidates?layouts
	?*?
	?*??
	?*?重绘布局?
	?*?方法将?选项布局itemsLayout?和?选中项布局?valueLayout?赋值为null?
	?*?同事将?滚动抵消?scrollingOffset?设置为0?
	?*?(这个scrollingOffset?还没搞明白做什么用)
	?*/
	private?void?invalidateLayouts()?{
		itemsLayout?=?null;
		valueLayout?=?null;
		scrollingOffset?=?0;
	}

	/**
	?*?Initializes?resources
	?*
	?*?
	?*?初始化源?
	?*?这个方法是这样的,?判断前面定义的画笔、背景资源等私有属性的值,如果是null就重新从静态常量中取值,并付给响应的属性。?
	?*?如果这些必要属性为空的话,这个函数应该起到了初始化的作用
	?*/
	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
	?*?
	?*?
	?*?获得理想的控件高度,并保证其不低于建议的最小高度
	?*/
	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
	?*?
	?*??
	?*?指定索引,获得选项的文本值(Sring)?
	?*?如果索引超出范围,控件又不是循环的(isCyclic),则返回null?
	?*?如果是循环的情况,方法内部进行了处理,?
	?*?为负数则+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
	?*?
	?*??
	?*?依赖当前项?构建文本?
	?*?获得一个字符串,如果当前项目的索引为5,?可见项目数为3??
	?*?则字符串的值为?getTextItem(3).append("\n").getTextItem(4).append("\n").getTextItem(5).append("\n").getTextItem(6).append("\n").getTextItem(7)?
	?*?如果?参数useCurrentValue为false?
	?*?则返回的字符串为?getTextItem(3).append("\n").getTextItem(4).append("\n").getTextItem(6).append("\n").getTextItem(7)?
	?*?如果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
	?*?
	?*??
	?*??获得最大的文本长度
	?*??后面计算控件宽度用的
	?*/
	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
	?*?
	?*?
	?*?获得选项高
	?*/
	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
	?*?
	?*?
	?*?计算布局宽
	?*/
	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)?{
				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
	?*?
	?*?
	?*?创建布局
	?*/
	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
	?*?
	?*?
	?*?绘制控件顶端?和底部的?阴影区域?
	?*?需要传入画布对象
	?*/
	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);
	}

	/**
	?*?触摸事件的?回调方法
	?*?看到了吧?如果适配器?adapter是null?的话,这里是什么都不会做的。
	?*?如果适配器不为空,将MotionEvent传递给手势识别器。?并判断是否已ACTION_UP?
	?*?如果是说明操作已结束?调用justify()方法。
	?*?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
	?*?
	?*?
	?*?滚动
	?*?好像只是重新定义了?scrollingOffset的值,
	?*?执行滚动的操作是这里吗?
	?*?先往后看吧。
	?*/
	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)

我要点评

本站介绍 合作联络 欢迎投稿 隐私声明 广告业务 网站地图 联系我们 诚聘英才

ICP许可证号:京ICP证070360号 21IC电子网 2000-2018 版权所有

京公网安备 11010802024347号