今天要來講述的是Android 中 Calendar View (日曆元件)

日曆元件這個東西,也不知道該算常出現呢...還是算不常XD

沒辦法...因為平常在使用其他的APP時也很少看到麻(´・д・`)

那我為何會講?因為這也是工作上不小心用到了麻(・∀・)

 

Calendar類其實Google是有內建給你的

但是我當時在寫開發的時候,也不曉得是沒研究透徹還是怎樣,一直都找不到自己想要的功能

而我要的功能簡單來說就是在相應的日期上面標註個Icon而已,很常見麻

1_j7nCIElmXJ3KwgdaBBRvOg

 

...黑啦,有這麼簡單就好了(茶🍵...)

所以我在這邊是使用了一個Calencar的第三方開源庫完成的

雖然啦,現在回頭再來做事覺得阿不就這樣而已

不過在當下還是會覺得OOXX

 

好滴,接著就要進入主題了

首先是今天的功能

Gif_20200725141437701_by_gifguru

 

以及GitHub

->https://github.com/thumbb13555/CalenderViewDemo

 


 

1.載入需要的開源庫

前面本次的內容我並非使用原生的Calendar完成

那麼就來提供庫的地址吧

https://github.com/Applandeo/Material-Calendar-View

 

Google搜尋的話搜尋Material-Calendar-View就可以找到摟

那請在目錄下的Gradle Scripts->build.gradle(Module: app)->dependencies加入這行

implementation 'com.applandeo:material-calendar-view:1.7.0'

 

截圖 2020-07-25 下午3.07.29

 

截圖 2020-07-25 下午3.08.00

 


 

2.寫介面

這個開源庫的Readme中其實有蠻詳盡的說明,甚至連範例都幫你寫好了( ゚Д゚)b

關於在xml檔案中的設定在這邊,我後面會說,但也可以先自己參考

->https://github.com/Applandeo/Material-Calendar-View#colors-customization

 

PO我今天的範例xml擋案吧

activity_main.xml

截圖 2020-07-25 下午3.17.53

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button_GetTheDay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="取得日期"
        app:layout_constraintEnd_toStartOf="@+id/button_Today"
        app:layout_constraintTop_toBottomOf="@+id/calendarView" />

    <com.applandeo.materialcalendarview.CalendarView
        android:id="@+id/calendarView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:type="one_day_picker"
        app:headerColor="#FF9800"
        app:todayLabelColor="#FF9800"
        app:selectionLabelColor="#FFFFFF"
        app:selectionColor="#FF9800"
        app:eventsEnabled="true"
        />

    <Button
        android:id="@+id/button_SetTarget"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:text="設置標記"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/button_GetTheDay" />

    <Button
        android:id="@+id/button_CancelTarget"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="解除標記"
        app:layout_constraintStart_toEndOf="@+id/button_SetTarget"
        app:layout_constraintTop_toTopOf="@+id/button_GetTheDay" />

    <Button
        android:id="@+id/button_Today"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:text="回到今日"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/button_GetTheDay" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

在xml內可以先行設定calendar顯示設定

app:type="one_day_picker"
app:headerColor="#FF9800"
app:todayLabelColor="#FF9800"
app:selectionLabelColor="#FFFFFF"
app:selectionColor="#FF9800"
app:eventsEnabled="true"

 

...簡單英文還看得懂吧?我就不詳細講囉XD

 

注意的是,type的部分共有

  • app:type="one_day_picker"
  • app:type="many_days_picker"
  • app:type="range_picker"

三種,其他的設定上方給的連結也都有喔,去翻閱一下吧XD

 

這時候按下執行~~

 

 

 

 

 

 

 

啊,閃退惹(╯=▃=)╯︵┻━┻

淦(・∀・)凸

 

原因下面會講,先進到下一章吧

 


 

3.撰寫主要內容

這邊開始講述我每個按鈕背後所做的事情

我會把每個程式拆開來講

那如果不想看我廢話的朋友

直接拉到最下面複製吧!我會貼完整程式

3-1 修正閃退

到這邊大家就要翻一下Log日誌了

在日誌中應該有這個關鍵字

 java.lang.RuntimeException: Unable to start activity ComponentInfo

 

這個原因是在於,這個開源庫的Calendar View本身呈現上就是個耗時的工作執行緒

如果將之放在主執行緒的onCreate中,就會造成閃退。

 

那要怎麼處理?一個比較直覺的作法就是不要放在主執行緒就好啦XDDDDDDD

所以我就乾脆這樣寫了(白眼

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ProgressDialog dialog = ProgressDialog.show(this, "", "請稍候");
    new Thread(() -> {
        /**由於此開源庫的Calender為耗時工作,故加入背景執行使載入介面時不會閃退*/
        runOnUiThread(() -> {
            setContentView(R.layout.activity_main);
            dialog.dismiss();
           
        });
    }).start();
}

 

再來就是按鈕的四個功能了,簡單描述一下

Gif_20200725141437701_by_gifguru

 

由左至右分別是"設置標記"、"刪除標記"、"取得(當下所選的)日期"、"回到今日"

這四項功能

 

設置標記就是在選到的日期上加入Saved的icon

刪除標記就是上面那個的反向操作

取得日期是指點下後會在Toast上顯示該選擇到的日期

回到今日就是跳回今天的日期

 

大致就是這樣...一項一項講吧!從取得日期開始

 

3-2 取得日期

先來看看這功能怎麼寫的

/**取得選中之日期*/
btGetDay.setOnClickListener(v -> {
    /**利用forEach迴圈找出指定元素*/
    for (Calendar calendar : calendarView.getSelectedDates()) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Toast.makeText(this, sdf.format(calendar.getTime()), Toast.LENGTH_SHORT).show();
    }
});

 

Calendar View 的呈現原理實際上就是陣列+RecyclerView+ViewPager的應用

故要找出相應的日期的話,就是用從Calendar中撈出所需要的那個“它”

於是我們便使用Foreach迴圈,就可以找出該值

然後再加上SimpleDateFormat就可以顯示出希望的格式了

 

3-3 回到(跳回)今日

一樣先看程式

/**跳至今日(指定)日期*/
btToday.setOnClickListener(v -> {
    /**取得今日之Date*/
    Date date = new Date();
    Calendar calendar = Calendar.getInstance();
    SimpleDateFormat sdfY = new SimpleDateFormat("yyyy");
    SimpleDateFormat sdfM = new SimpleDateFormat("MM");
    SimpleDateFormat sdfD = new SimpleDateFormat("dd");
    /**calender設置為今日*/
    calendar.set(Integer.parseInt(sdfY.format(date))
            , Integer.parseInt(sdfM.format(date)) - 1
            , Integer.parseInt(sdfD.format(date)));
    try {
        /**刷新介面*/
        calendarView.setDate(calendar);
    } catch (OutOfDateRangeException e) {
        e.printStackTrace();
    }
});

 

關鍵是

calendar.set(Integer.parseInt(sdfY.format(date)) , Integer.parseInt(sdfM.format(date)) - 1 , Integer.parseInt(sdfD.format(date)));

這一行

 

這行他的功能就是將整個介面跳回指定日期(包還指定跳回今日)

那要如何跳到指定的時間呢?答案就在Date()的部分

只要改變Date()設定的時間,就可以跳到不一樣的時過囉

 

再來是設置Icon的部分

 

3-4 設置標記

在開始前,先去找出存擋的這個Icon吧

這個Icon是Android studio 送你的

新增:

drawable->New->Vector Asset

截圖 2020-07-25 下午4.26.35

 

點擊Clip Art

截圖 2020-07-25 下午4.27.38

截圖 2020-07-25 下午4.27.21

 

最後按回OK就可以囉(´∀`)

 

來看程式吧

List<EventDay> event = new ArrayList<>();
/**設置標記*/
btSetTarget.setOnClickListener(v -> {
    new Thread(() -> {
        /**利用forEach迴圈找出指定元素*/
        for (Calendar calendar : calendarView.getSelectedDates()) {
            /**取得選定日之Date*/
            calendar.setTime(calendar.getTime());
            /**在event陣列中新增一個元素*/
            event.add(new EventDay(calendar, R.drawable.ic_baseline_save_24));
            runOnUiThread(() -> {
                /**刷新介面*/
                calendarView.setEvents(event);
            });
        }
    }).start();
});

 

其實也是一樣的套路

1. 用Foreach找出日期

2. 選定指定日期的時間

3. Event陣列中新增一個元素

 

就是這樣而已

那重點是這行

event.add(new EventDay(calendar, R.drawable.ic_baseline_save_24));

EventDay是一個實體類,把他加入event陣列而已,就是這樣(・ωー)~☆

 

3-5 刪除標記

看程式

/**解除標記*/
btClearTarget.setOnClickListener(v -> {
    new Thread(() -> {
        /**利用forEach迴圈找出指定元素*/
        for (Calendar calendar : calendarView.getSelectedDates()) {
            /**取得選定日之Date*/
            calendar.setTime(calendar.getTime());
            /**利用for迴圈找出指定元素之index*/
            for (int i = 0; i < event.size(); i++) {
                Long select = calendar.getTimeInMillis();
                Long target = event.get(i).getCalendar().getTimeInMillis();
                if (select.equals(target)){
                    /**刪除指定元素*/
                    event.remove(i);
                    runOnUiThread(() -> {
                        /**刷新介面*/
                        calendarView.setEvents(event);
                    });
                }
            }
        }
    }).start();
});

 

這裡的重點除了跟上面的套路一樣之外,重點是刪除指定元素

所以在第三個註解中,我寫了個for迴圈找出相應的index

然後套入event的陣列,並刪除之

 

好啦最後就是完整程式啦

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ProgressDialog dialog = ProgressDialog.show(this, "", "請稍候");
        new Thread(() -> {
            /**由於此開源庫的Calender為耗時工作,故加入背景執行使載入介面時不會閃退*/
            runOnUiThread(() -> {
                setContentView(R.layout.activity_main);
                dialog.dismiss();
                setView();
            });
        }).start();
    }

    private void setView() {
        Button btSetTarget, btClearTarget, btToday, btGetDay;
        CalendarView calendarView = findViewById(R.id.calendarView);
        btSetTarget = findViewById(R.id.button_SetTarget);
        btClearTarget = findViewById(R.id.button_CancelTarget);
        btToday = findViewById(R.id.button_Today);
        btGetDay = findViewById(R.id.button_GetTheDay);

        List<EventDay> event = new ArrayList<>();
        /**設置標記*/
        btSetTarget.setOnClickListener(v -> {
            new Thread(() -> {
                /**利用forEach迴圈找出指定元素*/
                for (Calendar calendar : calendarView.getSelectedDates()) {
                    /**取得選定日之Date*/
                    calendar.setTime(calendar.getTime());
                    /**在event陣列中新增一個元素*/
                    event.add(new EventDay(calendar, R.drawable.ic_baseline_save_24));
                    runOnUiThread(() -> {
                        /**刷新介面*/
                        calendarView.setEvents(event);
                    });
                }
            }).start();
        });

        /**解除標記*/
        btClearTarget.setOnClickListener(v -> {
            new Thread(() -> {
                /**利用forEach迴圈找出指定元素*/
                for (Calendar calendar : calendarView.getSelectedDates()) {
                    /**取得選定日之Date*/
                    calendar.setTime(calendar.getTime());
                    /**利用for迴圈找出指定元素之index*/
                    for (int i = 0; i < event.size(); i++) {
                        Long select = calendar.getTimeInMillis();
                        Long target = event.get(i).getCalendar().getTimeInMillis();
                        if (select.equals(target)){
                            /**刪除指定元素*/
                            event.remove(i);
                            runOnUiThread(() -> {
                                /**刷新介面*/
                                calendarView.setEvents(event);
                            });
                        }
                    }
                }
            }).start();
        });

        /**取得選中之日期*/
        btGetDay.setOnClickListener(v -> {
            /**利用forEach迴圈找出指定元素*/
            for (Calendar calendar : calendarView.getSelectedDates()) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
                Toast.makeText(this, sdf.format(calendar.getTime()), Toast.LENGTH_SHORT).show();
            }
        });

        /**跳至今日(指定)日期*/
        btToday.setOnClickListener(v -> {
            /**取得今日之Date*/
            Date date = new Date();
            Calendar calendar = Calendar.getInstance();
            SimpleDateFormat sdfY = new SimpleDateFormat("yyyy");
            SimpleDateFormat sdfM = new SimpleDateFormat("MM");
            SimpleDateFormat sdfD = new SimpleDateFormat("dd");
            /**calender設置為今日*/
            calendar.set(Integer.parseInt(sdfY.format(date))
                    , Integer.parseInt(sdfM.format(date)) - 1
                    , Integer.parseInt(sdfD.format(date)));
            try {
                /**刷新介面*/
                calendarView.setDate(calendar);
            } catch (OutOfDateRangeException e) {
                e.printStackTrace();
            }
        });
    }
}

 


 

結語

本質上,用了第三方庫感覺就輸了

 

也沒有啦,第三方庫就是給人類帶來方便的東西,是人類偉大的發明(笑)

如果有做出來的朋友,在運行的時候應該多少會發現

每次在進行操作的時候,多多少少都會畫面頓一下

這姑且算是這個庫的問題,因為上面也說過了

Calendar View的本質就是用陣列+RecyclerView+ViewPager組成的

在遍歷的過程中會拖到速度也算是正常現象

了不起就加個Therad就可以解決啦XDDDDD

 

好啦,那文章到這邊

thank-you-lebron-memes

arrow
arrow

    碼農日常 發表在 痞客邦 留言(2) 人氣()