高自定义布局日历

作者: 坑逼的严 | 来源:发表于2018-07-24 14:32 被阅读154次

    最近得到通知需要做一个日历,还最好把他做成一个自定义的控件,方便其他界面的使用,没办法做呗,幸好这个要求不急,能有一天时间,但是身为程序员,肯定是想做半天休息半天的,所以最好两小时之内搞定吧,然后就只要玩了。

    做日历嘛,肯定要看看日期怎么搞,之前我是纯自己算一个月的时间,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

    至于自定义属性,可以自己定义,比如标题文字大小,左右选择图片大小都能扩展。

    相关文章

      网友评论

      本文标题:高自定义布局日历

      本文链接:https://www.haomeiwen.com/subject/hcpemftx.html