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