一個非常好用的Android PickerView庫,内部提供3種常用類型的Picker。支援擴充自定義Picker。TimePicker:時間選擇器,包含日期
MixedTimePicker::聚合的時間選擇器
OptionPicker:關聯選擇器
Screenshot
APK
Picker
通過組裝PickerView實作常用的Picker選擇器。上面已經列舉提供的3中常用的Picker。
BasePicker
Picker基類:封裝了TopBar,PickerView容器,create and add PickerView方法,Picker彈窗等方法。 三種Picker都繼承自BasePicker,你也可以繼承它擴充自己的Picker。
APIapidescriptionsetPickerBackgroundColor設定picker背景
setPadding設定PickerView父容器padding 機關:px
setTag給Picker 設定tag,用于區分不同的picker等。用法同View setTag
getRootLayout擷取PickerView的父容器,建立DefaultTopBar時必須指定
setOnPickerChooseListener設定picker取消,确定按鈕監聽。可用于攔截選中操作
setTopBar設定自定義TopBar
setInterceptor設定攔截器
createPickerView建立PickerView
getPickerViews擷取Picker中所有的pickerview集合
addPicker将建立的PickerView 添加到上面集合中,createPickerView内部已調用該方法
findPickerViewByTag通過tag找到對應的PickerView
isScrolling是否滾動未停止。滾動未停止的時候,不響應Picker的取消,确定按鍵
getPickerDialog擷取Picker彈窗。可以在new之後設定dialog屬性
show顯示picker彈窗對比github上最受歡迎的同類庫 Android-PickerView 本庫将TopBar等通用相關邏輯封裝在基類中,并提供代碼中建立PickerView方法,不需要再依賴xml。使用者自定義Picker時,繼承BasePicker,隻需要處理自己的邏輯即可,簡單便捷。 而對Android-PickerView來說,實作自定義Picker,依然需要處理TopBar等邏輯。造成大量重複代碼。
TopBar
TopBar:TopBar通過抽象接口ITopBar來管理,實作Picker與TopBar的解耦。提供預設實作DefaultTopBar。可實作接口定制自己的TopBar。public interface ITopBar {
View getTopBarView();
View getBtnCancel();
View getBtnConfirm();
TextView getTitleView();
}
DefaultTopBar APIapidescriptionsetDividerColor設定topbar bottom line color
setDividerHeight設定bottom divider line height
getDivider擷取TopBar bottom line
getTitleView擷取TopBar title view
Interceptor
攔截器:用于在pickerview建立時攔截,設定pickerview的屬性。Picker内部并不提供對PickerView的設定方法,而是通過Interceptor實作。這種設計用來實作Picker和PickerView的屬性設定完美解耦。private void init(){
mTimePicker.setInterceptor(new BasePicker.Interceptor() {
@Override public void intercept(PickerView pickerView) {
pickerView.setVisibleItemCount(5);
// 将年月設定為循環的
int type = (int) pickerView.getTag();
if (type == TimePicker.TYPE_YEAR || type == TimePicker.TYPE_MONTH) {
pickerView.setIsCirculation(true);
}
}
})
}
這一點對比 Android-PickerView, 每個Picker都需要聲明對PickerView的設定方法,與PickerView嚴重耦合。需要開發者copy大量重複代碼,且無法區分每一個PickerView設定不同的屬性。
TimePicker
常用的時間選擇器,支援 年、月、日、時、分時間類型type的設計:自由組合、随心所欲(當然應該是有意義的)TYPE_YEAR | TYPE_MONTH | TYPE_DAY | TYPE_HOUR | TYPE_MINUTE
對比 Android-PickerView TimePickerView
setType(boolean[] type)
// 本項目設定type方法:簡單易懂,組合友善
setType(TYPE_DATE | TYPE_HOUR)完美支援時間區間設定以及選中關聯
支援Format,如顯示今年,明年
APIapidescriptiontype時間類型,需要在Builder構造方法中指定,不能改變
OnTimeSelectListener選中時間回調,需要在Builder構造方法中指定,不能改變
setRangDate設定起止時間
setSelectedDate設定選中時間戳
setInterceptor設定攔截器
setFormatter設定Formatter,内部提供預設的Formatter
create通過Builder建構 TimePicker
以上是TimePicker.Builder的,下面是TimePicker的
setFormatter同上
setSelectedDate同上
getType擷取type
hasType判斷是否包含某種type
Formatter
TimePicker Formatter:用于根據type和num格式化時間文案public interface Formatter {
CharSequence format(TimePicker picker, int type, int position, int num);
}
内部提供預設的 Formatter實作DefaultFormatter。使用者可以設定自定義Formatter或繼承DefaultFormatter進行擴充。TimePicker初始化,如果未設定時間區間,會使用預設區間。三種Picker都采用Builder模式初始化。且使用者自定義的Picker也應該采用這種模式進行初始化。
Simple ExamplemTimePicker = new TimePicker.Builder(mActivity, type, this)
// 設定時間區間
.setRangDate(1526361240000L, 1893563460000L)
// 設定選中時間
//.setSelectedDate()
// 設定pickerview樣式
.setInterceptor(new BasePicker.Interceptor() {
@Override public void intercept(PickerView pickerView) {
pickerView.setVisibleItemCount(5);
// 将年月設定為循環的
int type = (int) pickerView.getTag();
if (type == TimePicker.TYPE_YEAR || type == TimePicker.TYPE_MONTH) {
pickerView.setIsCirculation(true);
}
}
})
// 設定 Formatter
.setFormatter(new TimePicker.DefaultFormatter() {
// 自定義Formatter顯示去年,今年,明年
@Override public CharSequence format(TimePicker picker, int type, int position, int num) {
if (type == TimePicker.TYPE_YEAR) {
int offset = num - mCurrYear;
if (offset == -1) return "去年";
if (offset == 0) return "今年";
if (offset == 1) return "明年";
return num + "年";
}
return super.format(picker, type, position, num);
}
}).create();
//mTimePicker.setSelectedDate(1549349843000L);
mTimePicker.show();
MixedTimePicker
常用的聚合時間選擇器。日期(年、月、日)聚合,時間(小時、分鐘)聚合。混合模式:github上的TimePicker庫基本都不提供該種類型的Picker
支援自定義日期格式,時間格式
支援設定時間間隔
支援設定區間以及選中關聯
支援設定純日期,純時間模式,采用type同TimePicker
APIapidescriptiontype類型,需要在Builder構造方法中指定,不能改變
OnTimeSelectListener選中時間回調,需要在Builder構造方法中指定,不能改變
setRangDate設定起止時間
setSelectedDate設定選中時間戳
setTimeMinuteOffset設定時間間隔分鐘數(60%offset==0才有效),以0為起始邊界
setContainsStarDate設定mTimeMinuteOffset作用時,是否包含超出的startDate
setContainsEndDate設定mTimeMinuteOffset作用時,是否包含超出的endDate
setInterceptor設定攔截器
setFormatter設定Formatter,内部提供預設的Formatter
create通過Builder建構 MixedTimePicker
以上是MixedTimePicker.Builder的,下面是MixedTimePicker的
setFormatter同上
setSelectedDate同上
getType擷取type
hasType判斷是否包含某種type
Formatter
MixedTimePicker Formatter:用于自定義日期和時間格式。内部提供預設的 Formatter實作。public interface Formatter {
CharSequence format(MixedTimePicker picker, int type, Date date, int position);
}MixedTimePicker 的 Formatter 完美展現了Formatter設計的精妙之處。使用者可以根據回調中的type和date自定義日期和時間格式。比如顯示今天,或 xx月xx日 星期 x
Simple ExamplemTimePicker = new MixedTimePicker.Builder(mActivity, MixedTimePicker.TYPE_ALL, this)
// 設定不包含超出的結束時間<=
.setContainsEndDate(false)
// 設定時間間隔為30分鐘
.setTimeMinuteOffset(30)
.setRangDate(1517771651000L, 1577976666000L)
.setFormatter(new MixedTimePicker.DefaultFormatter() {
@Override
public CharSequence format(MixedTimePicker picker, int type, Date date, int position) {
if (type == MixedTimePicker.TYPE_DATE) {
CharSequence text;
int dayOffset = DateUtil.getDayOffset(date.getTime(), System.currentTimeMillis());
if (dayOffset == 0) {
text = "今天";
} else if (dayOffset == 1) {
text = "明天";
} else { // xx月xx日 星期 x
text = mDateFormat.format(date);
}
return text;
}
return super.format(picker, type, date, position);
}
})
.create();
// 2018/2/5 03:14:11 - 2020/1/2 22:51:6
Dialog pickerDialog = mTimePicker.getPickerDialog();
pickerDialog.setCanceledOnTouchOutside(true);
DefaultTopBar topBar = (DefaultTopBar) mTimePicker.getTopBar();
topBar.getTitleView().setText("請選擇時間");不同于TimePicker, MixedTimePicker 由于支援純時間模式(日期取選中時間的日期),不提供預設區間。如果模式中包含日期模式,則會強制要求設定時間區間
OptionPicker支援設定層級
構造資料源及其簡單,隻需要實作OptionDataSet接口
支援通過對應選中的values設定選中項。内部處理選中項邏輯,避免使用者記錄下标且麻煩的周遊處理對比 Android-PickerView的 OptionsPickerViewfunctionAndroid-PickerViews本控件多級最多支援3級(寫死的)構造時設定級别(無限制)
構造資料源需要建構每一級的集合,二三級為嵌套一級資料entity實作OptionDataSet接口即可
設定資料源提供三個方法,分别用于一、二、三級的隻需要設定一級資料集
關聯選中提供三個,隻能設定選中的下标。需要使用者自己通過多層周遊定位每一級别選中的下标,然後再設定隻需要傳入選中的values(可變長數組),不需要任何計算
Android-PickerView 中的 OptionsPickerView 代碼。由于不知道層級,是以每個方法都提供3個用來對應(最多)3級選擇。// 提供3個選中的方法,分别對應1,2,3級關聯的情況
public void setSelectOptions(int option1)
public void setSelectOptions(int option1, int option2)
public void setSelectOptions(int option1, int option2, int option3)
// 提供3個設定資料源的方法,分别對應1,2,3級關聯的情況
public void setPicker(List optionsItems)
public void setPicker(List options1Items, List> options2Items)
public void setPicker(List options1Items, List> options2Items,
List>> options3Items) {
}
本庫中的OptionPicker
public void setDataWithValues(List extends OptionDataSet> options, String... values) {
mOptions = options;
setSelectedWithValues(values);
}
public void setSelectedWithValues(String... values) {
...
}如上面對比表格中所列舉的,無論是層級,構造資料源和設定資料源,還是設定選中的選項,本庫的API都十分簡單,友善。
APIapidescriptionmHierarchy層級,需要在Builder構造方法中指定,不能改變
OnOptionSelectListener選中回調,需要在Builder構造方法中指定,不能改變
setInterceptor設定攔截器
setFormatter設定Formatter
create通過Builder建構 OptionPicker
以上是OptionPicker.Builder的,下面是OptionPicker的
setFormatter同上
setDataWithValues根據選中的values初始化選中的position并初始化pickerview資料。values參數為可變長數組,可以不設定。
setDataWithIndexs設定資料和選中position。不建議使用,建議使用 setDataWithValues
setSelectedWithValues根據選中的values初始化選中的position
setSelectedWithIndexs設定選中的position。不建議使用,建議使用 setSelectedWithValues
getOptions擷取資料集
getSelectedPosition擷取選中的下标,數組size=mHierarchy,如果為-1表示該列沒有資料
getSelectedOptions擷取選中的選項,如果指定index為null則表示該列沒有資料需要注意的是:本庫中的OptionPicker隻用于關聯的,不支援多級别且不關聯。 基本沒有這種需求,如果大家有這種需求,我會在後續疊代中支援。
Others奇葩設計:部分default屬性聲明為static而非final
全局設定default屬性
奇葩也好,亮點也罷。作為一個UI控件,不同的app,不同的UI,不同的産品自然會有不同的樣式。 考慮到在一個app中我們會用到很多Picker,而我們又需要定制自己的UI的樣式,如果通過動态方法設定樣式就太麻煩了。 故做此設計。你可以通過配置這些static變量來快速定制一個滿足自己app樣式需求的Picker。 當然你也可以通過封裝方法來處理PickerView,Picker,裝飾器等樣式,但這樣一樣十分麻煩。我相信你自己都會煩。
靜态預設值所有的這些靜态屬性值都以 sDefault 開頭BasePickerViewfielddescriptiondefaultValuesDefaultVisibleItemCount預設可見的item個數5
sDefaultItemSize預設itemSize50(dp)
sDefaultIsCirculation預設是否循環falsePickerViewfielddescriptiondefaultValuesOutTextSizedefault out text size18(dp)
sCenterTextSizedefault center text size22(dp)
sCenterColordefault center text colorColor.BLUE
sOutColordefault out text colorColor.GRAYBasePickerfielddescriptiondefaultValuesDefaultPaddingRectpickerView父容器的 default paddingnull(無padding)
sDefaultPickerBackgroundColordefault picker background colorColor.WHITE
sDefaultTopBarCreator用于建構自定義defaultTopBar的接口nullDefaultCenterDecorationfielddescriptiondefaultValuesDefaultLineColordefault line colorColor.BLUE
sDefaultLineWidthdefault line width1(dp)
sDefaultDrawabledefault item background drawablenull
sDefaultMarginRectdefault line marginnull(無margin)建議初始化這些屬性值放到Application中完成,避免app發生crash而導緻失效
Simple Examplepublic class MyApplication extends Application {
@Override public void onCreate() {
super.onCreate();
// 建議在application中初始化picker 預設屬性實作全局設定
initDefaultPicker();
}
private void initDefaultPicker() {
// 利用修改靜态預設屬性值,快速定制一套滿足自己app樣式需求的Picker.
// BasePickerView
PickerView.sDefaultVisibleItemCount = 3;
PickerView.sDefaultItemSize = 50;
PickerView.sDefaultIsCirculation = true;
// PickerView
PickerView.sOutTextSize = 18;
PickerView.sCenterTextSize = 18;
PickerView.sCenterColor = Color.RED;
PickerView.sOutColor = Color.GRAY;
// BasePicker
int padding = Util.dip2px(this, 20);
BasePicker.sDefaultPaddingRect = new Rect(padding, padding, padding, padding);
BasePicker.sDefaultPickerBackgroundColor = Color.WHITE;
// 自定義 TopBar
BasePicker.sDefaultTopBarCreator = new BasePicker.IDefaultTopBarCreator() {
@Override public ITopBar createDefaultTopBar(LinearLayout parent) {
return new CustomTopBar(parent);
}
};
// DefaultCenterDecoration
DefaultCenterDecoration.sDefaultLineWidth = 1;
DefaultCenterDecoration.sDefaultLineColor = Color.RED;
//DefaultCenterDecoration.sDefaultDrawable = new ColorDrawable(Color.WHITE);
int leftMargin = Util.dip2px(this, 10);
int topMargin = Util.dip2px(this, 2);
DefaultCenterDecoration.sDefaultMarginRect =
new Rect(leftMargin, -topMargin, leftMargin, -topMargin);
}
}