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

Jetpack Compose聊天机器人实现消息列表自动滚动到底部

问题:Jetpack Compose LazyColumn 无法自动滚动到最新消息

我正在开发一款聊天机器人应用,采用Jetpack Compose的LazyColumn展示消息列表。列表初始为空,先添加一条Bot消息引导用户交互,后续用户发送消息后会自动添加Bot回复,形成交互循环。当消息数量超出屏幕范围时,希望界面能自动滚动到最新消息位置,但由于列表是动态填充的,且不同设备屏幕尺寸不同,目前无法实现自动滚动,用户需手动滚动,体验较差。截图中可见最新消息(user:dsdssd)未自动显示在可视区域。


相关代码

FirstScreen 代码

@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun FirstScreen(
    viewModel: ChatBotViewModel = hiltViewModel()
) {

    val list = remember { mutableStateListOf<Message>() }
    val botList = listOf("Peter", "Francesca", "Luigi", "Igor")
    val random = (0..3).random()
    val botName: String = botList[random]
    val hashMap: HashMap<String, String> = HashMap()

    viewModel.customBotMessage(message = Message1, list)

    Column(modifier = Modifier.fillMaxHeight(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Top) {
        Divider()
        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            ){
            items(list.size) { i ->
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement =
                    if (list[i].id == RECEIVE_ID)
                        Arrangement.Start
                    else
                        Arrangement.End
                ) {
                    if (list[i].id == RECEIVE_ID) {
                        Item(
                            message = list[i],
                            botName,
                            botcolor,
                            list,
                            simpleTextFlag = true,
                            hashMap,
                            modifier = Modifier
                                .padding(
                                    start = 32.dp,
                                    end = 4.dp,
                                    top = 4.dp
                                ),
                        )
                    }
                    else {
                        Item(
                            message = list[i],
                            "user",
                            usercolor,
                            list,
                            simpleTextFlag = false,
                            hashMap,
                            Modifier.padding(
                                start = 4.dp,
                                end = 32.dp,
                                top = 4.dp
                            )
                        )
                    }
                }
            }
        }
    }
}

Item 模块代码

@Composable
fun Item(
    message: Message,
    person: String,
    color: Color,
    list: SnapshotStateList<Message>,
    simpleTextFlag: Boolean,
    hashMap: HashMap<String, String>,
    modifier: Modifier,
    viewModel: ChatBotViewModel = hiltViewModel()
) {

    Column(verticalArrangement = Arrangement.Top) {
        Card(
            modifier = Modifier
                .padding(10.dp),
            backgroundColor = color,
            elevation = 10.dp
        ) {
            Row(
                verticalAlignment = Alignment.Top,
                modifier = Modifier.padding(3.dp)
            ) {
                Text(
                    buildAnnotatedString {
                        withStyle(
                            style = SpanStyle(
                                fontWeight = FontWeight.Medium,
                                color = Color.Black
                            )
                        ) {
                            append("$person: ")
                        }
                    },
                    modifier = Modifier
                        .padding(4.dp)
                )
                Text(
                    buildAnnotatedString {
                        withStyle(
                            style = SpanStyle(
                                fontWeight = FontWeight.W900,
                                color = Color.White//Color(/*0xFF4552B8*/)
                            )
                        )
                        {
                            append(message.message)
                        }
                    }
                )
            }
        }
        if (simpleTextFlag && list.size != 13) {
            SimpleText(list = list, hashMap, viewModel)
        }
    }
}

@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
@Composable
fun SimpleText(
    list: SnapshotStateList<Message>,
    hashMap: HashMap<String, String>,
    viewModel: ChatBotViewModel,
) {

    val coroutineScope = rememberCoroutineScope()
    val keyboardController = LocalSoftwareKeyboardController.current
    val bringIntoViewRequester = remember { BringIntoViewRequester() }
    var visible by remember { mutableStateOf(true) }

    var text by remember { mutableStateOf("") }

    AnimatedVisibility(
        visible = visible,
        enter = fadeIn() + slideInHorizontally(),
        exit = fadeOut() + slideOutHorizontally()
    ) {
        Column {
            Row(
                verticalAlignment = Alignment.Bottom,
                horizontalArrangement = Arrangement.End,
                modifier = Modifier.bringIntoViewRequester(bringIntoViewRequester)
            ) {
                OutlinedTextField(
                    modifier = Modifier
                        .padding(2.dp)
                        .onFocusEvent { focusState ->
                            if (focusState.isFocused) {
                                coroutineScope.launch {
                                    bringIntoViewRequester.bringIntoView()
                                }
                            }
                        },
                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
                    keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
                    value = text,
                    onValueChange = { text = it },
                    shape = RoundedCornerShape(12.dp),
                    label = { Text("send message") })
                IconButton(onClick = {
                    if (text.isNotEmpty()) {
                        //Adds it to our local list
                        list.add(
                            Message(
                                text,
                                Constants.SEND_ID,
                                Timestamp(System.currentTimeMillis()).toString()
                            )
                        )
                        hashMap[Messages.listOfMessageKeys[viewModel.index.value - 1]] = text
                        viewModel.customBotMessage(listOfMessages[viewModel.index.value], list)
                        viewModel.index.value += 1
                        visible = false
                    }
                    text = ""
                }) {
                    Icon(
                        modifier = Modifier.padding(2.dp),
                        painter = painterResource(id = R.drawable.ic_baseline_send_24),
                        contentDescription = "send message img"
                    )
                }
            }
            if (list.size == 3 || list.size == 5 || list.size == 7) {
                Button()
                if(viewModel.isClickedBtn.value){
                    visible = false
                    viewModel.isClickedBtn.value = false
                    list.add(
                        Message(
                            text,
                            Constants.SEND_ID,
                            Timestamp(System.currentTimeMillis()).toString()
                        )
                    )
                    hashMap[Messages.listOfMessageKeys[viewModel.index.value - 1]] = text
                    viewModel.customBotMessage(listOfMessages[viewModel.index.value], list)
                    viewModel.index.value += 1
                }
            }
        }
    }
}

@Composable
fun Button(
    viewModel: ChatBotViewModel = hiltViewModel()
) {

    Button(
        onClick = { viewModel.isClickedBtn.value = true },
    ) {
        Text("skip")
    }
}

ChatBotViewModel 代码

class ChatBotViewModel @Inject constructor() : ViewModel() {

    var index = mutableStateOf(value = 1)
    val isClickedBtn = mutableStateOf(false)

    fun customBotMessage(message: String, list: SnapshotStateList<Message>) {

        GlobalScope.launch {
            delay(1000)
            withContext(Dispatchers.Main) {
                list.add(
                    Message(
                        message,
                        Constants.RECEIVE_ID,
                        Timestamp(System.currentTimeMillis()).toString()
                    )
                )
            }
        }
    }
}

问题截图

最新消息未显示在可视区域


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 05:20:49