character_view.dart 14 KB

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