| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- import 'package:cached_network_image/cached_network_image.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter_screenutil/flutter_screenutil.dart';
- import 'package:get/get.dart';
- import 'package:keyboard/base/base_view.dart';
- import 'package:keyboard/module/character/content/character_group_content_view.dart';
- import 'package:keyboard/resource/string.gen.dart';
- import '../../resource/assets.gen.dart';
- import 'character_controller.dart';
- class CharacterView extends BaseView<CharacterController> {
- const CharacterView({super.key});
- @override
- backgroundColor() {
- return Colors.transparent;
- }
- @override
- Widget buildBody(BuildContext context) {
- return Scaffold(
- backgroundColor: Color(0xFFF6F5FA),
- body: Builder(
- builder: (context) {
- return NestedScrollView(
- headerSliverBuilder: (context, innerBoxIsScrolled) {
- return [
- /// **🔹 让背景图滑动时裁剪掉上方部分**
- SliverPersistentHeader(
- pinned: true,
- delegate: CharacterHeaderDelegate(
- expandedHeight: 240.h,
- minHeight: 100.h,
- // bottomWidget: SizedBox(),
- onTap: controller.clickMyKeyboard,
- ),
- ),
- SliverPersistentHeader(
- pinned: true,
- // floating: true,
- delegate: TabBarDelegate(
- height: 180.h,
- child: _bottomAppBar(),
- ),
- ),
- ];
- },
- body: _pages(),
- );
- },
- ),
- );
- }
- /// **自定义 bottomAppBar**
- Widget _bottomAppBar() {
- return Container(
- decoration: ShapeDecoration(
- gradient: LinearGradient(
- begin: Alignment(0.50, -0.00),
- end: Alignment(0.50, 1.00),
- colors: [Color(0xFFEAE5FF), Color(0xFFF5F4F9)],
- ),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.only(
- topLeft: Radius.circular(20.r),
- topRight: Radius.circular(20.r),
- ),
- ),
- ),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- _customizeButton(),
- SizedBox(height: 14.h),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Assets.images.iconCharacterMarket.image(
- width: 73.w,
- height: 25.h,
- ),
- Obx(() {
- return DropdownButton<String>(
- // hint: Text(''),
- underline: Container(height: 0),
- style: TextStyle(
- color: Colors.black.withAlpha(102),
- fontSize: 14.sp,
- fontWeight: FontWeight.w400,
- ),
- icon: Assets.images.iconCharacterArrowDown.image(
- width: 20.r,
- height: 20.r,
- ),
- value: controller.currentKeyboardInfo.value?.name,
- onChanged: (String? newValue) {
- controller.updateSelectedValue(newValue);
- },
- items: List.generate(controller.keyboardInfoList.length, (
- index,
- ) {
- String? value = controller.keyboardInfoList[index].name;
- return DropdownMenuItem<String>(
- value: value,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding: EdgeInsets.symmetric(vertical: 8),
- child: Text(
- value ?? "",
- style: TextStyle(
- color: Colors.black.withAlpha(204),
- fontSize: 14.sp,
- fontWeight: FontWeight.w400,
- ),
- ),
- ),
- if (index != controller.keyboardInfoList.length - 1)
- Divider(
- color: Color(0xFFF6F6F6),
- thickness: 1,
- height: 1,
- ),
- ],
- ),
- );
- }),
- );
- }),
- ],
- ),
- SizedBox(height: 15.h),
- tabBar(),
- ],
- ),
- );
- }
- // 定制按钮
- Widget _customizeButton() {
- return Container(
- margin: EdgeInsets.only(left: 16.w),
- width: 220.w,
- height: 56.h,
- padding: EdgeInsets.symmetric(horizontal: 10.w),
- decoration: ShapeDecoration(
- color: const Color(0xFF121212),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(40.r),
- ),
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Assets.images.iconCharacterCustomized.image(
- width: 36.r,
- height: 36.r,
- ),
- SizedBox(width: 8.w),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(
- StringName.goCustomizeCharacter,
- textAlign: TextAlign.center,
- style: TextStyle(
- color: Colors.white,
- fontSize: 16.sp,
- fontWeight: FontWeight.w500,
- ),
- ),
- Text(
- StringName.goCustomizeCharacterDesc,
- style: TextStyle(
- color: Color(0xFFF5F4F9),
- fontSize: 11.sp,
- fontWeight: FontWeight.w400,
- ),
- ),
- ],
- ),
- Container(
- margin: EdgeInsets.only(left: 16.w),
- width: 24.r,
- height: 24.r,
- decoration: ShapeDecoration(
- color: Colors.white,
- shape: OvalBorder(),
- ),
- child: Assets.images.iconCharacterArrowRight.image(
- width: 16.r,
- height: 16.r,
- ),
- ),
- ],
- ),
- );
- }
- /// **TabBar**
- Widget tabBar() {
- return Obx(() {
- if (controller.characterGroupList.isEmpty) {
- return const SizedBox.shrink();
- }
- return TabBar(
- controller: controller.tabController.value,
- dividerHeight: 0,
- tabAlignment: TabAlignment.start,
- isScrollable: true,
- padding: EdgeInsets.symmetric(horizontal: 12.w),
- labelPadding: EdgeInsets.symmetric(horizontal: 4.w),
- indicator: const BoxDecoration(),
- onTap: (index) => controller.onTabChanged(index),
- tabs: List.generate(controller.characterGroupList.length, (index) {
- var e = controller.characterGroupList[index];
- bool isSelected = index == controller.currentTabBarIndex.value;
- return Container(
- width: 80.w,
- height: isSelected ? 38.h : 32.h,
- decoration:
- isSelected
- ? BoxDecoration(
- borderRadius: BorderRadius.circular(36.r),
- image: DecorationImage(
- image:
- Assets.images.iconCharacterGroupSelected.provider(),
- fit: BoxFit.cover,
- ),
- )
- : BoxDecoration(
- color: Colors.white.withAlpha(204),
- borderRadius: BorderRadius.circular(36.r),
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- if (e.iconUrl != null)
- CachedNetworkImage(
- imageUrl: e.iconUrl!,
- width: 20.r,
- height: 20.r,
- ),
- Text(
- e.name ?? "",
- style: TextStyle(
- color:
- isSelected ? Colors.black : Colors.black.withAlpha(104),
- fontSize: 14.sp,
- fontWeight: FontWeight.w500,
- ),
- ),
- ],
- ),
- );
- }),
- );
- });
- }
- Widget _pages() {
- return Obx(() {
- if (controller.characterGroupList.isEmpty) {
- return const Center(child: CircularProgressIndicator());
- }
- return PageView(
- controller: controller.pageController,
- onPageChanged: (index) {
- controller.onPageChanged(index);
- },
- children:
- controller.characterGroupList.map((group) {
- return CharacterGroupContentView();
- }).toList(),
- );
- });
- }
- }
- /// **🔹 让背景图滑动时裁剪掉上方部分**
- class CharacterHeaderDelegate extends SliverPersistentHeaderDelegate {
- final double expandedHeight;
- final double minHeight;
- // final Widget bottomWidget;
- final VoidCallback onTap;
- CharacterHeaderDelegate({
- required this.expandedHeight,
- required this.minHeight,
- // required this.bottomWidget,
- required this.onTap,
- });
- @override
- Widget build(
- BuildContext context,
- double shrinkOffset,
- bool overlapsContent,
- ) {
- final currentVisibleHeight = (expandedHeight - shrinkOffset).clamp(
- minHeight,
- expandedHeight,
- );
- final tabBarOffset = expandedHeight - currentVisibleHeight; // 计算 TabBar 位移
- final opacity = 1 - currentVisibleHeight / expandedHeight;
- return Stack(
- clipBehavior: Clip.none,
- children: [
- // 背景图片,动态裁剪
- Positioned(
- top: 0,
- left: 0,
- right: 0,
- height: currentVisibleHeight,
- child: ClipRect(
- child: Image.asset(
- Assets.images.bgCharacterBoyBanner.path,
- fit: BoxFit.cover,
- height: expandedHeight,
- alignment: Alignment.topCenter,
- ),
- ),
- ),
- // 遮罩层 Positioned(用于控制背景的可见性)
- Positioned(
- top: 0,
- left: 0,
- right: 0,
- height: currentVisibleHeight,
- child: Container(color: Colors.black.withValues(alpha: opacity)),
- ),
- Positioned(
- top: 0,
- child: SafeArea(
- child: GestureDetector(
- onTap: onTap,
- child: Container(
- margin: EdgeInsets.symmetric(horizontal: 16.w),
- width: 96.w,
- height: 32.h,
- padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
- decoration: ShapeDecoration(
- color: Colors.white.withValues(alpha: 153),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10),
- ),
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.center,
- spacing: 4.r,
- children: [
- Container(
- width: 24.r,
- height: 24.r,
- clipBehavior: Clip.antiAlias,
- decoration: BoxDecoration(),
- child: Assets.images.iconCharacterKeyboard.image(
- width: 24.r,
- height: 24.r,
- ),
- ),
- Text(
- StringName.myKeyboard,
- textAlign: TextAlign.center,
- style: TextStyle(
- color: Colors.black.withAlpha(204),
- fontSize: 14.sp,
- fontWeight: FontWeight.w400,
- ),
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- // TabBar 定位
- // Positioned(
- // bottom: tabBarOffset,
- // left: 0,
- // right: 0,
- // child: Transform.translate(
- // offset: Offset(0, tabBarOffset),
- // child: bottomWidget,
- // ),
- // ),
- ],
- );
- }
- @override
- double get maxExtent => expandedHeight;
- @override
- double get minExtent => minHeight;
- @override
- bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
- true;
- }
- class TabBarDelegate extends SliverPersistentHeaderDelegate {
- final Widget child;
- final double height;
- TabBarDelegate({required this.child, required this.height});
- @override
- Widget build(
- BuildContext context,
- double shrinkOffset,
- bool overlapsContent,
- ) {
- return SizedBox(height: height, child: child);
- }
- @override
- double get maxExtent => height; // 固定最大高度
- @override
- double get minExtent => height; // 固定最小高度
- @override
- bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
- true;
- }
|