本篇给童鞋们写一个自定义的MonthView。
前言
本篇给童鞋们自定义一个展示日历某一个月份的日期View
。对于这类相对不太复杂的自定义View
,我们尽量避免使用xml布局文件,这样有利于提高我们的code性能。如果你使用的是kotlin
语法,自行翻译,Android Studio
本身自带转换工具。
效果图
大家看到效果图之后第一个想到的就是利用GridView
、Recyclerview
来实现。其实不用那么麻烦,直接代码创建child,直接addview(View child)
就好,因为child数量不多,不需要创建一个adapter来复用子控件。这只是一个moth view,如果要实现日历view的话,利用ViewPager
做分页适配实现就好,不会很复杂!
立即体验
扫描以下二维码下载体验App(从0.2.3
版本开始,体验App内嵌版本更新检测功能):
JSCKit库传送门:https://github.com/JustinRoom/JSCKit
实现
-
创建日期
View
。
我们给它命名DayView
。
DayView.java
public class DayView extends RelativeLayout {
private TextView tvLabel;
private TextView tvSubLabel;
public DayView(@NonNull Context context) {
this(context, null);
}
public DayView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DayView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setGravity(Gravity.CENTER_HORIZONTAL);
addView(layout, params);
tvLabel = new TextView(context);
tvLabel.setTextColor(0xFF333333);
tvLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
layout.addView(tvLabel, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
tvSubLabel = new TextView(context);
tvSubLabel.setTextColor(Color.YELLOW);
tvSubLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
layout.addView(tvSubLabel, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
/**
* 对外提供是否显示子标签
* @param show
*/
public void showSubLabel(boolean show){
tvSubLabel.setVisibility(show ? VISIBLE : GONE);
}
}
-
数据模型
JavaBean
。
我们给它命名DayItem
。
DayItem.java
public class DayItem {
private int key;//标识键(这个属性相当于一张数据表的主键字段,辅助你完成一些其他的操作)
private int background;//背景drawable
private String label;//主标签,也是效果图中展示的日期,如1、2、3、...、30
private int labelTextColor;//主标签字体颜色
private float labelTextSize;//主标签字体大小
private String subLabel;//副标签,也是效果图中展示的以下提示性的信息,如"可约"、"不可约"。这个我们可以看需要情况是否关掉,后面会提供开光属性。
private int subLabelTextColor;//副标签字体颜色
private float subLabelTextSize;//副标签字体大小
private long date;//既然是日期view,它就是用来存放时间的
/**
* 在构造函数里,我们初始化一些默认的东西
*/
public DayItem() {
setBackground(-1);
setLabelTextColor(0xFF333333);
setLabelTextSize(14);
setSubLabelTextColor(0xFF333333);
setSubLabelTextSize(8);
}
}
-
在
DayView
中展示数据模型。
private DayItem dayItem;
public void setDayItem(DayItem dayItem) {
this.dayItem = dayItem;
notifyDataChanged();
}
public DayItem getDayItem() {
return dayItem;
}
/**
* 你们也许感觉到这个方法定义的有点多余。其实不然,当你直接修改dayItem里面的属性时,提供一个快速刷新的入口!每个人的编码风格不一样,你可以根据自己的编码风格来书写,这只是我的想法!
*/
public void notifyDataChanged(){
if (dayItem == null)
return;
if (dayItem.getBackground() != -1)
setBackgroundResource(dayItem.getBackground());
tvLabel.setTextColor(dayItem.getLabelTextColor());
tvLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, dayItem.getLabelTextSize());
tvLabel.setText(TextUtils.isEmpty(dayItem.getLabel()) ? "" : dayItem.getLabel());
tvSubLabel.setTextColor(dayItem.getSubLabelTextColor());
tvSubLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, dayItem.getSubLabelTextSize());
tvSubLabel.setText(TextUtils.isEmpty(dayItem.getSubLabel()) ? "" : dayItem.getSubLabel());
}
-
创建
MonthView
。 - 创建
MothView
的所有的DayView
。共5 X 7 = 35
个DayView
。
MonthView.java
/**
* @author jsc
*/
public class MonthView extends LinearLayout {
private OnDayClickListener onDayClickListener;//日期view点击事件的监听
private OnDayLongClickListener onDayLongClickListener;//日期view长按事件的监听
private boolean showSubLabel = true;//控制是否显示副标签,默认显示
private int rowSpace = 40;//日期view的行间距
private int columnSpace = 20;//日期view的列间距
private View.OnClickListener clickListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (onDayClickListener != null)
onDayClickListener.onDayClick((DayView) v);
}
};
private View.OnLongClickListener longClickListener = new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return onDayLongClickListener != null && onDayLongClickListener.onDayLongClick((DayView) v);
}
};
public MonthView(Context context) {
this(context, null);
}
public MonthView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MonthView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
/**
* 创建所有的日期view。
*/
private void init(Context context) {
setOrientation(VERTICAL);
for (int i = 0; i < 5; i++) {
LinearLayout rowLayout = new LinearLayout(context);
rowLayout.setOrientation(HORIZONTAL);
rowLayout.setWeightSum(7);
rowLayout.setPadding(0, rowSpace / 2, 0, rowSpace / 2);
for (int j = 0; j < 7; j++) {
LayoutParams params = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
if (j == 0) {
params.rightMargin = columnSpace / 2;
} else if (j == 6) {
params.leftMargin = columnSpace / 2;
} else {
params.leftMargin = columnSpace / 2;
params.rightMargin = columnSpace / 2;
}
DayView dayView = new DayView(context);
rowLayout.addView(dayView, params);
dayViews.add(dayView);
dayView.setOnClickListener(clickListener);
dayView.setOnLongClickListener(longClickListener);
}
addView(rowLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
}
public void showSubLabel(boolean showSubLabel) {
this.showSubLabel = showSubLabel;
for (DayView v : dayViews) {
v.showSubLabel(showSubLabel);
}
}
public void setOnDayClickListener(OnDayClickListener onDayClickListener) {
this.onDayClickListener = onDayClickListener;
}
public void setOnDayLongClickListener(OnDayLongClickListener onDayLongClickListener) {
this.onDayLongClickListener = onDayLongClickListener;
}
public interface OnDayClickListener {
void onDayClick(DayView dayView);
}
public interface OnDayLongClickListener {
boolean onDayLongClick(DayView dayView);
}
}
- 创建
TitleView
。我们提供让外部调用人员可以添加自定义的TitleView
方法(你爱怎么显示就怎么显示,随你意,是不是很爽?
)。如果外部调用人员没有自行定义,我们也不能空着,给外部调用人员一个默认的TitleView
,在创建MonthView
的时候给它加上去。
/**
* 设置自定义title view
*
* @param customTitleView
*/
public void setCustomTitleView(View customTitleView) {
if (customTitleView == null)
customTitleView = getDefaultTitleView();
if (getChildCount() > 5)
removeViewAt(0);
addView(customTitleView, 0, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
private View getDefaultTitleView() {
String[] title = {"日", "一", "二", "三", "四", "五", "六"};
LinearLayout layout = new LinearLayout(getContext());
layout.setOrientation(HORIZONTAL);
layout.setWeightSum(7);
for (int i = 0; i < 7; i++) {
TextView textView = new TextView(layout.getContext());
textView.setTextColor(0xFF00BA86);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
textView.setGravity(Gravity.CENTER);
layout.addView(textView, new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1));
textView.setText(title[i]);
}
return layout;
}
- 在
init(Context context)
方法体末尾添加TitleView
。
setCustomTitleView(null);
- 提供设置数据的方法。
public void setDays(List<DayItem> days) {
if (days == null)
days = new ArrayList<>();
int len = days.size();
for (int i = 0; i < dayViews.size(); i++) {
DayView dayView = dayViews.get(i);
dayView.setVisibility(i < len ? VISIBLE : INVISIBLE);
if (i < len) {
dayView.setDayItem(days.get(i));
}
}
}
public void notifyItemChanged(int index) {
if (index < 0 || index > dayViews.size() - 1)
return;
dayViews.get(index).notifyDataChanged();
}
-
根据年份和月份计算该月的天数。
MonthUtils.java
/**
* @author jsc
*/
public class MonthUtils {
public static List<DayItem> getMonthDays(int year, int month) {
List<DayItem> dayItems = new ArrayList<>();
int maxShowCount = 5 * 7;
addCurrentMonthDays(dayItems, year, month);
addPreMonthDays(dayItems);
addNextMonthDays(dayItems, maxShowCount - dayItems.size());
return dayItems;
}
/**
* 添加本月所有天数
*
* @param dayItems
*/
private static void addCurrentMonthDays(List<DayItem> dayItems, int year, int month) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
int curMonthDayCount = getMonthDayCount(calendar);
for (int i = 0; i < curMonthDayCount; i++) {
calendar.set(Calendar.DAY_OF_MONTH, i + 1);
DayItem item = new DayItem();
item.setKey(0);
item.setDate(calendar.getTimeInMillis());
item.setLabel(String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)));
dayItems.add(item);
}
}
/**
* 添加上个月的最后几天
*
* @param dayItems
*/
private static void addPreMonthDays(List<DayItem> dayItems) {
DayItem firstItem = dayItems.get(0);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(firstItem.getDate());
int weekDay = calendar.get(Calendar.DAY_OF_WEEK);
if (weekDay > Calendar.SUNDAY) {
calendar.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH) - 1);
DayItem item = new DayItem();
item.setKey(-1);
item.setDate(calendar.getTimeInMillis());
item.setLabel(String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)));
dayItems.add(0, item);
addPreMonthDays(dayItems);
}
}
/**
* 添加下个月的前几天
*
* @param dayItems
*/
private static void addNextMonthDays(List<DayItem> dayItems, int count) {
if (count == 0)
return;
DayItem lastItem = dayItems.get(dayItems.size() - 1);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(lastItem.getDate());
for (int i = 0; i < count; i++) {
calendar.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH) + 1);
DayItem item = new DayItem();
item.setKey(1);
item.setDate(calendar.getTimeInMillis());
item.setLabel(String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)));
dayItems.add(item);
}
}
/**
* 获取某一个月的天数
*
* @param millis
* @return
*/
public static int getMonthDayCount(long millis) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
return getMonthDayCount(calendar);
}
/**
* 获取某一个月的天数
*
* @param date
* @return
*/
public static int getMonthDayCount(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return getMonthDayCount(calendar);
}
/**
* 获取某一个月的天数
*
* @param calendar
* @return
*/
public static int getMonthDayCount(Calendar calendar) {
if (calendar == null)
calendar = Calendar.getInstance();
return calendar.getActualMaximum(Calendar.DATE);
}
}
用法
MonthView monthView;
int year;
int month;
protected void initView() {
monthView.setOnDayClickListener(new MonthView.OnDayClickListener() {
@Override
public void onDayClick(DayView dayView) {
}
});
monthView.setOnDayLongClickListener(new MonthView.OnDayLongClickListener() {
@Override
public boolean onDayLongClick(DayView dayView) {
return false;
}
});
Calendar calendar = Calendar.getInstance();
year = calendar.get(Calendar.YEAR);
month = calendar.get(Calendar.MONTH) + 1;
tvYearMonth.setText(year + "-" + month);
initMonthView(year, month);
}
private void initMonthView(int year, int month){
List<DayItem> dayItems = MonthUtils.getMonthDays(year, month);
for (int i = 0; i < dayItems.size(); i++) {
boolean enable = new Random().nextBoolean();
DayItem item = dayItems.get(i);
switch (item.getKey()){
case -1://上个月的最后几天
case 1://下个月的前几天
item.setBackground(R.drawable.circle_gray_light_shape);
break;
case 0://本月的所有天数
item.setBackground(R.drawable.circle_theme_light_shape);
break;
}
item.setSubLabel(enable ? "可约" : "不可约");
item.setSubLabelTextColor(enable ? Color.YELLOW : 0xFF666666);
}
monthView.setDays(dayItems);
}
整个实现过程看下来,是不是很简单?。如果这个效果不是你想要的,简单修改源码就能达到你要的效果。
结尾
书山有路勤为径,学海无涯苦作舟。
您的支持就是我最大的动力!QQ:1006368252
。
网友评论