Поиск по этому блогу

понедельник, 10 июля 2017 г.

Кастомный календарь для назначения и выполнения целей

Был у меня как-то проект, который я так и не смог довести до ума, причин было много, из-за этого осталось много наработок которые лежат без дела мертвым грузом, и вот я решил поделиться своим опытом создания самопального календаря. Я очень долго не мог понять как мне его нужно сделать, да и до написания статьи я потратил прилично времени что бы причесать код и дизайн календаря до нормального вида, это можно увидеть в коммитах на гитхабе. 

Проект я делал еще года два назад, код не совсем идеальный, я его конечно по максимуму отрефакторил, но все равно остались куски от которых хочется плакать. Но я думаю со временем я доведу все до ума на столько что буду гордиться этим кодом :) Так сказать поживем увидим. 

Суть календаря в том что у нас в шапке находится список дней, с понедельника по воскресенье и даты этих дней отсортированные по дням недели, с боку от этих дней левее распологается кнопки листающие дни на 7 дней назад или вперед, а по центру отображается месяц относящийся к этим дням.

Под верхним меню с датами у нас будет находится собственно сам календарь, в нем у нас будет название задачи, сколько она длится и кружки которые показывают уровень выполнения заданий каждый день. То есть у нас есть задача например каждый день отжиматься по 10 подходов, в календаре будет стоять 10 подходов, сколько выполнено и сколько надо выполнить и по мере выполнения мы отмечаем по нажатию на кружок, и он делится на кусочки. В общем чувствую что по описанию я сам запутаюсь, просто приведу скриншот, надеюсь так будет понятней. Ну а еще у нас будет экран создания задания в календарь, он у нас будет вызываться по нажатию на плюсик в статус баре. В нем мы будет просто устанавливать нужные параметры для календаря.

 

Ну окей, давайте начнем писать код, тут его много, по этому предстоит много писать. Оформлять код буду без комментариев, буду описывать методы ниже под кодом.

Наверно начнем все с создания проекта, у нас создастся проект с одним файлом активити и разметкой. Давайте сперва наверно мы создадим экран создания таска, так будет как по мне логичней. В проекте мы будем использовать несколько библиотек:

  • Realm
  • Joda time
  • Butter knife

Про работу с Realm я расказывал в этой статье, там я расказал в деталях как работать с ним

Давайте их добавим в наш app/build.gradle. Вот так у нас он будет выглядеть:

app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'android-apt'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.project.piechartcallendarexample"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        multiDexEnabled true
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    dexOptions {
        javaMaxHeapSize "4g"
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:25.3.1'

    compile 'com.jakewharton:butterknife:8.0.1'
    apt 'com.jakewharton:butterknife-compiler:8.0.1'

    compile 'io.realm:realm-android:0.87.+'

    compile 'joda-time:joda-time:2.4'
}

В этом коде нас интересует вот этот блок dependencies в котором у нас подключаются библиотеки, и в самом верху вот эта строчка apply plugin: 'android-apt', ее мы добавляем для подключения butter knife. Ну и далее нам нужно в файл build.gradle добавить еще одну строчку, весь код файла ниже:

build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.1'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

В этом куске исходника мы добавили в dependencies вот эту строчку classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' которая позволит нам подключить butter knife к проекту.

Так, с библиотеками мы закончили, теперь мы готовы писать божественный код который будет творить магию. Начнем мы с экрана который будет создавать цели, для этого мы создадим новую активити и фрагмент в котором у нас будет находится соответственно все элементы экрана.

Для начала создадим базовую активити и базовый фрагменты, они нам нужны для упрощения кода и для того что бы поменьше было повторяющегося кода.

BaseActivity.java
public abstract class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getViewId());
    }

    public abstract int getViewId();
}

Тут мы сделали метод в котором задаем леяут для каждой активити которая будет наследовать этот класс. getViewId() — метод который будет создаваться в каждом унаследованном классе, и в нем мы будем указывать леяут который мы хотим что бы отображался.

BaseFragment.java
public abstract class BaseFragment extends Fragment {

    public Activity context;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(getViewId(), container, false);
        ButterKnife.bind(this, view);
        setHasOptionsMenu(true);
        return view;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        context = activity;
    }

    public abstract int getViewId();
}

Здесь делаем тоже самое что мы делали в BaseActivity, но только мы еще добавили инициализацию контекста для того что бы его можно было использовать вместо метода getActivity().

NewTaskActivity.java
public class NewTaskActivity extends BaseActivity {

    @Override
    public int getViewId() {
        return R.layout.activity_new_task;
    }
}

Теперь создаем класс NewTaskActivity и дальше наследуем его от BaseActivity, и дальше имплементим метод getViewId() и задаем в нем наш леяут.

activity_new_task.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="match_parent"
    xmlns:tools="http://schemas.android.com/tools">

    <fragment
        android:id="@+id/add"
        android:name="com.project.piechartcallendarexample.ui.add.NewTaskFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout="@layout/fragment_add_task"
        android:layout_weight="0.1" />

</LinearLayout>

Здесь мы просто задали какой фрагмент будем отображать в активити. Теперь нам нужно создать фрагмент в котором мы опишем функционал добавления целей в БД. Давайте опишем работу с реалмом для текущей задачи. 

Нам нужно создать модели для БД, у нас будет 3 таблицы, главная таблица в которой у нас будет вся важная информация по цели, таблица о днях которые у нас будут активными и таблица в которой у нас будет сохраняться количество дней на протяжении которых мы будем выполнять наши цели.

Главной таблице у нас будет RealmTaskModel. Таблицей для хранения активных дней RealmBooleanModel и таблица для хранения дней называться RealmTaskHistoryModel.

RealmTaskModel.java
import io.realm.RealmList;
import io.realm.RealmObject;

public class RealmTaskModel extends RealmObject {

    private int id;
    private String title;
    private int countDays;
    private int countRepeats;
    private String dateStart;
    private String dateFinish;
    private RealmList<RealmBooleanModel> fixDaysList;
    private RealmList<RealmTaskHistoryModel> realmTaskHistoryModels = new RealmList<>();

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDateStart() {
        return dateStart;
    }

    public void setDateStart(String dateStart) {
        this.dateStart = dateStart;
    }

    public String getDateFinish() {
        return dateFinish;
    }

    public void setDateFinish(String dateFinish) {
        this.dateFinish = dateFinish;
    }

    public RealmList<RealmTaskHistoryModel> getRealmTaskHistoryModels() {
        return realmTaskHistoryModels;
    }

    public void setRealmTaskHistoryModels(RealmList<RealmTaskHistoryModel> realmTaskHistoryModels) {
        this.realmTaskHistoryModels = realmTaskHistoryModels;
    }

    public int getCountDays() {
        return countDays;
    }

    public void setCountDays(int countDays) {
        this.countDays = countDays;
    }

    public int getCountRepeats() {
        return countRepeats;
    }

    public void setCountRepeats(int countRepeats) {
        this.countRepeats = countRepeats;
    }

    public RealmList<RealmBooleanModel> getFixDaysList() {
        return fixDaysList;
    }

    public void setFixDaysList(RealmList<RealmBooleanModel> fixDaysList) {
        this.fixDaysList = fixDaysList;
    }
}

RealmBooleanModel.java
import io.realm.RealmObject;

public class RealmBooleanModel extends RealmObject {
    private int id;
    private boolean fixDay;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public boolean isFixDay() {
        return fixDay;
    }

    public void setFixDay(boolean fixDay) {
        this.fixDay = fixDay;
    }
}

RealmTaskHistoryModel.java
import io.realm.RealmObject;

public class RealmTaskHistoryModel extends RealmObject {

    private int id = 0;
    private String date = "";
    private int progress = 0;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public int getProgress() {
        return progress;
    }

    public void setProgress(int progress) {
        this.progress = progress;
    }
}

По сути просто создали сеттеры и геттеры и поля в БД с помощью родителя RealmObject. Теперь нам нужно все это закодить что бы оно умело записывать, обновлять и доставать данные из БД. 

TaskController.java
import android.content.Context;

import com.project.piechartcallendarexample.ui.calendar.db.model.RealmBooleanModel;
import com.project.piechartcallendarexample.ui.calendar.db.model.RealmTaskHistoryModel;
import com.project.piechartcallendarexample.ui.calendar.db.model.RealmTaskModel;

import java.util.ArrayList;

import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmResults;

public class TaskController {

    private Context context;
    private Realm realm;

    public TaskController(Context context) {
        this.context = context;

        RealmConfiguration config = new RealmConfiguration.Builder(context).build();
        realm.setDefaultConfiguration(config);
        realm = Realm.getInstance(context);
    }

    public void addTask(RealmTaskModel model) {
        realm.beginTransaction();
        RealmTaskModel realmTaskModel = realm.createObject(RealmTaskModel.class);
        realmTaskModel.setId(getNextKey());
        realmTaskModel.setTitle(model.getTitle());
        realmTaskModel.setDateStart(model.getDateStart());
        realmTaskModel.setDateFinish(model.getDateFinish());
        realmTaskModel.setCountDays(model.getCountDays());
        realmTaskModel.setCountRepeats(model.getCountRepeats());
        for (RealmBooleanModel booleanModel : model.getFixDaysList())
            realmTaskModel.getFixDaysList().add(booleanModel);
        for (RealmTaskHistoryModel taskHistoryModel : model.getRealmTaskHistoryModels())
            realmTaskModel.getRealmTaskHistoryModels().add(taskHistoryModel);
        realm.commitTransaction();
    }

    private int getNextKey() {
        return realm.where(RealmTaskModel.class).max("id").intValue() + 1;
    }

    public ArrayList<RealmTaskModel> getAllTasks() {
        ArrayList<RealmTaskModel> realmTaskModels = new ArrayList<>();
        RealmResults<RealmTaskModel> results = realm.where(RealmTaskModel.class).findAll();
        for (int i = 0; i < results.size(); i++) {
            RealmTaskModel u = results.get(i);
            realmTaskModels.add(u);
        }
        return realmTaskModels;
    }

    public void updateTaskProgress(int id, int progress) {
        RealmTaskHistoryModel realmTargetModel = realm.where(RealmTaskHistoryModel.class).equalTo("id", id).findFirst();
        realm.beginTransaction();
        realmTargetModel.setProgress(progress);
        realm.commitTransaction();
    }

    public void clearDatabase() {
        realm.beginTransaction();
        realm.clear(RealmTaskModel.class);
        realm.commitTransaction();
    }
}

В конструкторе мы создаем объект Realm и дальше мы создаем методы для записи, чтения и т.д. 

addTask() — тут мы начинаем транзакцию и сетим данные в таблицу, и коммитим ее.
getNextKey() — метод который определяет какой id является последним. Возвращает id который идет следующим по логике.
getAllTasks() — возвращает список всех записей из БД.
updateTaskProgress() — метод который обновляет прогресс в определенной цели, определенного кружочка.
clearDatabase() — чистит БД целиком и полностью.

Теперь нам нужно создать фрагмент. В нем у нас используется два кастомных элемента которые я вынес в отдельные вьюхи. Создадим сперва их. В первом у нас как я описывал выше будет поля и кнопки для задания определенных настроек, вторая вьюха у нас будет входить в первую, она будет чисто списком кнопок которые мы по нажатию будем выбирать или развыбирать, в зависимости от надобности. В общем увидите, сперва создадим ScheduleView.

ScheduleView.java
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;

import com.project.piechartcallendarexample.R;

import java.util.List;

import butterknife.BindColor;
import butterknife.BindView;
import butterknife.ButterKnife;

public class ScheduleView extends LinearLayout implements View.OnClickListener {

    @BindView(R.id.weeksView)
    WeeksView weeksView;
    @BindView(R.id.repeatSpinner)
    Spinner repeatSpinner;
    @BindView(R.id.deadLine)
    TextView deadLine;
    @BindView(R.id.withoutDeadLine)
    TextView withoutDeadLine;
    @BindView(R.id.schletudeQuestion)
    ImageView schletudeQuestion;

    @BindColor(R.color.colorPrimary)
    int blueColor;
    @BindColor(R.color.dark_gray_text)
    int grayColor;

    private boolean withoutDeadline;
    private int repeatValue;

    private Context context;

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

    public ScheduleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ScheduleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        inflate(context, R.layout.view_new_target_shletude, this);
        setOrientation(VERTICAL);
        ButterKnife.bind(this);

        setupViews();
    }

    private void setupViews() {
        schletudeQuestion.setOnClickListener(this);
        withoutDeadLine.setOnClickListener(this);

        repeatValue = Integer.valueOf(getResources().getStringArray(R.array.task_repeats)[0]);
        setRepeatSpinnerAdapter();
    }

    public void setRepeatSpinnerAdapter() {
        ArrayAdapter<String> adapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, getResources().getStringArray(R.array.task_repeats));
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        repeatSpinner.setAdapter(adapter);
        repeatSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                repeatValue = Integer.valueOf(getResources().getStringArray(R.array.task_repeats)[position]);
            }

            @Override
            public void onNothingSelected(AdapterView<?> arg0) { }
        });
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.withoutDeadLine:
                onWithoutDeadlineChecked(view);
                break;
        }
    }

    public void onWithoutDeadlineChecked(View v) {
        if(v.getId() == R.id.withoutDeadLine) {
            if(!withoutDeadline) {
                ((TextView) v).setTextColor(blueColor);
                deadLine.setEnabled(false);
                withoutDeadline = true;
            } else {
                ((TextView) v).setTextColor(grayColor);
                deadLine.setEnabled(true);
                withoutDeadline = false;
            }
        }
    }

    public String getDeadline() {
        return deadLine.getText().toString();
    }

    public int getRepeatValue() {
        return repeatValue;
    }

    public List<Boolean> getFixDaysList() {
        return weeksView.getFixDaysList();
    }

    public boolean isWithoutDeadline() {
        return withoutDeadline;
    }
}

В этом классе у нас есть спиннер, по клику на который по клику мы выбираем нужные данные из массива который у нас указан в string.xml. Да и вообще я забыл указать весь этот файл, так что я его приведу ниже.

string.xml
<resources>
    <string name="app_name">Horizontal Goal Calendar</string>
    <string name="calendar">Calendar</string>
    <string name="add">Add goal</string>

    <string name="task.new.target.mon">MON</string>
    <string name="task.new.target.thu">TUE</string>
    <string name="task.new.target.wen">WED</string>
    <string name="task.new.target.sut">THU</string>
    <string name="task.new.target.fr">FRI</string>
    <string name="task.new.target.surth">SAT</string>
    <string name="task.new.target.sun">SUN</string>

    <string name="task.new.target.shletude">Schedule</string>
    <string name="task.new.target.repeat">Repeats</string>
    <string name="task.new.target.each.dat">a day</string>
    <string name="task.new.target.deadline">Terms</string>
    <string name="task.new.target.days">days</string>
    <string name="task.new.target.without.deadline">Without terms</string>
    <string name="task.new.target.toast.error">Chose please any day from list above.</string>

    <string name="task.details.times.day" formatted="false">%s times a day, during %s days</string>

    <string-array name="task_repeats">
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
        <item>5</item>
        <item>6</item>
        <item>7</item>
        <item>8</item>
        <item>9</item>
        <item>10</item>
    </string-array>

</resources>

Вот он. Собственно тут мы мы сетим адаптеры в этой вьюхе, для спиннера, он клик лисенеры для вьюх и т. д. По клику на кнопку without deadlines у нас делается или активным или не активным поле для ввода количества дней, ну и подствечиваем эту кнопку. И куча сеттеров и геттеров для получения данных из этой вьюхи. 

XML будет иметь такой вид:

view_new_target_shletude.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/border_with_shadow"
        android:layout_marginTop="10dp">

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/task.new.target.shletude"
                android:textAllCaps="true"
                android:textAppearance="?android:attr/textAppearanceMedium" />

            <LinearLayout
                android:orientation="horizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="right" >

                <ImageView
                    android:layout_width="25dp"
                    android:layout_height="25dp"
                    android:id="@+id/schletudeQuestion"
                    android:src="@mipmap/question_grey" />
            </LinearLayout>
        </LinearLayout>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:padding="10dp">

            <LinearLayout
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <com.project.piechartcallendarexample.ui.add.view.WeeksView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:id="@+id/weeksView">
                </com.project.piechartcallendarexample.ui.add.view.WeeksView>
            </LinearLayout>

            <LinearLayout
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:layout_marginTop="10dp">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="@string/task.new.target.repeat"
                        android:textAppearance="?android:attr/textAppearanceSmall"
                        android:textColor="@android:color/black"
                        android:textStyle="bold" />

                    <Spinner
                        android:layout_width="75dp"
                        android:layout_height="40dp"
                        android:id="@+id/repeatSpinner"
                        android:layout_marginLeft="10dp"
                        android:gravity="center_horizontal" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="10dp"
                        android:text="@string/task.new.target.each.dat"
                        android:textAppearance="?android:attr/textAppearanceSmall"
                        android:textColor="@android:color/black"
                        android:textStyle="bold" />
                </LinearLayout>

                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:layout_marginTop="10dp">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="@string/task.new.target.deadline"
                        android:textAppearance="?android:attr/textAppearanceSmall"
                        android:textColor="@android:color/black"
                        android:textStyle="bold" />

                    <EditText
                        android:layout_width="50dp"
                        android:layout_height="wrap_content"
                        android:id="@+id/deadLine"
                        android:inputType="numberDecimal"
                        android:maxLength="4"
                        android:gravity="center_horizontal" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="10dp"
                        android:text="@string/task.new.target.days"
                        android:textAppearance="?android:attr/textAppearanceSmall"
                        android:textColor="@android:color/black"
                        android:textStyle="bold" />

                    <LinearLayout
                        android:orientation="horizontal"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:gravity="right">

                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:textAppearance="?android:attr/textAppearanceSmall"
                            android:text="@string/task.new.target.without.deadline"
                            android:id="@+id/withoutDeadLine"
                            android:layout_marginLeft="10dp"
                            android:textStyle="bold" />
                    </LinearLayout>

                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

А еще у нас есть WeeksView в котором у нас список кнопок дней недели. Сама вьюха представляет из себя просто список текствьюх которые по клику делаются активными или нет и добавляем их в массив с которого потом читаем какие у нас выбранны и записываем в БД.

WeeksView.java
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.project.piechartcallendarexample.R;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindColor;
import butterknife.BindView;
import butterknife.ButterKnife;

public class WeeksView extends LinearLayout implements View.OnClickListener {

    @BindView(R.id.monBtn)
    TextView monBtn;
    @BindView(R.id.thuBtn)
    TextView thuBtn;
    @BindView(R.id.wedBtn)
    TextView wedBtn;
    @BindView(R.id.thurBtn)
    TextView thurBtn;
    @BindView(R.id.frBtn)
    TextView frBtn;
    @BindView(R.id.surthBtn)
    TextView sutBtn;
    @BindView(R.id.sunBtn)
    TextView sunBtn;

    @BindColor(R.color.colorPrimary)
    int blueColor;
    @BindColor(R.color.dark_gray_text)
    int grayColor;

    private boolean monday;
    private boolean tuesday;
    private boolean wednesday;
    private boolean thursday;
    private boolean friday;
    private boolean saturday;
    private boolean sunday;
    private List<Boolean> fixDaysList;

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

    public WeeksView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public WeeksView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        inflate(context, R.layout.view_tasks_weeks, this);
        setOrientation(VERTICAL);
        ButterKnife.bind(this);
        setupViews();
    }

    private void setupViews() {
        monBtn.setOnClickListener(this);
        thuBtn.setOnClickListener(this);
        wedBtn.setOnClickListener(this);
        thurBtn.setOnClickListener(this);
        frBtn.setOnClickListener(this);
        sutBtn.setOnClickListener(this);
        sunBtn.setOnClickListener(this);

        fixDaysList = new ArrayList<>(6);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.monBtn:
            case R.id.thuBtn:
            case R.id.wedBtn:
            case R.id.thurBtn:
            case R.id.frBtn:
            case R.id.surthBtn:
            case R.id.sunBtn:
                onWeekChecked(view);
                break;
        }
    }

    public void onWeekChecked(View v) {
        switch(v.getId()) {
            case R.id.monBtn:
                monday = selectDayOfWeek(v, monday);
                fixDaysList.add(monday);
                break;
            case R.id.thuBtn:
                tuesday = selectDayOfWeek(v, tuesday);
                fixDaysList.add(tuesday);
                break;
            case R.id.wedBtn:
                wednesday = selectDayOfWeek(v, wednesday);
                fixDaysList.add(wednesday);
                break;
            case R.id.thurBtn:
                thursday = selectDayOfWeek(v, thursday);
                fixDaysList.add(thursday);
                break;
            case R.id.frBtn:
                friday = selectDayOfWeek(v, friday);
                fixDaysList.add(friday);
                break;
            case R.id.surthBtn:
                saturday = selectDayOfWeek(v, saturday);
                fixDaysList.add(saturday);
                break;
            case R.id.sunBtn:
                sunday = selectDayOfWeek(v, sunday);
                fixDaysList.add(sunday);
                break;
        }
    }

    private boolean selectDayOfWeek(View v, boolean day) {
        if(!day) {
            ((TextView) v).setTextColor(blueColor);
            day = true;
        } else {
            ((TextView) v).setTextColor(grayColor);
            day = false;
        }
        return day;
    }

    public List<Boolean> getFixDaysList() {
        fixDaysList.add(0 ,monday);
        fixDaysList.add(1, tuesday);
        fixDaysList.add(2, wednesday);
        fixDaysList.add(3, thursday);
        fixDaysList.add(4, friday);
        fixDaysList.add(5, saturday);
        fixDaysList.add(6, sunday);
        return fixDaysList;
    }
}

Вьюха состоит всего из 7 текствьюх запиханные в LinearLayout.

view_tasks_weeks.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/task.new.target.mon"
            android:id="@+id/monBtn"
            android:padding="10dp"
            android:textStyle="bold"
            android:textColor="@color/dark.gray.text" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/task.new.target.thu"
            android:id="@+id/thuBtn"
            android:padding="10dp"
            android:textStyle="bold"
            android:textColor="@color/dark.gray.text" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/task.new.target.wen"
            android:id="@+id/wedBtn"
            android:padding="10dp"
            android:textStyle="bold"
            android:textColor="@color/dark.gray.text" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/task.new.target.sut"
            android:id="@+id/thurBtn"
            android:padding="10dp"
            android:textStyle="bold"
            android:textColor="@color/dark.gray.text" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/task.new.target.fr"
            android:id="@+id/frBtn"
            android:padding="10dp"
            android:textStyle="bold"
            android:textColor="@color/dark.gray.text" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/task.new.target.surth"
            android:id="@+id/surthBtn"
            android:padding="10dp"
            android:textStyle="bold"
            android:textColor="@color/dark.gray.text" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/task.new.target.sun"
            android:id="@+id/sunBtn"
            android:padding="10dp"
            android:textStyle="bold"
            android:textColor="@color/dark.gray.text" />
    </LinearLayout>
</LinearLayout>

Ну, а дальше мы уже можем создавать фрагмент и использовать все наши компоненты которые мы создали.

NewTaskFragment.java
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.format.Time;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.project.piechartcallendarexample.R;
import com.project.piechartcallendarexample.etc.RandomUtils;
import com.project.piechartcallendarexample.ui.BaseFragment;
import com.project.piechartcallendarexample.ui.add.view.ScheduleView;
import com.project.piechartcallendarexample.ui.calendar.db.TaskController;
import com.project.piechartcallendarexample.ui.calendar.db.model.RealmBooleanModel;
import com.project.piechartcallendarexample.ui.calendar.db.model.RealmTaskHistoryModel;
import com.project.piechartcallendarexample.ui.calendar.db.model.RealmTaskModel;

import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.Calendar;
import java.util.List;

import butterknife.BindColor;
import butterknife.BindView;
import io.realm.RealmList;

/**
 * Created by gleb on 6/16/17.
 */

public class NewTaskFragment extends BaseFragment {

    public static final int WITHOUD_DEADLINE = 5475;
    public static final int START_DATE = 0;

    @BindView(R.id.schletudeView)
    public ScheduleView scheduleView;
    @BindView(R.id.target)
    public EditText target;

    @BindColor(R.color.colorPrimary)
    int blueColor;
    @BindColor(R.color.dark_gray_text)
    int grayColor;

    private RealmTaskModel realmTaskModel;
    private RealmList<RealmTaskHistoryModel> taskHistoryModels;
    public TaskController taskController;

    @Override
    public int getViewId() {
        return R.layout.fragment_add_task;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        taskController = new TaskController(context);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        taskHistoryModels = new RealmList<>();
        realmTaskModel = new RealmTaskModel();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add:
                int deadlineValue = WITHOUD_DEADLINE;
                if(!TextUtils.isEmpty(scheduleView.getDeadline()))
                    deadlineValue = Integer.valueOf(scheduleView.getDeadline());
                realmTaskModel.setTitle(target.getText().toString());
                realmTaskModel.setDateStart(getDate(START_DATE));
                realmTaskModel.setDateFinish(getDate(daysBetweenDates(deadlineValue)));
                realmTaskModel.setCountDays(scheduleView.isWithoutDeadline() ? WITHOUD_DEADLINE : deadlineValue);
                realmTaskModel.setCountRepeats(scheduleView.getRepeatValue());

                List<Boolean> boo = scheduleView.getFixDaysList();
                RealmList<RealmBooleanModel> realmListBooleans = new RealmList<>();
                for(int k = 0; k < boo.size(); k++) {
                    RealmBooleanModel booleanModel = new RealmBooleanModel();
                    booleanModel.setId(RandomUtils.getRandomValue());
                    booleanModel.setFixDay(boo.get(k));
                    realmListBooleans.add(booleanModel);
                }
                realmTaskModel.setFixDaysList(realmListBooleans);

                Calendar calendar = Calendar.getInstance();
                calendar.add(Calendar.DATE, -1);
                for(int i = 0; i < daysBetweenDates(deadlineValue); i++) {
                    RealmTaskHistoryModel realmTaskHistoryModel = new RealmTaskHistoryModel();
                    calendar.add(Calendar.DATE, 1);
                    realmTaskHistoryModel.setId(RandomUtils.getRandomValue());
                    realmTaskHistoryModel.setDate(calendar.get(Calendar.YEAR) + " " + calendar.get(Calendar.MONTH) + " " + calendar.get(Calendar.DATE));
                    taskHistoryModels.add(realmTaskHistoryModel);
                }
                realmTaskModel.setRealmTaskHistoryModels(taskHistoryModels);

                if (realmTaskModel.getFixDaysList().size() > 0)
                    new InsertDataAboutTask().execute();
                else
                    Toast.makeText(context, getString(R.string.task_new_target_toast_error), Toast.LENGTH_LONG).show();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    public String getDate(int daysCount) {
        Time time = new Time();
        Calendar now = Calendar.getInstance();
        if(daysCount == START_DATE) {
            time.set(now.get(Calendar.DATE), now.get(Calendar.MONTH), now.get(Calendar.YEAR));
        } else {
            now.add(Calendar.DATE, daysCount);
            time.set(now.get(Calendar.DATE), now.get(Calendar.MONTH), now.get(Calendar.YEAR));
        }
        return time.format("%d %m %Y");
    }

    public int daysBetweenDates(int daysCount) {
        DateTimeFormatter formatter = DateTimeFormat.forPattern("dd MM yyyy");
        DateTime d1 = formatter.parseDateTime(getDate(START_DATE));
        DateTime d2 = formatter.parseDateTime(getDate(daysCount));
        Duration duration = new Duration(d1, d2);
        int days = (int)duration.getStandardDays();
        return days;
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_add, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    private class InsertDataAboutTask extends AsyncTask<Void, Void, Void> {

        private TaskController taskController;

        @Override
        protected Void doInBackground(Void... params) {
            taskController = new TaskController(context);
            taskController.addTask(realmTaskModel);
            return null;
        }

        @Override
        protected void onPreExecute() { }

        @Override
        protected void onPostExecute(Void sum) {
            Intent intent = new Intent();
            context.setResult(context.RESULT_OK, intent);
            context.finish();
        }
    }
}

getViewId() — мы указываем какую вьюху гам использовать.
onCreate() — инициализируем наш контроллер по записи в БД.
onViewCreated() — инициализируем нудные списки и модели.
onOptionsItemSelected() — по клику на кнопку add мы сохраняем данные в БД.
getDate() — возвращает стартовую дату в виде строки.
daysBetweenDates() — возвращает количество дней между днями указаными от начальной даты и конечной.

В классе InsertDataAboutTask у нас идет запись в БД, это мы делаем для разделения потоков записи, так как если без этого таска у нас приложение зависнет просто, хоть разработчики реалма и говорят что оно создает отдельные потоки каждый раз когда выполняется какое-то действие, но почему-то не всегда это срабатывает.

По окончанию выполнения таска в onPostExecute() у нас вызывается закрытие активити и setResult() метод для возвращения того что все ок.

А еще у нас есть такая штука как RandomUtils которая создаем рандомное число для записи id для таблицы количества дней и таблицы для хранения активных дней.

RandomUtils.java
import java.util.Random;

public class RandomUtils {
    
    public static int getRandomValue() {
        Random rand = new Random();
        return Math.abs(rand.nextInt());
    }
}

Теперь мы умеем писать в БД, и оно умеет создавать цели для календаря, далее нам нужно начать писать календарь. У нас есть класс MainActivity, я его переименовал себе в CalendarActivity что бы было понятней, да и поэстетичней впринципе выглядит. В нем у нас как и в активити новой цели так же всего один метод getViewId().

CalendarActivity.java
import com.project.piechartcallendarexample.R;
import com.project.piechartcallendarexample.ui.BaseActivity;

public class CalendarActivity extends BaseActivity {

    @Override
    public int getViewId() {
        return R.layout.activity_main;
    }
}

В activity_main все так же как и в new task activity мы прописали фрагмент который у нас будет отображаться в активитии леяут который мы хотим отображать по дефолту.

activity_main.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="match_parent"
    xmlns:tools="http://schemas.android.com/tools">

    <fragment
        android:id="@+id/main"
        android:name="com.project.piechartcallendarexample.ui.calendar.CalendarFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout="@layout/fragment_calendar"
        android:layout_weight="0.1" />

</LinearLayout>

А дальше давайте создадим фрагмент, в нем у нас будет использоваться один список который мы в адаптере будем разбивать на нужные айтемы. 

CalendarFragment.java
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;

import com.project.piechartcallendarexample.R;
import com.project.piechartcallendarexample.ui.BaseFragment;
import com.project.piechartcallendarexample.ui.add.NewTaskActivity;
import com.project.piechartcallendarexample.ui.calendar.adapter.CalendarAdapter;
import com.project.piechartcallendarexample.ui.calendar.adapter.model.CalendarModel;
import com.project.piechartcallendarexample.ui.calendar.db.TaskController;
import com.project.piechartcallendarexample.ui.calendar.db.model.RealmTaskModel;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;

import butterknife.BindView;

public class CalendarFragment extends BaseFragment implements CalendarAdapter.OnClickCallback {

    public static final int REQUEST_RESULT = 256;
    public static final String[] dayOfWeeksArray = { "MON", "THU", "WED", "THU", "FRI", "SAT", "SUN" };

    @BindView(R.id.listView)
    ListView listView;

    public TaskController taskController;
    public CalendarAdapter calendarAdapter;
    public Calendar current;

    @Override
    public int getViewId() {
        return R.layout.fragment_calendar;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        taskController = new TaskController(context);
        current = Calendar.getInstance();
        current.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        calendarAdapter = new CalendarAdapter(context);
        calendarAdapter.setItem(taskController.getAllTasks().size() > 0 ? taskController.getAllTasks() : new ArrayList<RealmTaskModel>());
        calendarAdapter.setCalendarDated(showDatesInView());
        calendarAdapter.setCalendar(current);
        calendarAdapter.setOnClickListener(this);
        listView.setAdapter(calendarAdapter);
    }

    public String getDayOfWeek(Calendar calendar) {
        return new SimpleDateFormat("EE").format(calendar.getTime());
    }

    public void refreshAdapter() {
        calendarAdapter.setCalendar(current);
        calendarAdapter.setCalendarDated(showDatesInView());
        listView.setAdapter(calendarAdapter);
    }

    public ArrayList<CalendarModel> showDatesInView() {
        ArrayList<CalendarModel> dates = new ArrayList<>();
        for(int i = 0; i < dayOfWeeksArray.length; i++) {
            CalendarModel model = new CalendarModel();
            model.setDay(current.get(Calendar.DATE));
            model.setMonth(current.get(Calendar.MONTH));
            model.setYear(current.get(Calendar.YEAR));
            model.setDayOfWeek(getDayOfWeek(current));
            current.add(Calendar.DATE, 1);
            dates.add(model);
        }
        return dates;
    }

    @Override
    public void onNextClick() {
        current.add(Calendar.DATE, 0);
        refreshAdapter();
    }

    @Override
    public void onPrevClick() {
        current.add(Calendar.DATE, -14);
        refreshAdapter();
    }

    @Override
    public void onDateClick(int dayCount) { }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_calendar, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add:
                startActivityForResult(new Intent(context, NewTaskActivity.class), REQUEST_RESULT);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        calendarAdapter.setCalendar(current);
        calendarAdapter.setItem(taskController.getAllTasks());
        listView.setAdapter(calendarAdapter);
    }
}

Здесь у нас есть несколько моментов. У нас есть dayOfWeeksArray в котором у нас прописаны дни недели, у нас их 7, можно впринципе просто сделать константу которая будет равна семи, но я подумал что так сделать будет получше.

onCreate() — у нас создается объект нашего таск контроллера который является контроллером для работы с БД. Проинициализировали календарь и сказали ему что у нас понедельник является первым днем недели, а не воскресенье.
onViewCreated() — создали в нем адаптер, засетили туда все нужные данные что бы в адаптере были данные для отображения и отправили все это в листвью.
getDayOfWeek() — возвращает текущий день недели.
refreshAdapter() — обновляет адаптер с новыми данными каждый раз когда мы его вызываем.
showDatesInView() — в этом методе мы создаем даты для шапки которая у нас отображает дни недели и даты. Тут мы записываем это все в список предварительно прогнав календарь в цикле и ретурном возвращаем в метод CalnedarModel приведу его ниже.
onClick() — методы которые по клику что-то делают, думаю они не требуют особого внимания.
onActivityResult() — обновляем адаптер по возвращению на этот фрагмент. 

CalendarModel.java
public class CalendarModel implements Comparable<CalendarModel> {

    private String dayOfWeek;
    private Integer day;
    private Integer month;
    private Integer year;

    public String getDayOfWeek() {
        return dayOfWeek;
    }

    public void setDayOfWeek(String dayOfWeek) {
        this.dayOfWeek = dayOfWeek;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int date) {
        this.day = date;
    }

    @Override
    public int compareTo(CalendarModel another) {
        return day.compareTo(another.day);
    }

    public Integer getYear() {
        return year;
    }

    public void setYear(Integer year) {
        this.year = year;
    }

    public Integer getMonth() {
        return month;
    }

    public void setMonth(Integer month) {
        this.month = month;
    }
}

А теперь у нас начинается екшн — будем писать адаптер для календаря, он у нас довольно сложно выглядит со стороны, но на деле там все очень просто, давайте посмотрим на код с начала, а потом уже буду разъяснять что к чему там.

CalendarAdapter.java
import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;

import com.project.piechartcallendarexample.R;
import com.project.piechartcallendarexample.ui.calendar.adapter.holder.HeaderHolder;
import com.project.piechartcallendarexample.ui.calendar.adapter.holder.ItemHolder;
import com.project.piechartcallendarexample.ui.calendar.adapter.model.CalendarModel;
import com.project.piechartcallendarexample.ui.calendar.db.TaskController;
import com.project.piechartcallendarexample.ui.calendar.db.model.RealmTaskModel;
import com.project.piechartcallendarexample.ui.calendar.view.CalendarDaysView;
import com.project.piechartcallendarexample.ui.calendar.view.CalendarProgressView;
import com.project.piechartcallendarexample.ui.calendar.view.ProgressView;

import java.util.ArrayList;
import java.util.Calendar;

public class CalendarAdapter extends BaseAdapter {

    private static final int TYPE_HEAD = 0;
    private static final int TYPE_ITEM = 1;
    private static final String DATE_TEMPLATE = "MMMM \nyyyy";

    private HeaderHolder headHolder;
    private ItemHolder itemHolder;

    private TaskController taskController;
    private OnClickCallback onClickCallback;
    private DateFormat dateFormatter = new DateFormat();

    private int datePosition = 0;

    private Context context;
    private Calendar calendar;

    private ArrayList<RealmTaskModel> listData = new ArrayList<>();
    private ArrayList<CalendarModel> getDatesInView;

    public CalendarAdapter(Context context) {
        this.context = context;
        taskController = new TaskController(context);
    }

    public void setItem(ArrayList<RealmTaskModel> articleResultModels) {
        this.listData = articleResultModels;
        notifyDataSetChanged();
    }

    public void setCalendarDated(ArrayList<CalendarModel> calendarDated) {
        this.getDatesInView = calendarDated;
        notifyDataSetChanged();
    }

    public void setCalendar(Calendar calendar) {
        this.calendar = calendar;
        notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        if(position == 0)
            return TYPE_HEAD;
        else
            return TYPE_ITEM;
    }

    @Override
    public int getViewTypeCount() {
        return super.getViewTypeCount() + 1;
    }

    @Override
    public int getCount() {
        return listData.size() + 1;
    }

    @Override
    public RealmTaskModel getItem(int position) {
        if(position == 0) return null;
        return listData.size() > 0 ? listData.get(position - 1) : new RealmTaskModel();
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = ((Activity) context).getLayoutInflater();
        TableRow.LayoutParams rowParams = new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT, 0.1f);
        TableRow tableRow = new TableRow(context);
        tableRow.setOrientation(TableLayout.HORIZONTAL);

        int type = getItemViewType(position);
        switch(type) {
            case TYPE_HEAD:
                convertView = inflater.inflate(R.layout.item_calendar_days, parent, false);
                headHolder = new HeaderHolder(convertView);

                headHolder.currentMonth.setFirstCupText(dateFormatter.format(DATE_TEMPLATE, calendar.getTime()));

                for(int i = 0; i < 7; i++) {
                    CalendarDaysView daysView = new CalendarDaysView(context);
                    daysView.setDate(String.valueOf(getDatesInView.get(i).getDay()));
                    daysView.setDayOfWeek(getDatesInView.get(i).getDayOfWeek());
                    tableRow.addView(daysView, rowParams);
                }
                headHolder.daysRow.addView(tableRow);

                headHolder.nextMonth.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        datePosition += 7;
                        onClickCallback.onNextClick();
                    }
                });
                headHolder.prevMonth.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (datePosition > 0)
                            datePosition -= 7;
                        onClickCallback.onPrevClick();
                    }
                });
                headHolder.currentMonth.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        onClickCallback.onDateClick(datePosition);
                    }
                });
                break;
            case TYPE_ITEM:
                convertView = inflater.inflate(R.layout.item_calendar_progress, parent, false);
                itemHolder = new ItemHolder(convertView);

                final RealmTaskModel model = getItem(position);
                itemHolder.title.setText(model.getTitle());
                itemHolder.days.setText(String.format(context.getString(R.string.task_details_times_day), model.getCountRepeats(), model.getCountDays()));

                for(int numOfDaysInc = 0; numOfDaysInc < 7; numOfDaysInc++) {
                    CalendarProgressView progressView = new CalendarProgressView(context);
                    for (int daysInc = 0; daysInc < (7 + datePosition); daysInc++) {
                        if (compareDates(getFullDate(numOfDaysInc), getRealmDate(position, daysInc))) {
                            setRepeatsInfo(progressView.getText(), model, daysInc, model.getFixDaysList().get(numOfDaysInc).isFixDay());
                            onProgressClick(progressView.getProgressView(), model, daysInc, model.getFixDaysList().get(numOfDaysInc).isFixDay(), position);
                        }
                    }
                    tableRow.addView(progressView, rowParams);
                }
                itemHolder.progressRow.addView(tableRow);
                break;
        }
        return convertView;
    }

    private void onProgressClick(ProgressView progressView, final RealmTaskModel model, final int daysInc, boolean isFixed, final int position) {
        if(isFixed) {
            progressView.setValue(model.getRealmTaskHistoryModels().get(daysInc).getProgress());
            progressView.setProgressMaximum(model.getCountRepeats());
            progressView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int progress = model.getRealmTaskHistoryModels().get(daysInc).getProgress() + 1;
                    int id = model.getRealmTaskHistoryModels().get(daysInc).getId();
                    if (progress <= model.getCountRepeats())
                        setProgress(progress, position, daysInc, id);
                    else
                        setProgress(0, position, daysInc, id);
                    notifyDataSetChanged();
                }
            });
        }
    }

    private void setProgress(int progress, int position, int daysInc, int id) {
        if (compareDates(getRealmDate(position, daysInc), getCurrentDate()))
            taskController.updateTaskProgress(id, progress);
    }

    private void setRepeatsInfo(TextView repeatText, RealmTaskModel model, int i, boolean isFixed) {
        if(isFixed) {
            if (!TextUtils.isEmpty(model.getRealmTaskHistoryModels().get(i).getDate())) {
                repeatText.setVisibility(View.VISIBLE);
                repeatText.setText(model.getCountRepeats() + "/" + model.getRealmTaskHistoryModels().get(i).getProgress());
            }
        }
    }

    public boolean compareDates(String date1, String date2) {
        if (date1.trim().equals(date2.trim())) {
            return true;
        } else {
            return false;
        }
    }

    private String getRealmDate(int position, int id) {
        String value = "1900 01 01";
        if (id < getItem(position).getRealmTaskHistoryModels().size())
            value = getItem(position).getRealmTaskHistoryModels().get(id).getDate();
        return value;
    }

    private String getFullDate(int position) {
        int year = getDatesInView.get(position).getYear();
        int month = getDatesInView.get(position).getMonth();
        int day = getDatesInView.get(position).getDay();
        return year + " " + month + " " + day;
    }

    private String getCurrentDate() {
        Calendar cal = Calendar.getInstance();
        return cal.get(Calendar.YEAR) + " " + cal.get(Calendar.MONTH) + " " + cal.get(Calendar.DATE);
    }

    public void setOnClickListener(OnClickCallback onNextMonthClickListener) {
        this.onClickCallback = onNextMonthClickListener;
    }

    public interface OnClickCallback {
        void onNextClick();
        void onPrevClick();
        void onDateClick(int dayCount);
    }
}

Вот такой у нас адаптер получается, в нем у нас солянка сборная. Тут мы и сетим данные, и обрабатываем их, и выплевываем обратно в список, в общем щас будем все по порядку разбирать. В этом адаптере у нас есть два вида View. Первый у нас это шапка, он у нас обозначен константой TYPE_HEAD, а второй у нас обычный айтем с тайтлом, дескрипшеном и кружочками которые будем заполнять, он же обозначен как TYPE_ITEM. Еще у нас есть непонятная константа DATE_TEMPLATE, она нам нужна для того что бы дата отображалась у нас сверху месяц и ниже год, так ничего не вылазит за пределы ограничивающих полей. 

Дальше у нас там идет пачка переменных которые мы сетим в сеттерах вместе с инициализацией адаптера, там по сути оч простой код, просто в одну переменную передаем другу. А вот некоторые методы нас интенересуют особенно. 

getItemViewType() — метод который разбивает наш адаптер на две части. В нем мы говорим что если позиция равна 0 то мы обозначаем ее как TYPE_HEAD и в ней мы отображаем вьюху шапки, а остальное мы отображаем как TYPE_ITEM. По сути так можно разбивать на сколько угодно частей адаптер, особой магии тут нет.
getViewTypeCount() — возвращает количество элементов в адаптере. + 1 — обозначает что у нас плюс одна вьюха которой у нас является наша шапка, если хотите добавить третью вьюху какую-то, то надо написать + 2 и т.д.
getCount() — возвращает количество элементов в списке. Так же, + 1 обозначает что отображать мы будем на с 0 позиции, так как у нас там шапка, а со второй.
getItem() — возвращает айтем со списка. Та проверка которая возвращает нулл нужна для того что бы у нас было смещение на 0 позицию, так как у нас там шапка и там используется другой список.
getView() — главный метод адаптера, в нем происходит сетап данных в адаптер и тут же обновление и вся остальная магия. В самом начале мы инициализируем LayoutInflater, тут он нужен для захвата вьюх, на нем особо не будем заострять внимания. Дальше у нас идет TableRow, его мы создали для того что бы отцентровать вьюхи по центру, сделать им горизонтальную орниентацию и вообще сделать их красивыми. 

Далее у нас идет свитч который у нас разбивает адаптер на шапку и айтемы. 

TYPE_HEAD же значит что мы будем работать с шапкой. В нем мы задали леяут с которым будем работать, задали шаблон для отображения месяца и года, и дальше в цикле прогоняем дни недели и отображаем их в TableRow который мы создали выше и потом сетим его во вьюху на леяуте. А ниже просто добавляем лисенеры на клик на кнопки даты, и кнопки вперед и назад. Тут у нас используются колбеки которые будут возвращать события в фрагмент.

TYPE_ITEM же значит что мы будем работать с обычными айтемами которые будут идти ниже шапки. В нем мы так же как и в шапке задали леяут и дальше засетили данные в поля тайтла, дескрипшена и кружочков прогресса и добавили это все в TableRow и потом его же во вьюху на леяуте.

onProgressClick() — метод который отслеживает клик по кружочку прогресса, в нем мы проверяем активен ли наш кружочек для нажатия, и сетим в него данные если да, а дальше по клику если он возможен мы задаем прогресс с помощью метода setProgress().
setProgress() — метод который апдейтит прогресс конкретного кружочка, если дата текущего дня совпадает с датой нажатого кружочка, ведь мы можем отмечать прогресс только в тот день когда мы его выполняем, то есть в текущий.
setRepeatsInfo() — если у нас кружочек прогресса активен то отображаем количество повторений ниже него.
compareDates() — сравнивает даты и если они равны возвращает тру если не — то фолс.
getRealmDate() — возвращает дату которая у нас хранится в БД по нужному айди и позиции.
getFullDate() — так же возаращет дату но уже из другого списка, по позиции.
getCurrentDate() — очевидно что возвращает текущую дату.
setOnClickListener() — сеттер для интерфейса клика.
OnClickCallback — сам интерфейс в котором у нас обозначены он клик лисенеры.

Вот как то так выглядит у нас наш календарь, надо еще привести наши леяуты которые мы используем. Приведу их ниже.

item_calendar_days.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:slanting_progress="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorPrimaryDark">

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical">

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="125dp"
            android:layout_height="60dp"
            android:gravity="center_vertical">

            <ImageView
                android:id="@+id/prevMonth"
                android:src="@mipmap/arrow_white_left"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="10dp"
                android:background="@color/colorPrimaryDark">
            </ImageView>

            <com.project.piechartcallendarexample.ui.calendar.view.CapFirstLetterTextView
                android:id="@+id/currentMonth"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="0.1"
                android:gravity="center_horizontal"
                android:text="date"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:textColor="@color/white" />

            <ImageView
                android:id="@+id/nextMonth"
                android:src="@mipmap/arrow_white_write"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="10dp"
                android:background="@color/colorPrimaryDark">
            </ImageView>
        </LinearLayout>

        <TableLayout
            android:id="@+id/daysRow"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" />

    </LinearLayout>
</LinearLayout>

Тут интересный момент есть, вот этот класс CapFirstLetterTextView, мы его используем что бы сделать месяц отображаемый с большой буквы, так как по умолчанию в классе Calendar он идет с маленькой буквы… Так что пришлось повыпендриваться. Его код я приведу чуть попозже.

item_calendar_progress.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:slanting_progress="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/enter.button.color">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="70dp"
        android:gravity="center_vertical">

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="125dp"
            android:layout_height="60dp"
            android:padding="5dp"
            android:id="@+id/nameLayout"
            android:gravity="center_vertical">

            <LinearLayout
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp">

                <TextView
                    android:id="@+id/title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:singleLine="true"
                    android:text="Large Text"
                    android:textAppearance="?android:attr/textAppearanceMedium" />

                <TextView
                    android:id="@+id/days"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:singleLine="true"
                    android:text="Small Text"
                    android:textAppearance="?android:attr/textAppearanceSmall" />

            </LinearLayout>
        </LinearLayout>

        <TableLayout
            android:id="@+id/progressRow"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal" />

    </LinearLayout>

</LinearLayout>

И не забываем про холдеры, они у нас тоже в отдельных классах. Если кто не знает, холдеры у нас для того что бы можно было обращаться к вьюхам напрямую из леяута.

HeaderHolder.java
import android.view.View;
import android.widget.ImageView;
import android.widget.TableLayout;

import com.project.piechartcallendarexample.R;
import com.project.piechartcallendarexample.ui.calendar.view.CapFirstLetterTextView;

import butterknife.BindColor;
import butterknife.BindView;
import butterknife.ButterKnife;

public class HeaderHolder {
    @BindView(R.id.currentMonth)
    public CapFirstLetterTextView currentMonth;
    @BindView(R.id.prevMonth)
    public ImageView prevMonth;
    @BindView(R.id.nextMonth)
    public ImageView nextMonth;

    @BindView(R.id.daysRow)
    public TableLayout daysRow;

    @BindColor(R.color.colorPrimary)
    public int primaryColor;
    @BindColor(R.color.colorPrimaryDark)
    public int primaryDarcColor;
    @BindColor(R.color.colorGrayLight)
    public int colorGrayLight;
    @BindColor(R.color.enter_button_color)
    public int backgroundColor;

    public HeaderHolder(View view){
        ButterKnife.bind(this, view);
    }
}

ItemHolder.java
import android.view.View;
import android.widget.TableLayout;
import android.widget.TextView;

import com.project.piechartcallendarexample.R;

import butterknife.BindView;
import butterknife.ButterKnife;

public class ItemHolder {
    @BindView(R.id.title)
    public TextView title;
    @BindView(R.id.days)
    public TextView days;

    @BindView(R.id.progressRow)
    public TableLayout progressRow;

    public ItemHolder(View view){
        ButterKnife.bind(this, view);
    }
}

Вы вот щас смотрите на код и такие, а что же мы сетим, ведь в леяутах пусто, и там нету ничего что мы отображаем в адаптере, а я вам скажу что да, у нас есть кастомные две вьюхи еще которые у нас в циклах сетятся, я думаю у тех кто скопировал просто код они как раз подкрашиваются красным. Первая вьюха у нас CalendarDaysView, ее мы используем для шапки и для дней недели, в ней у нас две текствьюхи, день недели и дата.

CalendarDaysView.java
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.project.piechartcallendarexample.R;

import butterknife.BindView;
import butterknife.ButterKnife;

public class CalendarDaysView extends LinearLayout {

    @BindView(R.id.date)
    TextView date;
    @BindView(R.id.dayOfWeek)
    TextView dayOfWeek;

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

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

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

    private void init(Context context) {
        inflate(context, R.layout.view_calendar_days_item, this);
        setOrientation(HORIZONTAL);
        ButterKnife.bind(this);
    }

    public void setDate(String date) {
        this.date.setText(date);
    }

    public void setDayOfWeek(String dayOfWeek) {
        this.dayOfWeek.setText(dayOfWeek);
    }
}

В конструкторе у нас идет сетап метода init() — в нем мы задаем леяут который мы используем для вьюхи, задаем ориентацию горизонтальную для того что бы просто так. И инициализируем баттер найф. А дальше у нас просто два сеттера которые получают и сетят данные в текствьюхи. 

view_calendar_days_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="60dp"
    android:layout_weight="1"
    android:background="@color/colorPrimaryDark"
    android:gravity="center_vertical|center_horizontal"
    android:orientation="vertical"
    android:padding="5dp">

    <TextView
        android:id="@+id/date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="false"
        android:text="0"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textColor="@color/white" />

    <TextView
        android:id="@+id/dayOfWeek"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="false"
        android:text="@string/task.new.target.mon"
        android:textAllCaps="true"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textColor="@color/white" />
</LinearLayout>

А вторая вьюха у нас CalendarProgressView, мы ее используем для отображения кружочков прогресса. В ней у нас собственно кружочек прогресса и текствьюха которая отображает количество повторений.

CalendarProgressView.java
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.project.piechartcallendarexample.R;

import butterknife.BindView;
import butterknife.ButterKnife;

public class CalendarProgressView extends LinearLayout {

    @BindView(R.id.progresBar)
    ProgressView progresBar;
    @BindView(R.id.text)
    TextView text;

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

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

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

    private void init(Context context) {
        inflate(context, R.layout.view_calendar_progress_item, this);
        setOrientation(HORIZONTAL);
        ButterKnife.bind(this);
    }

    public ProgressView getProgressView() {
        return progresBar;
    }

    public TextView getText() {
        return text;
    }
}

Собственно тоже самое что и в предыдущей вьюхе, только тут мы возвращаем текст и прогресс вью вместо того что бы в них сетить что-то, это мы делаем непосредственно в адаптере. Единственное что еще оставляет вопросы, что такое ProgressView, это опять же кастомная вьюха которая умеет очень многое, ее код опять же приведу ниже. А пока посмотрите на этот великлепный xml.

view_calendar_progress_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="70dp"
    android:layout_weight="0.1"
    android:gravity="center_vertical|center_horizontal"
    android:orientation="vertical">

    <com.project.piechartcallendarexample.ui.calendar.view.ProgressView
        android:id="@+id/progresBar"
        android:layout_width="35dp"
        android:layout_height="35dp" />

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="2dp"
        android:text="5/5"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textColor="@color/circle.fifth.color"
        android:textSize="11sp"
        android:visibility="invisible" />
</LinearLayout>

Ну вот собственно почти все готово, осталось добавить пару вьюх которые у нас остались, а это CapFirstLetterTextView и ProgressView. Начнем с простого, создатим кастомную текствьюху для того что бы наш текст всегда стартовал с заглавной буквы.

CapFirstLetterTextView.java
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;

public class CapFirstLetterTextView extends TextView {

    public CapFirstLetterTextView(Context context) {
        super(context);
    }

    public CapFirstLetterTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CapFirstLetterTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setFirstCupText(CharSequence text) {
        String upperString = text.toString().substring(0, 1).toUpperCase() + text.toString().substring(1);
        setText(upperString);
    }
}

setFirstCupText() — метод который работает так же как и с setText, только увеличивает нашу первую букву автоматом.

А дальше у нас еще есть наш ProgressView который красивенько отображает прогресс, умеет его увеличивать и уменьшать. Давайте же посмотрим что это такое и как это работает.

ProgressView.java
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.view.View;

import com.project.piechartcallendarexample.R;
import com.project.piechartcallendarexample.etc.CircleRadiusHelper;

public class ProgressView extends View {

    private final RectF mRect = new RectF();
    private final RectF mRectInner = new RectF();
    private final Paint mPaintForeground = new Paint();
    private final Paint mPaintStroke = new Paint();
    private final Paint mPaintBackground = new Paint();
    private final Paint mPaintErase = new Paint();

    private static final Xfermode PORTER_DUFF_CLEAR = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
    private int mColorForeground = Color.WHITE;
    private int mColorStroke = Color.WHITE;
    private int mColorBackground = Color.BLACK;

    private float mValue = -1f;
    private static final float PADDING = 4;
    private float mPadding;
    private Bitmap mBitmap;
    private Paint emptyData = new Paint(Paint.ANTI_ALIAS_FLAG);
    private int progressMax = 1;

    private static final float INNER_RADIUS_RATIO = 0.84f;

    public ProgressView(Context context) {
        this(context, null);
    }

    public ProgressView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Resources r = context.getResources();
        float scale = r.getDisplayMetrics().density;
        mPadding = scale * PADDING ;
        mPaintForeground.setColor(mColorForeground);
        mPaintForeground.setAntiAlias(true);
        mPaintBackground.setColor(mColorBackground);
        mPaintBackground.setAntiAlias(true);
        mPaintErase.setXfermode(PORTER_DUFF_CLEAR);
        mPaintErase.setAntiAlias(true);
        emptyData.setColor(Color.GRAY);
        setForegroundColor(getResources().getColor(R.color.progressbar_foreground_color));
        setBackgroundColor(getResources().getColor(R.color.progressbar_background_color));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(mBitmap, getWidth() / 2 - mBitmap.getWidth() / 2, getHeight() / 2 - mBitmap.getHeight() / 2, null);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        float bitmapWidth = w - 2 * mPadding;
        float bitmapHeight = h - 2 * mPadding;
        float radius = Math.min(bitmapWidth / 2, bitmapHeight / 2);
        mRect.set(0, 0, bitmapWidth, bitmapHeight);
        radius *= INNER_RADIUS_RATIO;
        mRectInner.set(bitmapWidth / 2f - radius, bitmapHeight / 2f - radius, bitmapWidth / 2f + radius, bitmapHeight / 2f + radius);
        updateBitmap();
    }

    private void updateBitmap() {
        if (mRect == null || mRect.width() == 0) return;

        mBitmap = Bitmap.createBitmap((int) mRect.width(), (int) mRect.height(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(mBitmap);
        float angle = mValue * (3600 / progressMax);
        if (mValue < 0) {
            setBackgroundColor(getResources().getColor(R.color.progressbar_empty_color));
            setBackgroundStrokeColor(getResources().getColor(R.color.round_color_center_circle_text));
            setForegroundColor(getResources().getColor(R.color.progressbar_empty_color));
            canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, CircleRadiusHelper.getRadius(canvas) / 2, emptyData);
        } else {
            if(mValue == 0) {
                emptyData.setStyle(Paint.Style.STROKE);
                canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, CircleRadiusHelper.getRadius(canvas), emptyData);
            } else {
                canvas.drawArc(mRect, -90, 360, true, mPaintBackground);
                canvas.drawArc(mRect, -90, angle, true, mPaintForeground);
            }
        }
        postInvalidate();
    }

    public void setForegroundColor(int color) {
        this.mColorForeground = color;
        mPaintForeground.setColor(color);
        invalidate();
    }

    public void setBackgroundColor(int color) {
        this.mColorBackground = color;
        mPaintBackground.setColor(color);
        invalidate();
    }

    public void setBackgroundStrokeColor(int color) {
        this.mColorStroke = color;
        mPaintStroke.setColor(color);
        mPaintStroke.setStrokeWidth(3);
        mPaintStroke.setStyle(Paint.Style.STROKE);
        invalidate();
    }

    public synchronized void setValue(float value) {
        mValue = value / 10f;
        if(Float.isInfinite(mValue))
            mValue = 0;
        updateBitmap();
    }

    public void setProgressMaximum(int value) {
        this.progressMax = value;
    }
}

По сути это вьюха, которую мы рисуем на канвасе и перерисовываем каждый раз когда выполняем какое действие над ней. У нас в этой вьюхе есть несколько состояний, первое это не активное, второе это активное но пустое, третье активное заполненное на какую-то часть, часть зеленым часть красным, и активная но заполненная полностью зеленым цветом.

В конструкторе мы инициализируем все нужные нам параметры Paint, у нас их там порядка 4 штук, и они имеют разные состояния которые я описал выше. 
onDraw() — рисует «битмап» по центру выделенной области и заполняет ее стилем, в данном случае null, а это значит что он у нас просто пустой.
onSizeChanged() — метод который по изменению размера экрана будет подстраивать вьюху под нужные размеры, что бы она была всегда одинаковой.
updateBitmap() — метод который по обновлении вьюхи проверяет какой стиль к ней применить, если например у нас mValue меньше нуля — то у нас кружочек пустой, если mValue больше нуля то — делаем его заполненным на нужный угол и нужные цвет. А если mValue равно нулю то просто делаем его пустым. А по умолчанию у нас создается серый маленький кружок который не нажимается и не имеет вообще никаких действий. В конце вызываем метод обновления вьюхи.
setForegroundColor(), setBackgroundColor(), setBackgroundStrokeColor() — методы по задаванию нужного стиля кружочку.
setValue() — метод который мы вызываем когда кликаем на кружочек что бы увеличить его диаметер занимаемой поверхности на ту или иную mValue.
setProgressMaximum() — метод в котором мы задаем на сколько кусочков разбиваем наш кружочек, пусть то будет или 1 или 10 или 15, сколько укажите на столько оно и будет раз разбивать.

Тут еще один метод у нас используется, но он был вынесен в отдельный класс, так как я его использовал в других вьюхах в этом же проекте. Вы его уже видели в статье про создание настраиваемых PieChartView. Этот метод расчитывает радиус холста на котором у нас рисуется вьюха.

CircleRadiusHelper.java
import android.graphics.Canvas;

public class CircleRadiusHelper {
    public static float getRadius(Canvas canvas) {
        float width = canvas.getWidth();
        float height = canvas.getHeight();
        float minSize = width > height ? height : width;
        float radius = minSize / 2;
        return radius;
    }
}

Вроде бы все. Весь код который используется для создания такого календаря я вынес в статью. Возможно кому-то эта статья поможет в написании чего-то похожего. В любом случае я хотел поделиться этим опытом, так как он довольно таки много времени у меня отнял :) В будущем надеюсь еще подрефакторить код, возможно опишу в отдельной статье что я отрефакторил и как. Может даже красивше будет, и побыстрей. Поживем увидим.

Исходники:
GitHub

Комментариев нет:

Отправить комментарий