background

About Google’s new Dialog in Metrail Design style,BottomSheetDialog It can be seen in many apps, such as Douyin and netease News. It is good in terms of style and novel interaction. Chinese designers are used to adding a functional button at the bottom of popover

The status quo

Similar to the picture above, the height of the pop-up window should be adapted according to the number of data items. If we use ordinary dialog, there is no good interactive experience, and we need to calculate the height of the pop-up, which undoubtedly increases the difficulty of development.

Looking for a solution

Using the BottomSheetDialog, the bottom button to pay is always below the data. If only two or three data are ok, if there is too much data, you need to slide the list to the bottom to see the function keys, which is undoubtedly a very bad interaction. Today we will check the BottomSheetDialog To modify it, so that it can always put down the bottom of the area, first look at his source code

public class BottomSheetDialog extends AppCompatDialog { .... @override public void setContentView(@layoutres int layoutResId) { super.setContentView(wrapInBottomSheet(layoutResId, null, null)); } /** * we know that BottomSheetDialog does not require BottomSheetBehavior support, **/ private View wrapInBottomSheet(int layoutResId, View View, ViewGroup.LayoutParams params) { final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(), R.layout.design_bottom_sheet_dialog, null); // Load a layout with BottomSheetBehavior if (layoutResId! = 0 && view == null) { view = getLayoutInflater().inflate(layoutResId, coordinator, false); } FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet); mBehavior = BottomSheetBehavior.from(bottomSheet); / / get the layout BottomSheetBehavior mBehavior. SetBottomSheetCallback (mBottomSheetCallback); mBehavior.setHideable(mCancelable); if (params == null) { bottomSheet.addView(view); // Add the contentView to the container} else {bottomSheet. AddView (view, params); } // We treat the CoordinatorLayout as outside the dialog though it is g inside coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) { cancel(); }}}); return coordinator; }... Omit part of code}Copy the code

In fact, nothing can be seen from the above code, except that contentView is placed in a container with BottomSheetBehavior. If you want to add an extensible bottom function area on the original basis, you need to modify the original layout.

Design_bottom_sheet_dialog.xml layout file

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <androidx.coordinatorlayout.widget.CoordinatorLayout android:id="@+id/coordinator" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <View android:id="@+id/touch_outside" android:layout_width="match_parent" android:layout_height="match_parent" android:importantForAccessibility="no" android:soundEffectsEnabled="false" tools:ignore="UnusedAttribute"/> <FrameLayout  android:id="@+id/design_bottom_sheet" style="?attr/bottomSheetStyle" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|top" app:layout_behavior="@string/bottom_sheet_behavior"/> </androidx.coordinatorlayout.widget.CoordinatorLayout> </FrameLayout>Copy the code

CoordinatorLayout wraps our contentView container, which is the design_bottom_sheet FrameLayout, and if you want to add a button to the bottom, you just have to let CoordinatorLayout The marginBottom is equal to the height of the bottom area.

The modified file is as follows:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <androidx.coordinatorlayout.widget.CoordinatorLayout android:id="@+id/coordinator" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <View android:id="@+id/touch_outside" android:layout_width="match_parent" android:layout_height="match_parent" android:importantForAccessibility="no" android:soundEffectsEnabled="false" tools:ignore="UnusedAttribute"/> <FrameLayout  android:id="@+id/design_bottom_sheet" style="?attr/bottomSheetStyle" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|top" app:layout_behavior="@string/bottom_sheet_behavior"/> </androidx.coordinatorlayout.widget.CoordinatorLayout> <FrameLayout android:id="@+id/bottom_design_bottom_sheet" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom"/> </FrameLayout>Copy the code

The modified BottomSheetDialog is as follows

open class BaseBottomSheetDialog : AppCompatDialog {
    private var mBehavior: TsmBottomSheetBehavior<FrameLayout>? = null
    private var mCancelable = true
    private var mCanceledOnTouchOutside = true
    private var mCanceledOnTouchOutsideSet = false
    protected var mContext: Activity? = null
    
    constructor(context: Activity) : this(context, 0) {
        this.mContext = context
    }
    
    constructor(context: Context, @StyleRes theme: Int) : super(
        context,
        getThemeResId(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)
    }

    protected constructor(
        context: Context, cancelable: Boolean,
        cancelListener: DialogInterface.OnCancelListener?
    ) : super(context, cancelable, cancelListener) {
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
        mCancelable = cancelable
    }

    override fun setContentView(@LayoutRes layoutResId: Int) {
        super.setContentView(wrapInBottomSheet(layoutResId, 0, null, null))
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window!!.setLayout(
            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
        )
    }

    override fun setContentView(view: View) {
        super.setContentView(wrapInBottomSheet(0, 0, view, null))
    }

    override fun setContentView(view: View, params: ViewGroup.LayoutParams?) {
        super.setContentView(wrapInBottomSheet(0, 0, view, params))
    }

    fun setContentView(view: View?, @LayoutRes bottom: Int) {
        super.setContentView(wrapInBottomSheet(0, bottom, view, null))
    }

    override fun setCancelable(cancelable: Boolean) {
        super.setCancelable(cancelable)
        if (mCancelable != cancelable) {
            mCancelable = cancelable
            if (mBehavior != null) {
                mBehavior!!.isHideable = cancelable
            }
        }
    }

    override fun setCanceledOnTouchOutside(cancel: Boolean) {
        super.setCanceledOnTouchOutside(cancel)
        if (cancel && !mCancelable) {
            mCancelable = true
        }
        mCanceledOnTouchOutside = cancel
        mCanceledOnTouchOutsideSet = true
    }

    private fun wrapInBottomSheet(
        layoutResId: Int,
        bottomLayoutId: Int,
        view: View?,
        params: ViewGroup.LayoutParams?
    ): View {
        var view = view
        val parent = View.inflate(context, R.layout.zr_bottom_sheet_dialog_with_bottom, null)
        val coordinator = parent.findViewById<View>(R.id.coordinator) as CoordinatorLayout
        if (layoutResId != 0 && view == null) {
            view = layoutInflater.inflate(layoutResId, coordinator, false)
        }
        if (bottomLayoutId != 0) {
            val bottomView = layoutInflater.inflate(bottomLayoutId, coordinator, false)
            val fl = parent.findViewById<FrameLayout>(R.id.bottom_design_bottom_sheet)
            fl.addView(bottomView)
            coordinator.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
                override fun onGlobalLayout() {
                    coordinator.viewTreeObserver.removeOnGlobalLayoutListener(this)
                    val p2 = coordinator.layoutParams as FrameLayout.LayoutParams
                    p2.setMargins(0, dp2px(context, 60f), 0, bottomView.height)
                    coordinator.layoutParams = p2
                    val p1 = fl.layoutParams
                    p1.height = bottomView.height
                    fl.layoutParams = p1
                }
            })
//            bottomView.isClickable=true
        }
        val bottomSheet = coordinator.findViewById<View>(R.id.design_bottom_sheet) as FrameLayout
        bottomSheet.setOnClickListener { }
        mBehavior = TsmBottomSheetBehavior.from(bottomSheet)
        mBehavior?.setBottomSheetCallback(mBottomSheetCallback)
        mBehavior?.setHideable(mCancelable)
        if (params == null) {
            bottomSheet.addView(view)
        } else {
            bottomSheet.addView(view, params)
        }
        // We treat the CoordinatorLayout as outside the dialog though it is technically inside
        coordinator.findViewById<View>(R.id.touch_outside).setOnClickListener {
            if (mCancelable && isShowing && shouldWindowCloseOnTouchOutside()) {
                cancel()
            }
        }

        parent?.findViewById<View>(R.id.container)?.setOnClickListener {
            if (mCancelable && isShowing && shouldWindowCloseOnTouchOutside()) {
                cancel()
            }
        }

        return parent
    }

    private fun shouldWindowCloseOnTouchOutside(): Boolean {
        if (!mCanceledOnTouchOutsideSet) {
            if (Build.VERSION.SDK_INT < 11) {
                mCanceledOnTouchOutside = true
            } else {
                val a =
                    context.obtainStyledAttributes(intArrayOf(android.R.attr.windowCloseOnTouchOutside))
                mCanceledOnTouchOutside = a.getBoolean(0, true)
                a.recycle()
            }
            mCanceledOnTouchOutsideSet = true
        }
        return mCanceledOnTouchOutside
    }

    private val mBottomSheetCallback: TsmBottomSheetBehavior.TsmBottomSheetCallback = object : TsmBottomSheetBehavior.TsmBottomSheetCallback() {
        override fun onStateChanged(
            bottomSheet: View,
            @BottomSheetBehavior.State newState: Int
        ) {
//            if (newState == BottomSheetBehavior.STATE_HIDDEN) {
//                dismiss();
//            }
        }

        override fun onSlide(bottomSheet: View, slideOffset: Float) {}
    }

    companion object {
        private fun getThemeResId(context: Context, themeId: Int): Int {
            var themeId = themeId
            if (themeId == 0) {
                // If the provided theme is 0, then retrieve the dialogTheme from our theme
                val outValue = TypedValue()
                themeId = if (context.theme.resolveAttribute(
                        R.attr.bottomSheetDialogTheme, outValue, true
                    )
                ) {
                    outValue.resourceId
                } else {
                    // bottomSheetDialogTheme is not provided; we default to our light theme
                    R.style.Theme_Design_Light_BottomSheetDialog
                }
            }
            return themeId
        }
    }


    open fun dp2px(context: Context, dp: Float): Int {
        val scale: Float = context.getResources().getDisplayMetrics().density
        return (dp * scale + 0.5f).toInt()
    }

}

Copy the code

Now, when I’m done, I’m done

The height of the bottom is calculated from the layout. You do not need to specify the height, but you need to pass a separate ID for the fixed part of the bottom and then add it dynamically

At this time, the modified BottomSheetDialog has some crude, and can be used once again encapsulated

abstract class TsmBaseBottomSheetDialog : BaseBottomSheetDialog { constructor(context: Activity) :super(context, R.style.bottom_sheet_dilog){initDialog()} open fun initDialog() {// You need to set this to set the status bar and navigation bar color if (build.version Build.VERSION_CODES.LOLLIPOP){ window? AddFlags (WindowManager. LayoutParams. FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) / / set the window status bar color? Val View = LayoutInflater. From (context).Layoutid, inflate(const). null) setContentView(view, bottomLayoutId) behaver = TsmBottomSheetBehavior.from(view.parent as View) behaver?.setBottomSheetCallback(object : TsmBottomSheetBehavior.TsmBottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { if (newState == BottomSheetBehavior.STATE_HIDDEN) { dismiss() } } override fun onSlide(bottomSheet: View, slideOffset: Float) {}})} /** * can control the menu state */ var behaver: TsmBottomSheetBehavior<View>?=null override fun show() { initViews() super.show() } protected abstract val layoutId: Int protected open val bottomLayoutId: Int protected get() = 0 protected abstract fun initViews() }Copy the code

Use after re-encapsulation will be convenient many use after encapsulation as follows

class TsmBottomSheetDialog(context: Activity) : TsmBaseBottomSheetDialog(context) { override val layoutId: Int protected get() = R.layout.dialog_tsm_bottom_sheet override fun initViews() { val recycler_view = findViewById<RecyclerView>(R.id.recycler_view) recycler_view!! .adapter = object :BaseQuickAdapter<String,BaseViewHolder>(R.layout.item_simple_test,getList(23)){ override fun convert(holder: BaseViewHolder, item: String) { holder? .setText(R.id.tv_item, item) } } } private fun getList(count: Int): MutableList<String>? { var list: MutableList<String> = MutableList(count,init = { it.toString() }) return list } override val bottomLayoutId: Int protected get() = R.layout.botttom_sheet_bottom_view }Copy the code

However, in the process of use, the product is not very satisfied with the performance of the BottomSheetDialog at this stage

The problem

Here are some of our questions:

  1. Due to the addition of a top margin to the BottomSheetDialog, the BottomSheetDialog does not shrink when the BottomSheetDialog is fully expanded and the top transparent area is clicked
  2. When the product looks at the display form of native BottomSheetDialog, it feels better to display the form similar to diyin, that is, the whole BottomSheetDialog is not unfolded but hidden, and we don’t want the folded state in the middle. We go up and down the final effect drawing

1. Click the external problem that cannot disappear

So let’s fix the first problem, so it’s easy to fix the problem of not being able to click on the outside, we just make the outermost View clickable,

<? The XML version = "1.0" encoding = "utf-8"? > <! -- ~ Copyright (C) 2015 The Android Open Source Project ~ ~ 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. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" Android: fitsSystemWindows = "true" android: clickable = "true" > / / / / / here can increase click, click event is added in the dialog at the same time <androidx.coordinatorlayout.widget.CoordinatorLayout android:id="@+id/coordinator" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <View android:id="@+id/touch_outside" android:layout_width="match_parent" android:layout_height="match_parent" android:importantForAccessibility="no" android:soundEffectsEnabled="false" tools:ignore="UnusedAttribute"/> <FrameLayout android:id="@+id/design_bottom_sheet" style="?attr/bottomSheetStyle" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|top" app:layout_behavior="com.tsm.tsmmodelapp.behavior.TsmBottomSheetBehavior"/> </androidx.coordinatorlayout.widget.CoordinatorLayout> <FrameLayout android:id="@+id/bottom_design_bottom_sheet" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" </FrameLayout> </FrameLayout> </FrameLayout>Copy the code

2. Folding problem

The second problem is more serious. Since we cannot get much information of the BottomSheetDialog from the outside world, we must rewrite the BottomSheetBehavior and modify its code in order to implement the above mentioned problems. Let’s first see what we need to change here.

public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) { ... Omit part of the code if (state = = STATE_EXPANDED) {ViewCompat. OffsetTopAndBottom (child, getExpandedOffset ()); } else if (state == STATE_HALF_EXPANDED) { ViewCompat.offsetTopAndBottom(child, halfExpandedOffset); } else if (hideable && state == STATE_HIDDEN) { ViewCompat.offsetTopAndBottom(child, parentHeight); } else if (state == STATE_COLLAPSED) { ViewCompat.offsetTopAndBottom(child, collapsedOffset); } else if (state == STATE_DRAGGING || state == STATE_SETTLING) { ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop()); }... Omit part of code}Copy the code

The onLayoutChild method is called after the layout is done, and you can see that after the layout is done it’s going to be presented in different states, and our requirement is a little bit rough, it’s going to be all or nothing, so it’s just going to be

public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) { ... Omit part of the code ViewCompat. OffsetTopAndBottom (child, getExpandedOffset ()); . Omit part of code}Copy the code

It’s all fully expanded, and then there’s only one drag event left, and since it’s using NestScroll and a bunch of other events distribution, we’re just going to focus on the last frame slide event, so we need to look at onStopNestedScroll

public void onStopNestedScroll( @NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, Int type) {if (child.getTop() == getExpandedOffset()) {/// If the state is 3 setStateInternal(STATE_EXPANDED); return; } if (nestedScrollingChildRef == null || target ! = nestedScrollingChildRef.get() || ! nestedScrolled) return; } int top; int targetState; if (lastNestedScrollDy > 0) { if (fitToContents) { top = fitToContentsOffset; targetState = STATE_EXPANDED; } else { int currentTop = child.getTop(); if (currentTop > halfExpandedOffset) { top = halfExpandedOffset; targetState = STATE_HALF_EXPANDED; } else { top = expandedOffset; targetState = STATE_EXPANDED; } } } else if (hideable && shouldHide(child, getYVelocity())) { top = parentHeight; targetState = STATE_HIDDEN; } else if (lastNestedScrollDy == 0) { int currentTop = child.getTop(); if (fitToContents) { if (Math.abs(currentTop - fitToContentsOffset) < Math.abs(currentTop - collapsedOffset)) { top = fitToContentsOffset; targetState = STATE_EXPANDED; } else { top = collapsedOffset; targetState = STATE_COLLAPSED; } } else { if (currentTop < halfExpandedOffset) { if (currentTop < Math.abs(currentTop - collapsedOffset)) { top = expandedOffset; targetState = STATE_EXPANDED; } else { top = halfExpandedOffset; targetState = STATE_HALF_EXPANDED; } } else { if (Math.abs(currentTop - halfExpandedOffset) < Math.abs(currentTop - collapsedOffset)) { top = halfExpandedOffset; targetState = STATE_HALF_EXPANDED; } else { top = collapsedOffset; targetState = STATE_COLLAPSED; } } } } else { if (fitToContents) { top = collapsedOffset; targetState = STATE_COLLAPSED; } else { // Settle to nearest height. int currentTop = child.getTop(); if (Math.abs(currentTop - halfExpandedOffset) < Math.abs(currentTop - collapsedOffset)) { top = halfExpandedOffset; targetState = STATE_HALF_EXPANDED; } else { top = collapsedOffset; targetState = STATE_COLLAPSED; } } } startSettlingAnimation(child, targetState, top, false); nestedScrolled = false; }Copy the code

In fact, I don’t know much about many properties here, but I can take a look at the general, the specific modified code is as follows,

public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int type) { if (child.getTop() == this.getExpandedOffset()) { this.setStateInternal(3); } else if (this.nestedScrollingChildRef ! = null && target == this.nestedScrollingChildRef.get() && this.nestedScrolled) { int top; byte targetState; if (this.lastNestedScrollDy > 0) { top = this.getExpandedOffset(); targetState = 3; } else if (this.hideable && this.shouldHide(child, this.getYVelocity())) { top = this.parentHeight; targetState = 5; } else { int currentTop; If (this.lastnestedScrolldy == 0) {currentTop = child.getTop(); If (currentTop < this.halfExpandeDoffset){// smaller than the collapse height then shrink targetState = STATE_HIDDEN; top = this.parentHeight; }else{// Other expansions top = this.getExpandeDoffSet (); targetState = 3; } }else { currentTop = child.getTop(); If (currenttop-this.halfexpandedoffset) < Math.abs(currenttop-this.collapsedoffset)) {/// Collapse more than the collapsedOffset and less than the maximum collapsedOffset top = this.getExpandedOffset(); targetState = 3; } else { top = this.parentHeight; TargetState = 5; } } } this.startSettlingAnimation(child, targetState, top, false); this.nestedScrolled = false; }}Copy the code

It looks a lot simpler to remove a lot of the intermediate state code, but here’s the nested slide correction. If there’s no nested slide, we call onViewReleased, so we’ll change that as well

public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { int top; byte targetState; int currentTop; If (yvel < 0.0 F) {if (TsmBottomSheetBehavior. This. FitToContents) {top = TsmBottomSheetBehavior.this.fitToContentsOffset; targetState = STATE_EXPANDED; } else { currentTop = releasedChild.getTop(); if (currentTop > TsmBottomSheetBehavior.this.halfExpandedOffset) { top = TsmBottomSheetBehavior.this.halfExpandedOffset;  targetState = STATE_EXPANDED; } else { top = TsmBottomSheetBehavior.this.expandedOffset; targetState = STATE_EXPANDED; } } } else if (TsmBottomSheetBehavior.this.hideable && TsmBottomSheetBehavior.this.shouldHide(releasedChild, yvel) && (releasedChild.getTop() > TsmBottomSheetBehavior.this.collapsedOffset || Math.abs(xvel) < Math.abs(yvel))) { top = TsmBottomSheetBehavior.this.parentHeight; targetState = STATE_HIDDEN; } else if (yvel ! = 0.0 F && Math. Abs (xvel) < = Math. Abs (yvel)) {if (TsmBottomSheetBehavior. This. FitToContents) {top = TsmBottomSheetBehavior.this.parentHeight; targetState = STATE_HIDDEN; } else { currentTop = releasedChild.getTop(); if (Math.abs(currentTop - TsmBottomSheetBehavior.this.halfExpandedOffset) < Math.abs(currentTop - TsmBottomSheetBehavior.this.collapsedOffset)) { top = TsmBottomSheetBehavior.this.expandedOffset; targetState = STATE_EXPANDED; } else { top = TsmBottomSheetBehavior.this.parentHeight; targetState = STATE_HIDDEN; } } } else { currentTop = releasedChild.getTop(); if (TsmBottomSheetBehavior.this.fitToContents) { if (Math.abs(currentTop - TsmBottomSheetBehavior.this.fitToContentsOffset) < Math.abs(currentTop - TsmBottomSheetBehavior.this.collapsedOffset)) {  top = TsmBottomSheetBehavior.this.fitToContentsOffset; targetState = STATE_EXPANDED; } else { top = TsmBottomSheetBehavior.this.parentHeight; targetState = STATE_HIDDEN; } } else if (currentTop < TsmBottomSheetBehavior.this.halfExpandedOffset) { if (currentTop < Math.abs(currentTop - TsmBottomSheetBehavior.this.collapsedOffset)) { top = TsmBottomSheetBehavior.this.expandedOffset; targetState = STATE_EXPANDED; } else { top = TsmBottomSheetBehavior.this.parentHeight; targetState = STATE_HIDDEN; } } else if (Math.abs(currentTop - TsmBottomSheetBehavior.this.halfExpandedOffset) < Math.abs(currentTop - TsmBottomSheetBehavior.this.collapsedOffset)) { top = TsmBottomSheetBehavior.this.parentHeight; targetState = STATE_HIDDEN; } else { top = TsmBottomSheetBehavior.this.parentHeight; targetState = STATE_HIDDEN; } } TsmBottomSheetBehavior.this.startSettlingAnimation(releasedChild, targetState, top, true); }}Copy the code

That’s where it ends

Because this code is a lot of changes, so let’s share a github address, so that you can use github to see >>

Tian Shouming, Big Front Research and Development Center