RecyclerViewでカレンダーを自作する【Kotlin,Android】

Androidアプリ開発
記事内に広告が含まれています。

この記事ではKotlinでRecyclerViewを使ってシンプルなカレンダーを作る方法を詳しく解説します。

初心者の方でも理解しやすいように具体的な実装方法をサンプルコード付きで解説しているので、ぜひ参考にして実装してみてください。

完成イメージ

今回作るのはシンプルな機能のカレンダーです。

ボタンを押して前月・翌月のカレンダーに変えられるようにします。

実装

ここから実装方法を解説していきます。

先に全体のソースコードを確認したい方はこちらから読んでください。

レイアウト

activity_main.xml

activity_main.xmlでカレンダーのレイアウトを作成します。

ConstraintLayoutにLenearLayoutを追加してその中に、前月・翌月のカレンダーに変更するためのボタンと何月のカレンダーか表示するためのTextViewを配置しましょう。

TextViewにandroid:layout_weight=”1″android:gravity=”center”を記述することで両端にボタン、中央にTextViewという配置を作れます。

<LinearLayout
    android:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="12dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <Button
        android:id="@+id/previousButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:text="previous" />

    <TextView
        android:id="@+id/monthText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Dec 2023" />

    <Button
        android:id="@+id/nextButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="24dp"
        android:text="next" />

</LinearLayout>

先ほど追加したLinearLayoutの下に、さらにLinearLayoutを追加して曜日を表示するためのTextViewを配置しましょう。

TextViewのpaddingは位置を調整するために記述しています。文字のサイズなどに応じて調整してください。

<LinearLayout
    android:id="@+id/linearLayout2"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="12dp"
    app:layout_constraintTop_toBottomOf="@+id/linearLayout">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Sun" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Mon" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Tue" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Wed" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:paddingEnd="2dp"
        android:text="Thu" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:paddingEnd="6dp"
        android:text="Fri" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:paddingEnd="5dp"
        android:text="Sat" />

</LinearLayout>

LinearLayoutの下にRecyclerViewを配置します。

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toBottomOf="@+id/linearLayout2"
    tools:layout_editor_absoluteX="0dp" />

one_layout.xml

layoutにone_layout.xmlを作成しましょう。

one_layoutではカレンダーに表示する日付1つ分のレイアウトを作ります。

<?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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/day_layout">

    <TextView
        android:id="@+id/day_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1"
        android:layout_marginTop="10dp"
        android:textSize="12sp"
        android:layout_marginBottom="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

カレンダーを実装

ここからカレンダーを実装していきます。

RecyclerViewの実装方法についての詳しい解説はこちらの記事で書いています。
基本的なRecyclerViewの実装方法を知りたい方はこちらも併せてお読みください。

ViewHolderItem

ViewHolderItemという名前のファイルを追加します。

ViewHolderItemではRecyclerViewに日付1つ分を表示するための設定をしています。

import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class ViewHolderItem(v:View): RecyclerView.ViewHolder(v){
    var oneDayText:TextView = v.findViewById(R.id.day_text)
}

RecyclerviewAdapter

RecyclerviewAdapterという名前のファイルを追加しましょう。

RecyclerviewAdapterではファイル名の通りRecyclerViewのAdapterを定義します。

class RecyclerviewAdapter(daysInMonth: ArrayList<Long>) : RecyclerView.Adapter<ViewHolderItem>(){
    …
}

コンストラクタのdaysOfMonthはRecyclerViewに表示する日付をリスト化したものです。後ほどMainActivity内で作成します。

RecyclerviewAdapter内に以下のコードを追加しましょう。

private var daysOfMonth = daysInMonth

onCreateViewHolder内でセルのレイアウト(one_layout.xml)の読み込みとViewHolderItemとの紐づけをします。

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderItem {
    val oneXml = LayoutInflater.from(parent.context).inflate(R.layout.one_layout,parent,false)
    return ViewHolderItem(oneXml)
}

onBindViewHolderで日付をセルに表示するようにします。

override fun onBindViewHolder(holder: ViewHolderItem, position: Int) {
    if (daysOfMonth[position] == 0L){
        holder.oneDayText.text = ""
    }else{
        holder.oneDayText.text = daysOfMonth[position].toString()
    }
}

getItemCountではdaysOfMonthの要素の数をカウントします。

override fun getItemCount(): Int {
    return daysOfMonth.size
}

MainActivity

MainActivityではRecyclerViewのレイアウト指定やセルを表示するための設定、ボタンが押された時の挙動を指定などをします。

まずはMainActivity内に以下のlateinitを使ったvarを追加しましょう。

private lateinit var calendarRecyclerView:RecyclerView
private lateinit var monthYearText:TextView
private lateinit var selectedDate:LocalDate

initWidgetsという名前の関数を作成します。ここでは先ほど追加したvarにRecyclerViewとカレンダーの年月を表示するTextViewを代入しています。

private fun initWidgets() {
    calendarRecyclerView = findViewById(R.id.rv)
    monthYearText = findViewById(R.id.monthText)
}

onCreate内に以下のコードを追加しましょう。上2行では前月・翌月のカレンダーにするためのボタンを定義、下2行でcalendarRecyclerView・monthYearTextの定義とselectedDateへの現在の年月日の代入をしています。

val nextButton:Button = findViewById(R.id.nextButton)
val previousButton:Button = findViewById(R.id.previousButton)

initWidgets()
selectedDate = LocalDate.now()

新たに関数setMonthViewを追加しましょう。setMonthViewはRecyclerViewのレイアウトの指定などをしているRecyclerViewにセルを表示するための関数です。

前月・翌月のカレンダーに変更するボタンを押したときにもこの関数が呼び出されます。

private fun setMonthView() {
    monthYearText.text = monthYear(selectedDate)
    val daysInMonth = daysInMonthArray(selectedDate)
    val calendarAdapter = RecyclerviewAdapter(daysInMonth)
    val layoutManager: LayoutManager = GridLayoutManager(applicationContext, 7)
    calendarRecyclerView.layoutManager = layoutManager
    calendarRecyclerView.adapter = calendarAdapter
}

関数daysInMonthArrayを追加します。ここではselectedDateから日付を取得してリスト化しています。ここでできたリストdaysInMonthArrayをRecyclerViewに表示します。

private fun daysInMonthArray(date: LocalDate?): ArrayList<Long> {
    val daysInMonthArray = ArrayList<Long>()
    val yearMonth = YearMonth.from(date)
    val daysInMonth = yearMonth.lengthOfMonth()
    val firstOfMonth = selectedDate.withDayOfMonth(1)
    val dayOfWeek = firstOfMonth.dayOfWeek.value
    for (i in 1..42) {
        if (i <= dayOfWeek || i > daysInMonth + dayOfWeek) {
            daysInMonthArray.add(0)
        } else {
            daysInMonthArray.add((i - dayOfWeek).toLong())
        }
    }
    return daysInMonthArray
}

monthYearという名前の関数を追加します。この関数ではmonthYearTextに表示する年月を設定しています。DateTimeFormatter.ofPattern(“MMMM yyyy”)を記述することで、年月は「February 2024」のような表記になります。

private fun monthYear(date: LocalDate?): String {
    val formatter = DateTimeFormatter.ofPattern("MMMM yyyy")
    return date!!.format(formatter)
}

onCreate内に以下のコードを追加しましょう。ここでは前月のカレンダーにするボタン(previousButton)と翌月のカレンダーに変更するボタン(nextButton)が押された時の動きを設定します。

previousButton.setOnClickListener {
    initWidgets()
    selectedDate = selectedDate.minusMonths(1)
    setMonthView()
}

nextButton.setOnClickListener {
    initWidgets()
    selectedDate = selectedDate.plusMonths(1)
    setMonthView()
}

これでRecyclerViewで作るカレンダーの完成です。

サンプルコード

RecyclerviewAdapter

package com.example.sample

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class RecyclerviewAdapter(daysInMonth: ArrayList<Long>) : RecyclerView.Adapter<ViewHolderItem>(){

    private var daysOfMonth = daysInMonth

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderItem {
        val oneXml = LayoutInflater.from(parent.context).inflate(R.layout.one_layout,parent,false)
        return ViewHolderItem(oneXml)
    }

    override fun onBindViewHolder(holder: ViewHolderItem, position: Int) {
        if (daysOfMonth[position] == 0L){
            holder.oneDayText.text = ""
        }else{
            holder.oneDayText.text = daysOfMonth[position].toString()
        }
    }

    override fun getItemCount(): Int {
        return daysOfMonth.size
    }
}

ViewHolderItem

package com.example.sample

import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class ViewHolderItem(v:View): RecyclerView.ViewHolder(v){
    var oneDayText:TextView = v.findViewById(R.id.day_text)
}

MainActivity

package com.example.sample

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager
import java.time.LocalDate
import java.time.YearMonth
import java.time.format.DateTimeFormatter

class MainActivity : AppCompatActivity() {

    private lateinit var calendarRecyclerView:RecyclerView
    private lateinit var monthYearText:TextView
    private lateinit var selectedDate:LocalDate

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val nextButton:Button = findViewById(R.id.nextButton)
        val previousButton:Button = findViewById(R.id.previousButton)

        initWidgets()
        selectedDate = LocalDate.now()
        setMonthView()

        previousButton.setOnClickListener {
            initWidgets()
            selectedDate = selectedDate.minusMonths(1)
            setMonthView()
        }

        nextButton.setOnClickListener {
            initWidgets()
            selectedDate = selectedDate.plusMonths(1)
            setMonthView()
        }


    }

    private fun initWidgets() {
        calendarRecyclerView = findViewById(R.id.rv)
        monthYearText = findViewById(R.id.monthText)
    }

    private fun setMonthView() {
        monthYearText.text = monthYear(selectedDate)
        val daysInMonth = daysInMonthArray(selectedDate)
        val calendarAdapter = RecyclerviewAdapter(daysInMonth)
        val layoutManager: LayoutManager = GridLayoutManager(applicationContext, 7)
        calendarRecyclerView.layoutManager = layoutManager
        calendarRecyclerView.adapter = calendarAdapter
    }

    private fun daysInMonthArray(date: LocalDate?): ArrayList<Long> {
        val daysInMonthArray = ArrayList<Long>()
        val yearMonth = YearMonth.from(date)
        val daysInMonth = yearMonth.lengthOfMonth()
        val firstOfMonth = selectedDate.withDayOfMonth(1)
        val dayOfWeek = firstOfMonth.dayOfWeek.value
        for (i in 1..42) {
            if (i <= dayOfWeek || i > daysInMonth + dayOfWeek) {
                daysInMonthArray.add(0)
            } else {
                daysInMonthArray.add((i - dayOfWeek).toLong())
            }
        }
        return daysInMonthArray
    }

    private fun monthYear(date: LocalDate?): String {
        val formatter = DateTimeFormatter.ofPattern("MMMM yyyy")
        return date!!.format(formatter)
    }
}

activity_main.xml

<?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">

    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="12dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <Button
            android:id="@+id/previousButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="24dp"
            android:text="previous" />

        <TextView
            android:id="@+id/monthText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="Dec 2023" />

        <Button
            android:id="@+id/nextButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="24dp"
            android:text="next" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        app:layout_constraintTop_toBottomOf="@+id/linearLayout">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="Sun" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="Mon" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="Tue" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="Wed" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:paddingEnd="2dp"
            android:text="Thu" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:paddingEnd="6dp"
            android:text="Fri" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:paddingEnd="5dp"
            android:text="Sat" />

    </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/linearLayout2"
        tools:layout_editor_absoluteX="0dp" />


</androidx.constraintlayout.widget.ConstraintLayout>

one_layout.xml

<?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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/day_layout">

    <TextView
        android:id="@+id/day_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1"
        android:layout_marginTop="10dp"
        android:textSize="12sp"
        android:layout_marginBottom="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

まとめ

RecyclerViewを使ってカレンダーを作る方法を解説しました。いかがだったでしょうか。

RecyclerViewはリスト表示だけでなくカレンダーを作るなど色々な用途に活用できます。是非この記事を参考に実装してみてください。

以下の記事では「RecyclerViewの画面遷移の実装方法」について解説しています。こちらの記事もぜひ併せて読んでみてください。

コメント

タイトルとURLをコピーしました