动态更改BottomSheetBehavior的高度

49

我最近在使用谷歌发布的AppCompat v23.2中提供的BottomSheetBehavior。我的底部面板的高度取决于底部面板内显示的内容(类似于谷歌自己在其地图应用程序中所做的操作)。

它可以很好地处理初始加载的数据,但是当我的应用程序在运行时更改显示的内容时,底部面板仍保留在旧的高度上,这会导致底部出现未使用的空间或者被切割视图。

是否有任何方法可以通知底部面板布局重新计算用于展开状态的高度(当ViewGroup的高度设置为MATCH_HEIGHT时),或者手动设置所需高度?


编辑:我还尝试手动调用invalidate()ViewGroup和其父级上,但没有任何成功。


你可以将视图高度设置为wrap_parent,然后在内容加载完成后使视图无效。 - Rakeeb Rajbhandari
视图高度设置为wrap_parent,但我的问题是一旦视图失效,它不会重新计算其高度,底部表单仍然保持之前的状态。 - miho
看一下BottomSheetBehavior的代码,它在onLayoutChild方法中计算高度,该方法由CoordinatorLayout调用。你尝试过通过在其上调用requestLayout()来使其失效吗? - appmattus
3
这里报告了一个状态为 FutureRelease 的问题。也许有一天他们会修复它。 - wrozwad
10个回答

39

我与RelativeLayout作为我的底部面板遇到了同样的问题,高度无法重新计算。我不得不通过设置新的重新计算后的高度值并调用BottomSheetBehavior.onLayoutChild来解决这个问题。

这是我的临时解决方案:

coordinatorLayout = (CoordinatorLayout)findViewById(R.id.coordinator_layout);
bottomSheet = findViewById(R.id.bottom_sheet);

int accountHeight = accountTextView.getHeight();
accountTextView.setVisibility(View.GONE);

bottomSheet.getLayoutParams().height = bottomSheet.getHeight() - accountHeight;
bottomSheet.requestLayout();
behavior.onLayoutChild(coordinatorLayout, bottomSheet, ViewCompat.LAYOUT_DIRECTION_LTR);

2
我很希望能看到更好的解决方案,但现在似乎这是唯一的方法。 - miho
4
在我修改视图后,对于我来说bottomSheet.requestLayout()已经足够了。 - Odys
1
撤回之前的评论,即时运行正在愚弄我。 - Odys
如果您首先获取LayoutParams,然后修改并将其设置回去,那么请求布局(requestLayout)将自动为您调用。 - koufa
1
我正在onCreateView中使用它,但setPeekHeight、requestLayout和setState都没有起作用。我应该把这段代码放在哪里? - Code Wiget

16

你可以使用BottomSheetBehavior#setPeekHeight来实现。

FrameLayout bottomSheet = (FrameLayout) findViewById(R.id.bottom_sheet);
BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setPeekHeight(newHeight);

这不会自动将底部表移动到峰值高度。您可以调用 BottomSheetBehavior#setState 来调整您的底部表至新的峰值高度。

behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);

2
不,我要找的是扩展后的尺寸。我已经使用了折叠状态和预览高度。但是我的预览大小总是限制在预览标题上,而扩展后的大小取决于可用内容的数量。(例如,对象的描述。) - miho
1
我正在onCreateView中使用它,但setPeekHeight和setState没有起作用。我应该把这段代码放在哪里? - Code Wiget

4

虽然在>=24.0.0版本的支持库中已经解决了这个问题,但如果由于某种原因您仍然需要使用旧版本,则可以采用以下解决方法。

mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull final View bottomSheet, int newState) {
            bottomSheet.post(new Runnable() {
                @Override
                public void run() {
                    //workaround for the bottomsheet  bug
                    bottomSheet.requestLayout();
                    bottomSheet.invalidate();
                }
            });
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
        }
    });

3

关于底部弹出式对话框碎片,请阅读此文章:Bottom Sheet Dialog Fragment Expand Full Height

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
   super.onActivityCreated(savedInstanceState);

   BottomSheetDialog dialog = (BottomSheetDialog) getDialog();

   FrameLayout bottomSheet = dialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
   BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
   behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
   behavior.setPeekHeight(0);
}

非常感谢您提供的解决方案,它对我非常有效。节省了我很多时间。 - Rohit Sharma

1

我也遇到了同样的问题,当尝试基于内容更新最大高度时,会发现之前布局的高度。这是有道理的,因为新布局尚未生效。通过在UI线程上发布,可以在新布局后计算布局高度,并发出另一个布局请求以将底部表单更新到正确的高度。

void show() {
    setVisibility(View.VISIBLE);
    post(new Runnable() {
        @Override
        public void run() {
            mBottomSheetBehavior.setPeekHeight(findViewById(R.id.sheetPeek).getHeight());
            requestLayout();
        }
    })
}

1

我曾经遇到过类似于你的问题。

手动设置bottomSheet的高度对我来说是解决方案。

有一个视图viewA,它具有BottomSheetBehaviour和自定义方法modifyHeight(),可以修改视图的高度:

viewA?.modifyHeight()
viewA?.measure(
            MeasureSpec.makeMeasureSpec(
                width,
                MeasureSpec.EXACTLY
            ),
            MeasureSpec.makeMeasureSpec(
                0,
                MeasureSpec.UNSPECIFIED
            )
        )
val layoutParams = LayoutParams(viewA.measuredWidth, viewA.measuredHeight)
val bottomSheet = BottomSheetBehavior.from(viewA)
layoutParams.behavior = bottomSheet
viewA.layoutParams = layoutParams

我的布局会是这样的:

    <com.yourpackage.ViewA
    android:id="@+id/viewA"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:behavior_peekHeight="50dp"
    app:layout_behavior="@string/bottom_sheet_behavior" />

重用旧的layoutParams中的bottomSheetBehaviour非常重要,因为它包含了您可能已经附加的peekHeight和监听器。


0

我遵循了@HaraldUnander的建议,得到了一个实际可行的想法。如果在以编程方式设置BottomSheetBehavior.stateSTATE_COLLAPSED后运行线程(无法像他那样使用post方法),则可以已经获取视图的高度并根据其内容设置peekHeight

因此,首先设置BottomSheetBehavior

BottomSheetBehavior.from(routeCaptionBottomSheet).state = BottomSheetBehavior.STATE_COLLAPSED

然后你可以动态地设置peekHeight:

thread {
    activity?.runOnUiThread {
        val dynamicHeight = yourContainerView.height
        BottomSheetBehavior.from(bottomSheetView).peekHeight = dynamicHeight
    }
}

如果使用Java(我正在使用Kotlin和Anko进行线程处理),可以这样做:
new Thread(new Runnable() {
    public void run() {
        int dynamicHeight = yourContainerView.getHeight();
        BottomSheetBehavior.from(bottomSheetView).setPeekHeight(dynamicHeight);
    }
}).start();

0

当我在底部工作表中使用RecyclerView并且项目动态更改时,我遇到了相同的问题。正如@sosite在他的评论中提到的那样,该问题已被记录,并已在最新版本中修复。 问题日志在此处

只需将设计支持库更新到版本24.0.0并进行检查。


1
我遇到了同样的问题,我正在使用新的AndroidX库。 - Andrey Solera

0
这是我实现的用于设置底部弹出窗口选取高度并带有动画效果的切换按钮点击监听器。
FrameLayout standardBottomSheet = findViewById(R.id.standardBottomSheet);

BottomSheetBehavior<FrameLayout> bottomSheetBehavior = BottomSheetBehavior.from(standardBottomSheet);    

btnToggleBottomSheet.setOnClickListener(new HPFM_OnSingleClickListener() {
                @Override
                public void onSingleClick(View v) {
                    if (bottomSheetBehavior.getPeekHeight() == 0) {
                        ObjectAnimator.ofInt(bottomSheetBehavior, "peekHeight", 200).setDuration(300).start();
                    }
                    else {
                        ObjectAnimator.ofInt(bottomSheetBehavior, "peekHeight", 0).setDuration(300).start();
                    }
                }
            });

0
以下代码片段帮助我解决了这个问题,我在布局中切换不同视图的可见性,底部表单的高度会自动更改。
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.your_bottom_sheet_layout, container, false)
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
    dialog.setContentView(R.layout.your_bottom_sheet_layout)

    dialog.setOnShowListener {
        val castDialog = it as BottomSheetDialog
        val bottomSheet = castDialog.findViewById<View?>(R.id.design_bottom_sheet)
        val behavior = BottomSheetBehavior.from(bottomSheet)
        behavior.state = BottomSheetBehavior.STATE_EXPANDED
        behavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onStateChanged(bottomSheet: View, newState: Int) {
                if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                    behavior.state = BottomSheetBehavior.STATE_EXPANDED
                }
            }

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

    return dialog
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接