Android 日历 MonthView

作者: SwitchLife | 来源:发表于2018-01-26 20:29 被阅读60次

    本篇给童鞋们写一个自定义的MonthView。

    前言

    本篇给童鞋们自定义一个展示日历某一个月份的日期View。对于这类相对不太复杂的自定义View,我们尽量避免使用xml布局文件,这样有利于提高我们的code性能。如果你使用的是kotlin语法,自行翻译,Android Studio本身自带转换工具。

    效果图

    大家看到效果图之后第一个想到的就是利用GridViewRecyclerview来实现。其实不用那么麻烦,直接代码创建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 = 35DayView
    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

    相关文章

      网友评论

        本文标题:Android 日历 MonthView

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