项目需求,需要根据UI自定义日期选择器(Android自带的DatePicker极丑)。
所以自定义了WheelView,并在此基础上自定义日期选择器。
参考1.WheelView TimePicker CityPicker 滚轮(联动)选择器
2.使用第三方WheelView制作日期选择器
3.WheelPicker(推荐参考,功能强大)
自定义WheelView代码如下:
/**
* WheelView滚轮
*
*/
public class WheelView extends View {
/**
* 控件宽度
*/
private float controlWidth;
/**
* 控件高度
*/
private float controlHeight;
/**
* 是否滑动中
*/
private boolean isScrolling = false;
/**
* 选择的内容
*/
private ArrayList itemList = new ArrayList<>();
/**
* 设置数据
*/
private ArrayList dataList = new ArrayList<>();
/**
* 按下的坐标
*/
private int downY;
/**
* 按下的时间
*/
private long downTime = 0;
/**
* 短促移动
*/
private long goonTime = 200;
/**
* 短促移动距离
*/
private int goonDistance = 100;
/**
* 画线画笔
*/
private Paint linePaint;
/**
* 线的默认颜色
*/
private int lineColor = 0xff000000;
/**
* 线的默认宽度
*/
private float lineHeight = 2f;
/**
* 默认字体
*/
private float normalFont = 14.0f;
/**
* 选中的时候字体
*/
private float selectedFont = 22.0f;
/**
* 单元格高度
*/
private int unitHeight = 50;
/**
* 显示多少个内容
*/
private int itemNumber = 7;
/**
* 默认字体颜色
*/
private int normalColor = 0xff000000;
/**
* 选中时候的字体颜色
*/
private int selectedColor = 0xffff0000;
/**
* 蒙板高度
*/
private float maskHeight = 48.0f;
/**
* 选择监听
*/
private OnSelectListener onSelectListener;
/**
* 是否可用
*/
private boolean isEnable = true;
/**
* 刷新界面
*/
private static final int REFRESH_VIEW = 0x001;
/**
* 移动距离
*/
private static final int MOVE_NUMBER = 5;
/**
* 是否允许选空
*/
private boolean noEmpty = true;
/**
* 正在修改数据,避免ConcurrentModificationException异常
*/
private boolean isClearing = false;
public WheelView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
initData();
}
public WheelView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
initData();
}
public WheelView(Context context) {
super(context);
initData();
}
/**
* 初始化,获取设置的属性
*
* @param context
* @param attrs
*/
private void init(Context context, AttributeSet attrs) {
TypedArray attribute = context.obtainStyledAttributes(attrs, R.styleable.WheelView);
unitHeight = (int) attribute.getDimension(R.styleable.WheelView_unitHeight, unitHeight);
itemNumber = attribute.getInt(R.styleable.WheelView_itemNumber, itemNumber);
normalFont = attribute.getDimension(R.styleable.WheelView_normalTextSize, normalFont);
selectedFont = attribute.getDimension(R.styleable.WheelView_selectedTextSize, selectedFont);
normalColor = attribute.getColor(R.styleable.WheelView_normalTextColor, normalColor);
selectedColor = attribute.getColor(R.styleable.WheelView_selectedTextColor, selectedColor);
lineColor = attribute.getColor(R.styleable.WheelView_lineColor, lineColor);
lineHeight = attribute.getDimension(R.styleable.WheelView_lineHeight, lineHeight);
maskHeight = attribute.getDimension(R.styleable.WheelView_maskHeight, maskHeight);
noEmpty = attribute.getBoolean(R.styleable.WheelView_noEmpty, true);
isEnable = attribute.getBoolean(R.styleable.WheelView_isEnable, true);
attribute.recycle();
controlHeight = itemNumber * unitHeight;
}
/**
* 初始化数据
*/
private void initData() {
isClearing = true;
itemList.clear();
for (int i = 0; i < dataList.size(); i++) {
ItemObject itemObject = new ItemObject();
itemObject.id = i;
itemObject.itemText = dataList.get(i);
itemObject.x = 0;
itemObject.y = i * unitHeight;
itemList.add(itemObject);
}
isClearing = false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
controlWidth = getMeasuredWidth();
if (controlWidth != 0) {
setMeasuredDimension(getMeasuredWidth(), itemNumber * unitHeight);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawLine(canvas);
drawList(canvas);
drawMask(canvas);
}
/**
* 绘制线条
*
* @param canvas
*/
private void drawLine(Canvas canvas) {
if (linePaint == null) {
linePaint = new Paint();
linePaint.setColor(lineColor);
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth(lineHeight);
}
canvas.drawLine(0, controlHeight / 2 - unitHeight / 2 + lineHeight,
controlWidth, controlHeight / 2 - unitHeight / 2 + lineHeight, linePaint);
canvas.drawLine(0, controlHeight / 2 + unitHeight / 2 - lineHeight,
controlWidth, controlHeight / 2 + unitHeight / 2 - lineHeight, linePaint);
}
private synchronized void drawList(Canvas canvas) {
if (isClearing)
return;
try {
for (ItemObject itemObject : itemList) {
itemObject.drawSelf(canvas, getMeasuredWidth());
}
} catch (Exception e) {
}
}
/**
* 绘制遮盖板
*
* @param canvas
*/
private void drawMask(Canvas canvas) {
LinearGradient lg = new LinearGradient(0, 0, 0, maskHeight, 0x00f2f2f2,
0x00f2f2f2, Shader.TileMode.MIRROR);
Paint paint = new Paint();
paint.setShader(lg);
canvas.drawRect(0, 0, controlWidth, maskHeight, paint);
LinearGradient lg2 = new LinearGradient(0, controlHeight - maskHeight,
0, controlHeight, 0x00f2f2f2, 0x00f2f2f2, Shader.TileMode.MIRROR);
Paint paint2 = new Paint();
paint2.setShader(lg2);
canvas.drawRect(0, controlHeight - maskHeight, controlWidth,
controlHeight, paint2);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnable)
return true;
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isScrolling = true;
downY = (int) event.getY();
downTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
actionMove(y - downY);
onSelectListener();
break;
case MotionEvent.ACTION_UP:
int move = Math.abs(y - downY);
// 判断这段时间移动的距离
if (System.currentTimeMillis() - downTime < goonTime && move > goonDistance) {
goonMove(y - downY);
} else {
actionUp(y - downY);
noEmpty();
isScrolling = false;
}
break;
default:
break;
}
return true;
}
/**
* 继续移动一定距离
*/
private synchronized void goonMove(final int move) {
new Thread(new Runnable() {
@Override
public void run() {
int distance = 0;
while (distance < unitHeight * MOVE_NUMBER) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
actionThreadMove(move > 0 ? distance : distance * (-1));
distance += 10;
}
actionUp(move > 0 ? distance - 10 : distance * (-1) + 10);
noEmpty();
}
}).start();
}
/**
* 不能为空,必须有选项
*/
private void noEmpty() {
if (!noEmpty)
return;
for (ItemObject item : itemList) {
if (item.isSelected())
return;
}
int move = (int) itemList.get(0).moveToSelected();
if (move < 0) {
defaultMove(move);
} else {
defaultMove((int) itemList.get(itemList.size() - 1)
.moveToSelected());
}
for (ItemObject item : itemList) {
if (item.isSelected()) {
if (onSelectListener != null)
onSelectListener.endSelect(WheelView.this, item.id, item.itemText);
break;
}
}
}
/**
* 移动的时候
*
* @param move
*/
private void actionMove(int move) {
for (ItemObject item : itemList) {
item.move(move);
}
invalidate();
}
/**
* 移动,线程中调用
*
* @param move
*/
private void actionThreadMove(int move) {
for (ItemObject item : itemList) {
item.move(move);
}
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);
}
/**
* 松开的时候
*
* @param move
*/
private void actionUp(int move) {
int newMove = 0;
if (move > 0) {
for (int i = 0; i < itemList.size(); i++) {
if (itemList.get(i).isSelected()) {
newMove = (int) itemList.get(i).moveToSelected();
if (onSelectListener != null)
onSelectListener.endSelect(WheelView.this, itemList.get(i).id,
itemList.get(i).itemText);
break;
}
}
} else {
for (int i = itemList.size() - 1; i >= 0; i--) {
if (itemList.get(i).isSelected()) {
newMove = (int) itemList.get(i).moveToSelected();
if (onSelectListener != null)
onSelectListener.endSelect(WheelView.this, itemList.get(i).id,
itemList.get(i).itemText);
break;
}
}
}
for (ItemObject item : itemList) {
item.newY(move + 0);
}
slowMove(newMove);
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);
}
/**
* 缓慢移动
*
* @param move
*/
private synchronized void slowMove(final int move) {
new Thread(new Runnable() {
@Override
public void run() {
// 判断正负
int m = move > 0 ? move : move * (-1);
int i = move > 0 ? 1 : (-1);
// 移动速度
int speed = 1;
while (true) {
m = m - speed;
if (m <= 0) {
for (ItemObject item : itemList) {
item.newY(m * i);
}
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
for (ItemObject item : itemList) {
item.newY(speed * i);
}
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (ItemObject item : itemList) {
if (item.isSelected()) {
if (onSelectListener != null)
onSelectListener.endSelect(WheelView.this, item.id, item.itemText);
break;
}
}
}
}).start();
}
/**
* 移动到默认位置
*
* @param move
*/
private void defaultMove(int move) {
for (ItemObject item : itemList) {
item.newY(move);
}
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);
}
/**
* 滑动监听
*/
private void onSelectListener() {
if (onSelectListener == null)
return;
for (ItemObject item : itemList) {
if (item.isSelected()) {
onSelectListener.selecting(item.id, item.itemText);
}
}
}
/**
* 设置数据 (第一次)
*
* @param data
*/
public void setData(ArrayList data) {
this.dataList = data;
initData();
}
/**
* 重置数据
*
* @param data
*/
public void refreshData(ArrayList data) {
setData(data);
invalidate();
}
/**
* 获取返回项 id
*
* @return
*/
public int getSelected() {
for (ItemObject item : itemList) {
if (item.isSelected())
return item.id;
}
return -1;
}
/**
* 获取返回的内容
*
* @return
*/
public String getSelectedText() {
for (ItemObject item : itemList) {
if (item.isSelected())
return item.itemText;
}
return "";
}
/**
* 是否正在滑动
*
* @return
*/
public boolean isScrolling() {
return isScrolling;
}
/**
* 是否可用
*
* @return
*/
public boolean isEnable() {
return isEnable;
}
/**
* 设置是否可用
*
* @param isEnable
*/
public void setEnable(boolean isEnable) {
this.isEnable = isEnable;
}
/**
* 设置默认选项
*
* @param index
*/
public void setDefault(int index) {
if (index > itemList.size() - 1)
return;
float move = itemList.get(index).moveToSelected();
defaultMove((int) move);
}
/**
* 获取列表大小
*
* @return
*/
public int getListSize() {
if (itemList == null)
return 0;
return itemList.size();
}
/**
* 获取某项的内容
*
* @param index
* @return
*/
public String getItemText(int index) {
if (itemList == null)
return "";
return itemList.get(index).itemText;
}
/**
* 监听
*
* @param onSelectListener
*/
public void setOnSelectListener(OnSelectListener onSelectListener) {
this.onSelectListener = onSelectListener;
}
@SuppressLint("HandlerLeak")
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case REFRESH_VIEW:
invalidate();
break;
default:
break;
}
}
};
/**
* 单条内容
*
* @author JiangPing
*/
private class ItemObject {
/**
* id
*/
public int id = 0;
/**
* 内容
*/
public String itemText = "";
/**
* x坐标
*/
public int x = 0;
/**
* y坐标
*/
public int y = 0;
/**
* 移动距离
*/
public int move = 0;
/**
* 字体画笔
*/
private TextPaint textPaint;
/**
* 字体范围矩形
*/
private Rect textRect;
public ItemObject() {
super();
}
/**
* 绘制自身
*
* @param canvas 画板
* @param containerWidth 容器宽度
*/
public void drawSelf(Canvas canvas, int containerWidth) {
if (textPaint == null) {
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
}
if (textRect == null)
textRect = new Rect();
// 判断是否被选择
if (isSelected()) {
textPaint.setColor(selectedColor);
// 获取距离标准位置的距离
float moveToSelect = moveToSelected();
moveToSelect = moveToSelect > 0 ? moveToSelect : moveToSelect * (-1);
// 计算当前字体大小
float textSize = normalFont
+ ((selectedFont - normalFont) * (1.0f - moveToSelect / (float) unitHeight));
textPaint.setTextSize(textSize);
} else {
textPaint.setColor(normalColor);
textPaint.setTextSize(normalFont);
}
// 返回包围整个字符串的最小的一个Rect区域
itemText = (String) TextUtils.ellipsize(itemText, textPaint, containerWidth, TextUtils.TruncateAt.END);
textPaint.getTextBounds(itemText, 0, itemText.length(), textRect);
// 判断是否可视
if (!isInView())
return;
// 绘制内容
canvas.drawText(itemText, x + controlWidth / 2 - textRect.width() / 2,
y + move + unitHeight / 2 + textRect.height() / 2, textPaint);
}
/**
* 是否在可视界面内
*
* @return
*/
public boolean isInView() {
if (y + move > controlHeight || (y + move + unitHeight / 2 + textRect.height() / 2) < 0)
return false;
return true;
}
/**
* 移动距离
*
* @param _move
*/
public void move(int _move) {
this.move = _move;
}
/**
* 设置新的坐标
*
* @param _move
*/
public void newY(int _move) {
this.move = 0;
this.y = y + _move;
}
/**
* 判断是否在选择区域内
*
* @return
*/
public boolean isSelected() {
if ((y + move) >= controlHeight / 2 - unitHeight / 2 + lineHeight
&& (y + move) <= controlHeight / 2 + unitHeight / 2 - lineHeight) {
return true;
}
if ((y + move + unitHeight) >= controlHeight / 2 - unitHeight / 2 + lineHeight
&& (y + move + unitHeight) <= controlHeight / 2 + unitHeight / 2 - lineHeight) {
return true;
}
if ((y + move) <= controlHeight / 2 - unitHeight / 2 + lineHeight
&& (y + move + unitHeight) >= controlHeight / 2 + unitHeight / 2 - lineHeight) {
return true;
}
return false;
}
/**
* 获取移动到标准位置需要的距离
*/
public float moveToSelected() {
return (controlHeight / 2 - unitHeight / 2) - (y + move);
}
}
/**
* 选择监听
*
* @author JiangPing
*/
public interface OnSelectListener {
/**
* 结束选择
*
* @param id
* @param text
*/
void endSelect(View view, int id, String text);
/**
* 选中的内容
*
* @param id
* @param text
*/
void selecting(int id, String text);
}
}
日期选择器的效果如图所示:
public class DateSelectionDialog extends Dialog implements WheelView.OnSelectListener {
@BindView(R.id.staff_birthday_selection_cancel)
Button mStaffBirthdaySelectionCancel;
@BindView(R.id.staff_birthday_selection_confrim)
Button mStaffBirthdaySelectionConfrim;
@BindView(R.id.year)
WheelView mYear;
@BindView(R.id.month)
WheelView mMonth;
@BindView(R.id.day)
WheelView mDay;
private OnClickListener positiveButtonClickListener;
private OnClickListener negativeButtonClickListener;
public void setNegativeButton(OnClickListener listener) {
this.negativeButtonClickListener = listener;
}
public void setPositiveButton(OnClickListener listener) {
this.positiveButtonClickListener = listener;
}
public DateSelectionDialog(@NonNull Context context) {
super(context, R.style.select_dialog);
}
public DateSelectionDialog(@NonNull Context context, int themeResId) {
super(context, themeResId);
}
protected DateSelectionDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_date_selection_dialog);
ButterKnife.bind(this);
// 格式化当前时间,并转换为年月日整型数据
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
String[] split = sdf.format(new Date()).split("-");
int currentYear = Integer.parseInt(split[0]);
int currentMonth = Integer.parseInt(split[1]);
int currentDay = Integer.parseInt(split[2]);
// 设置默认年月日为当前日期
mYear.setData(getYearData());
mYear.setDefault(currentYear - 1900);
mMonth.setData(getMonthData());
mMonth.setDefault(currentMonth - 1);
mDay.setData(getDayData(getLastDay(currentYear, currentMonth)));
mDay.setDefault(currentDay - 1);
mYear.setOnSelectListener(this);
mMonth.setOnSelectListener(this);
mDay.setOnSelectListener(this);
}
@Override
protected void onStart() {
super.onStart();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = this.getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
window.getDecorView().setSystemUiVisibility(uiOptions);
getWindow().setStatusBarColor(Color.TRANSPARENT);
getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
}
@OnClick({R.id.staff_birthday_selection_cancel, R.id.staff_birthday_selection_confrim})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.staff_birthday_selection_cancel:
negativeButtonClickListener.onClick(this, DialogInterface.BUTTON_NEGATIVE);
break;
case R.id.staff_birthday_selection_confrim:
positiveButtonClickListener.onClick(this, DialogInterface.BUTTON_POSITIVE);
break;
}
}
private ArrayList getYearData() {
ArrayList list = new ArrayList<>();
for (int i = 1900; i <= 2050; i++) {
list.add(String.valueOf(i));
}
return list;
}
private ArrayList getMonthData() {
ArrayList list = new ArrayList<>();
for (int i = 1; i <= 12; i++) {
list.add(String.valueOf(i));
}
return list;
}
private ArrayList getDayData(int lastDay) {
//ignore condition
ArrayList list = new ArrayList<>();
for (int i = 1; i <= lastDay; i++) {
list.add(String.valueOf(i));
}
return list;
}
public String getYear() {
if (mDay == null) {
return null;
}
return mYear.getSelectedText();
}
public String getMonth() {
if (mMonth == null) {
return null;
}
return mMonth.getSelectedText();
}
public String getDay() {
if (mDay == null) {
return null;
}
return mDay.getSelectedText();
}
public String getDate() {
return getYear() + "." + getMonth() + "." + getDay();
}
/**
* 判断是否闰年
*
* @param year
* @return
*/
private boolean isLeapYear(int year) {
return (year % 100 == 0 && year % 400 == 0) || (year % 100 != 0 && year % 4 == 0);
}
/**
* 获取特定年月对应的天数
*
* @param year
* @param month
* @return
*/
private int getLastDay(int year, int month) {
if (month == 2) {
// 2月闰年的话返回29,防止28
return isLeapYear(year) ? 29 : 28;
}
// 一三五七八十腊,三十一天永不差
return month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 ? 31 : 30;
}
@Override
public void endSelect(View view, int id, String text) {
// 滚轮滑动停止后调用
switch (view.getId()) {
case R.id.year:
case R.id.month:
// 记录当前选择的天数
int selectDay = Integer.parseInt(getDay());
// 根据当前选择的年月获取对应的天数
int lastDay = getLastDay(Integer.parseInt(getYear()), Integer.parseInt(getMonth()));
// 设置天数
mDay.setData(getDayData(lastDay));
// 如果选中的天数大于实际天数,那么将默认天数设为实际天数;否则还是设置默认天数为选中的天数
if (selectDay > lastDay) {
mDay.setDefault(lastDay - 1);
} else {
mDay.setDefault(selectDay - 1);
}
break;
}
}
@Override
public void selecting(int id, String text) {
}
}