自定义时间选择器
这里使用符合控件的形式来编写,过于复杂这难于理解,能够高效实现需求。
一、时间选择器需求分析
这里主要作用是明确却所需要的功能,才能确定所需要的的技术,方便提前阅读相关资料
dialog0.png
二、技术分析
dialog1.png
- 创建显示弹窗,采用的DialogFragment,PopWindow的话,太多问题了,对于有虚拟按键的剧中显示,背景透明度等不好控制,而且官方推荐使用DialogFragment
能够有效关系生命周期
Bundle bundle = new Bundle();
bundle.putString(TimerPickDialogFragment.TITLE, "行程时间");
bundle.putLong(TimerPickDialogFragment.TIME_START, System.currentTimeMillis());
bundle.putLong(TimerPickDialogFragment.TIME_END, System.currentTimeMillis());
new TimerPickDialogFragment().setOnTimeClickListener(new TimerPickDialogFragment.OnTimeClickListener() {
@Override
public void onClick(View view, TimerPickDialogFragment timerPickDialogFragment) {
switch (view.getId()) {
case R.id.tvTimerPickerCancel:
timerPickDialogFragment.dismiss();
break;
case R.id.tvTimerPickerSure:
timerPickDialogFragment.dismiss();
break;
default:
break;
}
}
}).setOnStartCurrentDateListener(new TimerPickDialogFragment.OnStartCurrentDateListener() {
@Override
public void onStartCurrentDate(View view, long date, TimerPickDialogFragment timerPickDialogFragment) {
}
}).setOnEndCurrentDateListener(new TimerPickDialogFragment.OnEndCurrentDateListener() {
@Override
public void onEndCurrentDate(View view, long date, TimerPickDialogFragment timerPickDialogFragment) {
}
}).addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
}
}).isShowRemark(true)
.updateText("时间选择器")
.setBundle(bundle)
.show(getFragmentManager(), "Time");
这里首先要对DialogFragment控件熟知,不懂得同学可以先移步官方文档,这里也对一些背景设定做下说明
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
// 修改显示的背景色
Window window = getDialog().getWindow();
window.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(getActivity(), R.color.c_99000000)));
// 修改输入法对弹窗的影响
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
View view = inflater.inflate(R.layout.popwin_time_picker, container, false);
unbinder = ButterKnife.bind(this, view);
if (bundle != null) {
timeStart = bundle.getLong(TIME_START);
timeEnd = bundle.getLong(TIME_END);
tvTimerPickerTitle.setText(bundle.getString(TITLE));
} else if (timeStart <= 0 && timeEnd <= 0) {
timeStart = timeEnd = Calendar.getInstance().getTimeInMillis();
}
if (textWatcher != null) {
edTimerPickerRemark.addTextChangedListener(textWatcher);
}
if (isShow) {
if (!TextUtils.isEmpty(remark)) {
edTimerPickerRemark.setText(remark);
}
tvTimerPickerRemarkTitle.setVisibility(View.VISIBLE);
edTimerPickerRemark.setVisibility(View.VISIBLE);
} else {
tvTimerPickerRemarkTitle.setVisibility(View.GONE);
edTimerPickerRemark.setVisibility(View.GONE);
}
/**
* 设置不可点击
*/
// pvTimerPickerStart.setWheelEnabled(false);
//时间回调
pvTimerPickerStart.setOnCurrentDateListener(new PickView.OnCurrentDateListener() {
@Override
public void onCurrentDate(View view, long date) {
if (onStartCurrentDateListener != null) {
onStartCurrentDateListener.onStartCurrentDate(view, date, TimerPickDialogFragment.this);
}
}
});
pvTimerPickerEnd.setOnCurrentDateListener(new PickView.OnCurrentDateListener() {
@Override
public void onCurrentDate(View view, long date) {
if (onEndCurrentDateListener != null) {
onEndCurrentDateListener.onEndCurrentDate(view, date, TimerPickDialogFragment.this);
}
}
});
pvTimerPickerStart.updateDate(timeStart);
pvTimerPickerEnd.updateDate(timeEnd);
return view;
}
- 设计具体的滚动Item
时间选择器有年月日时分,五个大Item 组合而成
package wudiplk.com.mycustomview.custom_view;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* @author Wudi
* @date 2018/1/16
*/
public class PickView extends LinearLayout {
private Context context;
private WheelView rvYear, rvMonth, rvDay, rvHour, rvMinute;
private List<String> yearList = new ArrayList<>();
private List<String> monthList = new ArrayList<>();
private List<String> dayList = new ArrayList<>();
private List<String> hourList = new ArrayList<>();
private List<String> minuteList = new ArrayList<>();
private Calendar calendar;
private int yearPosition, monthPosition, dayPosition, hourPosition, minutePosition;
public PickView(Context context) {
super(context);
this.context = context;
}
public PickView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public PickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (calendar == null) {
calendar = Calendar.getInstance();
}
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
initView();
return false;
}
});
handler.sendEmptyMessage(1);
}
private void initView() {
setOrientation(HORIZONTAL);
// 初始化时间
initDate();
// 初始化控件
rvYear = new WheelView(context, calendar, Calendar.YEAR, getMeasuredWidth(), getMeasuredHeight(), yearList, yearPosition);
rvMonth = new WheelView(context, calendar, Calendar.MONTH, getMeasuredWidth(), getMeasuredHeight(), monthList, monthPosition);
rvDay = new WheelView(context, calendar, Calendar.DAY_OF_MONTH, getMeasuredWidth(), getMeasuredHeight(), dayList, dayPosition);
rvHour = new WheelView(context, calendar, Calendar.HOUR_OF_DAY, getMeasuredWidth(), getMeasuredHeight(), hourList, hourPosition);
rvMinute = new WheelView(context, calendar, Calendar.MINUTE, getMeasuredWidth(), getMeasuredHeight(), minuteList, minutePosition);
rvYear.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
@Override
public void onCurrent(int position, String content) {
if (onCurrentDateListener != null) {
int year = Integer.parseInt(content.replace("年", ""));
calendar.set(Calendar.YEAR, year);
onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
}
}
});
rvMonth.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
@Override
public void onCurrent(int position, String content) {
if (onCurrentDateListener != null) {
// -1 由于系统计算月份是由0开始
int month = Integer.valueOf(content.replace("月", "")) - 1;
calendar.set(Calendar.MONTH, month);
// 更新这个月的天数
dayPosition = 0;
getDas(calendar.get(Calendar.YEAR), month + 1, calendar.get(Calendar.DAY_OF_MONTH));
rvDay.updateData(dayList, dayPosition);
onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
}
}
});
rvDay.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
@Override
public void onCurrent(int position, String content) {
if (onCurrentDateListener != null) {
int day = Integer.valueOf(content.replace("日", ""));
calendar.set(Calendar.DAY_OF_MONTH, day);
onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
}
}
});
rvHour.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
@Override
public void onCurrent(int position, String content) {
if (onCurrentDateListener != null) {
int hour = Integer.valueOf(content.replace("时", ""));
calendar.set(Calendar.HOUR_OF_DAY, hour);
onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
}
}
});
rvMinute.setOnCurrentItemListener(new WheelView.OnCurrentItemListener() {
@Override
public void onCurrent(int position, String content) {
if (onCurrentDateListener != null) {
int minute = Integer.valueOf(content.replace("分", ""));
calendar.set(Calendar.MINUTE, minute);
onCurrentDateListener.onCurrentDate(PickView.this, calendar.getTimeInMillis());
}
}
});
// 添加所有初始化后的页面
addView(rvYear, 0);
addView(rvMonth, 1);
addView(rvDay, 2);
addView(rvHour, 3);
addView(rvMinute, 4);
if (!isEnable) {
rvYear.setWheelEnable(isEnable);
rvMonth.setWheelEnable(isEnable);
rvDay.setWheelEnable(isEnable);
rvHour.setWheelEnable(isEnable);
rvMinute.setWheelEnable(isEnable);
}
}
/**
* 设置初始时间
*
* @param updateDate
*/
public void updateDate(long updateDate) {
if (updateDate != 0) {
calendar = Calendar.getInstance();
calendar.setTime(new Date(updateDate));
}
}
/**
* 获取获取具体年份的天数,如果是初始化 当前的年月日
* 如果是获取某年某月的天数day 默认为1
*
* @param year
* @param month
* @return
*/
public void getDas(int year, int month, int day) {
Calendar newCalendar = Calendar.getInstance();
newCalendar.set(Calendar.YEAR, year);
newCalendar.set(Calendar.MONTH, month - 1);
newCalendar.set(Calendar.DATE, 1);
newCalendar.roll(Calendar.DATE, -1);
int days = newCalendar.get(Calendar.DATE);
dayList.clear();
for (int i = 1; i <= days; i++) {
if (i < 10) {
dayList.add("0" + i + "日");
} else {
dayList.add(i + "日");
}
if (i == day) {
dayPosition = i - 1;
}
}
}
/**
* 初始化时间数据
*/
private void initDate() {
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
// 初始化年数 和当前年
for (int i = year - 50; i <= (year + 100); i++) {
if (i == year) {
yearPosition = yearList.size();
}
yearList.add(i + "年");
}
// 初始化月份 和当前月
for (int i = 1; i <= 12; i++) {
if (i == month) {
monthPosition = i;
}
monthList.add(i + "月");
}
// 初始化天数 和当前天
getDas(year, month + 1, day);
// 初始化小时数 和当小时
for (int i = 0; i < 24; i++) {
if (hour == i) {
hourPosition = i;
}
if (i < 10) {
hourList.add("0" + i + "时");
} else {
hourList.add(i + "时");
}
}
// 初始化分钟数 和当分钟
for (int i = 0; i < 12; i++) {
if (minute / 5 == i) {
minutePosition = i;
calendar.set(Calendar.MINUTE, i * 5);
}
if (i < 2) {
minuteList.add("0" + i * 5 + "分");
} else {
minuteList.add(i * 5 + "分");
}
}
}
public interface OnCurrentDateListener {
/**
* 获取当前的日期
*
* @param view
* @param date
*/
void onCurrentDate(View view, long date);
}
private OnCurrentDateListener onCurrentDateListener;
public void setOnCurrentDateListener(OnCurrentDateListener onCurrentDateListener) {
this.onCurrentDateListener = onCurrentDateListener;
}
private boolean isEnable = true;
public void setWheelEnabled(boolean b) {
this.isEnable = b;
}
}
每个Item 都是由一个RecycleView(WheelView),关键代码如下
/**
* 初始化内部控件
*/
private void init() {
currentPosition = initPosition;
// 初始化滚动器的宽高
oneHeight = (parentHeight - DensityUtils.dp2px(context, 4)) / 3;
oneWidth = parentWidth / 5;
final int padding = dp2px(3);
LayoutParams params = new LayoutParams(oneWidth, parentHeight);
setLayoutParams(params);
this.setPadding(padding, 0, padding, 0);
// 设置内部控件左右两边的空隙
mRecyclerView = new RecyclerView(context);
mRecyclerView.setOverScrollMode(OVER_SCROLL_NEVER);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
// 指定显示三行数据
layoutManager = new LinearLayoutManager(context) {
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
if (getChildCount() > 0) {
View firstChildView = recycler.getViewForPosition(0);
measureChild(firstChildView, widthSpec, heightSpec);
setMeasuredDimension(MeasureSpec.getSize(widthSpec), firstChildView.getMeasuredHeight() * 3 - DensityUtils.dp2px(context, 4));
} else {
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
}
};
mRecyclerView.setLayoutManager(layoutManager);
// 添加数据
itemAdapter = new ItemAdapter();
mRecyclerView.setAdapter(itemAdapter);
// 滑动监听获取当前的item
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
RecyclerView.LayoutManager layoutManager;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
// now State 0静止,没有滚动 1 正在被外部拖拽,一般为用户正在用手指滚动 2 自动滚动开始
if (newState == 0) {
layoutManager = recyclerView.getLayoutManager();
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
if (linearLayoutManager.findLastCompletelyVisibleItemPosition() == currentPosition) {
// TODO
} else if (onCurrentItemListener != null) {
int nowPosition = (linearLayoutManager.findLastCompletelyVisibleItemPosition());
String text = stringList.get(nowPosition % stringList.size())
.replace("年", "")
.replace("月", "")
.replace("日", "")
.replace("时", "")
.replace("分", "");
// 判断是否是过去的日期
if (wheelType == Calendar.MONTH) {
selectCalendar.set(wheelType, Integer.valueOf(text) - 1);
} else {
selectCalendar.set(wheelType, Integer.valueOf(text));
}
// 判断是否是小于当前时间
if (selectCalendar.getTimeInMillis() >= System.currentTimeMillis()) {
onCurrentItemListener.onCurrent(nowPosition % stringList.size(), stringList.get(nowPosition % stringList.size()));
layoutManager.scrollToPosition(nowPosition + 1);
itemAdapter.notifyItemChanged(nowPosition, 0);
itemAdapter.notifyItemChanged(currentPosition, 1);
currentPosition = nowPosition;
} else {
// 判断是向上还是线下 isUpOrDown>向下,isUpOrDown<0向上
if (isUpOrDown > 0) {
currentPosition = initPosition;
// 由于滚动有些偏差,使用scrollToPositionWithOffset 来滚动
linearLayoutManager.scrollToPositionWithOffset(currentPosition - 1, -DensityUtils.dp2px(context, 3));
itemAdapter.notifyItemChanged(currentPosition, 0);
} else {
currentPosition = initPosition;
linearLayoutManager.scrollToPositionWithOffset(currentPosition - 1, -DensityUtils.dp2px(context, 3));
itemAdapter.notifyItemChanged(currentPosition, 0);
}
}
}
} else {
itemAdapter.notifyItemChanged(currentPosition, 1);
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
isUpOrDown = dy;
}
});
// 添加滚动器的的双杠线
View topLineView = new View(context);
View bottomLineView = new View(context);
topLineView.setBackgroundColor(ContextCompat.getColor(context, R.color.c_e5e5e5));
bottomLineView.setBackgroundColor(ContextCompat.getColor(context, R.color.c_e5e5e5));
LayoutParams topLayoutParam = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp2px(1));
topLayoutParam.setMargins(0, oneHeight, 0, 0);
topLayoutParam.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
LayoutParams bottomLayoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp2px(1));
bottomLayoutParams.setMargins(0, oneHeight * 2, 0, 0);
bottomLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
// 滑动到初始位置 -1是为了移动到中间位置
layoutManager.scrollToPosition(stringList.size() * 1000 + initPosition - 1);
currentPosition = stringList.size() * 1000 + initPosition;
// 添加所有初始化后的空间
addView(mRecyclerView, layoutParams);
addView(topLineView, topLayoutParam);
addView(bottomLineView, bottomLayoutParams);
}