Files
ai_english/client/lib/shared/widgets/custom_app_bar.dart
2025-11-17 13:39:05 +08:00

245 lines
6.8 KiB
Dart

import 'package:flutter/material.dart';
/// 自定义应用栏
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title;
final List<Widget>? actions;
final Widget? leading;
final bool centerTitle;
final Color? backgroundColor;
final Color? foregroundColor;
final double elevation;
final bool automaticallyImplyLeading;
final PreferredSizeWidget? bottom;
final VoidCallback? onBackPressed;
const CustomAppBar({
super.key,
required this.title,
this.actions,
this.leading,
this.centerTitle = true,
this.backgroundColor,
this.foregroundColor,
this.elevation = 0,
this.automaticallyImplyLeading = true,
this.bottom,
this.onBackPressed,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return AppBar(
title: Text(
title,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
color: foregroundColor ?? theme.colorScheme.onSurface,
),
),
centerTitle: centerTitle,
backgroundColor: backgroundColor ?? theme.colorScheme.surface,
foregroundColor: foregroundColor ?? theme.colorScheme.onSurface,
elevation: elevation,
automaticallyImplyLeading: automaticallyImplyLeading,
leading: leading ?? (onBackPressed != null
? IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: onBackPressed,
)
: null),
actions: actions,
bottom: bottom,
surfaceTintColor: Colors.transparent,
);
}
@override
Size get preferredSize => Size.fromHeight(
kToolbarHeight + (bottom?.preferredSize.height ?? 0.0),
);
}
/// 带搜索功能的应用栏
class SearchAppBar extends StatefulWidget implements PreferredSizeWidget {
final String title;
final String hintText;
final ValueChanged<String>? onSearchChanged;
final VoidCallback? onSearchSubmitted;
final List<Widget>? actions;
final bool automaticallyImplyLeading;
final VoidCallback? onBackPressed;
const SearchAppBar({
super.key,
required this.title,
this.hintText = '搜索...',
this.onSearchChanged,
this.onSearchSubmitted,
this.actions,
this.automaticallyImplyLeading = true,
this.onBackPressed,
});
@override
State<SearchAppBar> createState() => _SearchAppBarState();
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
class _SearchAppBarState extends State<SearchAppBar> {
bool _isSearching = false;
final TextEditingController _searchController = TextEditingController();
final FocusNode _searchFocusNode = FocusNode();
@override
void dispose() {
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}
void _startSearch() {
setState(() {
_isSearching = true;
});
_searchFocusNode.requestFocus();
}
void _stopSearch() {
setState(() {
_isSearching = false;
_searchController.clear();
});
_searchFocusNode.unfocus();
widget.onSearchChanged?.call('');
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return AppBar(
title: _isSearching
? TextField(
controller: _searchController,
focusNode: _searchFocusNode,
decoration: InputDecoration(
hintText: widget.hintText,
border: InputBorder.none,
hintStyle: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
),
),
style: theme.textTheme.bodyLarge,
onChanged: widget.onSearchChanged,
onSubmitted: (_) => widget.onSearchSubmitted?.call(),
)
: Text(
widget.title,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
centerTitle: !_isSearching,
backgroundColor: theme.colorScheme.surface,
foregroundColor: theme.colorScheme.onSurface,
elevation: 0,
automaticallyImplyLeading: widget.automaticallyImplyLeading && !_isSearching,
leading: _isSearching
? IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: _stopSearch,
)
: (widget.onBackPressed != null
? IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: widget.onBackPressed,
)
: null),
actions: _isSearching
? [
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
widget.onSearchChanged?.call('');
},
),
]
: [
IconButton(
icon: const Icon(Icons.search),
onPressed: _startSearch,
),
...?widget.actions,
],
surfaceTintColor: Colors.transparent,
);
}
}
/// 带标签页的应用栏
class TabAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title;
final List<Tab> tabs;
final TabController? controller;
final List<Widget>? actions;
final bool automaticallyImplyLeading;
final VoidCallback? onBackPressed;
const TabAppBar({
super.key,
required this.title,
required this.tabs,
this.controller,
this.actions,
this.automaticallyImplyLeading = true,
this.onBackPressed,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return AppBar(
title: Text(
title,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
centerTitle: true,
backgroundColor: theme.colorScheme.surface,
foregroundColor: theme.colorScheme.onSurface,
elevation: 0,
automaticallyImplyLeading: automaticallyImplyLeading,
leading: onBackPressed != null
? IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: onBackPressed,
)
: null,
actions: actions,
bottom: TabBar(
controller: controller,
tabs: tabs,
labelColor: theme.colorScheme.primary,
unselectedLabelColor: theme.colorScheme.onSurface.withOpacity(0.6),
indicatorColor: theme.colorScheme.primary,
indicatorWeight: 2,
labelStyle: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
),
unselectedLabelStyle: theme.textTheme.titleSmall,
),
surfaceTintColor: Colors.transparent,
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight + kTextTabBarHeight);
}