character_view.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. import 'package:cached_network_image/cached_network_image.dart';
  2. import 'package:dropdown_button2/dropdown_button2.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_screenutil/flutter_screenutil.dart';
  5. import 'package:get/get.dart';
  6. import 'package:keyboard/base/base_view.dart';
  7. import 'package:keyboard/module/character/content/character_group_content_view.dart';
  8. import 'package:keyboard/resource/string.gen.dart';
  9. import '../../resource/assets.gen.dart';
  10. import 'character_controller.dart';
  11. class CharacterView extends BaseView<CharacterController> {
  12. const CharacterView({super.key});
  13. @override
  14. backgroundColor() {
  15. return Colors.transparent;
  16. }
  17. @override
  18. Widget buildBody(BuildContext context) {
  19. return Scaffold(
  20. backgroundColor: Color(0xFFF6F5FA),
  21. body: Stack(
  22. children: [
  23. Builder(
  24. builder: (context) {
  25. return Column(
  26. children: [
  27. Expanded(
  28. child: NestedScrollView(
  29. headerSliverBuilder: (context, innerBoxIsScrolled) {
  30. return [
  31. SliverPersistentHeader(
  32. pinned: true,
  33. delegate: CharacterHeaderDelegate(
  34. expandedHeight: 380.h,
  35. //调整照片位置
  36. minHeight: 270.h,
  37. bottomWidget: _bottomAppBar(),
  38. onTap: controller.clickMyKeyboard,
  39. isBoy: controller.userInfo.value?.gender! == 1,
  40. ),
  41. ),
  42. ];
  43. },
  44. body: _pages(),
  45. ),
  46. ),
  47. ],
  48. );
  49. },
  50. ),
  51. ],
  52. ),
  53. );
  54. }
  55. /// **自定义 bottomAppBar**
  56. Widget _bottomAppBar() {
  57. return Column(
  58. children: [
  59. Stack(
  60. children: [
  61. Column(
  62. children: [
  63. SizedBox(height: 31.h),
  64. Container(
  65. decoration: ShapeDecoration(
  66. gradient: LinearGradient(
  67. begin: Alignment(0.50, -0.00),
  68. end: Alignment(0.50, 1.00),
  69. colors: [Color(0xFFEAE5FF), Color(0xFFF5F4F9)],
  70. ),
  71. shape: RoundedRectangleBorder(
  72. borderRadius: BorderRadius.only(
  73. topLeft: Radius.circular(20.r),
  74. topRight: Radius.circular(20.r),
  75. ),
  76. ),
  77. ),
  78. child: Column(
  79. mainAxisAlignment: MainAxisAlignment.center,
  80. crossAxisAlignment: CrossAxisAlignment.start,
  81. mainAxisSize: MainAxisSize.min,
  82. children: [
  83. // 定义按钮与人设之间的间距
  84. SizedBox(height: 53.h),
  85. // 人设市场标识和下拉框
  86. _marketSignAndDropDown(),
  87. SizedBox(height: 15.h),
  88. _tabBar(),
  89. ],
  90. ),
  91. ),
  92. ],
  93. ),
  94. Positioned(
  95. top: 0,
  96. left: 0,
  97. child: _customizeButton(onTap: controller.clickCustomCharacter),
  98. ),
  99. ],
  100. ),
  101. ],
  102. );
  103. }
  104. // 人设市场标识和下拉框
  105. Widget _marketSignAndDropDown() {
  106. return Container(
  107. padding: EdgeInsets.symmetric(horizontal: 16.w),
  108. child: Row(
  109. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  110. children: [
  111. Assets.images.iconCharacterMarket.image(width: 73.w, height: 25.h),
  112. Obx(() {
  113. return DropdownButton2<String>(
  114. isDense: true,
  115. underline: Container(height: 0),
  116. customButton: Row(
  117. children: [
  118. Text(
  119. controller.currentKeyboardInfo.value.name ?? "",
  120. style: TextStyle(
  121. color: Colors.black.withAlpha(102),
  122. fontSize: 14.sp,
  123. fontWeight: FontWeight.w400,
  124. ),
  125. ),
  126. Assets.images.iconCharacterArrowDown.image(
  127. width: 20.r,
  128. height: 20.r,
  129. ),
  130. ],
  131. ),
  132. dropdownSeparator: DropdownSeparator<String>(
  133. height: 1,
  134. child: Padding(
  135. padding: EdgeInsets.symmetric(horizontal: 15.w),
  136. child: Divider(
  137. height: 1,
  138. color: Color(0xFFF6F6F6),
  139. ),
  140. ),
  141. ),
  142. // 选项改变回调
  143. onChanged: (String? newValue) {
  144. controller.switchKeyboard(newValue);
  145. },
  146. // 生成下拉菜单项
  147. items: List.generate(controller.keyboardInfoList.length, (index) {
  148. String? value = controller.keyboardInfoList[index].name;
  149. return DropdownItem<String>(
  150. value: value,
  151. child: Column(
  152. crossAxisAlignment: CrossAxisAlignment.start,
  153. mainAxisSize: MainAxisSize.min,
  154. children: [
  155. Container(
  156. child: Text(
  157. value ?? "",
  158. style: TextStyle(
  159. color: Colors.black.withAlpha(204),
  160. fontSize: 14.sp,
  161. fontWeight: FontWeight.w400,
  162. ),
  163. ),
  164. ),
  165. ],
  166. ),
  167. );
  168. }),
  169. dropdownStyleData: DropdownStyleData(
  170. // 下拉菜单最大高度
  171. direction: DropdownDirection.left,
  172. maxHeight: 250.w,
  173. width: 102.w,
  174. decoration: BoxDecoration(
  175. color: Colors.white,
  176. borderRadius: BorderRadius.circular(8.w),
  177. ),
  178. ),
  179. );
  180. }),
  181. ],
  182. ),
  183. );
  184. }
  185. // 定制按钮
  186. Widget _customizeButton({required VoidCallback onTap}) {
  187. return GestureDetector(
  188. onTap: onTap,
  189. child: Container(
  190. margin: EdgeInsets.only(left: 16.w),
  191. width: 220.w,
  192. height: 56.h,
  193. padding: EdgeInsets.symmetric(horizontal: 10.w),
  194. decoration: ShapeDecoration(
  195. color: const Color(0xFF121212),
  196. shape: RoundedRectangleBorder(
  197. borderRadius: BorderRadius.circular(40.r),
  198. ),
  199. ),
  200. child: Row(
  201. mainAxisAlignment: MainAxisAlignment.start,
  202. children: [
  203. Assets.images.iconCharacterCustomized.image(
  204. width: 36.r,
  205. height: 36.r,
  206. ),
  207. SizedBox(width: 8.w),
  208. Column(
  209. crossAxisAlignment: CrossAxisAlignment.start,
  210. mainAxisAlignment: MainAxisAlignment.center,
  211. children: [
  212. Text(
  213. StringName.goCustomizeCharacter,
  214. textAlign: TextAlign.center,
  215. style: TextStyle(
  216. color: Colors.white,
  217. fontSize: 16.sp,
  218. fontWeight: FontWeight.w500,
  219. ),
  220. ),
  221. Text(
  222. StringName.goCustomizeCharacterDesc,
  223. style: TextStyle(
  224. color: Color(0xFFF5F4F9),
  225. fontSize: 11.sp,
  226. fontWeight: FontWeight.w400,
  227. ),
  228. ),
  229. ],
  230. ),
  231. Container(
  232. margin: EdgeInsets.only(left: 16.w),
  233. width: 24.r,
  234. height: 24.r,
  235. decoration: ShapeDecoration(
  236. color: Colors.white,
  237. shape: OvalBorder(),
  238. ),
  239. child: Assets.images.iconCharacterArrowRight.image(
  240. width: 16.r,
  241. height: 16.r,
  242. ),
  243. ),
  244. ],
  245. ),
  246. ),
  247. );
  248. }
  249. /// **TabBar**
  250. Widget _tabBar() {
  251. return Obx(() {
  252. if (controller.characterGroupList.isEmpty) {
  253. return const SizedBox.shrink();
  254. }
  255. return TabBar(
  256. controller: controller.tabController.value,
  257. dividerHeight: 0,
  258. tabAlignment: TabAlignment.start,
  259. isScrollable: true,
  260. padding: EdgeInsets.symmetric(horizontal: 12.w),
  261. labelPadding: EdgeInsets.symmetric(horizontal: 4.w),
  262. indicator: const BoxDecoration(),
  263. onTap: (index) => controller.onTabChanged(index),
  264. tabs: List.generate(controller.characterGroupList.length, (index) {
  265. var e = controller.characterGroupList[index];
  266. bool isSelected = index == controller.currentTabBarIndex.value;
  267. return Column(
  268. children: [
  269. Container(
  270. width: 80.w,
  271. height: isSelected ? 38.h : 32.h,
  272. padding:
  273. isSelected ? EdgeInsets.only(bottom: 4.h) : EdgeInsets.zero,
  274. decoration:
  275. isSelected
  276. ? BoxDecoration(
  277. borderRadius: BorderRadius.circular(36.r),
  278. image: DecorationImage(
  279. image:
  280. Assets.images.iconCharacterGroupSelected
  281. .provider(),
  282. fit: BoxFit.fill,
  283. ),
  284. )
  285. : BoxDecoration(
  286. color: Colors.white.withAlpha(204),
  287. borderRadius: BorderRadius.circular(36.r),
  288. ),
  289. child: Row(
  290. mainAxisAlignment: MainAxisAlignment.center,
  291. children: [
  292. if (e.iconUrl != null)
  293. CachedNetworkImage(
  294. imageUrl: e.iconUrl!,
  295. width: 20.r,
  296. height: 20.r,
  297. ),
  298. Text(
  299. e.name ?? "",
  300. style: TextStyle(
  301. color:
  302. isSelected
  303. ? Colors.black
  304. : Colors.black.withAlpha(104),
  305. fontSize: 14.sp,
  306. fontWeight: FontWeight.w500,
  307. ),
  308. ),
  309. ],
  310. ),
  311. ),
  312. !isSelected ? SizedBox(height: 4.h) : SizedBox(),
  313. ],
  314. );
  315. }),
  316. );
  317. });
  318. }
  319. Widget _pages() {
  320. return Obx(() {
  321. if (controller.characterGroupList.isEmpty) {
  322. return const Center(child: CircularProgressIndicator());
  323. }
  324. return PageView(
  325. controller: controller.pageController,
  326. onPageChanged: (index) {
  327. controller.onPageChanged(index);
  328. },
  329. children:
  330. controller.characterGroupList.map((group) {
  331. return CharacterGroupContentView();
  332. }).toList(),
  333. );
  334. });
  335. }
  336. }
  337. /// **🔹 可伸缩的
  338. class CharacterHeaderDelegate extends SliverPersistentHeaderDelegate {
  339. final double expandedHeight;
  340. final double minHeight;
  341. final Widget bottomWidget;
  342. final VoidCallback onTap;
  343. final bool isBoy;
  344. CharacterHeaderDelegate({
  345. required this.expandedHeight,
  346. required this.minHeight,
  347. required this.bottomWidget,
  348. required this.onTap,
  349. this.isBoy = true,
  350. });
  351. @override
  352. Widget build(
  353. BuildContext context,
  354. double shrinkOffset,
  355. bool overlapsContent,
  356. ) {
  357. final currentVisibleHeight = (expandedHeight - shrinkOffset).clamp(
  358. minHeight,
  359. expandedHeight,
  360. );
  361. final opacity = 1 - currentVisibleHeight / (shrinkOffset + expandedHeight);
  362. return Stack(
  363. // clipBehavior: Clip.none,
  364. children: [
  365. Positioned(
  366. top: 0,
  367. left: 0,
  368. right: 0,
  369. child: Image.asset(
  370. isBoy
  371. ? Assets.images.bgCharacterBoyBanner.path
  372. : Assets.images.bgCharacterGirlBanner.path,
  373. width: double.infinity,
  374. fit: BoxFit.fill,
  375. alignment: Alignment.topCenter,
  376. ),
  377. ),
  378. // 遮罩层 Positioned(用于控制背景的可见性)
  379. Positioned(
  380. top: 0,
  381. left: 0,
  382. right: 0,
  383. height: currentVisibleHeight,
  384. child: Opacity(
  385. opacity: opacity,
  386. child: Container(width: double.infinity, color: Color(0XffB683FD)),
  387. ),
  388. ),
  389. Positioned(bottom: 0, left: 0, right: 0, child: bottomWidget),
  390. // 我的键盘按钮
  391. myKeyboardButton(onTap: onTap),
  392. ],
  393. );
  394. }
  395. @override
  396. double get maxExtent => expandedHeight;
  397. @override
  398. double get minExtent => minHeight;
  399. @override
  400. bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
  401. true;
  402. }
  403. //我的键盘按钮
  404. Widget myKeyboardButton({required VoidCallback onTap}) {
  405. return Positioned(
  406. top: 0,
  407. child: SafeArea(
  408. child: GestureDetector(
  409. onTap: onTap,
  410. child: Container(
  411. margin: EdgeInsets.symmetric(horizontal: 16.w),
  412. height: 32.h,
  413. padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
  414. decoration: ShapeDecoration(
  415. color: Colors.white.withValues(alpha: 153),
  416. shape: RoundedRectangleBorder(
  417. borderRadius: BorderRadius.circular(10),
  418. ),
  419. ),
  420. child: Row(
  421. mainAxisAlignment: MainAxisAlignment.start,
  422. crossAxisAlignment: CrossAxisAlignment.center,
  423. spacing: 4.r,
  424. children: [
  425. Container(
  426. width: 24.r,
  427. height: 24.r,
  428. clipBehavior: Clip.antiAlias,
  429. decoration: BoxDecoration(),
  430. child: Assets.images.iconCharacterKeyboard.image(
  431. width: 24.r,
  432. height: 24.r,
  433. ),
  434. ),
  435. Text(
  436. StringName.myKeyboard,
  437. textAlign: TextAlign.center,
  438. style: TextStyle(
  439. color: Colors.black.withAlpha(204),
  440. fontSize: 14.sp,
  441. fontWeight: FontWeight.w400,
  442. ),
  443. ),
  444. ],
  445. ),
  446. ),
  447. ),
  448. ),
  449. );
  450. }