李海源 發表於 2025-7-11 18:54:00

Jetpack Compose学习(16)——ModalBottomSheet(底部弹窗)

<blockquote>
<p>原文地址: Jetpack Compose学习(16)——ModalBottomSheet(底部弹窗)-Stars-One的杂货小窝</p>
</blockquote>
<p>接手新公司项目里,有代码用到了这个弹窗,由于需要重构架构和进行相关统一组件封装,顺手学习下这个组件,发现还是踩了些坑(怪我以Compose里的Dialog来用了哈哈)</p>
<h2 id="介绍">介绍</h2>
<p>这个组件是属于M3里的组件,需要引入<code>androidx.compose.material3</code>这个依赖</p>
<p>不过新版本的Android Studio创建项目都是直接通过Bom引入了,是都会带有了这个依赖了(这里就不过多提及加入依赖了)</p>
<blockquote>
<p>PS:主要是现在新版本Android Studio,新创建的项目,依赖分了几个文件,贴的话会很麻烦,见谅哈哈</p>
</blockquote>
<pre><code>androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
</code></pre>
<h2 id="基本使用">基本使用</h2>
<p>效果:<br>
<img src="https://img2024.cnblogs.com/blog/1210268/202507/1210268-20250711180943746-19473053.gif" alt="PixPin_2025-07-11_18-08-42" loading="lazy"></p>
<p>代码:</p>
<pre><code class="language-kotlin">import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ModelSheetDemoPage(modifier: Modifier = Modifier) {
    val context = LocalContext.current

    Column(modifier = Modifier.statusBarsPadding()) {

      var openBottomSheet by rememberSaveable { mutableStateOf(false) }
      val scope = rememberCoroutineScope()
      val bottomSheetState = rememberModalBottomSheetState()

      // App content
      Column(
            horizontalAlignment = Alignment.Start,
            verticalArrangement = Arrangement.spacedBy(4.dp)
      ) {

            Button(
                onClick = { openBottomSheet = !openBottomSheet },
                modifier = Modifier.align(Alignment.CenterHorizontally)
            ) {
                Text(text = "Show Bottom Sheet")
            }
      }

      // Sheet content
      if (openBottomSheet) {
            ModalBottomSheet(
                onDismissRequest = { openBottomSheet = false },
                sheetState = bottomSheetState,
            ) {

                Text("内容数据")

                Button(
                  // Note: If you provide logic outside of onDismissRequest to remove the sheet,
                  // you must additionally handle intended state cleanup, if any.
                  onClick = {
                        scope
                            .launch { bottomSheetState.hide() }
                            .invokeOnCompletion {
                              if (!bottomSheetState.isVisible) {
                                    openBottomSheet = false
                              }
                            }
                  }
                ) {
                  Text("Hide Bottom Sheet")
                }

            }
      }
    }
}

</code></pre>
<p>这里需要注意的是:</p>
<ol>
<li>展示弹窗,实际和Dialog类似,也是控制一个Boolean数值变化从而弹出</li>
<li>关闭弹窗的代码得用上面的写法,否则就是没有下滑效果!!(之前就是在这踩坑了,虽然没人关注这点哈哈)</li>
</ol>
<blockquote>
<p>PS:后续代码为了保证重点和观感,会有所精简</p>
</blockquote>
<p><code>ModalBottomSheet</code>默认是有半屏和全屏模式的,但上面例子由于我们的内容元素不多,高度不是大,我们改造下,加个LazyColumn再看下效果</p>
<p>效果:</p>
<p><img src="https://img2024.cnblogs.com/blog/1210268/202507/1210268-20250711181747130-1580520299.gif" alt="PixPin_2025-07-11_18-17-34" loading="lazy"></p>
<p>代码:</p>
<pre><code class="language-kotlin">ModalBottomSheet(
        onDismissRequest = { openBottomSheet = false },
        sheetState = bottomSheetState,
) {

        //....

        LazyColumn {
                items(20) {
                        Row(modifier= Modifier.fillMaxWidth().height(48.dp).padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) {
                                Text("hello ${it}")
                        }
                }
        }

}
</code></pre>
<h2 id="自定义样式">自定义样式</h2>
<h3 id="去掉小横条">去掉小横条</h3>
<p>想要实现自定义的下拉样式,然后,不希望要这个小横条(如下图所示),要如何实现呢?<br>
<img src="https://img2024.cnblogs.com/blog/1210268/202507/1210268-20250711181901201-2120839342.png" alt="image" loading="lazy"></p>
<p>可以通过<code>dragHandle</code>属性来实现,如下代码</p>
<pre><code class="language-kotlin">ModalBottomSheet(
        onDismissRequest = { openBottomSheet = false },
        sheetState = bottomSheetState,
        dragHandle = {} //设置为空
) {
       
}
</code></pre>
<p>效果如下:<br>
<img src="https://img2024.cnblogs.com/blog/1210268/202507/1210268-20250711182909163-104505158.png" alt="image" loading="lazy"></p>
<h3 id="顶头区自定义按钮">顶头区自定义按钮</h3>
<p>这个dragHandle实际就是顶头那篇区域,如果想要自定义一个取消和确定,也可以实现,如下效果和代码</p>
<p>效果:</p>
<p><img src="https://img2024.cnblogs.com/blog/1210268/202507/1210268-20250711183616082-1148455246.gif" alt="PixPin_2025-07-11_18-36-04" loading="lazy"></p>
<p>代码:</p>
<pre><code class="language-kotlin">ModalBottomSheet(
        onDismissRequest = { openBottomSheet = false },
        sheetState = bottomSheetState,
        dragHandle = {
                Row(modifier= Modifier.fillMaxWidth().padding(horizontal = 24.dp)) {
                        Text("取消",modifier= Modifier.clickable{
                                scope
                                        .launch { bottomSheetState.hide() }
                                        .invokeOnCompletion {
                                                if (!bottomSheetState.isVisible) {
                                                        openBottomSheet = false
                                                }
                                        }
                        })
                        Spacer(modifier= Modifier.weight(1f))
                        Text("确定",modifier= Modifier.clickable{
                                scope
                                        .launch { bottomSheetState.hide() }
                                        .invokeOnCompletion {
                                                if (!bottomSheetState.isVisible) {
                                                        openBottomSheet = false
                                                }
                                        }
                        })
                }
        }
)
</code></pre>
<h3 id="禁止展示半屏">禁止展示半屏</h3>
<p>如果不想要展示半屏的效果,可以通过下面这样设置</p>
<pre><code class="language-kotlin">val bottomSheetState = rememberModalBottomSheetState(true)
</code></pre>
<p>意思是,如果你的内容满全屏了,则直接跳过半屏模式,直接展示全屏,效果如下图展示</p>
<p><img src="https://img2024.cnblogs.com/blog/1210268/202507/1210268-20250711184232159-1485689212.gif" alt="PixPin_2025-07-11_18-42-27" loading="lazy"></p>
<p><strong>那我内容元素总高度不满全屏高度,又该如何呢?</strong></p>
<p>那更简单,直接将你的内容容器用Modifier设置为全屏即可</p>
<pre><code class="language-kotlin">ModalBottomSheet(
        onDismissRequest = { openBottomSheet = false },
        sheetState = bottomSheetState,
) {

        //直接填充满屏即可!
        Column(modifier= Modifier.fillMaxSize()) {
                Text("内容数据")

                Button(
                        // Note: If you provide logic outside of onDismissRequest to remove the sheet,
                        // you must additionally handle intended state cleanup, if any.
                        onClick = {
                                scope
                                        .launch { bottomSheetState.hide() }
                                        .invokeOnCompletion {
                                                if (!bottomSheetState.isVisible) {
                                                        openBottomSheet = false
                                                }
                                        }
                        }
                ) {
                        Text("Hide Bottom Sheet")
                }
        }
}
</code></pre>
<h2 id="参考">参考</h2>
<ul>
<li>Bottom sheets &nbsp;|&nbsp; Jetpack Compose &nbsp;|&nbsp; Android Developers</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    <hr>
<span>提问之前,请先看</span>提问须知
<span>点击右侧图标发起提问</span>
<img border="0" src="http://wpa.qq.com/pa?p=2:1053894518:52" alt="联系我" title="联系我">
<span>或者加入QQ群一起学习</span>
<img border="0" src="//pub.idqqimg.com/wpa/images/group.png" alt="Stars-One安卓学习交流群" title="Stars-One安卓学习交流群">
TornadoFx学习交流群:1071184701
<img src="https://img2020.cnblogs.com/blog/1210268/202003/1210268-20200316120825333-1551152974.png" width="1000" height="auto">
<img src="https://img2018.cnblogs.com/blog/1210268/201905/1210268-20190508151523126-971809604.gif" width="1000" height="auto">
<!--<img src="https://img2020.cnblogs.com/blog/1210268/202004/1210268-20200413161422035-1188549898.gif" width="1000" height="auto">--><br><br>
来源:https://www.cnblogs.com/stars-one/p/18979413
頁: [1]
查看完整版本: Jetpack Compose学习(16)——ModalBottomSheet(底部弹窗)