character_view.dart 15 KB

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