You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose如何在onClick事件中正确调用Composable函数

报错原因

@Composable 函数仅能在Composable重组作用域内被调用,Modifier.clickable 的点击回调是普通函数上下文,不属于Composable作用域,直接在此处调用Composable函数就会抛出你看到的编译错误。

正确实现

Compose是状态驱动UI的框架,不能在事件回调里直接命令式调用Composable组件渲染,而是要通过状态变量控制UI的展示:

  1. 定义一个布尔类型的状态,用来标记是否需要打开网页
  2. 点击卡片时把这个状态设为true
  3. 在Composable的顶层作用域里判断这个状态,为true时渲染承载WebView的组件即可。

改动最小的实现是用全屏Dialog承载WebView,不需要额外接入导航组件,修改后的代码如下:

// 注意提前导入对应依赖
// import androidx.compose.runtime.getValue
// import androidx.compose.runtime.setValue
// import androidx.compose.ui.window.Dialog
// import androidx.compose.ui.window.DialogProperties
// import androidx.compose.material.Icon
// import androidx.compose.material.IconButton
// import androidx.compose.material.icons.Icons
// import androidx.compose.material.icons.filled.ArrowBack
@Composable
fun NewsCardView(
    article: Article,
    modifier: Modifier = Modifier,
) {
    // 定义状态控制WebView弹窗显隐
    var showWebView by remember { mutableStateOf(false) }

    // 状态为true时渲染全屏WebView弹窗
    if (showWebView) {
        Dialog(
            onDismissRequest = { showWebView = false },
            properties = DialogProperties(
                dismissOnBackPress = true,
                dismissOnClickOutside = false,
                usePlatformDefaultWidth = false
            )
        ) {
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.White)
            ) {
                // 顶部返回按钮
                IconButton(
                    onClick = { showWebView = false },
                    modifier = Modifier
                        .padding(16.dp)
                        .statusBarsPadding()
                ) {
                    Icon(Icons.Default.ArrowBack, contentDescription = "返回")
                }
                // WebView区域,预留顶部状态栏和返回按钮的位置
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(top = 56.dp)
                ) {
                    ShowOnWebView(url = article.url ?: "")
                }
            }
        }
    }

    Card(
        modifier = modifier
            .height(150.dp)
            .fillMaxWidth()
            .padding(2.dp)
            .clickable {
                // 点击时更新状态,触发重组展示WebView
                showWebView = true
            },
        elevation = 5.dp,
        backgroundColor = Color.White
    ) {
        val painter = rememberImagePainter(data = article.urlToImage) {
            crossfade(1000)
            error(R.drawable.ic_placeholder)
            placeholder(R.drawable.ic_placeholder)
        }
        Row(
            modifier = Modifier.fillMaxSize(),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {
            Column(
                modifier = Modifier.fillMaxWidth(.3f),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Image(
                    modifier = Modifier.size(100.dp),
                    painter = painter,
                    contentDescription = "News",
                    contentScale = ContentScale.Crop,
                )
                Text(
                    text = article.author ?: "",
                    fontSize = 12.sp,
                )
                Text(
                    text = article.publishedAt,
                    fontSize = 12.sp,
                )
            }
            Column(
                modifier = Modifier.fillMaxWidth(.7f),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(
                    modifier = Modifier.fillMaxWidth(),
                    text = article.title,
                    color = Color.Black,
                    fontWeight = FontWeight.Bold,
                    fontSize = 16.sp
                )
                Text(
                    modifier = Modifier.fillMaxWidth(),
                    text = article.description ?: "",
                    color = Color.Black,
                    fontWeight = FontWeight.Medium,
                    fontSize = 14.sp
                )
            }
        }
    }
}

你原来的ShowOnWebView实现存在小问题,直接取LocalContext.current在部分场景下会导致WebView加载异常,建议用AndroidView工厂参数传入的Context,同时加上JS支持适配绝大多数网页,优化后代码:

@Composable
fun ShowOnWebView(url: String) {
    AndroidView(
        factory = { context ->
            WebView(context).apply {
                webViewClient = WebViewClient()
                settings.javaScriptEnabled = true
                loadUrl(url)
            }
        },
        update = { webView ->
            // 处理url动态变化的场景
            if (webView.url != url) webView.loadUrl(url)
        }
    )
}
其他可选方案

如果项目已经接入Compose Navigation导航组件,更规范的做法是把点击事件上抛给上层页面:

  • 给NewsCardView增加一个onCardClick: () -> Unit的lambda参数
  • 在clickable回调里直接调用onCardClick()
  • 上层页面监听这个回调,通过导航动作跳转到独立的WebView页面承载ShowOnWebView组件即可,更符合单Activity架构的页面拆分逻辑。

内容的提问来源于stack exchange,提问作者hemant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 20:42:19