Flutter点击头像跳转个人主页:滑动面板收起及跳转异常问题
问题描述
- 需求:点击主页头像图标时,直接跳转至用户个人主页(UserPage),无需显示包含已上传故事的上滑面板。
- 现状:当前点击头像会跳转至UserHandling页面,该页面加载StoryProfile组件;StoryProfile通过SlidingUpPanel实现故事面板与个人主页的联动,仅当用户无故事时面板才会关闭。
- 问题:尝试在跳转逻辑中直接用
UserPage()替代UserHandling时出现红屏错误。
主页按钮代码
IconButton( icon: const Icon( Icons.perm_identity, color: Colors.black, ), onPressed: () { User user = FirebaseAuth.instance.currentUser!; Navigator.push( context, MaterialPageRoute( builder: (context) => UserHandling( uid: user.uid, ), ), ); })
UserHandling页面代码
class UserHandling extends StatefulWidget { final String uid; const UserHandling({super.key, required this.uid}); @override State<UserHandling> createState() => UserHandlingState(); } class UserHandlingState extends State<UserHandling> { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: StoryProfile( uid: widget.uid, currentUserID: widget.uid, ), ), ); } }
StoryProfile页面代码
class StoryProfile extends StatefulWidget { final String uid; final String currentUserID; const StoryProfile({ super.key, required this.uid, required this.currentUserID, }); @override State<StoryProfile> createState() => StoryProfileState(); } class StoryProfileState extends State<StoryProfile> { List<Story> storiesList = []; PanelController panelController = PanelController(); Color bgColor = Colors.transparent; late String currentUser; @override void initState() { super.initState(); currentUser = FirebaseAuth.instance.currentUser!.uid; FirebaseFirestore.instance .collection('Users/${widget.uid}/Story') .get() .then((value) { if (value.docs.isEmpty) { panelController.close(); } for (var element in value.docs) { storiesList.add(Story( url: (element.data() as Map)['url'], media: (element.data() as Map)['media'], id: element.id, )); } setState(() {}); getStoryData(); }); } @override Widget build(BuildContext context) { return Scaffold( body: SlidingUpPanel( color: bgColor, controller: panelController, onPanelOpened: () => setState(() => bgColor = Colors.white), onPanelClosed: () => setState(() => bgColor = Colors.transparent), panelBuilder: (ScrollController sc) { UserPage pg = UserPage(); pg.scrollController = sc; return pg; }, collapsed: const Center( child: Column( children: [ Icon( Icons.keyboard_arrow_up, color: Colors.white, ), Text( "SwipeUp to see more information", style: TextStyle(color: Colors.white), ), ], ), ), body: storiesList.isNotEmpty ? StoryScreen( stories: storiesList, uid: currentUser, currentUserID: currentUser, ) : const Center( child: Text( 'Aici trebuie adaugate imagini/videoclipuri cu serviciile dvs. cu scopul de promovare\nFolositi butonul "adauga story" ', textAlign: TextAlign.center, ), ), ), ); } void getStoryData() async { QuerySnapshot<Map<String, dynamic>> query = await FirebaseFirestore.instance .collection('Users/$currentUser/Story') .get(); storiesList.clear(); if (query.docs.isNotEmpty) { for (DocumentSnapshot<Map> doc in query.docs) { setState(() { storiesList.add(Story.fromDocument(doc)); }); } } } }
UserPage代码
class UserPage extends StatefulWidget { late ScrollController scrollController; UserPage({super.key}); @override State<UserPage> createState() => UserProfile(); } class UserProfile extends State<UserPage> { bool isFileImage = false; late User user; late File pickedFile; String? userImage; String? address; String? precise_address; String? userName; String? facebookUrl; String? instagramUrl; String? tikTokUrl; TextEditingController facebookController = TextEditingController(); TextEditingController instagramController = TextEditingController(); TextEditingController tiktokController = TextEditingController(); late String userId; Random randomGenerator = Random(); String? searchString; String? country; QuerySnapshot? querySnapshot; @override void initState() { super.initState(); initUser(); } initUser() async { user = FirebaseAuth.instance.currentUser!; userId = user.uid; logger.e("user id at init user =$userId"); getSocialData(); getData(); } final TextEditingController _userNameController = TextEditingController(); late File _image; final picker = ImagePicker(); Future getImage() async { final pickedFile = await picker.pickImage(source: ImageSource.gallery); if (pickedFile != null) { setState(() { _image = File(pickedFile.path); }); Reference storageReference = FirebaseStorage.instance.ref(); Uint8List imageData = _image.readAsBytesSync(); storageReference .child("Users Profile") .child(userId) .putData(imageData) .then((uploadTask) async { var dowurl = await uploadTask.ref.getDownloadURL(); String url = dowurl.toString(); Map<String, dynamic> urlNull = { 'avatarUrl': url, }; DocumentReference prereference = FirebaseFirestore.instance.collection('Users').doc(userId); await FirebaseFirestore.instance.runTransaction((transaction) async { transaction.update(prereference, urlNull); }); }); } } Future<void> displayPrediction(Prediction p, BuildContext context) async { var placeId = p.placeId; var address = await FlGeocoder(Constants.googlePlaceKey) .findAddressesFromPlaceId(placeId!); setState(() { searchString = p.description!; }); FirebaseFirestore.instance.collection('Users').doc(userId).update({ "precise_location": searchString, }); country = address.first.country!.longName; } var addresses; var first; double lat = 0.0; double long = 0.0; @override void didChangeDependencies() async { super.didChangeDependencies(); } @override Widget build(BuildContext context) { void viewReview(var ratings, String name, String review, String avatarUrl) { showGeneralDialog( context: context, barrierDismissible: false, transitionDuration: const Duration( milliseconds: 400, ), pageBuilder: (_, __, ___) { return Container( color: Colors.black.withOpacity(0.4), child: Scaffold( backgroundColor: Colors.transparent, appBar: AppBar( backgroundColor: Colors.transparent, automaticallyImplyLeading: false, actions: [ IconButton( icon: const Icon( Icons.close, color: Colors.white, ), onPressed: () { Navigator.pop(context); }, ), ], ), body: SafeArea( child: Container( padding: const EdgeInsets.all(8.0), child: Container( child: ListView( children: [ Container( margin: const EdgeInsets.only( right: 12, top: 8, bottom: 0), padding: const EdgeInsets.all(11), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 2, spreadRadius: 1) ], borderRadius: BorderRadius.circular(8)), child: Column( children: [ Row( children: [ CircleAvatar( backgroundImage: const AssetImage( 'assets/images/profilepic.png'), child: Image.network(avatarUrl), ), const SizedBox(width: 8), Text(name, style: const TextStyle( fontWeight: FontWeight.bold)), const SizedBox(width: 50), Row( children: [ _buildRatingStars(ratings), ], ) ], ), Container( margin: const EdgeInsets.only(top: 8), width: MediaQuery.of(context).size.width - 140, child: Text( review, textScaleFactor: 1.1, ), ), TextButton( style: TextButton.styleFrom( shape: const StadiumBorder(), backgroundColor: Colors.black, ), onPressed: () { Navigator.pop(context); }, child: const Text( 'Close', style: TextStyle( color: Colors.white, ), ), ), ], ), ), ], ), ), ), ), ), ); }, ); } return ListView( controller: widget.scrollController, padding: EdgeInsets.zero, children: <Widget>[ SizedBox( height: MediaQuery.of(context).size.height * 0.32, child: Stack( children: <Widget>[ Positioned( top: MediaQuery.of(context).size.height * 0.06, left: MediaQuery.of(context).size.width * 0.09, child: Row( children: [ Center( child: SizedBox( height: 84, width: 84, child: userImage != 'default' ? CircleAvatar( radius: 10, backgroundImage: NetworkImage(userImage ?? "")) : const CircleAvatar( radius: 10, backgroundImage: AssetImage('assets/images/profilepic.png'), ), ), ), ], ), ), Positioned( right: 24, top: MediaQuery.of(context).size.height * 0.2, child: Row( children: <Widget>[ SizedBox( height: 32, width: 100, child: ElevatedButton( onPressed: () async { await Alert( context: context, title: "", desc: "Adauga video-uri sau imagini cu afacerea ta", buttons: [ DialogButton( onPressed: () => addPicture(uploadTypes.STORY, uploadTypes.IMAGE, context), width: 120, child: const Text( "Image", style: TextStyle( color: Colors.white, fontSize: 20), ), ), DialogButton( onPressed: () => addPicture(uploadTypes.STORY, uploadTypes.VIDEO, context), width: 120, child: const Text( "Video", style: TextStyle( color: Colors.white, fontSize: 20), ), ), ], ).show(); }, child: const Text( "Adauga Story", textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 12, ), )), ), const SizedBox( width: 16, ), GestureDetector( onTap: () async { DocumentSnapshot snapshot = await FirebaseFirestore .instance .collection('Users') .doc(userId) .get(); UserModel user = UserModel.fromDocument(snapshot); Navigator.push( context, MaterialPageRoute( builder: (context) => SettingsPage(user: user)), ); }, child: Container( height: 32, width: 32, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/images/settings.png"), fit: BoxFit.cover), shape: BoxShape.circle, color: Colors.white), ), ), ], ), ), ], ), ), const SizedBox( height: 20, ), Row(children: [ const SizedBox( width: 30, ), Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ // 代码此处截断,保留原结构 ], ), ], ), ]), ], ); } }
解决方案
红屏错误的核心原因是UserPage的scrollController被声明为late,但直接跳转时未初始化——之前该控制器由StoryProfile的SlidingUpPanel在panelBuilder中传入。
修改步骤:
- 调整UserPage的控制器声明
将late ScrollController scrollController改为可选参数并提供默认逻辑:
class UserPage extends StatefulWidget { final ScrollController? scrollController; UserPage({super.key, this.scrollController}); @override State<UserPage> createState() => UserProfile(); }
- 在UserProfile中处理控制器
在build方法的ListView里,使用传入的控制器或创建默认控制器:
return ListView( controller: widget.scrollController ?? ScrollController(), padding: EdgeInsets.zero, // 其余代码保持不变 );
- 更新主页跳转逻辑
直接跳转至UserPage,无需额外参数:
IconButton( icon: const Icon( Icons.perm_identity, color: Colors.black, ), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => UserPage(), ), ); })
修改后,UserPage既能在SlidingUpPanel中正常接收传入的控制器,也能独立跳转使用默认控制器,解决红屏问题的同时满足直接跳转个人主页的需求。
内容的提问来源于stack exchange,提问作者sanda17
相关产品推荐
相关产品推荐

