高自定义布局日历
最近得到通知需要做一个日历,还最好把他做成一个自定义的控件,方便其他界面的使用,没办法做呗,幸好这个要求不急,能有一天时间,但是身为程序员,肯定是想做半天休息半天的,所以最好两小时之内搞定吧,然后就只要玩了。
做日历嘛,肯定要看看日期怎么搞,之前我是纯自己算一个月的时间,bug多不说,一个列表中区分上个月、本月、下个月就让我头痛了,所以再找其他方向。经常翻阅javaAPI的人可能就有印象,java.utile包下有一个封装好了的计算日历的类---》Calendar.java,下面先简单介绍下它的基本使用:
1、获取当前年份
Calendar.getInstance().get(Calendar.YEAR);
2、获取当前月份
// Calendar在月份上的常数值从Calendar.JANUARY开始是0,到Calendar.DECEMBER的11
Calendar.getInstance().get(Calendar.MONTH) + 1;
3、获取当前的时间为该月的第几天
Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
4、获取当前的时间为该周的第几天
Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
5、获取当前时间为该天的多少点
Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
6、获取当前的分钟时间
Calendar.getInstance().get(Calendar.MINUTE);
学会上面的就能完成日历了,有人可能会问,像手机系统日历一样,一页日历中能显示上个月、本月、和下个月的,那你上面介绍的也没有啊。额,先别急,这个是一个简单计算,我们先完成日历的布局。
定义CalendarView,继承RelativeLayout,这样方便我们加载一个布局,然后再布局中写日历样式。
public class CalendarView extends RelativeLayout implements View.OnClickListener {
private RecyclerView mRlList;
private List<RiLiBean> mList =new ArrayList<>();
private RiLiAdapter mRiLiAdapter;
private int year;
private int month;
private int day;
private ImageView mIvLeft;
private ImageView mIvRight;
private TextView mTvRiLiTitle;
public CalendarView(Context context) {
this(context,null);
}
public CalendarView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CalendarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
View view = inflate(context, R.layout.dati_rili_layout, this);
mIvLeft = view.findViewById(R.id.iv_left);
mIvRight = view.findViewById(R.id.iv_right);
mIvLeft.setOnClickListener(this);
mIvRight.setOnClickListener(this);
mRlList = view.findViewById(R.id.rl_list);
mTvRiLiTitle = view.findViewById(R.id.tv_riqi_title);
mRlList.setLayoutManager(new GridLayoutManager(context,7));
mRiLiAdapter = new RiLiAdapter(mList,context);
mRlList.setAdapter(mRiLiAdapter);
}
@Override
public void onClick(View view) {
if(view.getId()==R.id.iv_left){
}else if(view.getId()==R.id.iv_right){
}
}
}
布局calendar_layout.xml代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/rl_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/dp_10">
<TextView
android:id="@+id/tv_riqi_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:gravity="center"
android:textColor="@android:color/black"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textSize="@dimen/sp_19"/>
<ImageView
android:id="@+id/iv_left"
android:layout_width="@dimen/dp_30"
android:layout_height="@dimen/dp_30"
android:layout_toLeftOf="@id/tv_riqi_title"
android:layout_marginRight="@dimen/dp_5"
android:padding="@dimen/dp_5"
android:src="@mipmap/calendar_left"/>
<ImageView
android:id="@+id/iv_right"
android:layout_width="@dimen/dp_30"
android:layout_height="@dimen/dp_30"
android:layout_toRightOf="@id/tv_riqi_title"
android:layout_marginLeft="@dimen/dp_5"
android:padding="@dimen/dp_5"
android:src="@mipmap/calendar_right"/>
</RelativeLayout>
<LinearLayout
android:id="@+id/ll_riqi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/rl_title"
android:layout_marginTop="@dimen/dp_10"
android:layout_marginBottom="@dimen/dp_15">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/sp_17"
android:textColor="@android:color/black"
android:gravity="center"
android:text="日"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/sp_17"
android:textColor="@android:color/black"
android:gravity="center"
android:text="一"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/sp_17"
android:textColor="@android:color/black"
android:gravity="center"
android:text="二"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/sp_17"
android:textColor="@android:color/black"
android:gravity="center"
android:text="三"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/sp_17"
android:textColor="@android:color/black"
android:gravity="center"
android:text="四"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/sp_17"
android:textColor="@android:color/black"
android:gravity="center"
android:text="五"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/sp_17"
android:textColor="@android:color/black"
android:gravity="center"
android:text="六"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rl_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:layout_below="@id/ll_riqi">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
运行后,我们就能看到这个样子了
微信图片_20180724135820.png
下面日期的显示,我是用了RecyclerView,有些大神直接用代码画的但是那样的话,点击就会比较难,而且不容易让其他人懂,我们写一个代码尽量尊重知识最少原则。
获取日历每月的数据DateUtil
public class DateUtil {
/**
* 获取当前年份
*
* @return
*/
public static int getYear() {
return Calendar.getInstance().get(Calendar.YEAR);
}
/**
* 获取当前月份
*
* @return
*/
public static int getMonth() {
return Calendar.getInstance().get(Calendar.MONTH) + 1;// +1是因为返回来的值并不是代表月份,而是对应于Calendar.MAY常数的值,
// Calendar在月份上的常数值从Calendar.JANUARY开始是0,到Calendar.DECEMBER的11
}
/**
* 获取当前的时间为该月的第几天
*
* @return
*/
public static int getCurrentMonthDay() {
return Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
}
/**
* 获取当前的时间为该周的第几天
*
* @return
*/
public static int getWeekDay() {
return Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
}
/**
* 获取当前时间为该天的多少点
*
* @return
*/
public static int getHour() {
return Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
// Calendar calendar = Calendar.getInstance();
// System.out.println(calendar.get(Calendar.HOUR_OF_DAY)); // 24小时制
// System.out.println(calendar.get(Calendar.HOUR)); // 12小时制
}
/**
* 获取当前的分钟时间
*
* @return
*/
public static int getMinute() {
return Calendar.getInstance().get(Calendar.MINUTE);
}
/**
* 通过获得年份和月份确定该月的日期分布
*
* @param year
* @param month
* @return
*/
public static List<RiLiBean> getMonthNumFromDates(int year, int month) {
List<RiLiBean> list=new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.set(year, month - 1, 1);// -1是因为赋的值并不是代表月份,而是对应于Calendar.MAY常数的值,
int days[][] = new int[6][7];// 存储该月的日期分布
int firstDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);// 获得该月的第一天位于周几(需要注意的是,一周的第一天为周日,值为1)
int monthDaysNum = getMonthDaysNum(year, month);// 获得该月的天数
// 获得上个月的天数
int lastMonthDaysNum = getLastMonthDaysNum(year, month);
// 填充本月的日期
int dayNum = 1;
int lastDayNum = 1;
for (int i = 0; i < days.length; i++) {
for (int j = 0; j < days[i].length; j++) {
if (i == 0 && j < firstDayOfWeek - 1) {
// 填充上个月的剩余部分
RiLiBean riLiBean=new RiLiBean();
riLiBean.setType(riLiBean.LAST_MONTH);
riLiBean.setRiqi(lastMonthDaysNum - firstDayOfWeek + 2 + j+"");
list.add(riLiBean);
} else if (dayNum <= monthDaysNum) {// 填充本月
RiLiBean riLiBean=new RiLiBean();
riLiBean.setType(riLiBean.BENYUE);
riLiBean.setRiqi(dayNum+"");
list.add(riLiBean);
dayNum=dayNum+1;
} else {// 填充下个月的未来部分
RiLiBean riLiBean=new RiLiBean();
riLiBean.setType(riLiBean.NEXTMONTH);
riLiBean.setRiqi(lastDayNum+"");
list.add(riLiBean);
lastDayNum=lastDayNum + 1;
}
}
}
return list;
}
/**
* 根据年数以及月份数获得上个月的天数
*
* @param year
* @param month
* @return
*/
public static int getLastMonthDaysNum(int year, int month) {
int lastMonthDaysNum = 0;
if (month == 1) {
lastMonthDaysNum = getMonthDaysNum(year - 1, 12);
} else {
lastMonthDaysNum = getMonthDaysNum(year, month - 1);
}
return lastMonthDaysNum;
}
/**
* 根据年数以及月份数获得该月的天数
*
* @param year
* @param month
* @return 若返回为负一,这说明输入的年数和月数不符合规格
*/
public static int getMonthDaysNum(int year, int month) {
if (year < 0 || month <= 0 || month > 12) {// 对于年份与月份进行简单判断
return -1;
}
int[] array = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };// 一年中,每个月份的天数
if (month != 2) {
return array[month - 1];
} else {
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {// 闰年判断
return 29;
} else {
return 28;
}
}
}
}
RiLiBean 代码:
public class RiLiBean {
//上一个月
public final int LAST_MONTH=0;
//本月
public final int BENYUE=1;
//下一个月
public final int NEXTMONTH=2;
//日期
private String riqi;
//类型,他是用来记录这条数据是本月,还是其它月份的
private int type;
public String getRiqi() {
return riqi;
}
public void setRiqi(String riqi) {
this.riqi = riqi;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
接下来处理RecyclerView,先获取数据获取后更新Adapter
public class CalendarView extends RelativeLayout implements View.OnClickListener {
private RecyclerView mRlList;
private List<RiLiBean> mList =new ArrayList<>();
private RiLiAdapter mRiLiAdapter;
private int year;
private int month;
private int day;
private ImageView mIvLeft;
private ImageView mIvRight;
private TextView mTvRiLiTitle;
public CalendarView(Context context) {
this(context,null);
}
public CalendarView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CalendarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
View view = inflate(context, R.layout.calendar_layout, this);
mIvLeft = view.findViewById(R.id.iv_left);
mIvRight = view.findViewById(R.id.iv_right);
mIvLeft.setOnClickListener(this);
mIvRight.setOnClickListener(this);
mRlList = view.findViewById(R.id.rl_list);
mTvRiLiTitle = view.findViewById(R.id.tv_riqi_title);
mRlList.setLayoutManager(new GridLayoutManager(context,7));
mRiLiAdapter = new RiLiAdapter(mList,context);
mRlList.setAdapter(mRiLiAdapter);
init();
setDate();
}
private void init() {
year = DateUtil.getYear();
month = DateUtil.getMonth();
day = DateUtil.getCurrentMonthDay();
}
public void setDate(){
mList = DateUtil.getMonthNumFromDates(year,month);
String date = year + "年" + month + "月";
mTvRiLiTitle.setText(date);
updateList();
}
public void updateList() {
mRiLiAdapter.uploadAdapter(mList);
}
@Override
public void onClick(View view) {
if(view.getId()==R.id.iv_left){
onPreChoosed();
}else if(view.getId()==R.id.iv_right){
onNextChoosed();
}
}
/**
* 上个月
*/
public void onPreChoosed(){
if (month == 1){
year = year-1;
month = 12;
}else {
month = month -1;
}
day = 1;
setDate();
}
/**
* 下个月
*/
public void onNextChoosed(){
if (month == 12){
month = 1;
year = year+1;
}else {
month = month+1;
}
day = 1;
setDate();
}
}
接下来上Adapter,有时候用惯了封装的Adapter还不会写最基本的Adapter使用了,真搞笑。RiLiAdapter
public class RiLiAdapter extends RecyclerView.Adapter<RiLiAdapter.ViewHolder>{
private List<RiLiBean> mList;
private Context mContext;
public RiLiAdapter(List<RiLiBean> list, Context context){
mList=list;
mContext=context;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.rili_item,parent,false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
RiLiBean bean = mList.get(position);
if(bean.getType()==bean.LAST_MONTH||bean.getType()==bean.NEXTMONTH){
holder.mTvRiZi.setTextColor(mContext.getResources().getColor(R.color.riliItemTextColor));
}else{
holder.mTvRiZi.setTextColor(mContext.getResources().getColor(R.color.riliItemDefoultColor));
}
holder.mTvRiZi.setText(mList.get(position).getRiqi());
}
@Override
public int getItemCount() {
return mList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
private final TextView mTvRiZi;
public ViewHolder(View itemView) {
super(itemView);
mTvRiZi = itemView.findViewById(R.id.tv_rizi);
}
}
public void uploadAdapter(List<RiLiBean> list){
this.mList= list;
notifyDataSetChanged();
}
}
日历列表条目布局rili_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center">
<TextView
android:id="@+id/tv_rizi"
android:layout_width="@dimen/dp_30"
android:layout_height="@dimen/dp_30"
android:text="22"
android:gravity="center"
android:textSize="@dimen/sp_15"
android:background="@drawable/rili_item_defourte_bj_shape"
android:textColor="@android:color/black"
android:layout_marginBottom="@dimen/dp_15"/>
</LinearLayout>
然后就是使用这个自定义view了,在Activity的布局中加入
<com.easyar.icbcnotice.view.rili.CalendarView
android:id="@+id/calendarview"
android:layout_width="@dimen/dp_270"
android:layout_height="@dimen/dp_300"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/dp_170">
</com.easyar.icbcnotice.view.rili.CalendarView>
直接运行就能看到了。
微信图片_20180724141717.jpg个人认为这样写是一个良好的架子,比如我这个日历要做成打卡日历,那么我就把条目加个下划线或圆形背景就能表示打卡了,然后获取天数的时候在javabean里面做手脚,用一个变量判断有没有打卡,在Adapter设置条目数据时根据这个设置不通ui就行。比如下图:
微信图片_20180724142312.jpg
至于自定义属性,可以自己定义,比如标题文字大小,左右选择图片大小都能扩展。