Ver Fonte

WIP color chooser custom ARGB selection (#1630)

Thanks to @MFlisar!
MFlisar há 6 anos atrás
pai
commit
9793b5ad5f
21 ficheiros alterados com 735 adições e 36 exclusões
  1. 1 0
      color/build.gradle
  2. 1 1
      color/src/main/java/com/afollestad/materialdialogs/color/ColorGridAdapter.kt
  3. 53 0
      color/src/main/java/com/afollestad/materialdialogs/color/ColorPagerAdapter.kt
  4. 199 21
      color/src/main/java/com/afollestad/materialdialogs/color/DialogColorChooserExt.kt
  5. 35 0
      color/src/main/java/com/afollestad/materialdialogs/color/WrapContentViewPager.kt
  6. BIN
      color/src/main/res/drawable-nodpi/transparent_rect.png
  7. 4 0
      color/src/main/res/drawable/transparent_rect_repeat.xml
  8. 26 0
      color/src/main/res/layout-land/md_color_chooser_pager.xml
  9. 170 0
      color/src/main/res/layout-land/md_color_custom_color_chooser.xml
  10. 1 0
      color/src/main/res/layout/md_color_chooser_grid.xml
  11. 26 0
      color/src/main/res/layout/md_color_chooser_pager.xml
  12. 164 0
      color/src/main/res/layout/md_color_custom_color_chooser.xml
  13. 5 0
      color/src/main/res/values/strings.xml
  14. 2 2
      core/src/main/java/com/afollestad/materialdialogs/checkbox/DialogCheckboxExt.kt
  15. 2 0
      core/src/main/java/com/afollestad/materialdialogs/list/DialogMultiChoiceExt.kt
  16. 2 2
      core/src/main/java/com/afollestad/materialdialogs/utils/DialogExt.kt
  17. 0 10
      core/src/main/java/com/afollestad/materialdialogs/utils/StringExt.kt
  18. 21 0
      core/src/main/java/com/afollestad/materialdialogs/utils/Util.kt
  19. 1 0
      sample/build.gradle
  20. 14 0
      sample/src/main/java/com/afollestad/materialdialogssample/MainActivity.kt
  21. 8 0
      sample/src/main/res/layout/activity_main.xml

+ 1 - 0
color/build.gradle

@@ -30,6 +30,7 @@ android {
 
 dependencies {
   implementation 'androidx.recyclerview:recyclerview:' + versions.androidx
+  implementation 'com.google.android.material:material:' + versions.androidx
   implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin
 
   implementation project(':core')

+ 1 - 1
color/src/main/java/com/afollestad/materialdialogs/color/ColorGridAdapter.kt

@@ -107,7 +107,7 @@ internal class ColorGridAdapter(
     if (initialSelection != null) {
       selectedTopIndex = colors.indexOfFirst { it == initialSelection }
       if (selectedTopIndex == -1 && subColors != null) {
-        for (section in 0..subColors.size) {
+        for (section in 0 until subColors.size) {
           selectedSubIndex = subColors[section].indexOfFirst { it == initialSelection }
           if (selectedSubIndex != -1) {
             inSub = true

+ 53 - 0
color/src/main/java/com/afollestad/materialdialogs/color/ColorPagerAdapter.kt

@@ -0,0 +1,53 @@
+/*
+ * Licensed under Apache-2.0
+ *
+ * Designed and developed by Aidan Follestad (@afollestad)
+ */
+package com.afollestad.materialdialogs.color
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.StringRes
+import androidx.viewpager.widget.PagerAdapter
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.utils.Util
+
+internal class ColorPagerAdapter(
+  dialog: MaterialDialog,
+  @StringRes tabGridTextRes: Int? = null,
+  tabGridText: String? = null,
+  @StringRes tabCustomTextRes: Int? = null,
+  tabCustomText: String? = null
+) : PagerAdapter() {
+
+    private val actualTabGridTitle: CharSequence
+    private val actualTabCustomTitle: CharSequence
+
+    init {
+        actualTabGridTitle = tabGridText ?: Util.getString(dialog, tabGridTextRes, R.string.md_dialog_color_presets)!!
+        actualTabCustomTitle = tabCustomText ?: Util.getString(dialog, tabCustomTextRes, R.string.md_dialog_color_custom)!!
+    }
+
+    override fun instantiateItem(collection: ViewGroup, position: Int): Any {
+        var resId = 0
+        when (position) {
+            0 -> resId = R.id.rvGrid
+            1 -> resId = R.id.llCustomColor
+        }
+        return collection.findViewById(resId)
+    }
+
+    override fun getCount(): Int {
+        return 2
+    }
+
+    override fun isViewFromObject(arg0: View, arg1: Any): Boolean {
+        return arg0 === arg1 as View
+    }
+
+    override fun getPageTitle(position: Int): CharSequence? = when (position) {
+        0 -> actualTabGridTitle
+        1 -> actualTabCustomTitle
+        else -> null
+    }
+}

+ 199 - 21
color/src/main/java/com/afollestad/materialdialogs/color/DialogColorChooserExt.kt

@@ -6,18 +6,29 @@
 package com.afollestad.materialdialogs.color
 
 import android.annotation.SuppressLint
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.SeekBar
+import android.widget.TextView
 import androidx.annotation.CheckResult
 import androidx.annotation.ColorInt
+import androidx.annotation.StringRes
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import androidx.viewpager.widget.ViewPager
 import com.afollestad.materialdialogs.MaterialDialog
 import com.afollestad.materialdialogs.WhichButton.POSITIVE
 import com.afollestad.materialdialogs.actions.setActionButtonEnabled
 import com.afollestad.materialdialogs.customview.customView
 import com.afollestad.materialdialogs.customview.getCustomView
+import com.google.android.material.tabs.TabLayout
 
 typealias ColorCallback = ((dialog: MaterialDialog, color: Int) -> Unit)?
 
+private const val ALPHA_SOLID = 255
+
 /**
  * Shows a dialog with a grid of colors that the user can select from.
  *
@@ -26,6 +37,12 @@ typealias ColorCallback = ((dialog: MaterialDialog, color: Int) -> Unit)?
  * @param initialSelection The optionally initially selected color literal integer.
  * @param waitForPositiveButton When true, the selection isn't invoked until the user selects
  *    a color and taps on the positive action button. Defaults to true if the dialog has buttons.
+ * @param allowCustomColor allows to select a color with an (A)RGB slider view
+ * @param supportCustomAlpha allows to select alpha values in the custom values view or not
+ * @param resTabGrid provide a custom tab label as resource integer for the grid page
+ * @param textTabGrid provide a custom tab label as string for the grid page
+ * @param resTabCustom provide a custom tab label as resource integer for the custom page
+ * @param textTabCustom provide a custom tab label as string  for the custom page
  * @param selection An optional callback invoked when the user selects a color.
  */
 @SuppressLint("CheckResult")
@@ -35,35 +52,41 @@ fun MaterialDialog.colorChooser(
   subColors: Array<IntArray>? = null,
   @ColorInt initialSelection: Int? = null,
   waitForPositiveButton: Boolean = true,
+  allowCustomColor: Boolean = false,
+  supportCustomAlpha: Boolean = false,
+  @StringRes tabGridTextRes: Int? = null,
+  tabGridText: String? = null,
+  @StringRes tabCustomTextRes: Int? = null,
+  tabCustomText: String? = null,
   selection: ColorCallback = null
 ): MaterialDialog {
-  customView(R.layout.md_color_chooser_grid)
-  val customView = getCustomView() as RecyclerView
 
-  if (subColors != null && colors.size != subColors.size) {
-    throw IllegalStateException("Sub-colors array size should match the colors array size.")
-  }
+  if (!allowCustomColor) {
+    customView(R.layout.md_color_chooser_grid)
+    updateGridLayout(this, colors, subColors, initialSelection, waitForPositiveButton, selection)
+  } else {
+    customView(R.layout.md_color_chooser_pager)
+    val viewPager = getPager()
+    viewPager.adapter = ColorPagerAdapter(this, tabGridTextRes, tabGridText, tabCustomTextRes, tabCustomText)
+    viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
+      override fun onPageScrollStateChanged(state: Int) {}
+
+      override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
 
-  val gridColumnCount = windowContext.resources
-      .getInteger(R.integer.color_grid_column_count)
-  customView.layoutManager = GridLayoutManager(
-      windowContext, gridColumnCount
-  )
-
-  val adapter = ColorGridAdapter(
-      dialog = this,
-      colors = colors,
-      subColors = subColors,
-      initialSelection = initialSelection,
-      waitForPositiveButton = waitForPositiveButton,
-      callback = selection
-  )
-  customView.adapter = adapter
+      override fun onPageSelected(position: Int) {
+        setActionButtonEnabled(POSITIVE, selectedColor(this@colorChooser, allowCustomColor) != null)
+      }
+    })
+    val tabLayout = getTabLayout()
+    tabLayout.setupWithViewPager(viewPager)
+    updateGridLayout(this, colors, subColors, initialSelection, waitForPositiveButton, selection)
+    updateCustomPage(this, supportCustomAlpha, initialSelection, waitForPositiveButton, selection)
+  }
 
   if (waitForPositiveButton && selection != null) {
     setActionButtonEnabled(POSITIVE, false)
     positiveButton {
-      val color = adapter.selectedColor()
+      val color = selectedColor(this, allowCustomColor)
       if (color != null) {
         selection.invoke(this, color)
       }
@@ -72,3 +95,158 @@ fun MaterialDialog.colorChooser(
 
   return this
 }
+
+private fun updateGridLayout(
+  dialog: MaterialDialog,
+  colors: IntArray,
+  subColors: Array<IntArray>?,
+  @ColorInt initialSelection: Int?,
+  waitForPositiveButton: Boolean,
+  selection: ColorCallback
+) {
+    if (subColors != null && colors.size != subColors.size) {
+        throw IllegalStateException("Sub-colors array size should match the colors array size.")
+    }
+    val gridRecyclerView: RecyclerView = dialog.getCustomView()!!.findViewById(R.id.rvGrid)
+    val gridColumnCount = dialog.windowContext.resources
+            .getInteger(R.integer.color_grid_column_count)
+    gridRecyclerView.layoutManager = GridLayoutManager(
+            dialog.windowContext, gridColumnCount
+    )
+
+    val adapter = ColorGridAdapter(
+            dialog = dialog,
+            colors = colors,
+            subColors = subColors,
+            initialSelection = initialSelection,
+            waitForPositiveButton = waitForPositiveButton,
+            callback = selection
+    )
+    gridRecyclerView.adapter = adapter
+}
+
+private fun updateCustomPage(dialog: MaterialDialog, supportCustomAlpha: Boolean, @ColorInt initialSelection: Int?, waitForPositiveButton: Boolean, selection: ColorCallback) {
+  val customPage: View = dialog.getPageCustomView()
+  val vColor: View = customPage.findViewById(R.id.v_color)
+  val llAlpha: LinearLayout = customPage.findViewById(R.id.llAlpha)
+  val sbAlpha: SeekBar = customPage.findViewById(R.id.sb_alpha)
+  val sbRed: SeekBar = customPage.findViewById(R.id.sb_red)
+  val sbGreen: SeekBar = customPage.findViewById(R.id.sb_green)
+  val sbBlue: SeekBar = customPage.findViewById(R.id.sb_blue)
+  val tvAlphaValue: TextView = customPage.findViewById(R.id.tv_alpha_value)
+  val tvRedValue: TextView = customPage.findViewById(R.id.tv_red_value)
+  val tvGreenValue: TextView = customPage.findViewById(R.id.tv_green_value)
+  val tvBlueValue: TextView = customPage.findViewById(R.id.tv_blue_value)
+
+  sbAlpha.tint(Color.BLACK)
+  sbRed.tint(Color.RED)
+  sbGreen.tint(Color.GREEN)
+  sbBlue.tint(Color.BLUE)
+
+  initialSelection?.let {
+    if (supportCustomAlpha) {
+      sbAlpha.progress = Color.alpha(it)
+    }
+    sbRed.progress = Color.red(it)
+    sbGreen.progress = Color.green(it)
+    sbBlue.progress = Color.blue(it)
+  } ?: run {
+    sbAlpha.progress = ALPHA_SOLID
+  }
+
+  llAlpha.visibility = if (supportCustomAlpha) View.VISIBLE else View.GONE
+
+  val listener = object : SeekBar.OnSeekBarChangeListener {
+    override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
+      onCustomValueChanged(dialog, supportCustomAlpha, waitForPositiveButton, true, customPage, vColor, sbAlpha, sbRed, sbGreen, sbBlue, tvAlphaValue, tvRedValue, tvGreenValue, tvBlueValue, selection)
+    }
+
+    override fun onStartTrackingTouch(p0: SeekBar?) {}
+    override fun onStopTrackingTouch(p0: SeekBar?) { }
+  }
+
+  sbAlpha.setOnSeekBarChangeListener(listener)
+  sbRed.setOnSeekBarChangeListener(listener)
+  sbGreen.setOnSeekBarChangeListener(listener)
+  sbBlue.setOnSeekBarChangeListener(listener)
+
+  onCustomValueChanged(
+          dialog = dialog,
+          supportCustomAlpha = supportCustomAlpha,
+          waitForPositiveButton = waitForPositiveButton,
+          valueChanged = initialSelection != null, customView = customPage,
+          vColor = vColor,
+          sbAlpha = sbAlpha,
+          sbRed = sbRed,
+          sbGreen = sbGreen,
+          sbBlue = sbBlue,
+          tvAlphaValue = tvAlphaValue,
+          tvRedValue = tvRedValue,
+          tvGreenValue = tvGreenValue,
+          tvBlueValue = tvBlueValue,
+          selection = selection)
+}
+
+private fun onCustomValueChanged(
+  dialog: MaterialDialog,
+  supportCustomAlpha: Boolean,
+  waitForPositiveButton: Boolean,
+  valueChanged: Boolean,
+  customView: View,
+  vColor: View,
+  sbAlpha: SeekBar,
+  sbRed: SeekBar,
+  sbGreen: SeekBar,
+  sbBlue: SeekBar,
+  tvAlphaValue: TextView,
+  tvRedValue: TextView,
+  tvGreenValue: TextView,
+  tvBlueValue: TextView,
+  selection: ColorCallback
+) {
+  if (supportCustomAlpha) {
+    tvAlphaValue.text = sbAlpha.progress.toString()
+  }
+  tvRedValue.text = sbRed.progress.toString()
+  tvGreenValue.text = sbGreen.progress.toString()
+  tvBlueValue.text = sbBlue.progress.toString()
+
+  val color = Color.argb(if (supportCustomAlpha) sbAlpha.progress else ALPHA_SOLID, sbRed.progress, sbGreen.progress, sbBlue.progress)
+  vColor.setBackgroundColor(color)
+  // simple solution - we save the color as view tag
+  if (valueChanged) {
+    customView.setTag(color)
+    dialog.setActionButtonEnabled(POSITIVE, true)
+  }
+
+  if (!waitForPositiveButton && valueChanged) {
+    selection?.invoke(dialog, color)
+  }
+}
+
+// ----------------
+// Helper functions
+// ----------------
+
+private fun selectedColor(dialog: MaterialDialog, allowCustomColor: Boolean): Int? {
+  if (allowCustomColor) {
+    val viewPager = dialog.getPager()
+    if (viewPager.currentItem == 1) {
+      return dialog.getPageCustomView().getTag() as? Int
+    }
+  }
+  return (dialog.getPageGridView().adapter as ColorGridAdapter).selectedColor()
+}
+
+private fun MaterialDialog.getPageGridView() = findViewById(R.id.rvGrid) as RecyclerView
+
+private fun MaterialDialog.getPageCustomView() = findViewById(R.id.llCustomColor) as View
+
+private fun MaterialDialog.getPager() = findViewById(R.id.vpPager) as ViewPager
+
+private fun MaterialDialog.getTabLayout() = findViewById(R.id.tlTabls) as TabLayout
+
+private fun SeekBar.tint(color: Int) {
+  getProgressDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN)
+  getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN)
+}

+ 35 - 0
color/src/main/java/com/afollestad/materialdialogs/color/WrapContentViewPager.kt

@@ -0,0 +1,35 @@
+/*
+ * Licensed under Apache-2.0
+ *
+ * Designed and developed by Aidan Follestad (@afollestad)
+ */
+package com.afollestad.materialdialogs.color
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.viewpager.widget.ViewPager
+
+internal class WrapContentViewPager(
+  context: Context,
+  attrs: AttributeSet? = null
+) : ViewPager(context, attrs) {
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        var heightMeasureSpec = heightMeasureSpec
+
+        var height = 0
+        for (i in 0 until childCount) {
+            val child = getChildAt(i)
+            child.measure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
+            val h = child.measuredHeight
+            if (h > height) height = h
+        }
+
+        if (height != 0) {
+            heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
+        }
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+    }
+}

BIN
color/src/main/res/drawable-nodpi/transparent_rect.png


+ 4 - 0
color/src/main/res/drawable/transparent_rect_repeat.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+        android:src="@drawable/transparent_rect"
+        android:tileMode="repeat"/>

+ 26 - 0
color/src/main/res/layout-land/md_color_chooser_pager.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <com.google.android.material.tabs.TabLayout
+        android:id="@+id/tlTabls"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?attr/actionBarSize"
+        android:paddingLeft="@dimen/color_grid_item_padding_double"
+        android:paddingRight="@dimen/color_grid_item_padding_double" />
+
+    <androidx.viewpager.widget.ViewPager
+        android:id="@+id/vpPager"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <include layout="@layout/md_color_chooser_grid" />
+
+        <include layout="@layout/md_color_custom_color_chooser" />
+
+    </androidx.viewpager.widget.ViewPager>
+
+</LinearLayout>

+ 170 - 0
color/src/main/res/layout-land/md_color_custom_color_chooser.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout android:id="@+id/llCustomColor"
+              xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="horizontal"
+              android:paddingBottom="@dimen/color_grid_padding_bottom"
+              android:paddingLeft="@dimen/color_grid_item_padding_double"
+              android:paddingRight="@dimen/color_grid_item_padding_double"
+              tools:ignore="HardcodedText">
+
+    <FrameLayout
+        android:layout_width="128dp"
+        android:layout_height="128dp"
+        android:layout_marginRight="16dp"
+        android:background="@drawable/transparent_rect_repeat">
+
+        <View
+            android:id="@+id/v_color"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+
+    </FrameLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/llAlpha"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/tv_alpha"
+                style="@style/TextAppearance.AppCompat.Medium"
+
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="A"/>
+
+            <SeekBar
+                android:id="@+id/sb_alpha"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_weight="1"
+                android:max="255"/>
+
+            <TextView
+                android:id="@+id/tv_alpha_value"
+                style="@style/TextAppearance.AppCompat.Medium"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:ems="2"
+                android:gravity="right|end"
+                android:text="0"/>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="32dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/tv_red"
+                style="@style/TextAppearance.AppCompat.Medium"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="R"
+                tools:ignore="HardcodedText"/>
+
+            <SeekBar
+                android:id="@+id/sb_red"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_weight="1"
+                android:max="255"/>
+
+            <TextView
+                android:id="@+id/tv_red_value"
+                style="@style/TextAppearance.AppCompat.Medium"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:ems="2"
+                android:gravity="right|end"
+                android:text="0"/>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="32dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/tv_green"
+                style="@style/TextAppearance.AppCompat.Medium"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="G"
+                tools:ignore="HardcodedText"/>
+
+            <SeekBar
+                android:id="@+id/sb_green"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_weight="1"
+                android:max="255"/>
+
+            <TextView
+                android:id="@+id/tv_green_value"
+                style="@style/TextAppearance.AppCompat.Medium"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:ems="2"
+                android:gravity="right|end"
+                android:text="0"/>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="32dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/tv_blue"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="B"
+                tools:ignore="HardcodedText"/>
+
+            <SeekBar
+                android:id="@+id/sb_blue"
+                style="@style/TextAppearance.AppCompat.Medium"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_weight="1"
+                android:max="255"/>
+
+            <TextView
+                android:id="@+id/tv_blue_value"
+                style="@style/TextAppearance.AppCompat.Medium"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:ems="2"
+                android:gravity="right|end"
+                android:text="0"/>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>

+ 1 - 0
color/src/main/res/layout/md_color_chooser_grid.xml

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.recyclerview.widget.RecyclerView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/rvGrid"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:clipToPadding="false"

+ 26 - 0
color/src/main/res/layout/md_color_chooser_pager.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <com.google.android.material.tabs.TabLayout
+        android:id="@+id/tlTabls"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="?attr/actionBarSize"
+        android:paddingLeft="@dimen/color_grid_item_padding_double"
+        android:paddingRight="@dimen/color_grid_item_padding_double" />
+
+    <com.afollestad.materialdialogs.color.WrapContentViewPager
+        android:id="@+id/vpPager"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <include layout="@layout/md_color_chooser_grid" />
+
+        <include layout="@layout/md_color_custom_color_chooser" />
+
+    </com.afollestad.materialdialogs.color.WrapContentViewPager>
+
+</LinearLayout>

+ 164 - 0
color/src/main/res/layout/md_color_custom_color_chooser.xml

@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout android:id="@+id/llCustomColor"
+              xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical"
+              android:paddingBottom="@dimen/color_grid_padding_bottom"
+              android:paddingLeft="@dimen/color_grid_item_padding_double"
+              android:paddingRight="@dimen/color_grid_item_padding_double"
+              tools:ignore="HardcodedText">
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:layout_marginBottom="16dp"
+        android:background="@drawable/transparent_rect_repeat">
+
+        <View
+            android:id="@+id/v_color"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+
+    </FrameLayout>
+
+    <LinearLayout
+        android:id="@+id/llAlpha"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/tv_alpha"
+            style="@style/TextAppearance.AppCompat.Medium"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:text="A"
+            />
+
+        <SeekBar
+            android:id="@+id/sb_alpha"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="1"
+            android:max="255"/>
+
+        <TextView
+            android:id="@+id/tv_alpha_value"
+            style="@style/TextAppearance.AppCompat.Medium"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:ems="2"
+            android:gravity="right|end"
+            android:text="0"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="32dp"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/tv_red"
+            style="@style/TextAppearance.AppCompat.Medium"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:text="R"
+            />
+
+        <SeekBar
+            android:id="@+id/sb_red"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="1"
+            android:max="255"/>
+
+        <TextView
+            android:id="@+id/tv_red_value"
+            style="@style/TextAppearance.AppCompat.Medium"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:ems="2"
+            android:gravity="right|end"
+            android:text="0"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="32dp"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/tv_green"
+            style="@style/TextAppearance.AppCompat.Medium"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:text="G"
+            />
+
+        <SeekBar
+            android:id="@+id/sb_green"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="1"
+            android:max="255"/>
+
+        <TextView
+            android:id="@+id/tv_green_value"
+            style="@style/TextAppearance.AppCompat.Medium"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:ems="2"
+            android:gravity="right|end"
+            android:text="0"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="32dp"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/tv_blue"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:text="B"
+            />
+
+        <SeekBar
+            android:id="@+id/sb_blue"
+            style="@style/TextAppearance.AppCompat.Medium"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="1"
+            android:max="255"/>
+
+        <TextView
+            android:id="@+id/tv_blue_value"
+            style="@style/TextAppearance.AppCompat.Medium"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:ems="2"
+            android:gravity="right|end"
+            android:text="0"/>
+
+    </LinearLayout>
+
+
+</LinearLayout>

+ 5 - 0
color/src/main/res/values/strings.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="md_dialog_color_presets">Presets</string>
+    <string name="md_dialog_color_custom">Custom</string>
+</resources>

+ 2 - 2
core/src/main/java/com/afollestad/materialdialogs/checkbox/DialogCheckboxExt.kt

@@ -14,7 +14,7 @@ import androidx.annotation.StringRes
 import com.afollestad.materialdialogs.MaterialDialog
 import com.afollestad.materialdialogs.R
 import com.afollestad.materialdialogs.assertOneSet
-import com.afollestad.materialdialogs.utils.getString
+import com.afollestad.materialdialogs.utils.Util
 import com.afollestad.materialdialogs.utils.maybeSetTextColor
 
 typealias BooleanCallback = ((Boolean) -> Unit)?
@@ -40,7 +40,7 @@ typealias BooleanCallback = ((Boolean) -> Unit)?
   assertOneSet("checkBoxPrompt", text, res)
   view.buttonsLayout.checkBoxPrompt.apply {
     this.visibility = View.VISIBLE
-    this.text = text ?: getString(res)
+    this.text = text ?: Util.getString(this@checkBoxPrompt, res)
     this.isChecked = isCheckedDefault
     this.setOnCheckedChangeListener { _, checked ->
       onToggle?.invoke(checked)

+ 2 - 0
core/src/main/java/com/afollestad/materialdialogs/list/DialogMultiChoiceExt.kt

@@ -23,6 +23,8 @@ import com.afollestad.materialdialogs.utils.getStringArray
  * @param initialSelection The initially selected item indices.
  * @param waitForPositiveButton When true, the [selection] listener won't be called until
  *    the positive action button is pressed.
+ * @param allowEmptySelection When true, the dialog allows to select 0 items as well
+ *    otherwise at least one item must be selected
  * @param selection A listener invoked when an item in the list is selected.
  */
 @CheckResult fun MaterialDialog.listItemsMultiChoice(

+ 2 - 2
core/src/main/java/com/afollestad/materialdialogs/utils/DialogExt.kt

@@ -98,7 +98,7 @@ internal fun MaterialDialog.addContentMessageView(@StringRes res: Int?, text: Ch
     }
   }
   assertOneSet("message", text, res)
-  this.textViewMessage!!.text = text ?: getString(res)
+  this.textViewMessage!!.text = text ?: Util.getString(this@addContentMessageView, res)
 }
 
 internal fun MaterialDialog.preShow() {
@@ -143,7 +143,7 @@ internal fun MaterialDialog.populateText(
   typeface: Typeface?,
   textColor: Int? = null
 ) {
-  val value = text ?: getString(textRes, fallback)
+  val value = text ?: Util.getString(this, textRes, fallback)
   if (value != null) {
     (textView.parent as View).visibility = View.VISIBLE
     textView.visibility = View.VISIBLE

+ 0 - 10
core/src/main/java/com/afollestad/materialdialogs/utils/StringExt.kt

@@ -6,18 +6,8 @@
 package com.afollestad.materialdialogs.utils
 
 import androidx.annotation.ArrayRes
-import androidx.annotation.StringRes
 import com.afollestad.materialdialogs.MaterialDialog
 
-internal fun MaterialDialog.getString(
-  @StringRes res: Int? = null,
-  @StringRes fallback: Int? = null
-): CharSequence? {
-  val resourceId = res ?: (fallback ?: 0)
-  if (resourceId == 0) return null
-  return windowContext.resources.getText(resourceId)
-}
-
 internal fun MaterialDialog.getStringArray(@ArrayRes res: Int?): Array<String>? {
   if (res == null) return emptyArray()
   return windowContext.resources.getStringArray(res)

+ 21 - 0
core/src/main/java/com/afollestad/materialdialogs/utils/Util.kt

@@ -0,0 +1,21 @@
+/*
+ * Licensed under Apache-2.0
+ *
+ * Designed and developed by Aidan Follestad (@afollestad)
+ */
+package com.afollestad.materialdialogs.utils
+
+import androidx.annotation.StringRes
+import com.afollestad.materialdialogs.MaterialDialog
+
+object Util {
+    fun getString(
+      materialDialog: MaterialDialog,
+      @StringRes res: Int? = null,
+      @StringRes fallback: Int? = null
+    ): CharSequence? {
+        val resourceId = res ?: (fallback ?: 0)
+        if (resourceId == 0) return null
+        return materialDialog.windowContext.resources.getText(resourceId)
+    }
+}

+ 1 - 0
sample/build.gradle

@@ -31,6 +31,7 @@ dependencies {
   implementation 'androidx.gridlayout:gridlayout:' + versions.androidx
   implementation 'androidx.appcompat:appcompat:' + versions.androidx
   implementation 'androidx.recyclerview:recyclerview:' + versions.androidx
+  implementation 'com.google.android.material:material:' + versions.androidx
 
   implementation 'com.afollestad:assent:' + versions.assent
 }

+ 14 - 0
sample/src/main/java/com/afollestad/materialdialogssample/MainActivity.kt

@@ -86,6 +86,7 @@ import kotlinx.android.synthetic.main.activity_main.single_choice_buttons_titled
 import kotlinx.android.synthetic.main.activity_main.single_choice_disabled_items
 import kotlinx.android.synthetic.main.activity_main.single_choice_long_items
 import kotlinx.android.synthetic.main.activity_main.single_choice_titled
+import kotlinx.android.synthetic.main.activity_main.colorChooser_primaryColorsAndCustomValues
 
 /** @author Aidan Follestad (afollestad) */
 class MainActivity : AppCompatActivity() {
@@ -641,6 +642,19 @@ class MainActivity : AppCompatActivity() {
       }
     }
 
+    colorChooser_primaryColorsAndCustomValues.setOnClickListener {
+      MaterialDialog(this).show {
+        // title does not look good in landscape mode
+        //title(R.string.primary_colors)
+        colorChooser(PRIMARY_COLORS, PRIMARY_COLORS_SUB, allowCustomColor = true, supportCustomAlpha = true) { _, color ->
+          toast("Selected color: ${color.toHex()} | Alpha: ${Color.alpha(color)}")
+        }
+        positiveButton(R.string.select)
+        negativeButton(android.R.string.cancel)
+        debugMode(debugMode)
+      }
+    }
+
     file_chooser.setOnClickListener { showFileChooser() }
 
     file_chooser_buttons.setOnClickListener { showFileChooserButtons() }

+ 8 - 0
sample/src/main/res/layout/activity_main.xml

@@ -435,6 +435,14 @@
         android:text="Color Chooser, Custom + No Sub"
         />
 
+    <Button
+        android:id="@+id/colorChooser_primaryColorsAndCustomValues"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/sample_button_height"
+        android:layout_marginTop="@dimen/sample_button_spacing"
+        android:text="Color Chooser, Primary + Custom color with alpha"
+        />
+
     <!-- File Choosers -->
 
     <TextView