Skip to content
This repository has been archived by the owner on Sep 3, 2023. It is now read-only.

Commit

Permalink
Add DialogBehavior API and implement BottomSheet support. Other clean…
Browse files Browse the repository at this point in the history
…up to the library and sample
  • Loading branch information
afollestad committed May 8, 2019
1 parent ae8e448 commit b04bb2a
Show file tree
Hide file tree
Showing 68 changed files with 1,629 additions and 526 deletions.
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ core and normal-use functionality.
```gradle
dependencies {
...
implementation 'com.afollestad.material-dialogs:core:2.8.1'
implementation 'com.afollestad.material-dialogs:core:3.0.0-alpha1'
}
```

Expand All @@ -46,7 +46,7 @@ The `input` module contains extensions to the core module, such as a text input
```gradle
dependencies {
...
implementation 'com.afollestad.material-dialogs:input:2.8.1'
implementation 'com.afollestad.material-dialogs:input:3.0.0-alpha1'
}
```

Expand All @@ -63,7 +63,7 @@ The `files` module contains extensions to the core module, such as a file and fo
```gradle
dependencies {
...
implementation 'com.afollestad.material-dialogs:files:2.8.1'
implementation 'com.afollestad.material-dialogs:files:3.0.0-alpha1'
}
```

Expand All @@ -80,7 +80,7 @@ The `color` module contains extensions to the core module, such as a color choos
```gradle
dependencies {
...
implementation 'com.afollestad.material-dialogs:color:2.8.1'
implementation 'com.afollestad.material-dialogs:color:3.0.0-alpha1'
}
```

Expand All @@ -97,7 +97,7 @@ The `datetime` module contains extensions to make date, time, and date-time pick
```gradle
dependencies {
...
implementation 'com.afollestad.material-dialogs:datetime:2.8.1'
implementation 'com.afollestad.material-dialogs:datetime:3.0.0-alpha1'
}
```

Expand All @@ -112,6 +112,23 @@ The `lifecycle` module contains extensions to make dialogs work with AndroidX li
```gradle
dependencies {
...
implementation 'com.afollestad.material-dialogs:lifecycle:2.8.1'
implementation 'com.afollestad.material-dialogs:lifecycle:3.0.0-alpha1'
}
```

## Bottom Sheets

[ ![Lifecycle](https://img.shields.io/bintray/v/drummer-aidan/maven/material-dialogs:bottomsheets.svg?label=bottomsheets) ](https://bintray.com/drummer-aidan/maven/material-dialogs%3Abottomsheets/_latestVersion)

#### [Bottom Sheets Tutorial and Samples](documentation/BOTTOMSHEETS.md)

The `bottomsheets` module contains extensions to turn modal dialogs into bottom sheets, among
other functionality like showing a grid of items. Be sure to checkout the sample project for this,
too!

```gradle
dependencies {
...
implementation 'com.afollestad.material-dialogs:bottomsheets:3.0.0-alpha1'
}
```
1 change: 1 addition & 0 deletions bottomsheets/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
33 changes: 33 additions & 0 deletions bottomsheets/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply from: '../dependencies.gradle'

ext.shard = 'bottomsheets'
apply from: '../bintrayconfig.gradle'

android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools

defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.compileSdk
versionCode versions.publishVersionCode
versionName versions.publishVersion
}

sourceSets {
main.res.srcDirs = [
'src/main/res',
'src/main/res-public'
]
}
}

dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:' + versions.kotlin
implementation project(':core')
implementation 'com.google.android.material:material:' + versions.androidxMaterial
}

apply from: '../spotless.gradle'
1 change: 1 addition & 0 deletions bottomsheets/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="com.afollestad.materialdialogs.bottomsheets"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/**
* 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.bottomsheets

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater
import android.view.View.GONE
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager.LayoutParams
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.afollestad.materialdialogs.DialogBehavior
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.internal.button.DialogActionButtonLayout
import com.afollestad.materialdialogs.internal.button.shouldBeVisible
import com.afollestad.materialdialogs.internal.main.DialogLayout
import com.afollestad.materialdialogs.utils.MDUtil.getWidthAndHeight
import com.afollestad.materialdialogs.utils.MDUtil.waitForHeight
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
import kotlin.math.min
import kotlin.properties.Delegates.notNull

/** @author Aidan Follestad (@afollestad) */
class BottomSheet : DialogBehavior {
internal var bottomSheetBehavior: BottomSheetBehavior<*>? = null
private var bottomSheetView: ViewGroup? = null

private var rootView: CoordinatorLayout? = null
private var buttonsLayout: DialogActionButtonLayout? = null
private var dialog: MaterialDialog? = null

private var defaultPeekHeight: Int by notNull()
private var actualPeekHeight: Int by notNull()

@SuppressLint("InflateParams")
override fun createView(
context: Context,
window: Window,
layoutInflater: LayoutInflater,
dialog: MaterialDialog
): ViewGroup {
rootView = layoutInflater.inflate(
R.layout.md_dialog_base_bottomsheet,
null,
false
) as CoordinatorLayout

this.dialog = dialog
this.bottomSheetView = rootView!!.findViewById(R.id.md_root_bottom_sheet)
this.buttonsLayout = rootView!!.findViewById(R.id.md_button_layout)

val (_, windowHeight) = window.windowManager.getWidthAndHeight()
defaultPeekHeight = (windowHeight * DEFAULT_PEEK_HEIGHT_RATIO).toInt()
actualPeekHeight = defaultPeekHeight

setupBottomSheetBehavior()

return rootView!!
}

private fun setupBottomSheetBehavior() {
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetView)
.apply {
isHideable = true
// start at 0 so we can animate it up when the dialog lays out the view
peekHeight = 0
}

bottomSheetBehavior!!.setCallbacks(
onSlide = { currentHeight ->
// Slide the buttons layout down as the bottom sheet is hiding itself
val buttonsLayoutHeight = buttonsLayout?.measuredHeight ?: currentHeight + 1
if (currentHeight in 1..buttonsLayoutHeight) {
val diff = buttonsLayoutHeight - currentHeight
buttonsLayout?.translationY = diff.toFloat()
} else if (currentHeight > 0) {
buttonsLayout?.translationY = 0f
}
// Show divider over buttons layout if sheet is sliding down
invalidateDividers(currentHeight)
},
onHide = {
buttonsLayout?.visibility = GONE
dialog?.dismiss()
dialog = null
}
)

bottomSheetView!!.waitForHeight {
actualPeekHeight = min(defaultPeekHeight, min(this.measuredHeight, defaultPeekHeight))
bottomSheetBehavior?.animatePeekHeight(
view = bottomSheetView!!,
start = 0,
dest = actualPeekHeight,
duration = LAYOUT_PEEK_CHANGE_DURATION_MS,
onEnd = {
invalidateDividers(actualPeekHeight)
}
)
showButtons()
}
}

private fun invalidateDividers(currentHeight: Int) {
val contentLayout = dialog?.view?.contentLayout ?: return
val mainViewHeight = dialog?.view?.measuredHeight ?: return
val scrollView = contentLayout.scrollView
val recyclerView = contentLayout.recyclerView
when {
currentHeight < mainViewHeight -> buttonsLayout?.drawDivider = true
scrollView != null -> scrollView.invalidateDividers()
recyclerView != null -> recyclerView.invalidateDividers()
else -> buttonsLayout?.drawDivider = false
}
}

override fun getDialogLayout(root: ViewGroup): DialogLayout {
return (root.findViewById(R.id.md_root) as DialogLayout).also { dialogLayout ->
dialogLayout.attachButtonsLayout(buttonsLayout!!)
}
}

override fun setWindowConstraints(
context: Context,
window: Window,
view: DialogLayout,
maxWidth: Int?
) {
if (maxWidth == 0) {
// Postpone
return
}
window.setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val lp = LayoutParams()
.apply {
copyFrom(window.attributes)
width = LayoutParams.MATCH_PARENT
height = LayoutParams.MATCH_PARENT
}
window.attributes = lp
}

override fun setBackgroundColor(
context: Context,
window: Window,
view: DialogLayout,
color: Int,
cornerRounding: Float
) {
window.setBackgroundDrawable(null)
bottomSheetView?.background = GradientDrawable().apply {
cornerRadii = floatArrayOf(
cornerRounding, cornerRounding, // top left
cornerRounding, cornerRounding, // top right
0f, 0f, // bottom left
0f, 0f // bottom right
)
setColor(color)
}
buttonsLayout?.setBackgroundColor(color)
}

override fun onPreShow(dialog: MaterialDialog) {
if (dialog.cancelOnTouchOutside) {
// Clicking outside the bottom sheet dismisses the dialog
rootView?.setOnClickListener { this.dialog?.dismiss() }
}
}

override fun onPostShow(dialog: MaterialDialog) = Unit

override fun onDismiss(): Boolean {
if (dialog != null &&
bottomSheetBehavior != null &&
bottomSheetBehavior!!.state != STATE_HIDDEN
) {
bottomSheetBehavior!!.state = STATE_HIDDEN
hideButtons { cleanup() }
return true
}
return false
}

private fun cleanup() {
bottomSheetBehavior = null
bottomSheetView = null
buttonsLayout = null
rootView = null
}

private fun showButtons() {
if (!buttonsLayout.shouldBeVisible()) {
return
}
val start = buttonsLayout!!.measuredHeight
buttonsLayout?.translationY = start.toFloat()
val animator = animateValues(
from = start,
to = 0,
duration = BUTTONS_SHOW_DURATION_MS,
onUpdate = { buttonsLayout?.translationY = it.toFloat() }
)
buttonsLayout?.onDetach { animator.cancel() }
animator.apply {
startDelay = BUTTONS_SHOW_START_DELAY_MS
start()
}
}

private fun hideButtons(onEnd: () -> Unit) {
if (buttonsLayout.shouldBeVisible()) {
val animator = animateValues(
from = 0,
to = buttonsLayout!!.measuredHeight,
duration = LAYOUT_PEEK_CHANGE_DURATION_MS,
onUpdate = { buttonsLayout?.translationY = it.toFloat() },
onEnd = onEnd
)
buttonsLayout?.onDetach { animator.cancel() }
animator.start()
return
}
onEnd()
}

private companion object {
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
}
}
Loading

0 comments on commit b04bb2a

Please sign in to comment.