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

Flutter应用实现双标签Tab Bar并对接Firestore数据库求助

实现方案

基于你现有的代码完成双Tab页改造后的完整可运行代码如下:

import 'package:chat_app/group_chats/group_info.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

import '../constants.dart';
import '../global_data.dart';

class FinancialRoom extends StatefulWidget {
  final String groupChatId, groupName;

  const FinancialRoom({required this.groupName, required this.groupChatId, Key? key})
      : super(key: key);

  @override
  State<FinancialRoom> createState() => _FinancialRoomState();
}

class _FinancialRoomState extends State<FinancialRoom> with SingleTickerProviderStateMixin {
  late TabController _tabController;
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final _formKey = GlobalKey<FormState>();
  TextEditingController amountValue = TextEditingController();

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    amountValue.dispose();
    super.dispose();
  }

  void onSend() async {
    Map<String, dynamic> data = {
      "amount": amountValue.text,
      "sendBy": _auth.currentUser!.displayName,
      "type": "text",
      "time": FieldValue.serverTimestamp(),
    };

    amountValue.clear();

    await _firestore
        .collection('groups')
        .doc(widget.groupChatId)
        .collection('chats')
        .add(data);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.groupName),
        bottom: TabBar(
          controller: _tabController,
          tabs: const [
            Tab(text: 'Need Help'),
            Tab(text: 'Help Requests'),
          ],
        ),
        actions: [
          IconButton(
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (_) => GroupInfo(
                    groupName: widget.groupName,
                    groupId: widget.groupChatId,
                  ),
                ),
              ),
              icon: const Icon(Icons.more_vert)),
        ],
      ),
      body: SafeArea(
        child: TabBarView(
          controller: _tabController,
          children: [
            // 第一个Tab:数据上传表单页
            Padding(
              padding: const EdgeInsets.all(20.0),
              child: Form(
                key: _formKey,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    const SizedBox(height: 10.0),
                    TextFormField(
                      controller: amountValue,
                      decoration: InputDecoration(
                        hintText: 'Enter the amount you want',
                        labelText: 'Enter the amount you want',
                        prefixIcon: const Icon(Icons.account_balance_wallet_outlined),
                        enabledBorder: kEnabledBorder,
                        focusedBorder: kFocusedBorder,
                        errorBorder: kErrorBorder,
                        focusedErrorBorder: kErrorBorder,
                      ),
                      validator: (value) {
                        if (value!.isEmpty) {
                          return 'Please enter the amount you want';
                        }
                        return null;
                      },
                    ),
                    const SizedBox(height: kInputSpacing),
                    SizedBox(
                      width: double.infinity,
                      child: TextButton(
                        style: TextButton.styleFrom(
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(10.0)),
                          backgroundColor: Colors.blue,
                          foregroundColor: Colors.white,
                          padding: const EdgeInsets.symmetric(vertical: 16.0),
                        ),
                        onPressed: () {
                          if (_formKey.currentState!.validate()) {
                            onSend();
                          }
                        },
                        child: Text(
                          'SEND',
                          style: kButtonTextStyle,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            // 第二个Tab:数据拉取展示页
            StreamBuilder<QuerySnapshot>(
              stream: _firestore
                  .collection('groups')
                  .doc(widget.groupChatId)
                  .collection('chats')
                  .orderBy('time')
                  .snapshots(),
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  final Size size = MediaQuery.of(context).size;
                  return ListView.builder(
                    itemCount: snapshot.data!.docs.length,
                    itemBuilder: (context, index) {
                      Map<String, dynamic> data =
                      snapshot.data!.docs[index].data()
                      as Map<String, dynamic>;
                      return messageTile(size, data);
                    },
                  );
                } else {
                  return const Center(child: CircularProgressIndicator());
                }
              },
            )
          ],
        ),
      ),
    );
  }

  Widget messageTile(Size size, Map<String, dynamic> data) {
    return Builder(builder: (_) {
      if (data['type'] == "text") {
        return Container(
          width: size.width,
          alignment: data['sendBy'] == _auth.currentUser!.displayName
              ? Alignment.centerRight
              : Alignment.centerLeft,
          child: Container(
              padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 14),
              margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 8),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(15),
                color: Colors.blue,
              ),
              child: Column(
                children: [
                  Text(
                    data['sendBy'],
                    style: const TextStyle(
                      fontSize: 12,
                      fontWeight: FontWeight.w500,
                      color: Colors.white,
                    ),
                  ),
                  SizedBox(
                    height: size.height / 200,
                  ),
                  Text(
                    data['amount'],
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                      color: Colors.white,
                    ),
                  ),
                ],
              )),
        );
      } else if (data['type'] == "img") {
        return Container(
          width: size.width,
          alignment: data['sendBy'] == _auth.currentUser!.displayName
              ? Alignment.centerRight
              : Alignment.centerLeft,
          child: Container(
            padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
            margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 8),
            height: size.height / 2,
            child: Image.network(
              data['amount'],
            ),
          ),
        );
      } else if (data['type'] == "notify") {
        return Container(
          width: size.width,
          alignment: Alignment.center,
          child: Container(
            padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
            margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 8),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(5),
              color: Colors.black38,
            ),
            child: Text(
              data['message'],
              style: const TextStyle(
                fontSize: 14,
                fontWeight: FontWeight.bold,
                color: Colors.white,
              ),
            ),
          ),
        );
      } else {
        return const SizedBox();
      }
    });
  }
}

核心修改说明

  • 原无状态组件改为有状态组件,混入SingleTickerProviderStateMixin实现TabController的初始化,适配TabBar的切换动画需求
  • AppBar新增bottom属性配置TabBar,包含你需要的「Need Help」和「Help Requests」两个标签
  • 原金额提交表单放在第一个Tab页面,Firestore数据拉取列表放在第二个Tab页面,完全复用你原有业务逻辑
  • 修正了原代码中_formKey绑定错误的问题:原代码把表单key绑定到Container上,现已改为绑定到Form组件上,保证表单验证逻辑正常生效
  • 替换了Flutter 3.0+已废弃的FlatButton为TextButton,兼容新版本运行
  • 移除了原布局中嵌套ListView的结构,改用TabBarView承载两个页面,避免滑动冲突和布局溢出问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 16:36:06