From f3641c3b267c785c1e15f9ab1a608e37ba969b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Sousa?= Date: Sat, 20 Oct 2018 09:26:43 +0100 Subject: [PATCH] Improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove requirement of a inner fragment 🎉 * Fix some is always expanded issue --- README.md | 16 +- .../superbottomsheet/demo/MainActivity.kt | 20 +- ...demo_inner.xml => fragment_demo_sheet.xml} | 2 +- .../SuperBottomSheetDialog.kt | 208 ++++++++++++++++++ .../SuperBottomSheetFragment.kt | 87 +++----- .../layout/fragment_super_bottom_sheet.xml | 6 - .../res/layout/super_bottom_sheet_dialog.xml | 35 +++ 7 files changed, 280 insertions(+), 94 deletions(-) rename demo/src/main/res/layout/{fragment_demo_inner.xml => fragment_demo_sheet.xml} (94%) create mode 100644 lib/src/main/java/com/andrefrsousa/superbottomsheet/SuperBottomSheetDialog.kt delete mode 100644 lib/src/main/res/layout/fragment_super_bottom_sheet.xml create mode 100644 lib/src/main/res/layout/super_bottom_sheet_dialog.xml diff --git a/README.md b/README.md index 21a4acd..74d388e 100755 --- a/README.md +++ b/README.md @@ -43,26 +43,16 @@ We have a sample project in Kotlin that demonstrates the lib usages [here](https ## Usage It is recommended to check the sample project to get a complete understanding of all the features offered by the library. -In order to create a bottom sheet in your project you need two this: a class that extends SuperBottomSheetFragment, and a inner fragment that will be loaded. +In order to create a bottom sheet in your project you just need to extend SuperBottomSheetFragment. Example: ```kotlin class DemoBottomSheetFragment : superBottomSheetDialogFragment() { - override fun getInnerFragment() = DemoInnerFragment.newInstance() - - override fun getInnerFragmentTag() = "DemoInnerFragment" -} - -class DemoInnerFragment : Fragment() { - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_demo_inner, container, false) - } - - companion object { - internal fun newInstance() = DemoInnerFragment() + super.onCreateView(inflater, container, savedInstanceState) + return inflater.inflate(R.layout.fragment_demo_sheet, container, false) } } ``` diff --git a/demo/src/main/java/com/andrefrsousa/superbottomsheet/demo/MainActivity.kt b/demo/src/main/java/com/andrefrsousa/superbottomsheet/demo/MainActivity.kt index 84b96c4..d5546f0 100644 --- a/demo/src/main/java/com/andrefrsousa/superbottomsheet/demo/MainActivity.kt +++ b/demo/src/main/java/com/andrefrsousa/superbottomsheet/demo/MainActivity.kt @@ -25,7 +25,6 @@ package com.andrefrsousa.superbottomsheet.demo import android.graphics.Color import android.os.Bundle -import android.support.v4.app.Fragment import android.support.v7.app.AppCompatActivity import android.view.LayoutInflater import android.view.View @@ -48,9 +47,10 @@ class MainActivity : AppCompatActivity() { class DemoBottomSheetFragment : SuperBottomSheetFragment() { - override fun getInnerFragment() = DemoInnerFragment.newInstance() - - override fun getInnerFragmentTag() = "DemoInnerFragment" + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + super.onCreateView(inflater, container, savedInstanceState) + return inflater.inflate(R.layout.fragment_demo_sheet, container, false) + } override fun getCornerRadius(): Float { return context!!.resources.getDimension(R.dimen.demo_sheet_rounded_corner) @@ -60,15 +60,3 @@ class DemoBottomSheetFragment : SuperBottomSheetFragment() { return Color.RED } } - -class DemoInnerFragment : Fragment() { - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_demo_inner, container, false) - } - - // Constructor - companion object { - internal fun newInstance() = DemoInnerFragment() - } -} \ No newline at end of file diff --git a/demo/src/main/res/layout/fragment_demo_inner.xml b/demo/src/main/res/layout/fragment_demo_sheet.xml similarity index 94% rename from demo/src/main/res/layout/fragment_demo_inner.xml rename to demo/src/main/res/layout/fragment_demo_sheet.xml index 276bf1e..4430f2a 100644 --- a/demo/src/main/res/layout/fragment_demo_inner.xml +++ b/demo/src/main/res/layout/fragment_demo_sheet.xml @@ -3,7 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="1000dp"> + android:layout_height="wrap_content"> + + private var mCancelable = true + private var mCanceledOnTouchOutside = true + private var mCanceledOnTouchOutsideSet: Boolean = false + + constructor(context: Context?) : this(context, 0) + + constructor(context: Context?, theme: Int) : super(context, theme) { + // We hide the title bar for any style configuration. Otherwise, there will be a gap + // above the bottom sheet when it is expanded. + supportRequestWindowFeature(Window.FEATURE_NO_TITLE) + } + + constructor(context: Context?, cancelable: Boolean, cancelListener: OnCancelListener?) : super(context, cancelable, cancelListener) { + supportRequestWindowFeature(Window.FEATURE_NO_TITLE) + mCancelable = cancelable + } + + override fun setContentView(@LayoutRes layoutResId: Int) { + super.setContentView(wrapInBottomSheet(layoutResId, null, null)) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + window.runIfNotNull { + if (hasMinimumSdk(Build.VERSION_CODES.LOLLIPOP)) { + clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + } + + setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + } + } + + override fun setContentView(view: View) { + super.setContentView(wrapInBottomSheet(0, view, null)) + } + + override fun setContentView(view: View, params: ViewGroup.LayoutParams?) { + super.setContentView(wrapInBottomSheet(0, view, params)) + } + + override fun setCancelable(cancelable: Boolean) { + super.setCancelable(cancelable) + + if (mCancelable != cancelable) { + mCancelable = cancelable + + if (::behavior.isInitialized) { + behavior.isHideable = cancelable + } + } + } + + override fun onStart() { + super.onStart() + + if (::behavior.isInitialized) { + behavior.state = BottomSheetBehavior.STATE_COLLAPSED + } + } + + override fun setCanceledOnTouchOutside(cancel: Boolean) { + super.setCanceledOnTouchOutside(cancel) + + if (cancel && !mCancelable) { + mCancelable = true + } + + mCanceledOnTouchOutside = cancel + mCanceledOnTouchOutsideSet = true + } + + @SuppressLint("ClickableViewAccessibility") + private fun wrapInBottomSheet(layoutResId: Int, view: View?, params: ViewGroup.LayoutParams?): View { + var supportView = view + + val container = View.inflate(context, R.layout.super_bottom_sheet_dialog, null) + val coordinator = container.findViewById(R.id.coordinator) + + if (layoutResId != 0 && supportView == null) { + supportView = layoutInflater.inflate(layoutResId, coordinator, false) + } + + val bottomSheet = coordinator.findViewById(R.id.super_bottom_sheet) + behavior = BottomSheetBehavior.from(bottomSheet) + behavior.setBottomSheetCallback(mBottomSheetCallback) + behavior.isHideable = mCancelable + + if (params == null) { + bottomSheet.addView(supportView) + + } else { + bottomSheet.addView(supportView, params) + } + + // We treat the CoordinatorLayout as outside the dialog though it is technically inside + coordinator.findViewById(R.id.touch_outside).setOnClickListener { + if (mCancelable && isShowing && shouldWindowCloseOnTouchOutside()) { + cancel() + } + } + + // Handle accessibility events + ViewCompat.setAccessibilityDelegate(bottomSheet, object : AccessibilityDelegateCompat() { + + override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) { + super.onInitializeAccessibilityNodeInfo(host, info) + if (mCancelable) { + info.addAction(AccessibilityNodeInfoCompat.ACTION_DISMISS) + info.isDismissable = true + } else { + info.isDismissable = false + } + } + + override fun performAccessibilityAction(host: View, action: Int, args: Bundle): Boolean { + if (action == AccessibilityNodeInfoCompat.ACTION_DISMISS && mCancelable) { + cancel() + return true + } + return super.performAccessibilityAction(host, action, args) + } + }) + + bottomSheet.setOnTouchListener { _, _ -> + // Consume the event and prevent it from falling through + true + } + return container + } + + private fun shouldWindowCloseOnTouchOutside(): Boolean { + if (!mCanceledOnTouchOutsideSet) { + if (hasMinimumSdk(Build.VERSION_CODES.HONEYCOMB)) { + mCanceledOnTouchOutside = true + + } else { + val typedArray = context.obtainStyledAttributes(intArrayOf(android.R.attr.windowCloseOnTouchOutside)) + mCanceledOnTouchOutside = typedArray.getBoolean(0, true) + typedArray.recycle() + } + + mCanceledOnTouchOutsideSet = true + } + + return mCanceledOnTouchOutside + } + + private val mBottomSheetCallback = object : BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, @State newState: Int) { + if (newState == BottomSheetBehavior.STATE_HIDDEN) { + cancel() + } + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) {} + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/andrefrsousa/superbottomsheet/SuperBottomSheetFragment.kt b/lib/src/main/java/com/andrefrsousa/superbottomsheet/SuperBottomSheetFragment.kt index cd93bb2..abc915a 100644 --- a/lib/src/main/java/com/andrefrsousa/superbottomsheet/SuperBottomSheetFragment.kt +++ b/lib/src/main/java/com/andrefrsousa/superbottomsheet/SuperBottomSheetFragment.kt @@ -32,22 +32,20 @@ import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.RoundRectShape import android.os.Build import android.os.Bundle +import android.support.annotation.CallSuper import android.support.annotation.ColorInt import android.support.annotation.Dimension import android.support.annotation.UiThread import android.support.design.widget.BottomSheetBehavior -import android.support.design.widget.BottomSheetDialog import android.support.design.widget.BottomSheetDialogFragment -import android.support.v4.app.Fragment import android.support.v4.content.ContextCompat import android.util.TypedValue import android.view.* -import android.widget.FrameLayout -import kotlinx.android.synthetic.main.fragment_super_bottom_sheet.* abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { private lateinit var backgroundShape: ShapeDrawable + private lateinit var bottomSheet: CornerRadiusFrameLayout private lateinit var behavior: BottomSheetBehavior<*> // Customizable properties @@ -65,15 +63,13 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { /** Methods from [BottomSheetDialogFragment] */ - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - return BottomSheetDialog(this.context!!, R.style.superBottomSheetDialog) + final override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return SuperBottomSheetDialog(this.context!!, R.style.superBottomSheetDialog) } + @CallSuper @SuppressLint("NewApi") override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - // Inflate view - val contentView = inflater.inflate(R.layout.fragment_super_bottom_sheet, container, false) - // Change status bar on the condition: API >= 21 val supportsStatusBarColor = hasMinimumSdk(Build.VERSION_CODES.LOLLIPOP) canSetStatusBarColor = !context.isTablet() && supportsStatusBarColor @@ -93,15 +89,10 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { val isCancelableOnTouchOutside = propertyIsSheetCancelable && propertyIsSheetCancelableOnTouchOutside setCanceledOnTouchOutside(isCancelableOnTouchOutside) - - if (isCancelableOnTouchOutside) { - contentView.rootView.setOnClickListener { dialog.cancel() } - } } // Set window properties dialog.window.runIfNotNull { - requestFeature(Window.FEATURE_NO_TITLE) setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) setDimAmount(propertyDim) @@ -117,37 +108,28 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { } } - // Don't set a peek height if the sheet is always expanded (no collapsing state) - if (!propertyIsAlwaysExpanded) { - contentView.rootView.run { - minimumHeight = getPeekHeight() - } - } - - return contentView + return null } + @CallSuper override fun onStart() { super.onStart() // Init UI components iniBottomSheetUiComponents() - - // Set inner fragment - iniBottomSheetInnerFragment() } //region UI METHODS @UiThread private fun iniBottomSheetUiComponents() { + // Store views references + bottomSheet = dialog.findViewById(R.id.super_bottom_sheet) + val touchOutsideView = dialog.findViewById(R.id.touch_outside) + // Hack to find the bottom sheet holder view initBackgroundShape() - // Store views references - val bottomSheet: FrameLayout = dialog.findViewById(android.support.design.R.id.design_bottom_sheet) - val touchOutsideView: View = dialog.findViewById(android.support.design.R.id.touch_outside) - // Load bottom sheet behaviour behavior = BottomSheetBehavior.from(bottomSheet) @@ -165,24 +147,32 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { // If is always expanded, there is no need to set the peek height if (!propertyIsAlwaysExpanded) { behavior.peekHeight = getPeekHeight() + + bottomSheet.run { + minimumHeight = behavior.peekHeight + } + } else { + val layoutParams = bottomSheet.layoutParams + layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT + bottomSheet.layoutParams = layoutParams } // Only skip the collapse state when the device is in landscape or the sheet is always expanded - val deviceInLandscape = !context.isTablet() && !context.isInPortrait() - behavior.skipCollapsed = deviceInLandscape || propertyIsAlwaysExpanded + val deviceInLandscape = (!context.isTablet() && !context.isInPortrait()) || propertyIsAlwaysExpanded + behavior.skipCollapsed = deviceInLandscape if (deviceInLandscape) { behavior.state = BottomSheetBehavior.STATE_EXPANDED setStatusBarColor(Color.TRANSPARENT) // Load content container height - super_bottomsheet_content.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { + bottomSheet.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { - if (super_bottomsheet_content.height > 0) { - super_bottomsheet_content.viewTreeObserver.removeOnPreDrawListener(this) + if (bottomSheet.height > 0) { + bottomSheet.viewTreeObserver.removeOnPreDrawListener(this) // If the content sheet is expanded set the background and status bar properties - if (super_bottomsheet_content.height == touchOutsideView.height) { + if (bottomSheet.height == touchOutsideView.height) { setStatusBarColor(0f) if (propertyAnimateCornerRadius) { @@ -194,9 +184,6 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { return true } }) - - } else if (propertyIsAlwaysExpanded) { - behavior.state = BottomSheetBehavior.STATE_EXPANDED } // Override sheet callback events @@ -213,7 +200,7 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { return } - if (super_bottomsheet_content.height != touchOutsideView.height) { + if (bottomSheet.height != touchOutsideView.height) { canSetStatusBarColor = false return } @@ -234,19 +221,6 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { }) } - @UiThread - private fun iniBottomSheetInnerFragment() { - val innerFragment = getInnerFragment() - - // Add fragment to view - super_bottomsheet_content.run { - childFragmentManager - .beginTransaction() - .replace(id, innerFragment, getInnerFragmentTag()) - .commitNow() - } - } - //region STATUS BAR @UiThread @@ -278,12 +252,12 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { flags = Paint.ANTI_ALIAS_FLAG } - super_bottomsheet_content.setCornerRadius(propertyCornerRadius) + bottomSheet.setCornerRadius(propertyCornerRadius) } private fun setBackgroundShapeRadius(radius: Float) { backgroundShape.shape = getShape(radius) - super_bottomsheet_content.setCornerRadius(radius) + bottomSheet.setCornerRadius(radius) } private fun getShape(radius: Float): RoundRectShape { @@ -317,6 +291,7 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { return Math.max(peekHeightMin, displayMetrics.heightPixels - displayMetrics.heightPixels * 9 / 16) } + @Dimension open fun getDim(): Float { val floatId = context!!.getAttrId(R.attr.superBottomSheet_dim) @@ -404,9 +379,5 @@ abstract class SuperBottomSheetFragment : BottomSheetDialogFragment() { return resources.getBoolean(boolId) } - abstract fun getInnerFragment(): Fragment - - abstract fun getInnerFragmentTag(): String - //endregion } \ No newline at end of file diff --git a/lib/src/main/res/layout/fragment_super_bottom_sheet.xml b/lib/src/main/res/layout/fragment_super_bottom_sheet.xml deleted file mode 100644 index 4afd6ff..0000000 --- a/lib/src/main/res/layout/fragment_super_bottom_sheet.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/lib/src/main/res/layout/super_bottom_sheet_dialog.xml b/lib/src/main/res/layout/super_bottom_sheet_dialog.xml new file mode 100644 index 0000000..2c2d576 --- /dev/null +++ b/lib/src/main/res/layout/super_bottom_sheet_dialog.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + +