Creating Material Designed Event Calender Part-1

This new Material designed Event Calender which I created after too many research. I was creating a project in my organization and I was required to use calender which can show user’s event on dates, so I searched for third-party library too much but I didn’t found and I decided to create custom one.


I have created this Event Calender using Kotlin language in Android. To use Kotlin in your android studio project, define below code in Project level build.gradle file where 1.X.X is the latest version of kotlin.

buildscript {
ext.kotlin_version = '1.X.X'
...

dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

and in app level build.gradle file implement below library and Sync gradle.

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
}

Now, Create a Kotlin class named as CalenderView.kt in app module.

class CalenderView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {

init
{
orientation = LinearLayout.VERTICAL
}
}

I have used super class as LinearLayout, So all the day headers and days can be displayed linearly vertically by defining its orientation in init method.

Now, Create views which is to be displayed in Calender. So Let’s create header view for Calender which shows Month & Year with Next and Previous arrows to change month and Week days (i.e. Sun, Mon, etc…). Creating Layout as named layout_day_header_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
        <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
>
            <ImageView
android:id="@+id/btn_previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:padding="5dp"
android:src="@drawable/ic_previous_month"
android:tint="@color/button_background"
/>
            <TextView
android:id="@+id/txt_monthTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="10dp"
android:text="Month and Year"
android:textColor="@color/dark_gray"
android:textSize="20sp"
android:textStyle="bold"
/>
            <ImageView
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:padding="5dp"
android:src="@drawable/ic_next_month"
android:tint="@color/button_background"
/>
        </LinearLayout>
        <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
            <TextView
android:id="@+id/txt_sun"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="SUN"
android:textColor="@color/dark_gray"
android:textSize="16sp"
/>
            <TextView
android:id="@+id/txt_mon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="MON"
android:textColor="@color/dark_gray"
android:textSize="16sp"
/>
            <TextView
android:id="@+id/txt_tue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="TUE"
android:textColor="@color/dark_gray"
android:textSize="16sp"
/>
            <TextView
android:id="@+id/txt_wed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="WED"
android:textColor="@color/dark_gray"
android:textSize="16sp"
/>
            <TextView
android:id="@+id/txt_thu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="THU"
android:textColor="@color/dark_gray"
android:textSize="16sp"
/>
            <TextView
android:id="@+id/txt_fri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="FRI"
android:textColor="@color/dark_gray"
android:textSize="16sp"
/>
            <TextView
android:id="@+id/txt_sat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="SAT"
android:textColor="@color/dark_gray"
android:textSize="16sp"
/>
        </LinearLayout>
</LinearLayout>

Now, Create a single week view named as layout_day_date_view.xml where I have used CardView to give elevation and Z depth effect and FrameLayout used so Event can be put on Week View.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:orientation="horizontal"
>
    <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="50dp"
android:orientation="horizontal"
>
        <android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:cardCornerRadius="2dp"
app:cardElevation="5dp"
>
            <TextView
android:id="@+id/day1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/day_view_bg"
android:gravity="top|end"
android:padding="5dp"
android:text="1"
android:textColor="#000000"
android:textSize="16sp"
/>
        </android.support.v7.widget.CardView>
        <android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:cardCornerRadius="2dp"
app:cardElevation="5dp"
>
            <TextView
android:id="@+id/day2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/day_view_bg"
android:gravity="top|end"
android:padding="5dp"
android:text="2"
android:textColor="#000000"
android:textSize="16sp"
/>
        </android.support.v7.widget.CardView>
        <android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:cardCornerRadius="2dp"
app:cardElevation="5dp"
>
            <TextView
android:id="@+id/day3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/day_view_bg"
android:gravity="top|end"
android:padding="5dp"
android:text="3"
android:textColor="#000000"
android:textSize="16sp"
/>
        </android.support.v7.widget.CardView>
        <android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:cardCornerRadius="2dp"
app:cardElevation="5dp"
>
            <TextView
android:id="@+id/day4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/day_view_bg"
android:gravity="top|end"
android:padding="5dp"
android:text="4"
android:textColor="#000000"
android:textSize="16sp"
/>
        </android.support.v7.widget.CardView>
        <android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:cardCornerRadius="2dp"
app:cardElevation="5dp"
>
            <TextView
android:id="@+id/day5"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/day_view_bg"
android:gravity="top|end"
android:padding="5dp"
android:text="5"
android:textColor="#000000"
android:textSize="16sp"
/>
        </android.support.v7.widget.CardView>
        <android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:cardCornerRadius="2dp"
app:cardElevation="5dp"
>
            <TextView
android:id="@+id/day6"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/day_view_bg"
android:gravity="top|end"
android:padding="5dp"
android:text="6"
android:textColor="#000000"
android:textSize="16sp"
/>
        </android.support.v7.widget.CardView>
        <android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:cardCornerRadius="2dp"
app:cardElevation="5dp"
>
            <TextView
android:id="@+id/day7"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/day_view_bg"
android:gravity="top|end"
android:padding="5dp"
android:text="7"
android:textColor="#000000"
android:textSize="16sp"
/>
        </android.support.v7.widget.CardView>
    </LinearLayout>
    <LinearLayout
android:id="@+id/layout_tripEvents"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="5dp"
android:layout_marginTop="25dp"
android:animateLayoutChanges="true"
>
    </LinearLayout>
</FrameLayout>

Now, define constants and view objects so we can use it later. We are defining 6 Week objects as there can be max 6 week in month with previous month’s days and next month’s days. Default I’ve defined Max events to show on Calender is 3 but we can change it by programmatic by creating function.

class CalenderView ... {
var MAX_EVENT = 3

companion object {
const val monthYearFormat = "MMM yyyy"
const val dateFormat
= "dd-MM-yyyy"
const val dateTimeFormat
= "dd-MM-yyyy hh:mm:ss"
const val EXTRA_MARGIN
= 15
const val POST_TIME: Long = 100
}
private var eventList: ArrayList<EventItem> = ArrayList()    
private var calender
: Calendar = Calendar.getInstance()
private var layoutInflater: LayoutInflater = context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

    private var dayViewHeader: View = layoutInflater.inflate(R.layout.layout_day_header_view, this, false)
private var dayViewRow1: FrameLayout = layoutInflater.inflate(R.layout.layout_day_date_view, this, false) as FrameLayout
private var dayViewRow2: FrameLayout = layoutInflater.inflate(R.layout.layout_day_date_view, this, false) as FrameLayout
private var dayViewRow3: FrameLayout = layoutInflater.inflate(R.layout.layout_day_date_view, this, false) as FrameLayout
private var dayViewRow4: FrameLayout = layoutInflater.inflate(R.layout.layout_day_date_view, this, false) as FrameLayout
private var dayViewRow5: FrameLayout = layoutInflater.inflate(R.layout.layout_day_date_view, this, false) as FrameLayout
private var dayViewRow6: FrameLayout = layoutInflater.inflate(R.layout.layout_day_date_view, this, false) as FrameLayout
private var monthTitle: TextView? = dayViewHeader.findViewById(R.id.txt_monthTitle) as TextView
private var btn_next: ImageView? = dayViewHeader.findViewById(R.id.btn_next) as ImageView
private var btn_previous: ImageView? = dayViewHeader.findViewById(R.id.btn_previous) as ImageView
}

Now, Create function to remove all created event views at before time and refresh the views with selected month events using init function.

init {
removeAllViews() //In-built function in LinearLayout
orientation = LinearLayout.VERTICAL
addView(dayViewHeader)
addView(dayViewRow1)
addView(dayViewRow2)
addView(dayViewRow3)
addView(dayViewRow4)
addView(dayViewRow5)
addView(dayViewRow6)
    monthTitle?.text = SimpleDateFormat(monthYearFormat, Locale.getDefault()).format(calender.time)
btn_next?.setOnClickListener { nextMonth() }
btn_previous
?.setOnClickListener { previousMonth() }
    setOnTouchListener(object : OnCalendarSwipeListener(context) {
override fun onSwipeLeft() {
nextMonth()
}
        override fun onSwipeRight() {
previousMonth()
}
})
updateEventView(true)
}
fun nextMonth() {
calender.add(Calendar.MONTH, 1)
monthTitle?.text = SimpleDateFormat(monthYearFormat, Locale.getDefault()).format(calender.time)
updateEventView()
}
fun previousMonth() {
calender.add(Calendar.MONTH, -1)
monthTitle?.text = SimpleDateFormat(monthYearFormat, Locale.getDefault()).format(calender.time)
updateEventView()
}
private fun updateEventView(firstTime: Boolean = false) {
//Will implement this in next Part, Part-2
}

Now, Create OnCalenderSwipeListener interface to use swipe gesture in Calender, So we can change month by swiping it. This interface is created named as OnCalendarSwipeListener.java

public class OnCalendarSwipeListener implements View.OnTouchListener {
    private final GestureDetector gestureDetector;
    public OnCalendarSwipeListener(Context ctx) {
gestureDetector = new GestureDetector(ctx, new GestureListener());
}
    @Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
    public void onSwipeRight() {
    }
    public void onSwipeLeft() {
    }
    private void onSwipeTop() {
    }
    private void onSwipeBottom() {
    }
    private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
        private static final int SWIPE_THRESHOLD = 100;
private static final int SWIPE_VELOCITY_THRESHOLD = 100;
        @Override
public boolean onDown(MotionEvent e) {
return true;
}
        @Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
boolean result = false;
try {
float diffY = e2.getY() - e1.getY();
float diffX = e2.getX() - e1.getX();
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) {
onSwipeRight();
} else {
onSwipeLeft();
}
result = true;
}
} else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (diffY > 0) {
onSwipeBottom();
} else {
onSwipeTop();
}
result = true;
}
} catch (Exception exception) {
exception.printStackTrace();
}
return result;
}
}
}

and create EventItem as data model to keep data of event in Calender named as EventItem.kt

data class EventItem(var start: String, var end: String, var title: String = "My Event", var color: String = "#00FFFF", var id: String = "") {
fun getStartDateToSort(): Date {
val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
return sdf.parse(start)
}
}

Now, the main part of the code, which shows the dates and events on the day or days, we’ll see in the next part.

Thank you for reading this article. If you liked this, you can make 1, 2, 5 or 50 claps for this. Your Support will make me more enthusiastic and excited to write many more such articles.

Thanks again…