无聊,给大家用flutter写一个我的中心UI ,复制即用

57 阅读4分钟

image.png

import 'dart:ui';

  


import 'package:flutter/material.dart';

import 'package:flutter/services.dart';

import 'package:go_router/go_router.dart';

import 'package:signals/signals_flutter.dart';

import 'package:tuyu/components/image_widget.dart';

import 'package:tuyu/routes/ext/user_route_ext.dart';

import 'package:tuyu/store/user_signal.dart';

  


/// 个人中心页面

/// 显示用户信息、VIP卡片、功能网格等

class MinePage extends StatefulWidget {

const MinePage({super.key});

  


@override

State<MinePage> createState() => _MinePageState();

}

  


// 路由观察器,用于监听页面路由变化

final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();

  


class _MinePageState extends State<MinePage> with RouteAware {

@override

void didChangeDependencies() {

super.didChangeDependencies();

// 订阅路由观察器,监听页面生命周期

routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute);

}

  


// 页面被推入导航栈时刷新用户信息

@override

void didPush() => _refreshUser();

// 新页面被推入导航栈时刷新用户信息

@override

void didPushNext() => _refreshUser();

// 从其他页面返回时刷新用户信息

@override

void didPopNext() => _refreshUser();

  


// 刷新用户信息

void _refreshUser() {

if (userSignal.isLogin()) userSignal.fetchCurrentUser();

}

  


@override

void dispose() {

// 取消订阅路由观察器,防止内存泄漏

routeObserver.unsubscribe(this);

super.dispose();

}

  


@override

Widget build(BuildContext context) {

final double statusBarHeight = MediaQuery.of(context).padding.top;

  


return Scaffold(

backgroundColor: const Color(0xFFF5F7FA),

extendBodyBehindAppBar: true,

body: Stack(

children: [

CustomScrollView(

slivers: [

SliverToBoxAdapter(

child: Stack(

clipBehavior: Clip.none,

children: [

Stack(

clipBehavior: Clip.none,

children: [

ImageWidget(

url: 'https://picsum.photos/id/209/800/600',

width: double.infinity,

height: 300,

fit: BoxFit.cover,

useImageUtil: false,

),

Positioned(

bottom: -5,

left: 0,

right: 0,

height: 60,

child: ClipRect(

child: BackdropFilter(

filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),

child: Container(

decoration: BoxDecoration(

gradient: LinearGradient(

begin: Alignment.topCenter,

end: Alignment.bottomCenter,

colors: [

Colors.white.withValues(alpha: 0.0),

const Color(

0xFFF5F7FA,

).withValues(alpha: 1),

],

),

),

),

),

),

),

Watch((_) {

final isLogin = userSignal.isLogin.value;

return isLogin

? Positioned(

top: statusBarHeight + 10,

right: 16,

child: Row(

children: [

const SizedBox(width: 12),

_CircleIconBtn(

icon: Icons.settings_outlined,

onTap: () => context.push('/settings'),

),

],

),

)

: const SizedBox.shrink();

}),

],

),

Positioned(

top: 140,

left: 0,

right: 0,

child: const _MineHeader(),

),

],

),

),

Watch((_) {

final isLogin = userSignal.isLogin.value;

final double spacing = isLogin ? 80 : 20;

return SliverToBoxAdapter(child: SizedBox(height: spacing));

}),

  


const SliverToBoxAdapter(child: _VipCard()),

const SliverToBoxAdapter(child: SizedBox(height: 20)),

SliverToBoxAdapter(child: _MineFeatureGrid()),

const SliverToBoxAdapter(child: SizedBox(height: 32)),

const SliverToBoxAdapter(

child: Center(

child: Text(

"艺图语 V0.0.1",

style: TextStyle(fontSize: 14, color: Colors.grey),

),

),

),

const SliverToBoxAdapter(child: SizedBox(height: 40)),

],

),

],

),

);

}

}

  


/// 圆形图标按钮组件

/// 用于顶部操作栏的编辑和设置按钮

class _CircleIconBtn extends StatelessWidget {

final IconData icon;

final VoidCallback onTap;

const _CircleIconBtn({required this.icon, required this.onTap});

  


@override

Widget build(BuildContext context) {

return InkWell(

onTap: onTap,

borderRadius: BorderRadius.circular(20),

splashColor: Colors.white.withOpacity(0.3),

highlightColor: Colors.white.withOpacity(0.1),

child: Container(

padding: const EdgeInsets.all(8),

decoration: BoxDecoration(

color: Colors.black.withValues(alpha: 0.2),

shape: BoxShape.circle,

),

child: Icon(icon, color: Colors.white, size: 20),

),

);

}

}

  


/// 用户信息头部组件

/// 根据登录状态显示不同的用户信息卡片

class _MineHeader extends StatelessWidget {

const _MineHeader();

  


@override

Widget build(BuildContext context) {

return Watch((_) {

return userSignal.isLogin.value

? _loginHeader(context)

: _guestHeader(context);

});

}

  


Widget _loginHeader(BuildContext context) {

return Padding(

padding: const EdgeInsets.symmetric(horizontal: 16),

child: Stack(

clipBehavior: Clip.none,

children: [

Container(

width: MediaQuery.of(context).size.width,

height: 220,

decoration: BoxDecoration(

color: Colors.white,

borderRadius: BorderRadius.circular(15),

),

child: Padding(

padding: const EdgeInsets.only(top: 50, left: 16),

child: Column(

crossAxisAlignment: CrossAxisAlignment.start,

children: [

Text(

userSignal.currentUser.value?.name ?? '用户加载中',

style: const TextStyle(

fontSize: 20,

fontWeight: FontWeight.bold,

color: Colors.black87,

),

),

const SizedBox(height: 8),

InkWell(

onTap: () {

final userId = userSignal.currentUser.value?.id;

if (userId != null) {

Clipboard.setData(

ClipboardData(text: userId.toString()),

);

ScaffoldMessenger.of(context).showSnackBar(

const SnackBar(

content: Text('用户ID已复制'),

duration: Duration(seconds: 2),

),

);

}

},

borderRadius: BorderRadius.circular(12),

child: Container(

padding: const EdgeInsets.symmetric(

horizontal: 8,

vertical: 2,

),

decoration: BoxDecoration(

color: Colors.grey.shade200,

borderRadius: BorderRadius.circular(12),

),

child: Row(

mainAxisSize: MainAxisSize.min,

children: [

Text(

"ID: ${userSignal.currentUser.value?.id ?? '---'}",

style: TextStyle(

fontSize: 12,

color: Colors.grey.shade500,

),

),

const SizedBox(width: 10),

Icon(

Icons.copy_outlined,

size: 12,

color: Colors.grey.shade500,

),

],

),

),

),

const SizedBox(height: 10),

InkWell(

onTap: () => context.push('/user/profile'),

child: Row(

children: [

Flexible(

child: Text(

userSignal.currentUser.value?.intro ?? '暂无签名',

style: TextStyle(

fontSize: 13,

color: Colors.grey.shade500,

),

maxLines: 1,

overflow: TextOverflow.ellipsis,

),

),

Icon(

Icons.edit_outlined,

size: 16,

color: Colors.grey.shade500,

),

const SizedBox(width: 16),

],

),

),

const SizedBox(height: 16),

Row(

mainAxisAlignment: MainAxisAlignment.start,

children: [

_StatItem(

count: userSignal.currentUser.value?.albumCount ?? 0,

label: "作品数",

),

const SizedBox(width: 36),

_StatItem(

count: userSignal.currentUser.value?.followCount ?? 0,

label: "关注",

),

const SizedBox(width: 36),

_StatItem(

count: userSignal.currentUser.value?.fansCount ?? 0,

label: "粉丝",

),

],

),

],

),

),

),

Positioned(

top: -30,

left: 20,

child: Container(

width: 80,

height: 80,

decoration: BoxDecoration(

shape: BoxShape.circle,

border: Border.all(color: Colors.white, width: 4),

),

child: CircleImageWidget(

url: userSignal.currentUser.value?.face ?? '',

size: 64,

),

),

),

],

),

);

}

  


Widget _guestHeader(BuildContext context) {

return Padding(

padding: const EdgeInsets.symmetric(horizontal: 16),

child: Stack(

clipBehavior: Clip.none,

alignment: Alignment.center,

children: [

Container(

width: MediaQuery.of(context).size.width,

height: 160,

decoration: BoxDecoration(

color: Colors.white,

borderRadius: BorderRadius.circular(20),

),

child: Padding(

padding: const EdgeInsets.only(top: 60, left: 16),

child: Column(

crossAxisAlignment: CrossAxisAlignment.center,

children: [

InkWell(

onTap: () => context.pushLogin(),

child: const Text(

"登录/注册",

style: TextStyle(

fontSize: 23,

fontWeight: FontWeight.w500,

color: Color(0xFF4DD0E1),

),

),

),

const SizedBox(height: 12),

const Text(

"登录后,解锁更多功能",

style: TextStyle(color: Colors.grey),

),

],

),

),

),

Positioned(

top: -30,

child: CircleAvatar(

radius: 35,

backgroundColor: Colors.grey[400],

child: Icon(Icons.person, size: 35, color: Colors.grey.shade300),

),

),

],

),

);

}

}

  


/// 统计数据项组件

/// 显示用户的统计数据(如总点击、关注、粉丝等)

class _StatItem extends StatelessWidget {

final int count;

final String label;

  


const _StatItem({required this.count, required this.label});

  


@override

Widget build(BuildContext context) {

return Column(

children: [

Text(

count.toString(),

style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),

),

Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)),

],

);

}

}

  


/// VIP会员卡片组件

/// 显示会员特权信息和开通按钮

class _VipCard extends StatelessWidget {

const _VipCard();

  


@override

Widget build(BuildContext context) {

return Padding(

padding: const EdgeInsets.symmetric(horizontal: 16),

child: Container(

padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),

decoration: BoxDecoration(

gradient: const LinearGradient(

colors: [Color(0xFF2B231D), Color(0xFF4A3F35)],

),

borderRadius: BorderRadius.circular(16),

),

child: Row(

children: [

const Icon(Icons.wallet_giftcard_rounded, color: Color(0xFFFFD1A1)),

const SizedBox(width: 10),

const Expanded(

child: Text(

"会员尊享VIP作品免费读、\n不看广告等特权",

style: TextStyle(

color: Color(0xFFFFD1A1),

fontSize: 11,

height: 1.4,

),

),

),

ElevatedButton(

onPressed: () {

if (!userSignal.isLogin.value) {

context.pushLogin();

} else {

ScaffoldMessenger.of(context).showSnackBar(

const SnackBar(

content: Text('VIP功能开发中,敬请期待'),

duration: Duration(seconds: 2),

),

);

}

},

style: ElevatedButton.styleFrom(

backgroundColor: Colors.black,

foregroundColor: Colors.white,

elevation: 0,

shape: const StadiumBorder(),

padding: const EdgeInsets.symmetric(horizontal: 12),

),

child: const Text("立即开通", style: TextStyle(fontSize: 12)),

),

],

),

),

);

}

}

  


/// 功能网格组件

/// 显示用户常用功能入口

class _MineFeatureGrid extends StatelessWidget {

List<_FeatureItem> _items(BuildContext context) => [

_FeatureItem(

Icons.favorite_rounded,

"我的点赞",

Colors.red,

() => context.push('/liked'),

),

_FeatureItem(

Icons.explore_rounded,

"浏览历史",

Colors.orange,

() => context.push('/user/history'),

),

_FeatureItem(

Icons.videocam_rounded,

"我的信息",

Colors.blue,

() => context.push('/user/profile'),

),

// 我的关注

_FeatureItem(

Icons.people_rounded,

"我的关注",

Colors.green,

() => context.push('/following/list'),

),

// 我的粉丝

_FeatureItem(

Icons.group_rounded,

"我的粉丝",

Colors.purple,

() => context.push('/follower/list'),

),

// 成为摄影师

_FeatureItem(

Icons.storefront_rounded,

"成为摄影师",

Colors.amber,

() => context.push('/photographer/apply'),

),

// 设置

_FeatureItem(

Icons.settings_rounded,

"设置",

Colors.grey,

() => context.push('/settings'),

),

// 问题反馈

_FeatureItem(

Icons.feedback_rounded,

"问题反馈",

Colors.teal,

() => context.push('/feedback'),

),

// 功能交流

_FeatureItem(

Icons.chat_rounded,

"功能交流",

Colors.indigo,

() => context.push('/feedback'),

),

  


_FeatureItem(

Icons.palette_rounded,

"主题设置",

Colors.pink,

() => context.push('/settings/theme'),

),

];

  


@override

Widget build(BuildContext context) {

return Container(

margin: const EdgeInsets.symmetric(horizontal: 16),

padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 8),

decoration: BoxDecoration(

color: Colors.white,

borderRadius: BorderRadius.circular(20),

),

child: GridView.builder(

padding: EdgeInsets.zero,

shrinkWrap: true,

physics: const NeverScrollableScrollPhysics(),

gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(

crossAxisCount: 4,

mainAxisSpacing: 20,

childAspectRatio: 0.9,

),

itemCount: _items(context).length,

itemBuilder: (context, index) {

final item = _items(context)[index];

return InkWell(

onTap: () {

if (!userSignal.isLogin.value) {

context.pushLogin();

} else {

item.onTap?.call();

}

},

splashColor: const Color(0xFF4DD0E1).withValues(alpha: 0.2),

highlightColor: const Color(0xFF4DD0E1).withValues(alpha: 0.1),

borderRadius: BorderRadius.circular(15),

child: Column(

children: [

Container(

padding: const EdgeInsets.all(10),

decoration: BoxDecoration(

color: item.color.withValues(alpha: 0.1),

borderRadius: BorderRadius.circular(15),

),

child: Icon(item.icon, color: item.color, size: 24),

),

const SizedBox(height: 8),

Text(

item.label,

style: const TextStyle(fontSize: 11, color: Colors.black87),

),

],

),

);

},

),

);

}

}

  


/// 功能项数据类

/// 用于定义功能网格中的单个功能项

class _FeatureItem {

final IconData icon;

final String label;

final Function()? onTap;

final Color color;

const _FeatureItem(this.icon, this.label, this.color, [this.onTap]);

}