安卓开发博客Android之界面安卓xuexi

高自定义布局日历

2018-07-24  本文已影响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

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

上一篇下一篇

猜你喜欢

热点阅读