手把手教你最快撸一个日历控件

我们的态度是:每天进步一点点,理想终会被实现。

前言


可能很多人都会说,你还自己撸一个日历控件,GitHub有那么多好的开源项目,比如:material-calendarview(https://github.com/prolificinteractive/material-calendarview)4K多的star,而且人家的扩展性也很强,我干嘛要自己撸。我就是个不喜欢用别人的,想着别人能做出来的,自己干嘛不能做出来,再说要是后面的需求越改越多,要是满足不了了,那我们该怎么办?这时就需要我们自己撸了。

还有种情况就是,当我们在赶项目的时候,可能项目初期只需要一个基本的日历控件,其他的暂时用不上,需要我们快速的撸一个。这个时候就派上用场了,教你最快的撸一个日历控件。

看看我们几分钟实现的效果:

手把手教你最快撸一个日历控件_第1张图片

Calender日历控件


分析

自定义View的实现方式一般就三种:

  • 继承系统控件
  • 组合系统控件
  • 自定义绘制控件

具体这三种方式的详细我就不讲了,不清楚的小伙伴可以查看:

HenCoder Android 自定义 View 1-6: 属性动画(上手篇)(https://juejin.im/post/59af4b415188252427260c3d)
【HenCoder Android 开发进阶】自定义 View 1-7:属性动画(进阶篇)(https://juejin.im/post/59b5fe19f265da06710d8116)

既然我们讲的是快速构建一个日历控件,我们就不完全采用第三种方式绘制。我们主要采用组合系统控件。

分析需求

我们看下面这张图来分析:

手把手教你最快撸一个日历控件_第2张图片

从图中我们看出,我们将日历分为三个部分:

第一部分:左右箭头,年月显示
第二部分:星期几
第三部分:日历显示

这里我采用ConstrainLayout约束布局来讲这个布局显示出来,代码如下:

"1.0" encoding="utf-8"?>
.support.constraint.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   .support.v7.widget.AppCompatImageButton
       android:id="@+id/preMonth"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginStart="32dp"
       android:layout_marginTop="8dp"
       android:src="@drawable/ic_arrow_back_black_24dp"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>


   .support.v7.widget.AppCompatImageButton
       android:id="@+id/nextMonth"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginEnd="32dp"
       android:layout_marginTop="8dp"
       android:src="@drawable/ic_arrow_forward_black_24dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>

   .support.v7.widget.AppCompatTextView
       android:id="@+id/tv_date"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginBottom="8dp"
       android:layout_marginEnd="8dp"
       android:layout_marginLeft="8dp"
       android:layout_marginRight="8dp"
       android:layout_marginStart="8dp"
       android:layout_marginTop="8dp"
       android:text="2018年5月"
       app:layout_constraintBottom_toBottomOf="@+id/preMonth"
       app:layout_constraintEnd_toStartOf="@+id/nextMonth"
       app:layout_constraintStart_toEndOf="@+id/preMonth"
       app:layout_constraintTop_toTopOf="@+id/preMonth"/>

   .support.v7.widget.AppCompatTextView
       android:id="@+id/sunday"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"

       android:text="周天"
       android:textColor="@android:color/black"
       android:textSize="18sp"
       app:layout_constraintEnd_toStartOf="@+id/monday"
       app:layout_constraintHorizontal_bias="0.5"

       app:layout_constraintHorizontal_chainStyle="spread_inside"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/preMonth"/>

   .support.v7.widget.AppCompatTextView
       android:id="@+id/monday"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
       android:textColor="@android:color/black"
       android:textSize="18sp"
       android:text="周一"
       app:layout_constraintEnd_toStartOf="@+id/tuesday"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintHorizontal_chainStyle="spread"
       app:layout_constraintStart_toEndOf="@+id/sunday"
       app:layout_constraintTop_toBottomOf="@+id/preMonth"/>

   .support.v7.widget.AppCompatTextView
       android:id="@+id/tuesday"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
       android:textColor="@android:color/black"
       android:textSize="18sp"
       android:text="周二"
       app:layout_constraintEnd_toStartOf="@+id/wednesday"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintHorizontal_chainStyle="spread"
       app:layout_constraintStart_toEndOf="@+id/monday"
       app:layout_constraintTop_toBottomOf="@+id/preMonth"/>

   .support.v7.widget.AppCompatTextView
       android:id="@+id/wednesday"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
       android:textColor="@android:color/black"
       android:textSize="18sp"
       android:text="周三"
       app:layout_constraintEnd_toStartOf="@+id/thursday"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintHorizontal_chainStyle="spread"
       app:layout_constraintStart_toEndOf="@+id/tuesday"
       app:layout_constraintTop_toBottomOf="@+id/preMonth"/>

   .support.v7.widget.AppCompatTextView
       android:id="@+id/thursday"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textColor="@android:color/black"
       android:textSize="18sp"
       android:layout_marginTop="8dp"
       android:text="周四"
       app:layout_constraintEnd_toStartOf="@+id/friday"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintHorizontal_chainStyle="spread"
       app:layout_constraintStart_toEndOf="@+id/wednesday"
       app:layout_constraintTop_toBottomOf="@+id/nextMonth"/>

   .support.v7.widget.AppCompatTextView
       android:id="@+id/friday"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
       android:text="周五"
       android:textColor="@android:color/black"
       android:textSize="18sp"
       app:layout_constraintEnd_toStartOf="@+id/saturday"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintHorizontal_chainStyle="spread"

       app:layout_constraintStart_toEndOf="@+id/thursday"
       app:layout_constraintTop_toBottomOf="@+id/nextMonth"/>

   .support.v7.widget.AppCompatTextView
       android:id="@+id/saturday"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
       android:text="周六"
       android:textColor="@android:color/black"
       android:textSize="18sp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintHorizontal_chainStyle="spread"
       app:layout_constraintStart_toEndOf="@+id/friday"
       app:layout_constraintTop_toBottomOf="@+id/nextMonth"/>

   .support.constraint.Group
       android:id="@+id/group"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>

   .support.constraint.Group
       android:id="@+id/group2"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>

   .support.constraint.Barrier
       android:id="@+id/barrier2"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       app:barrierDirection="top"/>

   "@+id/gv_calendar"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_marginTop="16dp"
       android:numColumns="7"
       android:verticalSpacing="8dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/wednesday"/>

.support.constraint.ConstraintLayout>
   
     
     
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174

界面效果:

手把手教你最快撸一个日历控件_第3张图片

基本界面就已经搭建完成了。我们要简单快速的实现日历,我们的日历采用的GridView控件来显示,不熟悉GridView的老铁可以先搜索学习下。

处理业务逻辑


这里最核心的业务逻辑:

  • 计算当前月份的第一天星期几
  • 计算当前月份第一天前还要显示上个月几天
  • 计算要显示下个月几天

那么具体代码如下:

public class MyCalendar extends LinearLayout implements View.OnClickListener {

   private LayoutInflater mInflater;
   //上一个月、下一个月
   private AppCompatImageButton mPreMonth, mNextMonth;
   /**
    * 顶部年月
    */
   private AppCompatTextView mDate;
   /**
    * 日历列表
    */
   private GridView mGridView;


   private Calendar mCalendar;

   public MyCalendar(Context context) {
       super(context);
       init();
   }

   public MyCalendar(Context context, @Nullable AttributeSet attrs) {
       super(context, attrs);
       init();
   }

   public MyCalendar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
       super(context, attrs, defStyleAttr);
       init();
   }

   /**
    * 初始化
    */
   private void init() {
       initView();
       initListener();
       initCalenderCell();
   }

   private void initListener() {
       mNextMonth.setOnClickListener(this);
       mPreMonth.setOnClickListener(this);
   }

   private void initView() {
       mInflater = LayoutInflater.from(getContext());
       mInflater.inflate(R.layout.calender_view, this, true);
       mPreMonth = findViewById(R.id.preMonth);
       mNextMonth = findViewById(R.id.nextMonth);
       mDate = findViewById(R.id.tv_date);
       mGridView = findViewById(R.id.gv_calendar);

       mCalendar = Calendar.getInstance();
   }

   @Override
   public void onClick(View v) {
       switch (v.getId()) {
           //下月
           case R.id.nextMonth:
               //月份+1
               mCalendar.add(Calendar.MONTH, 1);
               initCalenderCell();
               break;
           //上月
           case R.id.preMonth:
               //月份-1
               mCalendar.add(Calendar.MONTH, -1);
               initCalenderCell();
               break;
           default:
               break;
       }
   }

   /**
    * 计算列表Cell的值
    */
   private void initCalenderCell() {
       //设置顶部年月
       SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月", Locale.getDefault());
       mDate.setText(sdf.format(mCalendar.getTime()));


       /**
        * 1.计算我们的cell的个数   7*6=42 任何一个月的天数都能包含在里面
        *
        * 2.计算这个当前月1号所在星期几,计算上个月显示的天数
        *
        * 3.将cell里面添加值
        */
       //日历每天的cell
       ArrayList cells = new ArrayList<>();
       //总的天数
       int count = 7 * 6;
       //克隆下calender
       Calendar calendar = (Calendar) mCalendar.clone();
       //将calender置于当月第一天
       calendar.set(Calendar.DAY_OF_MONTH, 1);
       //计算当月1号之前还有几天
       int preDays = calendar.get(Calendar.DAY_OF_WEEK) - 1;
       //将当前日期向前移动preDays
       calendar.add(Calendar.DAY_OF_MONTH, -preDays);
       //填满42个cells
       while (cells.size() < count) {
           //填充cell
           cells.add(calendar.getTime());
           //填充一次之后,向后移动一天
           calendar.add(Calendar.DAY_OF_MONTH, 1);
       }

       mGridView.setAdapter(new CalenderAdapter(getContext(), cells));
   }


   private class CalenderAdapter extends ArrayAdapter<Date> {
       private LayoutInflater mInflater;

       CalenderAdapter(@NonNull Context context, ArrayList dates) {
           super(context, R.layout.cell_layout, dates);
           mInflater = LayoutInflater.from(context);
       }

       @NonNull
       @Override
       public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
           final Date date = getItem(position);
           if (convertView == ) {
               convertView = mInflater.inflate(R.layout.cell_layout, parent, false);
           }
           if (date != ) {
               int day = date.getDate();
               ((TextView) convertView).setText(String.valueOf(day));
               convertView.setOnClickListener(new OnClickListener() {
                   @Override
                   public void onClick(View v) {
                       Toast.makeText(getContext(), new SimpleDateFormat("yyyy年MM月dd", Locale.getDefault()
                       ).format(date), Toast.LENGTH_SHORT).show();
                   }
               });
           }
           //获取当月日期
           Date now = new Date();

           boolean isSameMonth = false;
           //判断是否是本月
           if (date.getMonth() == now.getMonth()) {
               isSameMonth = true;
           }

           if (isSameMonth) {
               ((MyTextView) convertView).setTextColor(Color.BLACK);
           } else {
               ((MyTextView) convertView).setTextColor(Color.GRAY);
           }
           //判断是否是今日
           if (date.getDate() == now.getDate() && date.getMonth() == now.getMonth() && date.getYear() == now.getYear()) {
               ((MyTextView) convertView).isToday = true;
               ((MyTextView) convertView).setTextColor(Color.RED);
           }
           return convertView;
       }
   }
}
   
     
     
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166

细心的朋友可能看到了MyTextView,这个是自定义的TextView,为当天的日期画个红色的圈,代码如下:

public class MyTextView extends AppCompatTextView {
   private Paint mPaint;
   public boolean isToday = false;

   public MyTextView(Context context) {
       super(context);
       init();
   }

   public MyTextView(Context context, @Nullable AttributeSet attrs) {
       super(context, attrs);
       init();
   }

   public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
       super(context, attrs, defStyleAttr);
       init();
   }

   private void init() {
       mPaint = new Paint();
       mPaint.setColor(Color.RED);
       mPaint.setStyle(Paint.Style.STROKE);
       mPaint.setAntiAlias(true);
   }

   @Override
   protected void onDraw(Canvas canvas) {
       super.onDraw(canvas);
       if (isToday) {
           canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
       }
   }
}
   
     
     
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

最后使用就只需要的在XML中添加我们的自定义的MyCalendar即可:

手把手教你最快撸一个日历控件_第4张图片

这样就大功告成了,我们可以看到非常简单的自定义View显示日历就完成了,代码量很少,而且思路很清晰,很适合快速的开发。

这里还没有补充的就是那就是:比如箭头的图片、年月的显示格式颜色大小、星期几的文字颜色大小等这些都是默认,加入后期开发的时候要求可以改变,那么我们就在values文件下创建attrs的xml文件,配置相应的属性即可。

自己撸一个和用别人的东西还是不一样的感觉,至少我是这么认为的。
demo已经上传到github:https://github.com/lt13982250340/MyCalender

一句话总结

本文旨在快速的开发一个日历控件,比如GridView可以换成我们常用的recyclerview,这些都是后期再优化的细节。熟悉API或者流程的话,估计几分钟就可以撸一个日历控件。

在这个基础上再改造,就能得到你想要的效果,符合产品的需求。


温馨提示:
我创建了一个技术交流群,群里有各个行业的大佬都有,大家可以在群里畅聊技术方面内容,以及文章推荐;如果有想加入的伙伴加我微信号【luotaosc】备注一下“加群”
另外关注公众号,还有一些个人收藏的视频:
回复“Android” ,获取Android视频链接。
回复“Java” ,获取Java视频链接。
回复“C++” ,获取C++视频链接。
回复“C” ,获取C视频链接。
回复“Python” ,获取Python视频链接等等。

原创文章不易,如果觉得写得好,扫码关注一下点个赞,是我最大的动力。

关注我,一定会有意想不到的东西等你:
每天专注分享Android、JAVA干货

手把手教你最快撸一个日历控件_第5张图片

备注:程序圈LT

        
            

你可能感兴趣的:(手把手教你最快撸一个日历控件)