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

能否用BottomSheet替代弹窗展示Google账号?求Flutter/原生安卓iOS方案

实现Google登录账号选择BottomSheet的思路与方案

Flutter 实现方案

要替换默认的Google账号选择弹窗为BottomSheet,核心是自行获取已关联的Google账号列表,再自定义BottomSheet组件完成选择逻辑,步骤如下:

  1. 依赖配置
    确保项目中引入相关依赖,在pubspec.yaml中添加:

    dependencies:
      google_sign_in: ^6.2.1
      firebase_auth: ^4.16.0
    
  2. 获取已登录账号列表
    调用GoogleSignIn的accounts属性获取本地已关联的Google账号:

    final GoogleSignIn _googleSignIn = GoogleSignIn(scopes: ['email']);
    List<GoogleSignInAccount>? _accounts;
    
    Future<void> _fetchAccounts() async {
      _accounts = await _googleSignIn.signInSilently().then((_) => _googleSignIn.accounts);
    }
    
  3. 自定义BottomSheet展示账号
    用showModalBottomSheet弹出自定义列表,用户选择后触发登录:

    void _showAccountBottomSheet() async {
      await _fetchAccounts();
      if (_accounts?.isEmpty ?? true) {
        // 无账号时直接唤起系统登录弹窗
        await _googleSignIn.signIn();
        return;
      }
    
      showModalBottomSheet(
        context: context,
        builder: (context) => Container(
          padding: EdgeInsets.all(16),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text('选择Google账号', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              SizedBox(height: 16),
              ..._accounts!.map((account) => ListTile(
                leading: CircleAvatar(backgroundImage: NetworkImage(account.photoUrl ?? '')),
                title: Text(account.displayName ?? ''),
                subtitle: Text(account.email),
                onTap: () async {
                  Navigator.pop(context);
                  // 使用选中账号登录
                  final GoogleSignInAuthentication auth = await account.authentication;
                  // 若需Firebase验证,继续以下步骤
                  // final OAuthCredential credential = GoogleAuthProvider.credential(accessToken: auth.accessToken, idToken: auth.idToken);
                  // await FirebaseAuth.instance.signInWithCredential(credential);
                },
              )),
              SizedBox(height: 8),
              TextButton(
                child: Text('添加账号'),
                onPressed: () async {
                  Navigator.pop(context);
                  await _googleSignIn.signIn();
                },
              )
            ],
          ),
        ),
      );
    }
    
  4. 注意事项

    • 需要监听账号变化(比如用户新增/移除账号),可以通过_googleSignIn.onCurrentUserChanged流更新列表
    • 首次无账号时,还是需要唤起系统登录弹窗完成账号关联

原生Android 实现方案

Android中默认的Google SignIn弹窗是系统级的,要自定义BottomSheet需手动获取账号并实现选择逻辑:

  1. 配置Google SignIn
    在build.gradle中添加依赖:

    implementation 'com.google.android.gms:play-services-auth:20.7.0'
    
  2. 获取已关联账号
    通过GoogleSignInClient或AccountManager获取本地Google账号:

    private val googleSignInClient: GoogleSignInClient by lazy {
        GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestEmail()
            .build()
            .let { GoogleSignIn.getClient(context, it) }
    }
    
    private fun fetchAccounts(): List<GoogleSignInAccount>? {
        return GoogleSignIn.getLastSignedInAccount(context)?.let {
            // 获取所有已登录账号(需权限GET_ACCOUNTS)
            AccountManager.get(context).getAccountsByType("com.google")
                .mapNotNull { acc ->
                    GoogleSignIn.getAccountForEmail(context, acc.name)
                }
        }
    }
    
  3. 自定义BottomSheetDialogFragment
    创建底部弹窗展示账号列表:

    class GoogleAccountBottomSheet : BottomSheetDialogFragment() {
        private lateinit var accounts: List<GoogleSignInAccount>
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            accounts = arguments?.getParcelableArrayList("ACCOUNTS") ?: emptyList()
            return inflater.inflate(R.layout.bottom_sheet_accounts, container, false)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            val recyclerView = view.findViewById<RecyclerView>(R.id.account_recycler)
            recyclerView.adapter = AccountAdapter(accounts) { account ->
                dismiss()
                // 使用选中账号登录
                googleSignInClient.signInWithAccount(requireContext(), account)
                    .addOnCompleteListener { task ->
                        if (task.isSuccessful) {
                            // 登录成功,处理用户信息
                            val user = task.result?.signInAccount
                        }
                    }
            }
        }
    
        companion object {
            fun newInstance(accounts: List<GoogleSignInAccount>) = GoogleAccountBottomSheet().apply {
                arguments = Bundle().apply {
                    putParcelableArrayList("ACCOUNTS", ArrayList(accounts))
                }
            }
        }
    }
    
    // 适配器示例
    class AccountAdapter(private val accounts: List<GoogleSignInAccount>, private val onSelect: (GoogleSignInAccount) -> Unit) :
        RecyclerView.Adapter<AccountAdapter.ViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.item_account, parent, false)
            return ViewHolder(view)
        }
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val account = accounts[position]
            holder.name.text = account.displayName
            holder.email.text = account.email
            Glide.with(holder.itemView).load(account.photoUrl).into(holder.avatar)
            holder.itemView.setOnClickListener { onSelect(account) }
        }
    
        override fun getItemCount() = accounts.size
    
        class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val avatar: ImageView = itemView.findViewById(R.id.account_avatar)
            val name: TextView = itemView.findViewById(R.id.account_name)
            val email: TextView = itemView.findViewById(R.id.account_email)
        }
    }
    
  4. 唤起BottomSheet
    在登录按钮点击时判断是否有账号,有则弹出BottomSheet,无则唤起系统登录:

    fun onLoginClick() {
        val accounts = fetchAccounts()
        if (accounts.isNullOrEmpty()) {
            startActivityForResult(googleSignInClient.signInIntent, RC_SIGN_IN)
        } else {
            GoogleAccountBottomSheet.newInstance(accounts).show(parentFragmentManager, "AccountBottomSheet")
        }
    }
    

原生iOS 实现方案

iOS的GIDSignIn默认弹窗也是系统级的,自定义BottomSheet需要手动获取账号并设置模态弹窗样式:

  1. 配置依赖
    在Podfile中添加:

    pod 'GoogleSignIn', '~> 7.0'
    
  2. 获取已登录账号
    通过GIDSignIn获取已登录用户,或查询Keychain获取历史账号:

    import GoogleSignIn
    
    func fetchAccounts() -> [GIDGoogleUser] {
        var accounts = [GIDGoogleUser]()
        // 获取当前登录用户
        if let currentUser = GIDSignIn.sharedInstance.currentUser {
            accounts.append(currentUser)
        }
        // 可通过Keychain查询历史关联账号(需额外处理)
        return accounts
    }
    
  3. 自定义BottomSheet控制器
    创建UIViewController作为底部弹窗,设置UISheetPresentationController:

    class GoogleAccountSheetViewController: UIViewController {
        private var accounts: [GIDGoogleUser]
        private var onAccountSelected: ((GIDGoogleUser) -> Void)?
    
        init(accounts: [GIDGoogleUser], onSelected: @escaping (GIDGoogleUser) -> Void) {
            self.accounts = accounts
            self.onAccountSelected = onSelected
            super.init(nibName: nil, bundle: nil)
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .systemBackground
    
            // 创建列表
            let tableView = UITableView(frame: view.bounds)
            tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            tableView.dataSource = self
            tableView.delegate = self
            view.addSubview(tableView)
    
            // 添加"添加账号"按钮
            let addButton = UIButton(type: .system)
            addButton.setTitle("添加账号", for: .normal)
            addButton.addTarget(self, action: #selector(addAccountTapped), for: .touchUpInside)
            addButton.frame = CGRect(x: 0, y: view.bounds.height - 60, width: view.bounds.width, height: 50)
            view.addSubview(addButton)
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            // 设置底部弹窗样式
            if let sheet = presentationController as? UISheetPresentationController {
                sheet.detents = [.medium(), .large()]
                sheet.prefersGrabberVisible = true
            }
        }
    
        @objc private func addAccountTapped() {
            dismiss(animated: true) {
                GIDSignIn.sharedInstance.signIn(withPresenting: self) { result, error in
                    // 处理新账号登录结果
                }
            }
        }
    }
    
    extension GoogleAccountSheetViewController: UITableViewDataSource, UITableViewDelegate {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            accounts.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
            let user = accounts[indexPath.row]
            cell.textLabel?.text = user.profile?.name
            cell.detailTextLabel?.text = user.profile?.email
            if let url = user.profile?.imageURL(withDimension: 40) {
                // 加载头像(需SDWebImage等库)
                cell.imageView?.sd_setImage(with: url)
            }
            return cell
        }
    
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            tableView.deselectRow(at: indexPath, animated: true)
            let user = accounts[indexPath.row]
            dismiss(animated: true) {
                self.onAccountSelected?(user)
            }
        }
    }
    
  4. 唤起BottomSheet
    在登录按钮点击时触发:

    @IBAction func loginButtonTapped(_ sender: UIButton) {
        let accounts = fetchAccounts()
        if accounts.isEmpty {
            GIDSignIn.sharedInstance.signIn(withPresenting: self) { result, error in
                // 处理登录结果
            }
        } else {
            let sheetVC = GoogleAccountSheetViewController(accounts: accounts) { user in
                // 使用选中账号登录(可通过user.refreshToken获取凭证)
                GIDSignIn.sharedInstance.signIn(withUser: user, presenting: self) { result, error in
                    // 处理登录结果
                }
            }
            sheetVC.modalPresentationStyle = .pageSheet
            present(sheetVC, animated: true)
        }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 19:10:36