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 { 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( // 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( 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; }