Add bottomseet utils module

This commit is contained in:
Grigorii 2023-04-07 11:39:46 +04:00
parent 54d2482064
commit f0c8e3f1d7
9 changed files with 313 additions and 0 deletions

1
bottom-sheet/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

26
bottom-sheet/build.gradle Normal file
View File

@ -0,0 +1,26 @@
apply from: "../android-configs/lib-config.gradle"
apply plugin: 'kotlin-android'
dependencies {
implementation project(":navigation-base")
implementation 'androidx.core:core-ktx'
implementation 'com.google.android.material:material'
implementation("androidx.core:core-ktx") {
version {
require '1.9.0'
}
}
implementation("com.google.android.material:material") {
version {
require '1.4.0'
}
}
}
android {
buildFeatures {
viewBinding true
}
}

View File

@ -0,0 +1 @@
<manifest package="ru.touchin.roboswag.bottomsheet" />

View File

@ -0,0 +1,161 @@
package ru.touchin.roboswag.bottomsheet
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlin.math.abs
abstract class BaseBottomSheet : BottomSheetDialogFragment() {
protected abstract val layoutId: Int
protected open val bottomSheetOptions = BottomSheetOptions()
protected val decorView: View
get() = checkNotNull(dialog?.window?.decorView)
protected val bottomSheetView: FrameLayout
get() = decorView.findViewById(com.google.android.material.R.id.design_bottom_sheet)
protected val touchOutsideView: View
get() = decorView.findViewById(com.google.android.material.R.id.touch_outside)
protected val behavior: BottomSheetBehavior<FrameLayout>
get() = BottomSheetBehavior.from(bottomSheetView)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(layoutId, container)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bottomSheetOptions.styleId?.let { setStyle(DialogFragment.STYLE_NORMAL, it) }
}
override fun onStart() {
super.onStart()
bottomSheetOptions.defaultDimAmount?.let { dialog?.window?.setDimAmount(it) }
bottomSheetOptions.animatedMaxDimAmount?.let { setupDimAmountChanges(it) }
if (bottomSheetOptions.isShiftedWithKeyboard) setupShiftWithKeyboard()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
isCancelable = bottomSheetOptions.canDismiss
return super.onCreateDialog(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (bottomSheetOptions.isSkipCollapsed) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.skipCollapsed = true
}
if (bottomSheetOptions.isFullscreen) {
bottomSheetView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
}
if (bottomSheetOptions.canTouchOutside) {
setupTouchOutside()
}
bottomSheetOptions.fadeAnimationOptions?.let {
setupFadeAnimationOnHeightChanges(it)
}
bottomSheetOptions.heightStatesOptions?.let {
setupHeightOptions(it)
}
}
private fun setupDimAmountChanges(maxDimAmount: Float) {
behavior.peekHeight = 0
dialog?.window?.setDimAmount(maxDimAmount)
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) = Unit
override fun onSlide(bottomSheet: View, slideOffset: Float) {
dialog?.window?.setDimAmount(abs(slideOffset) * maxDimAmount)
}
})
}
private fun setupFadeAnimationOnHeightChanges(options: ContentFadeAnimationOptions) {
val foreground = checkNotNull(
ContextCompat.getDrawable(requireContext(), options.foregroundRes)
).apply {
alpha = 0
bottomSheetView.foreground = this
}
bottomSheetView.addOnLayoutChangeListener { _, _, top, _, _, _, oldTop, _, _ ->
if (top != oldTop) showFadeAnimation(foreground, options)
}
}
private fun showFadeAnimation(foreground: Drawable, options: ContentFadeAnimationOptions) {
val maxAlpha = 255
foreground.alpha = maxAlpha
bottomSheetView.alpha = options.minAlpha
ValueAnimator.ofInt(maxAlpha, 0).apply {
duration = options.duration
addUpdateListener {
val value = it.animatedValue as Int
foreground.alpha = value
bottomSheetView.alpha = (1 - value.toFloat() / maxAlpha).coerceAtLeast(options.minAlpha)
}
start()
}
}
private fun setupHeightOptions(options: HeightStatesOptions) = with(behavior) {
isFitToContents = false
peekHeight = options.collapsedHeightPx
halfExpandedRatio = options.halfExpandedHalfPx / Resources.getSystem().displayMetrics.heightPixels.toFloat()
state = BottomSheetBehavior.STATE_COLLAPSED
if (options.canTouchOutsideWhenCollapsed) setupTouchOutsideWhenCollapsed()
}
private fun setupTouchOutsideWhenCollapsed() {
setupTouchOutside()
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_COLLAPSED -> setupTouchOutside()
else -> touchOutsideView.setOnTouchListener(null)
}
}
})
}
@Suppress("DEPRECATION")
private fun setupShiftWithKeyboard() {
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
}
@SuppressLint("ClickableViewAccessibility")
private fun setupTouchOutside() {
touchOutsideView.setOnTouchListener { _, event ->
requireActivity().dispatchTouchEvent(event)
true
}
}
}

View File

@ -0,0 +1,29 @@
package ru.touchin.roboswag.bottomsheet
import androidx.annotation.DrawableRes
import androidx.annotation.StyleRes
data class BottomSheetOptions(
@StyleRes val styleId: Int? = null,
val canDismiss: Boolean = true,
val canTouchOutside: Boolean = false,
val isSkipCollapsed: Boolean = true,
val isFullscreen: Boolean = false,
val isShiftedWithKeyboard: Boolean = false,
val defaultDimAmount: Float? = null,
val animatedMaxDimAmount: Float? = null,
val fadeAnimationOptions: ContentFadeAnimationOptions? = null,
val heightStatesOptions: HeightStatesOptions? = null
)
data class ContentFadeAnimationOptions(
@DrawableRes val foregroundRes: Int,
val duration: Long,
val minAlpha: Float
)
data class HeightStatesOptions(
val collapsedHeightPx: Int,
val halfExpandedHalfPx: Int,
val canTouchOutsideWhenCollapsed: Boolean = true
)

View File

@ -0,0 +1,40 @@
package ru.touchin.roboswag.bottomsheet
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import ru.touchin.roboswag.bottomsheet.databinding.DefaultBottomSheetBinding
import ru.touchin.roboswag.navigation_base.fragments.viewBinding
abstract class DefaultBottomSheet : BaseBottomSheet() {
abstract fun createContentView(inflater: LayoutInflater): View
final override val layoutId = R.layout.default_bottom_sheet
override val bottomSheetOptions = BottomSheetOptions(
styleId = R.style.RoundedBottomSheetStyle,
fadeAnimationOptions = ContentFadeAnimationOptions(
foregroundRes = R.drawable.bottom_sheet_background_rounded_16,
duration = 150,
minAlpha = 0.5f
)
)
protected val rootBinding by viewBinding(DefaultBottomSheetBinding::bind)
protected val contentView: View get() = rootBinding.linearRoot.getChildAt(1)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
super.onCreateView(inflater, container, savedInstanceState)
.also {
DefaultBottomSheetBinding.bind(checkNotNull(it))
.linearRoot.addView(createContentView(inflater))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
rootBinding.closeText.setOnClickListener { dismiss() }
}
}

View File

@ -0,0 +1,11 @@
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="16dp"
android:topRightRadius="16dp" />
<solid android:color="@android:color/white" />
</shape>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linear_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/top_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center"
tools:ignore="ContentDescription"
tools:src="@android:mipmap/sym_def_app_icon" />
<TextView
android:id="@+id/close_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|left"
tools:text="Закрыть" />
</FrameLayout>
</LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="RoundedBottomSheetStyle" parent="Theme.Design.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="bottomSheetStyle">@style/RoundedBackground</item>
</style>
<style name="RoundedBackground" parent="Widget.Design.BottomSheet.Modal">
<item name="android:background">@drawable/bottom_sheet_background_rounded_16</item>
</style>
</resources>