瀏覽代碼

初始化

詹子聪 4 年之前
當前提交
c694ea51ab
共有 50 個文件被更改,包括 1893 次插入0 次删除
  1. 15 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 46 0
      app/build.gradle
  4. 21 0
      app/proguard-rules.pro
  5. 24 0
      app/src/androidTest/java/com/itant/calendarpicker/ExampleInstrumentedTest.kt
  6. 21 0
      app/src/main/AndroidManifest.xml
  7. 31 0
      app/src/main/java/com/itant/calendarpicker/MainActivity.kt
  8. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  9. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  10. 19 0
      app/src/main/res/layout/activity_main.xml
  11. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  12. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  13. 二進制
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  14. 二進制
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  15. 二進制
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  16. 二進制
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  17. 二進制
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  18. 二進制
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  19. 二進制
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  20. 二進制
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  21. 二進制
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  22. 二進制
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  23. 16 0
      app/src/main/res/values-night/themes.xml
  24. 10 0
      app/src/main/res/values/colors.xml
  25. 3 0
      app/src/main/res/values/strings.xml
  26. 16 0
      app/src/main/res/values/themes.xml
  27. 17 0
      app/src/test/java/com/itant/calendarpicker/ExampleUnitTest.kt
  28. 26 0
      build.gradle
  29. 1 0
      calendar/.gitignore
  30. 44 0
      calendar/build.gradle
  31. 0 0
      calendar/consumer-rules.pro
  32. 21 0
      calendar/proguard-rules.pro
  33. 24 0
      calendar/src/androidTest/java/com/itant/calendar/ExampleInstrumentedTest.kt
  34. 5 0
      calendar/src/main/AndroidManifest.xml
  35. 52 0
      calendar/src/main/java/com/itant/calendar/CalendarUtils.java
  36. 33 0
      calendar/src/main/java/com/itant/calendar/DatePickerController.java
  37. 123 0
      calendar/src/main/java/com/itant/calendar/DayPickerView.java
  38. 292 0
      calendar/src/main/java/com/itant/calendar/SimpleMonthAdapter.java
  39. 450 0
      calendar/src/main/java/com/itant/calendar/SimpleMonthView.java
  40. 49 0
      calendar/src/main/res/values/attrs.xml
  41. 7 0
      calendar/src/main/res/values/colors.xml
  42. 9 0
      calendar/src/main/res/values/dimens.xml
  43. 6 0
      calendar/src/main/res/values/strings.xml
  44. 17 0
      calendar/src/test/java/com/itant/calendar/ExampleUnitTest.kt
  45. 19 0
      gradle.properties
  46. 二進制
      gradle/wrapper/gradle-wrapper.jar
  47. 6 0
      gradle/wrapper/gradle-wrapper.properties
  48. 172 0
      gradlew
  49. 84 0
      gradlew.bat
  50. 3 0
      settings.gradle

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 46 - 0
app/build.gradle

@@ -0,0 +1,46 @@
+plugins {
+    id 'com.android.application'
+    id 'kotlin-android'
+}
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.3"
+
+    defaultConfig {
+        applicationId "com.itant.calendarpicker"
+        minSdkVersion 16
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+
+    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    implementation 'androidx.core:core-ktx:1.3.1'
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.google.android.material:material:1.2.1'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
+    implementation project(path: ':calendar')
+    testImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 24 - 0
app/src/androidTest/java/com/itant/calendarpicker/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.itant.calendarpicker
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.itant.calendarpicker", appContext.packageName)
+    }
+}

+ 21 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.itant.calendarpicker">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.CalendarPicker">
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 31 - 0
app/src/main/java/com/itant/calendarpicker/MainActivity.kt

@@ -0,0 +1,31 @@
+package com.itant.calendarpicker
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.itant.calendar.DatePickerController
+import com.itant.calendar.DayPickerView
+import com.itant.calendar.SimpleMonthAdapter
+
+
+class MainActivity : AppCompatActivity(), DatePickerController {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+
+        val pickerView = findViewById<DayPickerView>(R.id.pickerView)
+        pickerView.setController(this);
+    }
+
+    override fun getMaxYear(): Int {
+        return 2022
+    }
+
+    override fun onDayOfMonthSelected(year: Int, month: Int, day: Int) {
+
+    }
+
+    override fun onDateRangeSelected(selectedDays: SimpleMonthAdapter.SelectedDays<SimpleMonthAdapter.CalendarDay>?) {
+
+    }
+}

File diff suppressed because it is too large
+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml


+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 19 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <com.itant.calendar.DayPickerView
+        android:id="@+id/pickerView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:drawRoundRect="true"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

二進制
app/src/main/res/mipmap-hdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


二進制
app/src/main/res/mipmap-mdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


二進制
app/src/main/res/mipmap-xhdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


二進制
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


二進制
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 16 - 0
app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.CalendarPicker" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 10 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">CalendarPicker</string>
+</resources>

+ 16 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.CalendarPicker" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 17 - 0
app/src/test/java/com/itant/calendarpicker/ExampleUnitTest.kt

@@ -0,0 +1,17 @@
+package com.itant.calendarpicker
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}

+ 26 - 0
build.gradle

@@ -0,0 +1,26 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    ext.kotlin_version = "1.4.20"
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath "com.android.tools.build:gradle:4.2.0-beta02"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 1 - 0
calendar/.gitignore

@@ -0,0 +1 @@
+/build

+ 44 - 0
calendar/build.gradle

@@ -0,0 +1,44 @@
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+}
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.3"
+
+    defaultConfig {
+        minSdkVersion 16
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+
+    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    implementation 'androidx.core:core-ktx:1.3.1'
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.google.android.material:material:1.2.1'
+    testImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+}

+ 0 - 0
calendar/consumer-rules.pro


+ 21 - 0
calendar/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 24 - 0
calendar/src/androidTest/java/com/itant/calendar/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.itant.calendar
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.itant.calendar.test", appContext.packageName)
+    }
+}

+ 5 - 0
calendar/src/main/AndroidManifest.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.itant.calendar">
+
+</manifest>

+ 52 - 0
calendar/src/main/java/com/itant/calendar/CalendarUtils.java

@@ -0,0 +1,52 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.itant.calendar;
+
+import java.util.Calendar;
+
+
+public class CalendarUtils
+{
+	public static int getDaysInMonth(int month, int year) {
+        switch (month) {
+            case Calendar.JANUARY:
+            case Calendar.MARCH:
+            case Calendar.MAY:
+            case Calendar.JULY:
+            case Calendar.AUGUST:
+            case Calendar.OCTOBER:
+            case Calendar.DECEMBER:
+                return 31;
+            case Calendar.APRIL:
+            case Calendar.JUNE:
+            case Calendar.SEPTEMBER:
+            case Calendar.NOVEMBER:
+                return 30;
+            case Calendar.FEBRUARY:
+                return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) ? 29 : 28;
+            default:
+                throw new IllegalArgumentException("Invalid Month");
+        }
+	}
+}

+ 33 - 0
calendar/src/main/java/com/itant/calendar/DatePickerController.java

@@ -0,0 +1,33 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.itant.calendar;
+
+public interface DatePickerController {
+	public abstract int getMaxYear();
+
+	public abstract void onDayOfMonthSelected(int year, int month, int day);
+
+    public abstract void onDateRangeSelected(final SimpleMonthAdapter.SelectedDays<SimpleMonthAdapter.CalendarDay> selectedDays);
+
+}

+ 123 - 0
calendar/src/main/java/com/itant/calendar/DayPickerView.java

@@ -0,0 +1,123 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.itant.calendar;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class DayPickerView extends RecyclerView
+{
+    protected Context mContext;
+	protected SimpleMonthAdapter mAdapter;
+	private DatePickerController mController;
+    protected int mCurrentScrollState = 0;
+	protected long mPreviousScrollPosition;
+	protected int mPreviousScrollState = 0;
+    private TypedArray typedArray;
+    private OnScrollListener onScrollListener;
+
+    public DayPickerView(Context context)
+    {
+        this(context, null);
+    }
+
+    public DayPickerView(Context context, AttributeSet attrs)
+    {
+        this(context, attrs, 0);
+    }
+
+    public DayPickerView(Context context, AttributeSet attrs, int defStyle)
+    {
+        super(context, attrs, defStyle);
+        if (!isInEditMode())
+        {
+            typedArray = context.obtainStyledAttributes(attrs, R.styleable.DayPickerView);
+            setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+            init(context);
+        }
+    }
+
+    public void setController(DatePickerController mController)
+    {
+        this.mController = mController;
+        setUpAdapter();
+        setAdapter(mAdapter);
+    }
+
+
+	public void init(Context paramContext) {
+        setLayoutManager(new LinearLayoutManager(paramContext));
+		mContext = paramContext;
+		setUpListView();
+
+        onScrollListener = new OnScrollListener()
+        {
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy)
+            {
+                super.onScrolled(recyclerView, dx, dy);
+                final SimpleMonthView child = (SimpleMonthView) recyclerView.getChildAt(0);
+                if (child == null) {
+                    return;
+                }
+
+                mPreviousScrollPosition = dy;
+                mPreviousScrollState = mCurrentScrollState;
+            }
+        };
+	}
+
+
+	protected void setUpAdapter() {
+		if (mAdapter == null) {
+			mAdapter = new SimpleMonthAdapter(getContext(), mController, typedArray);
+        }
+		mAdapter.notifyDataSetChanged();
+	}
+
+	protected void setUpListView() {
+		setVerticalScrollBarEnabled(false);
+		setOnScrollListener(onScrollListener);
+		setFadingEdgeLength(0);
+	}
+
+    public SimpleMonthAdapter.SelectedDays<SimpleMonthAdapter.CalendarDay> getSelectedDays()
+    {
+        return mAdapter.getSelectedDays();
+    }
+
+    protected DatePickerController getController()
+    {
+        return mController;
+    }
+
+    protected TypedArray getTypedArray()
+    {
+        return typedArray;
+    }
+}

+ 292 - 0
calendar/src/main/java/com/itant/calendar/SimpleMonthAdapter.java

@@ -0,0 +1,292 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.itant.calendar;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.AbsListView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+
+public class SimpleMonthAdapter extends RecyclerView.Adapter<SimpleMonthAdapter.ViewHolder> implements SimpleMonthView.OnDayClickListener {
+    protected static final int MONTHS_IN_YEAR = 12;
+    private final TypedArray typedArray;
+	private final Context mContext;
+	private final DatePickerController mController;
+    private final Calendar calendar;
+    private final SelectedDays<CalendarDay> selectedDays;
+    private final Integer firstMonth;
+    private final Integer lastMonth;
+
+	public SimpleMonthAdapter(Context context, DatePickerController datePickerController, TypedArray typedArray) {
+        this.typedArray = typedArray;
+        calendar = Calendar.getInstance();
+        firstMonth = typedArray.getInt(R.styleable.DayPickerView_firstMonth, calendar.get(Calendar.MONTH));
+        lastMonth = typedArray.getInt(R.styleable.DayPickerView_lastMonth, (calendar.get(Calendar.MONTH) - 1) % MONTHS_IN_YEAR);
+        selectedDays = new SelectedDays<>();
+		mContext = context;
+		mController = datePickerController;
+		init();
+	}
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i)
+    {
+        final SimpleMonthView simpleMonthView = new SimpleMonthView(mContext, typedArray);
+        return new ViewHolder(simpleMonthView, this);
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder viewHolder, int position)
+    {
+        final SimpleMonthView v = viewHolder.simpleMonthView;
+        final HashMap<String, Integer> drawingParams = new HashMap<String, Integer>();
+        int month;
+        int year;
+
+        month = (firstMonth + (position % MONTHS_IN_YEAR)) % MONTHS_IN_YEAR;
+        year = position / MONTHS_IN_YEAR + calendar.get(Calendar.YEAR) + ((firstMonth + (position % MONTHS_IN_YEAR)) / MONTHS_IN_YEAR);
+
+        int selectedFirstDay = -1;
+        int selectedLastDay = -1;
+        int selectedFirstMonth = -1;
+        int selectedLastMonth = -1;
+        int selectedFirstYear = -1;
+        int selectedLastYear = -1;
+
+        if (selectedDays.getFirst() != null)
+        {
+            selectedFirstDay = selectedDays.getFirst().day;
+            selectedFirstMonth = selectedDays.getFirst().month;
+            selectedFirstYear = selectedDays.getFirst().year;
+        }
+
+        if (selectedDays.getLast() != null)
+        {
+            selectedLastDay = selectedDays.getLast().day;
+            selectedLastMonth = selectedDays.getLast().month;
+            selectedLastYear = selectedDays.getLast().year;
+        }
+
+        v.reuse();
+
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_BEGIN_YEAR, selectedFirstYear);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_LAST_YEAR, selectedLastYear);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_BEGIN_MONTH, selectedFirstMonth);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_LAST_MONTH, selectedLastMonth);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_BEGIN_DAY, selectedFirstDay);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_LAST_DAY, selectedLastDay);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_YEAR, year);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_MONTH, month);
+        drawingParams.put(SimpleMonthView.VIEW_PARAMS_WEEK_START, calendar.getFirstDayOfWeek());
+        v.setMonthParams(drawingParams);
+        v.invalidate();
+    }
+
+    public long getItemId(int position) {
+		return position;
+	}
+
+    @Override
+    public int getItemCount()
+    {
+        int itemCount = (((mController.getMaxYear() - calendar.get(Calendar.YEAR)) + 1) * MONTHS_IN_YEAR);
+
+        if (firstMonth != -1)
+            itemCount -= firstMonth;
+
+        if (lastMonth != -1)
+            itemCount -= (MONTHS_IN_YEAR - lastMonth) - 1;
+
+        return itemCount;
+    }
+
+    public static class ViewHolder extends RecyclerView.ViewHolder
+    {
+        final SimpleMonthView simpleMonthView;
+
+        public ViewHolder(View itemView, SimpleMonthView.OnDayClickListener onDayClickListener)
+        {
+            super(itemView);
+            simpleMonthView = (SimpleMonthView) itemView;
+            simpleMonthView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+            simpleMonthView.setClickable(true);
+            simpleMonthView.setOnDayClickListener(onDayClickListener);
+        }
+    }
+
+	protected void init() {
+        if (typedArray.getBoolean(R.styleable.DayPickerView_currentDaySelected, false))
+            onDayTapped(new CalendarDay(System.currentTimeMillis()));
+	}
+
+	public void onDayClick(SimpleMonthView simpleMonthView, CalendarDay calendarDay) {
+		if (calendarDay != null) {
+			onDayTapped(calendarDay);
+        }
+	}
+
+	protected void onDayTapped(CalendarDay calendarDay) {
+		mController.onDayOfMonthSelected(calendarDay.year, calendarDay.month, calendarDay.day);
+		setSelectedDay(calendarDay);
+	}
+
+	public void setSelectedDay(CalendarDay calendarDay) {
+        if (selectedDays.getFirst() != null && selectedDays.getLast() == null)
+        {
+            selectedDays.setLast(calendarDay);
+
+            if (selectedDays.getFirst().month < calendarDay.month)
+            {
+                for (int i = 0; i < selectedDays.getFirst().month - calendarDay.month - 1; ++i)
+                    mController.onDayOfMonthSelected(selectedDays.getFirst().year, selectedDays.getFirst().month + i, selectedDays.getFirst().day);
+            }
+
+            mController.onDateRangeSelected(selectedDays);
+        }
+        else if (selectedDays.getLast() != null)
+        {
+            selectedDays.setFirst(calendarDay);
+            selectedDays.setLast(null);
+        }
+        else
+            selectedDays.setFirst(calendarDay);
+
+		notifyDataSetChanged();
+	}
+
+	public static class CalendarDay implements Serializable
+    {
+        private static final long serialVersionUID = -5456695978688356202L;
+        private Calendar calendar;
+
+		int day;
+		int month;
+		int year;
+
+		public CalendarDay() {
+			setTime(System.currentTimeMillis());
+		}
+
+		public CalendarDay(int year, int month, int day) {
+			setDay(year, month, day);
+		}
+
+		public CalendarDay(long timeInMillis) {
+			setTime(timeInMillis);
+		}
+
+		public CalendarDay(Calendar calendar) {
+			year = calendar.get(Calendar.YEAR);
+			month = calendar.get(Calendar.MONTH);
+			day = calendar.get(Calendar.DAY_OF_MONTH);
+		}
+
+		private void setTime(long timeInMillis) {
+			if (calendar == null) {
+				calendar = Calendar.getInstance();
+            }
+			calendar.setTimeInMillis(timeInMillis);
+			month = this.calendar.get(Calendar.MONTH);
+			year = this.calendar.get(Calendar.YEAR);
+			day = this.calendar.get(Calendar.DAY_OF_MONTH);
+		}
+
+		public void set(CalendarDay calendarDay) {
+		    year = calendarDay.year;
+			month = calendarDay.month;
+			day = calendarDay.day;
+		}
+
+		public void setDay(int year, int month, int day) {
+			this.year = year;
+			this.month = month;
+			this.day = day;
+		}
+
+        public Date getDate()
+        {
+            if (calendar == null) {
+                calendar = Calendar.getInstance();
+            }
+            calendar.set(year, month, day);
+            return calendar.getTime();
+        }
+
+        @Override
+        public String toString()
+        {
+            final StringBuilder stringBuilder = new StringBuilder();
+            stringBuilder.append("{ year: ");
+            stringBuilder.append(year);
+            stringBuilder.append(", month: ");
+            stringBuilder.append(month);
+            stringBuilder.append(", day: ");
+            stringBuilder.append(day);
+            stringBuilder.append(" }");
+
+            return stringBuilder.toString();
+        }
+    }
+
+    public SelectedDays<CalendarDay> getSelectedDays()
+    {
+        return selectedDays;
+    }
+
+    public static class SelectedDays<K> implements Serializable
+    {
+        private static final long serialVersionUID = 3942549765282708376L;
+        private K first;
+        private K last;
+
+        public K getFirst()
+        {
+            return first;
+        }
+
+        public void setFirst(K first)
+        {
+            this.first = first;
+        }
+
+        public K getLast()
+        {
+            return last;
+        }
+
+        public void setLast(K last)
+        {
+            this.last = last;
+        }
+    }
+}

+ 450 - 0
calendar/src/main/java/com/itant/calendar/SimpleMonthView.java

@@ -0,0 +1,450 @@
+/***********************************************************************************
+ * The MIT License (MIT)
+
+ * Copyright (c) 2014 Robin Chutaux
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ***********************************************************************************/
+package com.itant.calendar;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.security.InvalidParameterException;
+import java.text.DateFormatSymbols;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Locale;
+
+class SimpleMonthView extends View
+{
+
+    public static final String VIEW_PARAMS_HEIGHT = "height";
+    public static final String VIEW_PARAMS_MONTH = "month";
+    public static final String VIEW_PARAMS_YEAR = "year";
+    public static final String VIEW_PARAMS_SELECTED_BEGIN_DAY = "selected_begin_day";
+    public static final String VIEW_PARAMS_SELECTED_LAST_DAY = "selected_last_day";
+    public static final String VIEW_PARAMS_SELECTED_BEGIN_MONTH = "selected_begin_month";
+    public static final String VIEW_PARAMS_SELECTED_LAST_MONTH = "selected_last_month";
+    public static final String VIEW_PARAMS_SELECTED_BEGIN_YEAR = "selected_begin_year";
+    public static final String VIEW_PARAMS_SELECTED_LAST_YEAR = "selected_last_year";
+    public static final String VIEW_PARAMS_WEEK_START = "week_start";
+
+    private static final int SELECTED_CIRCLE_ALPHA = 128;
+    protected static int DEFAULT_HEIGHT = 32;
+    protected static final int DEFAULT_NUM_ROWS = 6;
+	protected static int DAY_SELECTED_CIRCLE_SIZE;
+	protected static int DAY_SEPARATOR_WIDTH = 1;
+	protected static int MINI_DAY_NUMBER_TEXT_SIZE;
+	protected static int MIN_HEIGHT = 10;
+	protected static int MONTH_DAY_LABEL_TEXT_SIZE;
+	protected static int MONTH_HEADER_SIZE;
+	protected static int MONTH_LABEL_TEXT_SIZE;
+
+    protected int mPadding = 0;
+
+    private String mDayOfWeekTypeface;
+    private String mMonthTitleTypeface;
+
+    protected Paint mMonthDayLabelPaint;
+    protected Paint mMonthNumPaint;
+    protected Paint mMonthTitleBGPaint;
+    protected Paint mMonthTitlePaint;
+    protected Paint mSelectedCirclePaint;
+    protected int mCurrentDayTextColor;
+    protected int mMonthTextColor;
+    protected int mDayTextColor;
+    protected int mDayNumColor;
+    protected int mMonthTitleBGColor;
+    protected int mPreviousDayColor;
+    protected int mSelectedDaysColor;
+
+    private final StringBuilder mStringBuilder;
+
+    protected boolean mHasToday = false;
+    protected boolean mIsPrev = false;
+    protected int mSelectedBeginDay = -1;
+    protected int mSelectedLastDay = -1;
+    protected int mSelectedBeginMonth = -1;
+    protected int mSelectedLastMonth = -1;
+    protected int mSelectedBeginYear = -1;
+    protected int mSelectedLastYear = -1;
+    protected int mToday = -1;
+    protected int mWeekStart = 1;
+    protected int mNumDays = 7;
+    protected int mNumCells = mNumDays;
+    private int mDayOfWeekStart = 0;
+    protected int mMonth;
+    protected Boolean mDrawRect;
+    protected int mRowHeight = DEFAULT_HEIGHT;
+    protected int mWidth;
+    protected int mYear;
+    final Time today;
+
+	private final Calendar mCalendar;
+	private final Calendar mDayLabelCalendar;
+    private final Boolean isPrevDayEnabled;
+
+    private int mNumRows = DEFAULT_NUM_ROWS;
+
+	private DateFormatSymbols mDateFormatSymbols = new DateFormatSymbols();
+
+    private OnDayClickListener mOnDayClickListener;
+
+    public SimpleMonthView(Context context, TypedArray typedArray)
+    {
+        super(context);
+
+        Resources resources = context.getResources();
+        mDayLabelCalendar = Calendar.getInstance();
+        mCalendar = Calendar.getInstance();
+        today = new Time(Time.getCurrentTimezone());
+        today.setToNow();
+        //mDayOfWeekTypeface = resources.getString(R.string.sans_serif);
+        //mDayOfWeekTypeface = resources.getString(R.string.sans_serif);
+        mMonthTitleTypeface = "sans-serif";
+        mMonthTitleTypeface = "sans-serif";
+        mCurrentDayTextColor = typedArray.getColor(R.styleable.DayPickerView_colorCurrentDay, resources.getColor(R.color.normal_day));
+        mMonthTextColor = typedArray.getColor(R.styleable.DayPickerView_colorMonthName, resources.getColor(R.color.normal_day));
+        mDayTextColor = typedArray.getColor(R.styleable.DayPickerView_colorDayName, resources.getColor(R.color.normal_day));
+        mDayNumColor = typedArray.getColor(R.styleable.DayPickerView_colorNormalDay, resources.getColor(R.color.normal_day));
+        mPreviousDayColor = typedArray.getColor(R.styleable.DayPickerView_colorPreviousDay, resources.getColor(R.color.normal_day));
+        mSelectedDaysColor = typedArray.getColor(R.styleable.DayPickerView_colorSelectedDayBackground, resources.getColor(R.color.selected_day_background));
+        mMonthTitleBGColor = typedArray.getColor(R.styleable.DayPickerView_colorSelectedDayText, resources.getColor(R.color.selected_day_text));
+
+        mDrawRect = typedArray.getBoolean(R.styleable.DayPickerView_drawRoundRect, false);
+
+        mStringBuilder = new StringBuilder(50);
+
+        MINI_DAY_NUMBER_TEXT_SIZE = typedArray.getDimensionPixelSize(R.styleable.DayPickerView_textSizeDay, resources.getDimensionPixelSize(R.dimen.text_size_day));
+        MONTH_LABEL_TEXT_SIZE = typedArray.getDimensionPixelSize(R.styleable.DayPickerView_textSizeMonth, resources.getDimensionPixelSize(R.dimen.text_size_month));
+        MONTH_DAY_LABEL_TEXT_SIZE = typedArray.getDimensionPixelSize(R.styleable.DayPickerView_textSizeDayName, resources.getDimensionPixelSize(R.dimen.text_size_day_name));
+        MONTH_HEADER_SIZE = typedArray.getDimensionPixelOffset(R.styleable.DayPickerView_headerMonthHeight, resources.getDimensionPixelOffset(R.dimen.header_month_height));
+        DAY_SELECTED_CIRCLE_SIZE = typedArray.getDimensionPixelSize(R.styleable.DayPickerView_selectedDayRadius, resources.getDimensionPixelOffset(R.dimen.selected_day_radius));
+
+        mRowHeight = ((typedArray.getDimensionPixelSize(R.styleable.DayPickerView_calendarHeight, resources.getDimensionPixelOffset(R.dimen.calendar_height)) - MONTH_HEADER_SIZE) / 6);
+
+        isPrevDayEnabled = typedArray.getBoolean(R.styleable.DayPickerView_enablePreviousDay, true);
+
+        initView();
+
+    }
+
+    private int calculateNumRows() {
+        int offset = findDayOffset();
+        int dividend = (offset + mNumCells) / mNumDays;
+        int remainder = (offset + mNumCells) % mNumDays;
+        return (dividend + (remainder > 0 ? 1 : 0));
+	}
+
+	private void drawMonthDayLabels(Canvas canvas) {
+        int y = MONTH_HEADER_SIZE - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
+        int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+
+        for (int i = 0; i < mNumDays; i++) {
+            int calendarDay = (i + mWeekStart) % mNumDays;
+            int x = (2 * i + 1) * dayWidthHalf + mPadding;
+            mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
+            canvas.drawText(mDateFormatSymbols.getShortWeekdays()[mDayLabelCalendar.get(Calendar.DAY_OF_WEEK)].toUpperCase(Locale.getDefault()), x, y, mMonthDayLabelPaint);
+        }
+	}
+
+	private void drawMonthTitle(Canvas canvas) {
+        int x = (mWidth + 2 * mPadding) / 2;
+        int y = (MONTH_HEADER_SIZE - MONTH_DAY_LABEL_TEXT_SIZE) / 2 + (MONTH_LABEL_TEXT_SIZE / 3);
+        StringBuilder stringBuilder = new StringBuilder(getMonthAndYearString().toLowerCase());
+        stringBuilder.setCharAt(0, Character.toUpperCase(stringBuilder.charAt(0)));
+        canvas.drawText(stringBuilder.toString(), x, y, mMonthTitlePaint);
+	}
+
+	private int findDayOffset() {
+        return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
+                - mWeekStart;
+	}
+
+	private String getMonthAndYearString() {
+        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_NO_MONTH_DAY;
+        mStringBuilder.setLength(0);
+        long millis = mCalendar.getTimeInMillis();
+        return DateUtils.formatDateRange(getContext(), millis, millis, flags);
+    }
+
+	private void onDayClick(SimpleMonthAdapter.CalendarDay calendarDay) {
+		if (mOnDayClickListener != null && (isPrevDayEnabled || !((calendarDay.month == today.month) && (calendarDay.year == today.year) && calendarDay.day < today.monthDay))) {
+			mOnDayClickListener.onDayClick(this, calendarDay);
+        }
+	}
+
+	private boolean sameDay(int monthDay, Time time) {
+		return (mYear == time.year) && (mMonth == time.month) && (monthDay == time.monthDay);
+	}
+
+    private boolean prevDay(int monthDay, Time time) {
+        return ((mYear < time.year)) || (mYear == time.year && mMonth < time.month) || ( mMonth == time.month && monthDay < time.monthDay);
+    }
+
+	protected void drawMonthNums(Canvas canvas) {
+		int y = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH + MONTH_HEADER_SIZE;
+		int paddingDay = (mWidth - 2 * mPadding) / (2 * mNumDays);
+		int dayOffset = findDayOffset();
+		int day = 1;
+
+		while (day <= mNumCells) {
+			int x = paddingDay * (1 + dayOffset * 2) + mPadding;
+			if ((mMonth == mSelectedBeginMonth && mSelectedBeginDay == day && mSelectedBeginYear == mYear) || (mMonth == mSelectedLastMonth && mSelectedLastDay == day && mSelectedLastYear == mYear)) {
+                if (mDrawRect)
+                {
+                    RectF rectF = new RectF(x - DAY_SELECTED_CIRCLE_SIZE, (y  - MINI_DAY_NUMBER_TEXT_SIZE / 3) - DAY_SELECTED_CIRCLE_SIZE, x + DAY_SELECTED_CIRCLE_SIZE, (y  - MINI_DAY_NUMBER_TEXT_SIZE / 3) + DAY_SELECTED_CIRCLE_SIZE);
+                    canvas.drawRoundRect(rectF, 10.0f, 10.0f,mSelectedCirclePaint);
+                }
+                else
+                    canvas.drawCircle(x, y - MINI_DAY_NUMBER_TEXT_SIZE / 3, DAY_SELECTED_CIRCLE_SIZE, mSelectedCirclePaint);
+            }
+            if (mHasToday && (mToday == day)) {
+                mMonthNumPaint.setColor(mCurrentDayTextColor);
+                mMonthNumPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+            } else {
+				mMonthNumPaint.setColor(mDayNumColor);
+                mMonthNumPaint.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
+            }
+
+            if ((mMonth == mSelectedBeginMonth && mSelectedBeginDay == day && mSelectedBeginYear == mYear) || (mMonth == mSelectedLastMonth && mSelectedLastDay == day && mSelectedLastYear == mYear))
+                mMonthNumPaint.setColor(mMonthTitleBGColor);
+
+            if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear == mSelectedLastYear &&
+                    mSelectedBeginMonth == mSelectedLastMonth &&
+                    mSelectedBeginDay == mSelectedLastDay &&
+                    day == mSelectedBeginDay &&
+                    mMonth == mSelectedBeginMonth &&
+                    mYear == mSelectedBeginYear))
+                mMonthNumPaint.setColor(mSelectedDaysColor);
+
+            if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear == mSelectedLastYear && mSelectedBeginYear == mYear) &&
+                    (((mMonth == mSelectedBeginMonth && mSelectedLastMonth == mSelectedBeginMonth) && ((mSelectedBeginDay < mSelectedLastDay && day > mSelectedBeginDay && day < mSelectedLastDay) || (mSelectedBeginDay > mSelectedLastDay && day < mSelectedBeginDay && day > mSelectedLastDay))) ||
+                    ((mSelectedBeginMonth < mSelectedLastMonth && mMonth == mSelectedBeginMonth && day > mSelectedBeginDay) || (mSelectedBeginMonth < mSelectedLastMonth && mMonth == mSelectedLastMonth && day < mSelectedLastDay)) ||
+                    ((mSelectedBeginMonth > mSelectedLastMonth && mMonth == mSelectedBeginMonth && day < mSelectedBeginDay) || (mSelectedBeginMonth > mSelectedLastMonth && mMonth == mSelectedLastMonth && day > mSelectedLastDay))))
+            {
+                mMonthNumPaint.setColor(mSelectedDaysColor);
+            }
+
+            if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear != mSelectedLastYear && ((mSelectedBeginYear == mYear && mMonth == mSelectedBeginMonth) || (mSelectedLastYear == mYear && mMonth == mSelectedLastMonth)) &&
+                    (((mSelectedBeginMonth < mSelectedLastMonth && mMonth == mSelectedBeginMonth && day < mSelectedBeginDay) || (mSelectedBeginMonth < mSelectedLastMonth && mMonth == mSelectedLastMonth && day > mSelectedLastDay)) ||
+                    ((mSelectedBeginMonth > mSelectedLastMonth && mMonth == mSelectedBeginMonth && day > mSelectedBeginDay) || (mSelectedBeginMonth > mSelectedLastMonth && mMonth == mSelectedLastMonth && day < mSelectedLastDay)))))
+            {
+                mMonthNumPaint.setColor(mSelectedDaysColor);
+            }
+
+            if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear == mSelectedLastYear && mYear == mSelectedBeginYear) &&
+                    ((mMonth > mSelectedBeginMonth && mMonth < mSelectedLastMonth && mSelectedBeginMonth < mSelectedLastMonth) ||
+                     (mMonth < mSelectedBeginMonth && mMonth > mSelectedLastMonth && mSelectedBeginMonth > mSelectedLastMonth)))
+            {
+                mMonthNumPaint.setColor(mSelectedDaysColor);
+            }
+
+            if ((mSelectedBeginDay != -1 && mSelectedLastDay != -1 && mSelectedBeginYear != mSelectedLastYear) &&
+                    ((mSelectedBeginYear < mSelectedLastYear && ((mMonth > mSelectedBeginMonth && mYear == mSelectedBeginYear) || (mMonth < mSelectedLastMonth && mYear == mSelectedLastYear))) ||
+                     (mSelectedBeginYear > mSelectedLastYear && ((mMonth < mSelectedBeginMonth && mYear == mSelectedBeginYear) || (mMonth > mSelectedLastMonth && mYear == mSelectedLastYear)))))
+            {
+                mMonthNumPaint.setColor(mSelectedDaysColor);
+            }
+
+            if (!isPrevDayEnabled && prevDay(day, today) && today.month == mMonth && today.year == mYear)
+            {
+                mMonthNumPaint.setColor(mPreviousDayColor);
+                mMonthNumPaint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
+            }
+
+			canvas.drawText(String.format("%d", day), x, y, mMonthNumPaint);
+
+			dayOffset++;
+			if (dayOffset == mNumDays) {
+				dayOffset = 0;
+				y += mRowHeight;
+			}
+			day++;
+		}
+	}
+
+	public SimpleMonthAdapter.CalendarDay getDayFromLocation(float x, float y) {
+		int padding = mPadding;
+		if ((x < padding) || (x > mWidth - mPadding)) {
+			return null;
+		}
+
+		int yDay = (int) (y - MONTH_HEADER_SIZE) / mRowHeight;
+		int day = 1 + ((int) ((x - padding) * mNumDays / (mWidth - padding - mPadding)) - findDayOffset()) + yDay * mNumDays;
+
+        if (mMonth > 11 || mMonth < 0 || CalendarUtils.getDaysInMonth(mMonth, mYear) < day || day < 1)
+            return null;
+
+		return new SimpleMonthAdapter.CalendarDay(mYear, mMonth, day);
+	}
+
+	protected void initView() {
+        mMonthTitlePaint = new Paint();
+        mMonthTitlePaint.setFakeBoldText(true);
+        mMonthTitlePaint.setAntiAlias(true);
+        mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
+        mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
+        mMonthTitlePaint.setColor(mMonthTextColor);
+        mMonthTitlePaint.setTextAlign(Align.CENTER);
+        mMonthTitlePaint.setStyle(Style.FILL);
+
+        mMonthTitleBGPaint = new Paint();
+        mMonthTitleBGPaint.setFakeBoldText(true);
+        mMonthTitleBGPaint.setAntiAlias(true);
+        mMonthTitleBGPaint.setColor(mMonthTitleBGColor);
+        mMonthTitleBGPaint.setTextAlign(Align.CENTER);
+        mMonthTitleBGPaint.setStyle(Style.FILL);
+
+        mSelectedCirclePaint = new Paint();
+        mSelectedCirclePaint.setFakeBoldText(true);
+        mSelectedCirclePaint.setAntiAlias(true);
+        mSelectedCirclePaint.setColor(mSelectedDaysColor);
+        mSelectedCirclePaint.setTextAlign(Align.CENTER);
+        mSelectedCirclePaint.setStyle(Style.FILL);
+        mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+
+        mMonthDayLabelPaint = new Paint();
+        mMonthDayLabelPaint.setAntiAlias(true);
+        mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
+        mMonthDayLabelPaint.setColor(mDayTextColor);
+        mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
+        mMonthDayLabelPaint.setStyle(Style.FILL);
+        mMonthDayLabelPaint.setTextAlign(Align.CENTER);
+        mMonthDayLabelPaint.setFakeBoldText(true);
+
+        mMonthNumPaint = new Paint();
+        mMonthNumPaint.setAntiAlias(true);
+        mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
+        mMonthNumPaint.setStyle(Style.FILL);
+        mMonthNumPaint.setTextAlign(Align.CENTER);
+        mMonthNumPaint.setFakeBoldText(false);
+	}
+
+	protected void onDraw(Canvas canvas) {
+		drawMonthTitle(canvas);
+		drawMonthDayLabels(canvas);
+		drawMonthNums(canvas);
+	}
+
+	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+		setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows + MONTH_HEADER_SIZE);
+	}
+
+	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+		mWidth = w;
+	}
+
+	public boolean onTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            SimpleMonthAdapter.CalendarDay calendarDay = getDayFromLocation(event.getX(), event.getY());
+            if (calendarDay != null) {
+                onDayClick(calendarDay);
+            }
+        }
+        return true;
+	}
+
+	public void reuse() {
+        mNumRows = DEFAULT_NUM_ROWS;
+		requestLayout();
+	}
+
+	public void setMonthParams(HashMap<String, Integer> params) {
+        if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
+            throw new InvalidParameterException("You must specify month and year for this view");
+        }
+		setTag(params);
+
+        if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
+            mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
+            if (mRowHeight < MIN_HEIGHT) {
+                mRowHeight = MIN_HEIGHT;
+            }
+        }
+        if (params.containsKey(VIEW_PARAMS_SELECTED_BEGIN_DAY)) {
+            mSelectedBeginDay = params.get(VIEW_PARAMS_SELECTED_BEGIN_DAY);
+        }
+        if (params.containsKey(VIEW_PARAMS_SELECTED_LAST_DAY)) {
+            mSelectedLastDay = params.get(VIEW_PARAMS_SELECTED_LAST_DAY);
+        }
+        if (params.containsKey(VIEW_PARAMS_SELECTED_BEGIN_MONTH)) {
+            mSelectedBeginMonth = params.get(VIEW_PARAMS_SELECTED_BEGIN_MONTH);
+        }
+        if (params.containsKey(VIEW_PARAMS_SELECTED_LAST_MONTH)) {
+            mSelectedLastMonth = params.get(VIEW_PARAMS_SELECTED_LAST_MONTH);
+        }
+        if (params.containsKey(VIEW_PARAMS_SELECTED_BEGIN_YEAR)) {
+            mSelectedBeginYear = params.get(VIEW_PARAMS_SELECTED_BEGIN_YEAR);
+        }
+        if (params.containsKey(VIEW_PARAMS_SELECTED_LAST_YEAR)) {
+            mSelectedLastYear = params.get(VIEW_PARAMS_SELECTED_LAST_YEAR);
+        }
+
+        mMonth = params.get(VIEW_PARAMS_MONTH);
+        mYear = params.get(VIEW_PARAMS_YEAR);
+
+        mHasToday = false;
+        mToday = -1;
+
+		mCalendar.set(Calendar.MONTH, mMonth);
+		mCalendar.set(Calendar.YEAR, mYear);
+		mCalendar.set(Calendar.DAY_OF_MONTH, 1);
+		mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
+
+        if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
+            mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
+        } else {
+            mWeekStart = mCalendar.getFirstDayOfWeek();
+        }
+
+        mNumCells = CalendarUtils.getDaysInMonth(mMonth, mYear);
+        for (int i = 0; i < mNumCells; i++) {
+            final int day = i + 1;
+            if (sameDay(day, today)) {
+                mHasToday = true;
+                mToday = day;
+            }
+
+            mIsPrev = prevDay(day, today);
+        }
+
+        mNumRows = calculateNumRows();
+	}
+
+	public void setOnDayClickListener(OnDayClickListener onDayClickListener) {
+		mOnDayClickListener = onDayClickListener;
+	}
+
+	public static abstract interface OnDayClickListener {
+		public abstract void onDayClick(SimpleMonthView simpleMonthView, SimpleMonthAdapter.CalendarDay calendarDay);
+	}
+}

+ 49 - 0
calendar/src/main/res/values/attrs.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="DayPickerView">
+        <attr name="colorCurrentDay" format="color"/>
+        <attr name="colorSelectedDayBackground" format="color"/>
+        <attr name="colorSelectedDayText" format="color"/>
+        <attr name="colorPreviousDay" format="color"/>
+        <attr name="colorNormalDay" format="color" />
+        <attr name="colorMonthName" format="color" />
+        <attr name="colorDayName" format="color" />
+        <attr name="textSizeDay" format="dimension"/>
+        <attr name="textSizeMonth" format="dimension" />
+        <attr name="textSizeDayName" format="dimension" />
+        <attr name="headerMonthHeight" format="dimension" />
+        <attr name="selectedDayRadius" format="dimension" />
+        <attr name="calendarHeight" format="dimension" />
+        <attr name="enablePreviousDay" format="boolean" />
+        <attr name="currentDaySelected" format="boolean" />
+        <attr name="drawRoundRect" format="boolean" />
+        <attr name="firstMonth" format="enum">
+            <enum name="january" value="0" />
+            <enum name="february" value="1" />
+            <enum name="march" value="2" />
+            <enum name="april" value="3" />
+            <enum name="may" value="4" />
+            <enum name="june" value="5" />
+            <enum name="july" value="6" />
+            <enum name="august" value="7" />
+            <enum name="september" value="8" />
+            <enum name="october" value="9" />
+            <enum name="november" value="10" />
+            <enum name="december" value="11" />
+        </attr>
+        <attr name="lastMonth" format="enum">
+            <enum name="january" value="0" />
+            <enum name="february" value="1" />
+            <enum name="march" value="2" />
+            <enum name="april" value="3" />
+            <enum name="may" value="4" />
+            <enum name="june" value="5" />
+            <enum name="july" value="6" />
+            <enum name="august" value="7" />
+            <enum name="september" value="8" />
+            <enum name="october" value="9" />
+            <enum name="november" value="10" />
+            <enum name="december" value="11" />
+        </attr>
+    </declare-styleable>
+</resources>

+ 7 - 0
calendar/src/main/res/values/colors.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <color name="normal_day">#ff999999</color>
+    <color name="selected_day_background">#E75F49</color>
+    <color name="selected_day_text">#fff2f2f2</color>
+</resources>

+ 9 - 0
calendar/src/main/res/values/dimens.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="text_size_day">16sp</dimen>
+    <dimen name="text_size_month">16sp</dimen>
+    <dimen name="text_size_day_name">10sp</dimen>
+    <dimen name="header_month_height">50dip</dimen>
+    <dimen name="selected_day_radius">16dip</dimen>
+    <dimen name="calendar_height">270dip</dimen>
+</resources>

+ 6 - 0
calendar/src/main/res/values/strings.xml

@@ -0,0 +1,6 @@
+<resources>
+    <string name="app_name">CalendarListview</string>
+
+
+    <string name="sans_serif" translatable="false">sans-serif</string>
+</resources>

+ 17 - 0
calendar/src/test/java/com/itant/calendar/ExampleUnitTest.kt

@@ -0,0 +1,17 @@
+package com.itant.calendar
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}

+ 19 - 0
gradle.properties

@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official

二進制
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Tue Mar 23 09:18:10 CST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME

+ 172 - 0
gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 3 - 0
settings.gradle

@@ -0,0 +1,3 @@
+rootProject.name = "CalendarPicker"
+include ':app'
+include ':calendar'