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

Android Kotlin Compose:Button内调用@Composable函数报错的原因与修复

问题与解决方案

问题描述

基于developer.android.com Jetpack示例项目扩展MyColumnPainter函数实现骰子滚动绘制效果,其中前两次和第五次调用该函数可正常编译,但Button的onClick回调内的两次调用出现@Composable function must be called from within a @Composable function错误。

错误原因

  1. Composable函数调用规则限制:带@Composable注解的函数(比如MyColumnPainter)只能在Composable上下文中调用——也就是其他标注了@Composable的函数内部,或是Compose组件提供的可组合Lambda(如Column、Row的content参数)中。而Button的onClick回调是普通Kotlin函数(无@Composable注解),属于非Composable上下文,直接调用Composable函数违反规则。
  2. UI更新逻辑错误:试图通过循环调用绘制函数实现滚动动画的思路不符合Compose的状态驱动UI设计理念。Compose不会通过直接调用绘制函数更新界面,而是通过修改状态变量触发UI重组,自动刷新界面。

修复方案

  1. 移除非Composable上下文的Composable调用:删除onClick回调内直接调用MyColumnPainter的代码。
  2. 用状态驱动动画效果:使用LaunchedEffect配合状态变量,实现骰子滚动的逐帧切换动画;通过状态变更让Compose自动重组更新UI。
  3. 修正代码语法错误:比如MyImageList[]的语法错误、Integer类型改为Kotlin标准的Int等。

修改后的完整代码

/*
* Copyright (C) 2023 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
*
*      https://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.
*/
package com.example.diceroller

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import com.example.diceroller.ui.theme.DiceRollerTheme
import android.util.Log
import androidx.compose.runtime.LaunchedEffect
import kotlinx.coroutines.delay

val myImageList = intArrayOf(
    android.R.drawable.dice_1,
    android.R.drawable.dice_2,
    android.R.drawable.dice_3,
    android.R.drawable.dice_4,
    android.R.drawable.dice_5,
    android.R.drawable.dice_6
)

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            DiceRollerTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    DiceRollerApp()
                }
            }
        }
    }
}

@Preview
@Composable
fun DiceRollerApp() {
    DiceWithButtonAndImage(modifier = Modifier
        .fillMaxSize()
        .wrapContentSize(Alignment.Center)
    )
}

@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    MyColumnPainter(result = 1, modifier = Modifier, imageResource = android.R.drawable.dice_1)
    var x: Int
    var xString1: String
    var xString2: String
    var xString3: String

    var result by remember { mutableStateOf(1) }
    var isRolling by remember { mutableStateOf(false) }
    val imageResource: Int = myImageList[result - 1]

    Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
        Image(painter = painterResource(imageResource), contentDescription = result.toString())
        MyColumnPainter(result = 1, modifier = Modifier, imageResource = android.R.drawable.dice_1)

        Button(
            onClick = {
                isRolling = true
            },
        ) {
            Text(text = stringResource(R.string.roll), fontSize = 24.sp)
            MyColumnPainter(result = 1, modifier = Modifier, imageResource = android.R.drawable.dice_1)
        }

        // 触发滚动动画逻辑
        LaunchedEffect(isRolling) {
            if (isRolling) {
                repeat(6) { idx ->
                    result = (1..6).random()
                    xString1 = "x = ${idx+1} result = $result"
                    Log.i("FYI", xString1)
                    delay(100) // 控制每帧切换间隔
                }
                isRolling = false
            }
        }
    }
}

@Composable
fun MyColumnPainter(result: Int, modifier: Modifier, imageResource: Int) {
    Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
        Image(
            painter = painterResource(imageResource),
            contentDescription = result.toString()
        )
    }
}

说明

  • 新增isRolling状态变量标记是否处于滚动状态,点击按钮后触发动画。
  • 使用LaunchedEffect在后台协程中执行滚动逻辑,每帧随机切换骰子点数并延迟,结束后重置状态。
  • 所有MyColumnPainter调用均保留在Composable上下文中,符合调用规则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 00:07:37