character_view.dart 14 KB

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