一个自定义的时间选择器,逻辑并不复杂,涉及到两个RecyclerView的交互还有Calendar时间类的使用,今天从项目中剥离出来,供大家参考指正。
效果如图:
效果如上图所示,大致实现方式就是上下两个RecyclerView分别实现日期和时间的选择,然后以底部弹窗的方式展示出来,废话不多说,上代码。
/**
* Created by YHang on 18/5/28.
*/
public class TimeBottomDialog extends Dialog implements View.OnClickListener, DataAdapter.OnItemClickListener, TimeAdapter.OnTimeClickListener {
private ImageView close;
private Context mContext;
private RecyclerView dateList, timeList;
private DataAdapter mDataAdapter;
private TimeAdapter mTimeAdapter;
public void setMyTime(OnUpdateMyTime myTime) {
this.myTime = myTime;
}
private OnUpdateMyTime myTime;
public TimeBottomDialog(Context context) {
//重写dialog默认的主题
this(context, R.style.bottom_dialog);
this.mContext = context;
}
public TimeBottomDialog(Context context, int themeResId) {
super(context, themeResId);
this.mContext = context;
View convertView = getLayoutInflater().inflate(R.layout.time_bottom_dialog, null);
close = (ImageView) convertView.findViewById(R.id.iv_close);
dateList = convertView.findViewById(R.id.date_list);
timeList = convertView.findViewById(R.id.time_list);
close.setOnClickListener(this);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(convertView);
setCanceledOnTouchOutside(false);
LinearLayoutManager ms = new LinearLayoutManager(mContext);
ms.setOrientation(LinearLayoutManager.HORIZONTAL);
dateList.setLayoutManager(ms);
dateList.addItemDecoration(new SpacesItemDecoration(30));
mDataAdapter = new DataAdapter(mContext);
mDataAdapter.setItemClickListener(this);
dateList.setAdapter(mDataAdapter);
GridLayoutManager gm = new GridLayoutManager(mContext, 4);
gm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return mTimeAdapter.getItemViewType(position);
}
});
timeList.setLayoutManager(gm);
mTimeAdapter = new TimeAdapter(mContext);
mTimeAdapter.setTimeClickListener(this);
timeList.setAdapter(mTimeAdapter);
}
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
getWindow().setGravity(Gravity.BOTTOM);
setCanceledOnTouchOutside(true);
DisplayMetrics dm = new DisplayMetrics();
getWindow().getWindowManager().getDefaultDisplay().getMetrics(dm);
WindowManager.LayoutParams p = getWindow().getAttributes();
p.width = dm.widthPixels;
getWindow().setAttributes(p);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_close:
dismiss();
break;
}
}
@Override
public void onItemClick(int position) {
mDataAdapter.setSelectedPos(position);
if (mTimeAdapter != null) {
mTimeAdapter.setSelectDateMillis(mDataAdapter.getSelectedMillis());
}
}
@Override
public void onTimeClick(int position) {
mTimeAdapter.setSelectPosition(position);
String selectedDate = mDataAdapter.getSelectedDate();
String selectTime = mTimeAdapter.getSelectTime();
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
String year = String.valueOf(c.get(Calendar.YEAR));
StringBuilder builder = new StringBuilder();
builder.append(year)
.append("-")
.append(selectedDate)
.append(" ")
.append(selectTime);
if (myTime != null) {
myTime.updateMyTime(builder);
}
dismiss();
}
public interface OnUpdateMyTime {
void updateMyTime(StringBuilder builder);
}
class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
public SpacesItemDecoration(int space) {
this.space = space;
}
@Override
public void getItemOffsets(Rect outRect, View child,
RecyclerView parent, RecyclerView.State state) {
outRect.right = space;
}
}
}
这是toast主体类的内容,其中用来获取时间回调的:
1.日期回调:
@Override
public void onItemClick(int position) {
mDataAdapter.setSelectedPos(position);
if (mTimeAdapter != null) {
mTimeAdapter.setSelectDateMillis(mDataAdapter.getSelectedMillis());
}
}
2.时间回调
@Override
public void onTimeClick(int position) {
mTimeAdapter.setSelectPosition(position);
String selectedDate = mDataAdapter.getSelectedDate();
String selectTime = mTimeAdapter.getSelectTime();
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
String year = String.valueOf(c.get(Calendar.YEAR));
StringBuilder builder = new StringBuilder();
builder.append(year)
.append("-")
.append(selectedDate)
.append(" ")
.append(selectTime);
if (myTime != null) {
myTime.updateMyTime(builder);
}
dismiss();
}
通过以上的两个回调来最终获取数据,这里我在日期回调中使用
if (mTimeAdapter != null) {
mTimeAdapter.setSelectDateMillis(mDataAdapter.getSelectedMillis());
}
时间选择Adapter的setSelectDateMillis方法实现时间和日期的同步。
然后就是两个RecyclerView的Adapter了,其中DataAdapter相对简单就不贴代码了,有兴趣可以到我的Github中查看源码,这里贴一下TimeAdapter的代码:
/**
* Created by YHang on 18/5/28.
*/
public class TimeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static final long LENGTH_OF_HALF_HOUR_IN_MILLISECONDS = 1800000L;
private Context context;
private List<String> times = new ArrayList<>();
private List<Long> timesMillis = new ArrayList<>();
private long dateMillis;
private int selectPosition = 0;
private OnTimeClickListener mItemClickListener;
public interface OnTimeClickListener {
void onTimeClick(int position);
}
public void setTimeClickListener(OnTimeClickListener itemClickListener) {
mItemClickListener = itemClickListener;
}
public TimeAdapter(Context context) {
this.context = context;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
long time = dateToMilliseconds("08:30", sdf);
for (int i = 0; i < 19; i++) {
timesMillis.add(time);
String timeStr = sdf.format(time);
time += LENGTH_OF_HALF_HOUR_IN_MILLISECONDS;
times.add(timeStr);
}
}
public void setSelectDateMillis(long dateMillis) {
this.dateMillis = dateMillis;
notifyDataSetChanged();
}
public void setSelectPosition(int position) {
selectPosition = position;
notifyDataSetChanged();
}
public String getSelectTime() {
return times.get(selectPosition);
}
/**
* 获取指定时间的毫秒
*
* @param timeNow
* @param sdf
* @return
*/
public long dateToMilliseconds(String timeNow, SimpleDateFormat sdf) {
long time = 0L;
try {
Date date = sdf.parse(timeNow);
time = date.getTime();
} catch (ParseException e) {
e.printStackTrace();
}
return time;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 4:
return new TitleViewHolder(LayoutInflater.from(context).inflate(R.layout.time_list_item_title, parent, false));
default:
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.time_list_item, parent, false));
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ViewHolder) {
ViewHolder mHolder = (ViewHolder) holder;
if (position > 0) position--;
if (position > 8) position--;
mHolder.mTvTime.setText(times.get(position));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm");
String format = simpleDateFormat.format(new Date());
long time = dateToMilliseconds(format, simpleDateFormat);
if (dateMillis <= System.currentTimeMillis()) {
if (timesMillis.get(position) < time) {
mHolder.mTvTime.setEnabled(false);
} else {
mHolder.mTvTime.setEnabled(true);
}
} else {
mHolder.mTvTime.setEnabled(true);
}
final int finalPosition = position;
mHolder.mTvTime.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mItemClickListener != null) {
mItemClickListener.onTimeClick(finalPosition);
selectPosition = finalPosition;
}
}
});
} else if (holder instanceof TitleViewHolder) {
TitleViewHolder mHolder = (TitleViewHolder) holder;
String time = null;
if (position == 0) {
time = "上午";
} else if (position == 9) {
time = "下午";
}
mHolder.mTvTimeTitle.setText(time);
}
}
@Override
public int getItemCount() {
return 21;
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView mTvTime;
ViewHolder(View view) {
super(view);
this.mTvTime = view.findViewById(R.id.tv_time);
}
}
class TitleViewHolder extends RecyclerView.ViewHolder {
TextView mTvTimeTitle;
TitleViewHolder(View view) {
super(view);
this.mTvTimeTitle = view.findViewById(R.id.tv_time_title);
}
}
@Override
public int getItemViewType(int position) {
switch (position) {
case 0:
case 9:
return 4;
default:
return 1;
}
}
}
这里用到了RecyclerView的getItemViewType方法为指定的position分配不同的布局类型,以便在onCreateViewHolder根据不同的viewType对布局进行分类加载。最后需要注意一点
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ViewHolder) {
ViewHolder mHolder = (ViewHolder) holder;
if (position > 0) position--;
if (position > 8) position--;
.
.
.
这里虽然是两种布局,但是不管是什么布局他们的position仍然是按照顺序来排的,所以在开头 我们在固定的position上使用position--来重排item的position顺序,最后依旧通过自定义回调把数据传输回去。
好了,这个时间选择器大概就是这样了,项目做的急,还没来的急优化,有兴趣的同学可以做一下优化,或者自己的定制。最后,欢迎大家指正。
奉上项目地址: