天天看點

月曆簽到demo 實作記錄

先來效果圖:

月曆簽到demo 實作記錄
月曆簽到demo 實作記錄

月曆部分參考了網絡上找到的一個月曆類,裡面抽象出來了好多方法,基本上滿足了對簽到的需求,如有需要也可以根據項目實際情況進行定制。

public class SignCalendar extends ViewFlipper implements  android.view.GestureDetector.OnGestureListener

該類繼承自ViewFlipper,能夠很好的實作不同月的月曆之間的切換和動畫,還實作了手勢監聽。

首先介紹一下該類的一些主要方法:

 private void drawFrame(LinearLayout oneCalendar);//畫月曆的架構

 private void setCalendarDate();//填充月曆,包括日期、背景和标記

public synchronized void nextMonth() ;//滑動時顯示下一個月

 public synchronized void lastMonth() ;//滑動時顯示上一個月

//設定和移除日期标記的各種方法

 public void addMark(Date date, int id);public void removeMark(Date date);

public void addMark(String date, int id) ; public void removeMark(String date);

public void addMarks(Date[] date, int id);public void removeAllMarks();

public void addMarks(List<String> date, int id);

//設定和移除日期背景的方法

public void setCalendarDayBgColor(Date date, int color); public void removeCalendarDayBgColor(Date date);

 public void setCalendarDayBgColor(String date, int color); public void removeCalendarDayBgColor(String date);

public void setCalendarDaysBgColor(List<String> date, int color);public void removeAllBgColor();

 public void setCalendarDayBgColor(String[] date, int color);

public boolean hasMarked(String date);

//移除标記和背景設定

 public void clearAll();

此外還提供了兩個回調接口:

  private OnCalendarClickListener onCalendarClickListener; // 月曆翻頁回調

  private OnCalendarDateChangedListener onCalendarDateChangedListener; // 月曆點選回調,根據需要添加

接下來,介紹一下月曆架構的繪制,整個布局是在代碼裡實作的,先看下drawFrame方法吧:

private void drawFrame(LinearLayout oneCalendar) {

    // 添加周末線性布局
    LinearLayout title = new LinearLayout(getContext());
    title.setBackgroundColor(COLOR_BG_WEEK_TITLE);
    title.setOrientation(LinearLayout.HORIZONTAL);
    LinearLayout.LayoutParams layout =
        new LinearLayout.LayoutParams(MarginLayoutParams.MATCH_PARENT,
            MarginLayoutParams.WRAP_CONTENT, 0.5f);
    title.setLayoutParams(layout);
    oneCalendar.addView(title);

    // 添加周末TextView
    for (int i = 0; i < COLS_TOTAL; i++) {
      TextView view = new TextView(getContext());
      view.setGravity(Gravity.CENTER);
      view.setPadding(0, 10, 0, 10);
      view.setText(weekday[i]);
      view.setTextColor(Color.WHITE);
      view.setLayoutParams(new LinearLayout.LayoutParams(0, -1, 1));
      title.addView(view);
    }

    // 添加日期布局
    LinearLayout content = new LinearLayout(getContext());
    content.setOrientation(LinearLayout.VERTICAL);
    content.setLayoutParams(new LinearLayout.LayoutParams(-1, 0, 7f));
    oneCalendar.addView(content);

    // 添加日期TextView
    for (int i = 0; i < ROWS_TOTAL; i++) {
      LinearLayout row = new LinearLayout(getContext());
      row.setOrientation(LinearLayout.HORIZONTAL);
      row.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1));
      content.addView(row);
      // 繪制月曆上的列
      for (int j = 0; j < COLS_TOTAL; j++) {
        RelativeLayout col = new RelativeLayout(getContext());
        col.setLayoutParams(new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1));
        col.setBackgroundResource(R.drawable.calendar_day_bg);
        // col.setBackgroundResource(R.drawable.sign_dialog_day_bg);
        col.setClickable(false);
        row.addView(col);
        // 給每一個日子加上監聽
        col.setOnClickListener(new OnClickListener() {
          @Override
          public void onClick(View v) {
            ViewGroup parent = (ViewGroup) v.getParent();
            int row = 0, col = 0;

            // 擷取列坐标
            for (int i = 0; i < parent.getChildCount(); i++) {
              if (v.equals(parent.getChildAt(i))) {
                col = i;
                break;
              }
            }
            // 擷取行坐标
            ViewGroup pparent = (ViewGroup) parent.getParent();
            for (int i = 0; i < pparent.getChildCount(); i++) {
              if (parent.equals(pparent.getChildAt(i))) {
                row = i;
                break;
              }
            }
            if (onCalendarClickListener != null) {
              onCalendarClickListener.onCalendarClick(row, col, dates[row][col]);
            }
          }
        });
      }
    }
  }
           

    從代碼中可以看出,,先用水準方向的線性布局繪制了星期的布局,然後通過往裡面添加textView實作日期的顯示,這就實作了效果圖中星期的顯示。

下面就是繪制某月的月曆布局和添加日期的textview,還給日期增加了點選回調,可以根據需求使用。

日期的架構都有了,接下來就是添加文字、标記、和背景了:

/**
   * 填充月曆(包含日期、标記、背景等)
   */
  private void setCalendarDate() {
    // 根據月曆的日子擷取這一天是星期幾
    int weekday = calendarday.getDay();
    // 每個月第一天
    int firstDay = 1;
    // 每個月中間号,根據循環會自動++
    int day = firstDay;
    // 每個月的最後一天
    int lastDay = getDateNum(calendarday.getYear(), calendarday.getMonth());
    // 下個月第一天
    int nextMonthDay = 1;
    int lastMonthDay = 1;

    // 填充每一個空格
    for (int i = 0; i < ROWS_TOTAL; i++) {
      for (int j = 0; j < COLS_TOTAL; j++) {
        // 這個月第一天不是禮拜天,則需要繪制上個月的剩餘幾天
        if (i == 0 && j == 0 && weekday != 0) {
          int year = 0;
          int month = 0;
          int lastMonthDays = 0;
          // 如果這個月是1月,上一個月就是去年的12月
          if (calendarday.getMonth() == 0) {
            year = calendarday.getYear() - 1;
            month = Calendar.DECEMBER;
          } else {
            year = calendarday.getYear();
            month = calendarday.getMonth() - 1;
          }
          // 上個月的最後一天是幾号
          lastMonthDays = getDateNum(year, month);
          // 第一個格子展示的是幾号
          int firstShowDay = lastMonthDays - weekday + 1;
          // 上月
          for (int k = 0; k < weekday; k++) {
            lastMonthDay = firstShowDay + k;
            RelativeLayout group = getDateView(0, k);
            group.setGravity(Gravity.CENTER);
            TextView view = null;
            if (group.getChildCount() > 0) {
              view = (TextView) group.getChildAt(0);
            } else {
              LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, -1);
              view = new TextView(getContext());
              view.setLayoutParams(params);
              view.setGravity(Gravity.CENTER);
              group.addView(view);
            }
            view.setText(Integer.toString(lastMonthDay));
            view.setTextColor(COLOR_TX_OTHER_MONTH_DAY);
            dates[0][k] = format(new Date(year, month, lastMonthDay));
            // 設定日期背景色
            if (dayBgColorMap.get(dates[0][k]) != null) {
              view.setBackgroundResource(dayBgColorMap.get(dates[0][k]));
            } else {
              view.setBackgroundColor(Color.TRANSPARENT);
            }
            // 設定标記
            setMarker(group, 0, k);
          }
          j = weekday - 1;
          // 這個月第一天是禮拜天,不用繪制上個月的日期,直接繪制這個月的日期
        } else {
          RelativeLayout group = getDateView(i, j);
          group.setGravity(Gravity.CENTER);
          TextView view = null;
          if (group.getChildCount() > 0) {
            view = (TextView) group.getChildAt(0);
          } else {
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, -1);
            view = new TextView(getContext());
            view.setLayoutParams(params);
            view.setGravity(Gravity.CENTER);
            group.addView(view);
          }

          // 本月
          if (day <= lastDay) {
            dates[i][j] = format(new Date(calendarday.getYear(), calendarday.getMonth(), day));
            view.setText(Integer.toString(day));
            // 當天
            if (thisday.getDate() == day && thisday.getMonth() == calendarday.getMonth()
                && thisday.getYear() == calendarday.getYear()) {
              // view.setText("今天");
              view.setTextColor(COLOR_TX_THIS_MONTH_DAY);
              // view.setBackgroundResource(R.drawable.bg_sign_today);
            } else if (thisday.getMonth() == calendarday.getMonth()) {
              view.setTextColor(COLOR_TX_THIS_MONTH_DAY);
            } else {
              view.setTextColor(COLOR_TX_OTHER_MONTH_DAY);
            }
            // 上面首先設定了一下預設的"當天"背景色,當有特殊需求時,才給當日填充背景色
            // 設定日期背景色
            if (dayBgColorMap.get(dates[i][j]) != null) {
              // view.setTextColor(Color.WHITE);
              view.setBackgroundResource(dayBgColorMap.get(dates[i][j]));
            }
            // 設定标記
            setMarker(group, i, j);
            day++;
            // 下個月
          } else {
            if (calendarday.getMonth() == Calendar.DECEMBER) {
              dates[i][j] =
                  format(new Date(calendarday.getYear() + 1, Calendar.JANUARY, nextMonthDay));
            } else {
              dates[i][j] =
                  format(new Date(calendarday.getYear(), calendarday.getMonth() + 1, nextMonthDay));
            }
            view.setText(Integer.toString(nextMonthDay));
            view.setTextColor(COLOR_TX_OTHER_MONTH_DAY);
            // 設定日期背景色
            if (dayBgColorMap.get(dates[i][j]) != null) {
              // view.setBackgroundResource(dayBgColorMap
              // .get(dates[i][j]));
            } else {
              view.setBackgroundColor(Color.TRANSPARENT);
            }
            // 設定标記
            setMarker(group, i, j);
            nextMonthDay++;
          }
        }
      }
    }
  }
           

 thisday = new Date(); // 今天,calendarday; // 月曆這個月第一天(1号),有了這兩個概念,上面的代碼看起來就會一目了然,當然也可以在各種判斷語句中增加一些特定的需求,改成自己需要的樣式。接下來再簡單介紹一下setMarker方法:

// 設定标記
  private void setMarker(RelativeLayout group, int i, int j) {
    int childCount = group.getChildCount();
    if (marksMap.get(dates[i][j]) != null) {
      if (childCount < 2) {
        RelativeLayout.LayoutParams params =
            new RelativeLayout.LayoutParams((int) (tb * 0.7), (int) (tb * 0.7));
        params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
        params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        params.setMargins(0, 0, 1, 1);
        ImageView markView = new ImageView(getContext());
        markView.setImageResource(marksMap.get(dates[i][j]));
        markView.setLayoutParams(params);
        markView.setBackgroundResource(R.drawable.calendar_bg_tag);
        group.addView(markView);
      }
    } else {
      if (childCount > 1) {
        group.removeView(group.getChildAt(1));
      }
    }

  }
           

其中marksMap = new HashMap<String, Integer>(); // 儲存某個日子被标注的資源id,dates = new String[6][7]; 為目前月曆日期,group為包裝每個日子的布局;childCount = group.getChildCount(),為該布局的子view個數;首先從marksMap中查詢該日子是否需要标記,如果不需要,且該布局已經有兩個子view,即已經被設定了背景和标記,就移除标記;如果需要設定,且childCount小于2,就說明該點還未設定标記,添加标記布局。

至此幾個主要的方法已經介紹完畢,接下來就是如何使用了,其使用也是相當友善:

<com.test.signcalendar.SignCalendar
            android:id="@+id/popupwindow_calendar"
            android:layout_width="match_parent"
            android:layout_height="320dp"
            android:clickable="false" >
        </com.test.signcalendar.SignCalendar>
           

在布局中聲明一下,然後再代碼中擷取SignCalendar的執行個體就可以進行相應的操作了,就不在此贅述,獻上demo代碼供大家學習交流。

點選擷取源碼值得學習的demo

繼續閱讀