Browse Source

Add LayoutMode for bottom sheets along with setPeekHeight method, resolves #1801

Aidan Follestad 5 years ago
parent
commit
179ef6dd46

+ 13 - 13
README.md

@@ -24,12 +24,12 @@ are extensions to core.
 The `core` module contains everything you need to get started with the library. It contains all
 core and normal-use functionality.
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/basic_with_buttons.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/basic_with_buttons.png" width="250px" />
 
 ```gradle
 dependencies {
   ...
-  implementation 'com.afollestad.material-dialogs:core:3.0.0-alpha2'
+  implementation 'com.afollestad.material-dialogs:core:3.0.0-alpha3'
 }
 ```
 
@@ -41,12 +41,12 @@ dependencies {
  
 The `input` module contains extensions to the core module, such as a text input dialog.
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/input.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/input.png" width="250px" />
 
 ```gradle
 dependencies {
   ...
-  implementation 'com.afollestad.material-dialogs:input:3.0.0-alpha2'
+  implementation 'com.afollestad.material-dialogs:input:3.0.0-alpha3'
 }
 ```
  
@@ -58,12 +58,12 @@ dependencies {
 
 The `files` module contains extensions to the core module, such as a file and folder chooser.
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/file_chooser.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/file_chooser.png" width="250px" />
 
 ```gradle
 dependencies {
   ...
-  implementation 'com.afollestad.material-dialogs:files:3.0.0-alpha2'
+  implementation 'com.afollestad.material-dialogs:files:3.0.0-alpha3'
 }
 ```
 
@@ -75,12 +75,12 @@ dependencies {
 
 The `color` module contains extensions to the core module, such as a color chooser.
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/color_chooser.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/color_chooser.png" width="250px" />
 
 ```gradle
 dependencies {
   ...
-  implementation 'com.afollestad.material-dialogs:color:3.0.0-alpha2'
+  implementation 'com.afollestad.material-dialogs:color:3.0.0-alpha3'
 }
 ```
 
@@ -92,12 +92,12 @@ dependencies {
 
 The `datetime` module contains extensions to make date, time, and date-time picker dialogs.
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/datetimepicker.png" width="400px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/datetimepicker.png" width="500px" />
 
 ```gradle
 dependencies {
   ...
-  implementation 'com.afollestad.material-dialogs:datetime:3.0.0-alpha2'
+  implementation 'com.afollestad.material-dialogs:datetime:3.0.0-alpha3'
 }
 ```
 
@@ -111,12 +111,12 @@ The `bottomsheets` module contains extensions to turn modal dialogs into bottom
 other functionality like showing a grid of items. Be sure to checkout the sample project for this,
 too!
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/bottomsheet_customview.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/bottomsheet_customview.png" width="250px" />
 
 ```gradle
 dependencies {
   ...
-  implementation 'com.afollestad.material-dialogs:bottomsheets:3.0.0-alpha2'
+  implementation 'com.afollestad.material-dialogs:bottomsheets:3.0.0-alpha3'
 }
 ```
 
@@ -131,6 +131,6 @@ The `lifecycle` module contains extensions to make dialogs work with AndroidX li
 ```gradle
 dependencies {
   ...
-  implementation 'com.afollestad.material-dialogs:lifecycle:3.0.0-alpha2'
+  implementation 'com.afollestad.material-dialogs:lifecycle:3.0.0-alpha3'
 }
 ```

BIN
art/bottomsheet_peekheight.gif


+ 11 - 4
bottomsheets/src/main/java/com/afollestad/materialdialogs/bottomsheets/BottomSheet.kt

@@ -25,6 +25,8 @@ import android.view.Window
 import android.view.WindowManager.LayoutParams
 import androidx.coordinatorlayout.widget.CoordinatorLayout
 import com.afollestad.materialdialogs.DialogBehavior
+import com.afollestad.materialdialogs.LayoutMode
+import com.afollestad.materialdialogs.LayoutMode.MATCH_PARENT
 import com.afollestad.materialdialogs.MaterialDialog
 import com.afollestad.materialdialogs.internal.button.DialogActionButtonLayout
 import com.afollestad.materialdialogs.internal.button.shouldBeVisible
@@ -37,7 +39,9 @@ import kotlin.math.min
 import kotlin.properties.Delegates.notNull
 
 /** @author Aidan Follestad (@afollestad) */
-class BottomSheet : DialogBehavior {
+class BottomSheet(
+  private val layoutMode: LayoutMode = MATCH_PARENT
+) : DialogBehavior {
   internal var bottomSheetBehavior: BottomSheetBehavior<*>? = null
   private var bottomSheetView: ViewGroup? = null
 
@@ -45,7 +49,8 @@ class BottomSheet : DialogBehavior {
   private var buttonsLayout: DialogActionButtonLayout? = null
   private var dialog: MaterialDialog? = null
 
-  private var defaultPeekHeight: Int by notNull()
+  internal var defaultPeekHeight: Int by notNull()
+  internal var maxPeekheight: Int = -1
   private var actualPeekHeight: Int by notNull()
 
   @SuppressLint("InflateParams")
@@ -68,6 +73,7 @@ class BottomSheet : DialogBehavior {
     val (_, windowHeight) = window.windowManager.getWidthAndHeight()
     defaultPeekHeight = (windowHeight * DEFAULT_PEEK_HEIGHT_RATIO).toInt()
     actualPeekHeight = defaultPeekHeight
+    maxPeekheight = windowHeight
 
     setupBottomSheetBehavior()
 
@@ -132,6 +138,7 @@ class BottomSheet : DialogBehavior {
 
   override fun getDialogLayout(root: ViewGroup): DialogLayout {
     return (root.findViewById(R.id.md_root) as DialogLayout).also { dialogLayout ->
+      dialogLayout.layoutMode = layoutMode
       dialogLayout.attachButtonsLayout(buttonsLayout!!)
     }
   }
@@ -239,9 +246,9 @@ class BottomSheet : DialogBehavior {
     onEnd()
   }
 
-  private companion object {
+  companion object {
+    internal const val LAYOUT_PEEK_CHANGE_DURATION_MS = 250L
     private const val DEFAULT_PEEK_HEIGHT_RATIO = 0.6f
-    private const val LAYOUT_PEEK_CHANGE_DURATION_MS = 250L
 
     private const val BUTTONS_SHOW_START_DELAY_MS = 100L
     private const val BUTTONS_SHOW_DURATION_MS = 180L

+ 41 - 0
bottomsheets/src/main/java/com/afollestad/materialdialogs/bottomsheets/BottomSheets.kt

@@ -18,14 +18,19 @@
 package com.afollestad.materialdialogs.bottomsheets
 
 import androidx.annotation.CheckResult
+import androidx.annotation.DimenRes
 import androidx.annotation.IntegerRes
+import androidx.annotation.Px
 import androidx.recyclerview.widget.GridLayoutManager
 import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.bottomsheets.BottomSheet.Companion.LAYOUT_PEEK_CHANGE_DURATION_MS
 import com.afollestad.materialdialogs.internal.list.DialogAdapter
 import com.afollestad.materialdialogs.list.customListAdapter
 import com.afollestad.materialdialogs.list.getListAdapter
+import com.afollestad.materialdialogs.utils.MDUtil.assertOneSet
 import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
 import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
+import kotlin.math.min
 
 /** Expands the bottom sheet, so that it's at its maximum height. */
 fun MaterialDialog.expandBottomSheet(): MaterialDialog {
@@ -49,6 +54,42 @@ fun MaterialDialog.collapseBottomSheet(): MaterialDialog {
   return this
 }
 
+/**
+ * Changes the bottom sheet's peek height, animating it from the previous value if the dialog
+ * is currently shown.
+ */
+fun MaterialDialog.setPeekHeight(
+  @Px literal: Int? = null,
+  @DimenRes res: Int? = null
+): MaterialDialog {
+  check(dialogBehavior is BottomSheet) {
+    "This dialog is not a bottom sheet dialog."
+  }
+  assertOneSet("setPeekHeight", literal, res)
+
+  val bottomSheet = (dialogBehavior as BottomSheet)
+  val literalOrRes = literal ?: context.resources.getDimensionPixelSize(res!!)
+  val destinationPeekHeight = if (bottomSheet.maxPeekheight > 0) {
+    min(bottomSheet.maxPeekheight, literalOrRes)
+  } else {
+    literalOrRes
+  }
+  require(destinationPeekHeight > 0) { "Peek height must be > 0." }
+
+  bottomSheet.defaultPeekHeight = destinationPeekHeight
+  val bottomSheetBehavior = bottomSheet.bottomSheetBehavior
+  if (isShowing) {
+    bottomSheetBehavior?.animatePeekHeight(
+        view = this.view,
+        dest = destinationPeekHeight,
+        duration = LAYOUT_PEEK_CHANGE_DURATION_MS
+    )
+  } else {
+    bottomSheetBehavior!!.peekHeight = destinationPeekHeight
+  }
+  return this
+}
+
 typealias GridItemListener<IT> =
     ((dialog: MaterialDialog, index: Int, item: IT) -> Unit)?
 

+ 27 - 0
core/src/main/java/com/afollestad/materialdialogs/LayoutMode.kt

@@ -0,0 +1,27 @@
+/**
+ * Designed and developed by Aidan Follestad (@afollestad)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.afollestad.materialdialogs
+
+/** @author Aidan Follestad (afollestad) */
+enum class LayoutMode {
+  /** The layout height fills in the screen. */
+  MATCH_PARENT,
+  /**
+   * The layout height wraps its children, maxing out at the screen height in which case the
+   * children should be inside of a ScrollView.
+   */
+  WRAP_CONTENT
+}

+ 23 - 4
core/src/main/java/com/afollestad/materialdialogs/internal/main/DialogLayout.kt

@@ -16,6 +16,7 @@
 package com.afollestad.materialdialogs.internal.main
 
 import android.content.Context
+import android.content.Context.WINDOW_SERVICE
 import android.graphics.Canvas
 import android.graphics.Color.BLUE
 import android.graphics.Color.CYAN
@@ -30,13 +31,17 @@ import android.view.View.MeasureSpec.EXACTLY
 import android.view.View.MeasureSpec.UNSPECIFIED
 import android.view.View.MeasureSpec.getSize
 import android.view.View.MeasureSpec.makeMeasureSpec
+import android.view.WindowManager
 import android.widget.FrameLayout
 import androidx.annotation.ColorInt
+import com.afollestad.materialdialogs.LayoutMode
+import com.afollestad.materialdialogs.LayoutMode.WRAP_CONTENT
 import com.afollestad.materialdialogs.MaterialDialog
 import com.afollestad.materialdialogs.R
 import com.afollestad.materialdialogs.internal.button.DialogActionButtonLayout
 import com.afollestad.materialdialogs.internal.button.shouldBeVisible
 import com.afollestad.materialdialogs.utils.MDUtil.dimenPx
+import com.afollestad.materialdialogs.utils.MDUtil.getWidthAndHeight
 import com.afollestad.materialdialogs.utils.dp
 import com.afollestad.materialdialogs.utils.isRtl
 import com.afollestad.materialdialogs.utils.isVisible
@@ -67,7 +72,10 @@ class DialogLayout(
   lateinit var titleLayout: DialogTitleLayout
   lateinit var contentLayout: DialogContentLayout
   var buttonsLayout: DialogActionButtonLayout? = null
+  var layoutMode: LayoutMode = WRAP_CONTENT
+
   private var isButtonsLayoutAChild: Boolean = true
+  private var windowHeight: Int = -1
 
   override fun onFinishInflate() {
     super.onFinishInflate()
@@ -97,6 +105,13 @@ class DialogLayout(
     buttonsLayout?.drawDivider = showBottom
   }
 
+  override fun onAttachedToWindow() {
+    super.onAttachedToWindow()
+    val windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager
+    val (_, windowHeight) = windowManager.getWidthAndHeight()
+    this.windowHeight = windowHeight
+  }
+
   override fun onMeasure(
     widthMeasureSpec: Int,
     heightMeasureSpec: Int
@@ -126,10 +141,14 @@ class DialogLayout(
         makeMeasureSpec(remainingHeight, AT_MOST)
     )
 
-    val totalHeight = titleLayout.measuredHeight +
-        contentLayout.measuredHeight +
-        (buttonsLayout?.measuredHeight ?: 0)
-    setMeasuredDimension(specWidth, totalHeight)
+    if (layoutMode == WRAP_CONTENT) {
+      val totalHeight = titleLayout.measuredHeight +
+          contentLayout.measuredHeight +
+          (buttonsLayout?.measuredHeight ?: 0)
+      setMeasuredDimension(specWidth, totalHeight)
+    } else {
+      setMeasuredDimension(specWidth, windowHeight)
+    }
   }
 
   override fun onLayout(

+ 2 - 2
dependencies.gradle

@@ -3,8 +3,8 @@ ext.versions = [
     minSdk              : 16,
     compileSdk          : 28,
     buildTools          : '28.0.3',
-    publishVersion      : '3.0.0-alpha2',
-    publishVersionCode  : 244,
+    publishVersion      : '3.0.0-alpha3',
+    publishVersionCode  : 245,
 
     // Plugins
     gradlePlugin        : '3.4.0',

+ 82 - 2
documentation/BOTTOMSHEETS.md

@@ -4,8 +4,12 @@
 
 1. [Gradle Dependency](#gradle-dependency)
 2. [Usage](#usage)
-3. [Item Grids](#item-grids)
+3. [Layout Mode](#layout-mode)
+4. [Peek Height](#peek-height)
+5. [Item Grids](#item-grids)
+6. [Corner Radius](#corner-radius)
 
+---
 
 ## Gradle Dependency
 
@@ -32,6 +36,53 @@ MaterialDialog(this, BottomSheet()).show {
 }
 ```
 
+---
+
+## Layout Mode
+
+There are two layout modes:
+
+* `MATCH_PARENT` - the default. The bottom sheet can be expanded to fill the height of the screen. 
+When opened, the bottom sheet will be at its peek height.
+* `WRAP_CONTENT` - the bottom sheet can only be expanded as far as the height of the view it contains. 
+If the view it contains is as tall or taller than the screen, it's limited to the screen height, in 
+which case the content should be in a `ScrollView` as well.
+
+The layout mode is set in the constructor of the `BottomSheet` behavior, you don't need to 
+explicitly set it at all if you wish to use `MATCH_PARENT`:
+
+```kotlin
+MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show {
+  ...
+}
+```
+
+---
+
+## Peek Height
+
+If you've used Android bottom sheets before, peek height should be a familiar concept. The peek 
+height is the height of the bottom sheet when it's not fully expanded. It's a point between 
+expanded and hidden.
+
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/bottomsheet_peekheight.gif" width="250px" />
+
+The default peek height is 60% of the screen height. You can set a custom peek height if you wish: 
+
+```kotlin
+val dialog = MaterialDialog(this, BottomSheet()).show {
+  setPeekHeight(res = R.dimen.my_default_peek_height)
+}
+
+// You can continue to make calls to this method, and changes are still animated
+dialog.setPeekHeight(res = R.dimen.another_peek_height)
+```
+
+Changes to the peek height are animated for you. If you're using the `WRAP_CONTENT` layout mode, 
+the peek height is limited to the max height of your bottom sheet. 
+
+---
+
 ## Item Grids
 
 Since it's common to show a grid of items in a bottom sheet, this module contains a method to do 
@@ -65,7 +116,7 @@ width for the grid - you can have different widths for different resource config
 landscape, etc.)
 
 ```kotlin
-gridItems(
+fun gridItems(
   items: List<IT : GridItem>,
   @IntegerRes customGridWidth: Int? = null,
   disabledIndices: IntArray? = null,
@@ -73,3 +124,32 @@ gridItems(
   selection: GridItemListener<IT> = null
 ): MaterialDialog
 ```
+
+---
+
+### Corner Radius
+
+This is taken from the core module documentation. I'm reiterating it here to make sure people 
+know that it is possible, since it's more common to use rounding with bottom sheets than regular 
+modal dialogs.
+
+Corner radius can be globally changed with an attribute in your app theme. It defaults to 2dp:
+
+```xml
+<style name="AppTheme.Custom" parent="Theme.AppCompat">
+  ...
+  <item name="md_corner_radius">16dp</item>
+</style>
+```
+
+The above effects _all_ dialogs in your app, even the normal modal ones. There is also a 
+programmatic setter for this value which you can use per-dialog:
+
+```kotlin
+MaterialDialog(this, BottomSheet()).show {
+  // literal, internally converts to dp so 16dp
+  cornerRadius(16f)
+  // Using a dimen instead is encouraged as it's easier to have all instances changeable from one place
+  cornerRadius(res = R.dimen.my_corner_radius)
+}
+```

+ 22 - 11
documentation/CORE.md

@@ -43,7 +43,7 @@ dependencies {
 
 Here's a very basic example of creating and showing a dialog:
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/basic.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/basic.png" width="250px" />
 
 ```kotlin
 MaterialDialog(this).show {
@@ -77,7 +77,7 @@ dialog.show()
 
 There are simple methods for adding action buttons:
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/basic_with_buttons.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/basic_with_buttons.png" width="250px" />
 
 ```kotlin
 MaterialDialog(this).show {
@@ -113,13 +113,13 @@ MaterialDialog(this).show {
 If action buttons together are too long to fit in the dialog's width, they will be automatically
 stacked:
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/stacked_buttons.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/stacked_buttons.png" width="250px" />
 
 ## Adding an Icon
 
 You can display an icon to the left of the title:
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/icon.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/icon.png" width="250px" />
 
 ```kotlin
 MaterialDialog(this).show {
@@ -176,7 +176,7 @@ MaterialDialog(this).show {
 
 You can show lists using the `listItems` extension on `MaterialDialog`:
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/basic_list.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/basic_list.png" width="250px" />
 
 ```kotlin
 MaterialDialog(this).show {
@@ -209,7 +209,7 @@ MaterialDialog(this).show {
 You can show single choice (radio button) lists using the `listItemsSingleChoice` extension 
 on `MaterialDialog`:
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/single_choice_list.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/single_choice_list.png" width="250px" />
 
 ```kotlin
 MaterialDialog(this).show {
@@ -292,7 +292,7 @@ val checked: Boolean = dialog.isItemChecked(index)
 
 You can show multiple choice (checkbox) lists using the `listItemsMultiChoice` extension on `MaterialDialog`:
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/multi_choice_list.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/multi_choice_list.png" width="250px" />
 
 ```kotlin
 MaterialDialog(this).show {
@@ -415,7 +415,7 @@ val recyclerView: RecyclerView = dialog.getRecyclerView()
 Checkbox prompts can be used together with any other dialog type, it gets shown in the same view
 which shows the action buttons.
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/checkbox_prompt.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/checkbox_prompt.png" width="250px" />
 
 ```kotlin
 MaterialDialog(this).show {
@@ -460,7 +460,7 @@ MaterialDialog(this).show {
 A lot of the included extensions use custom views, such as the color chooser dialog. There's also 
 a simple example in the sample project.
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/custom_view.png" width="200px" /> 
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/custom_view.png" width="250px" /> 
 
 ```kotlin
 MaterialDialog(this).show {
@@ -539,7 +539,7 @@ theme for the ripple color of list items, buttons, etc. by default. You can over
 
 Corner radius is the rounding of dialog corners:
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/customtheme.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/customtheme.png" width="250px" />
 
 it can be changed with an attribute in your app theme. It defaults to 2dp:
 
@@ -551,6 +551,17 @@ it can be changed with an attribute in your app theme. It defaults to 2dp:
 </style>
 ```
 
+There is also a programmatic setter for this value:
+
+```kotlin
+MaterialDialog(this).show {
+  // literal, internally converts to dp so 16dp
+  cornerRadius(16f)
+  // Using a dimen instead is encouraged as it's easier to have all instances changeable from one place
+  cornerRadius(res = R.dimen.my_corner_radius)
+}
+```
+
 ### Text Color
 
 By default, `android:textColorPrimary` and `android:textColorSecondary` attributes from your Activity
@@ -585,7 +596,7 @@ using attributes in your app's theme.
 
 See the "Custom Theme" example in the sample project (open the overflow menu for the theme switcher).
 
-<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/customtheme.png" width="200px" />
+<img src="https://raw.githubusercontent.com/afollestad/material-dialogs/master/art/customtheme.png" width="250px" />
 
 ### Widget Color