fix(ui): 打磨推荐追剧我的设置页间距与图标
This commit is contained in:
1 parent
de7da64886
commit
5346be40a9
33 files changed
+1408
-811
No files matched your search
@@ -20,6 +20,8 @@ import '../../home/widgets/home_feed_card.dart';
|
||||
|
||||
const double _loadMoreThreshold = 240;
|
||||
const int _gridColumns = 3;
|
||||
// 标题两行39 + 上间距6 + 标签间距6 + 标签13 = 64,比 homeCardBodyExtent 更紧凑。
|
||||
const double _cardBodyExtent = 64;
|
||||
|
||||
/// 推荐页「全部短剧」筛选和三列列表。
|
||||
class AllDramasFilterGrid extends StatefulWidget {
|
||||
@@ -94,7 +96,7 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
|
||||
final itemWidth =
|
||||
(width - AppDimens.allDramasHorizontal * 2 - AppDimens.allDramasGridGap * 2) /
|
||||
_gridColumns;
|
||||
return itemWidth / AppDimens.cardCoverAspectRatio + AppDimens.homeCardBodyExtent;
|
||||
return itemWidth / AppDimens.cardCoverAspectRatio + _cardBodyExtent;
|
||||
}
|
||||
|
||||
Widget _dramaList(AppLocalizations l10n, double itemExtent) {
|
||||
@@ -309,7 +311,7 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
|
||||
? AppColors.filterChipTextSelected
|
||||
: AppColors.filterChipTextInactive,
|
||||
fontSize: AppDimens.filterChipFont,
|
||||
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,7 +6,6 @@ import '../../../core/data/models/drama.dart';
|
||||
import '../../../core/data/models/playlist_detail.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/theme/app_dimens.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../core/widgets/app_pull_to_refresh.dart';
|
||||
import '../../../core/widgets/paged_list_footer.dart';
|
||||
import '../../../core/widgets/remote_image.dart';
|
||||
@@ -19,6 +18,8 @@ const int _collageCoverCount = 5;
|
||||
const double _loadMoreThreshold = 200;
|
||||
const double _pageHorizontal = 14;
|
||||
const double _gridGap = 10;
|
||||
// 标题两行39 + 上间距6 + 标签间距6 + 标签13 = 64,比 homeCardBodyExtent 更紧凑。
|
||||
const double _cardBodyExtent = 64;
|
||||
const double _sprocketBand = 22;
|
||||
const Color _filmBody = Color(0xFF161311);
|
||||
const Color _filmHole = Color(0xFFE6D3BE);
|
||||
@@ -111,13 +112,23 @@ class _PlaylistDetailPageState extends State<PlaylistDetailPage> {
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.sizeOf(context).width;
|
||||
final itemWidth = (width - _pageHorizontal * 2 - _gridGap) / 2;
|
||||
final itemExtent =
|
||||
itemWidth / AppDimens.cardCoverAspectRatio + AppDimens.homeCardBodyExtent;
|
||||
final itemExtent = itemWidth / AppDimens.cardCoverAspectRatio + _cardBodyExtent;
|
||||
return Scaffold(
|
||||
key: const Key('playlist-detail-page'),
|
||||
backgroundColor: AppColors.ink,
|
||||
body: DecoratedBox(
|
||||
decoration: const BoxDecoration(gradient: AppTheme.pageGradient),
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
AppColors.followingGlow,
|
||||
AppColors.followingGlowMid,
|
||||
AppColors.appBackground,
|
||||
],
|
||||
stops: [0, 0.14, 0.3],
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -176,7 +187,7 @@ class _PlaylistDetailPageState extends State<PlaylistDetailPage> {
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: _gridGap,
|
||||
mainAxisSpacing: 16,
|
||||
mainAxisSpacing: 10,
|
||||
mainAxisExtent: itemExtent,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
@@ -228,7 +239,7 @@ class _PlaylistDetailPageState extends State<PlaylistDetailPage> {
|
||||
style: const TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: AppDimens.recommendSectionTitle,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
|
||||
@@ -5,13 +5,14 @@ import 'package:flutter/material.dart';
|
||||
import '../../../core/data/models/content_category.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import 'search_gift_glyphs.dart';
|
||||
|
||||
const double _navHeight = 56;
|
||||
const double _tabGap = 20;
|
||||
const double _tabFont = 17;
|
||||
const double _searchHitSize = 40;
|
||||
const double _searchIconSize = 26;
|
||||
const double _giftIconSize = 30;
|
||||
const double _giftIconSize = 26;
|
||||
const double _underlineWidth = 26;
|
||||
const double _underlineHeight = 3;
|
||||
|
||||
@@ -146,7 +147,7 @@ class _RecommendChannelNavState extends State<RecommendChannelNav> {
|
||||
),
|
||||
iconSize: _searchIconSize,
|
||||
color: AppColors.textPrimary,
|
||||
icon: const Icon(Icons.search_rounded),
|
||||
icon: SearchGlyph(size: _searchIconSize, color: AppColors.textPrimary),
|
||||
),
|
||||
),
|
||||
if (widget.onGift != null)
|
||||
@@ -162,12 +163,7 @@ class _RecommendChannelNavState extends State<RecommendChannelNav> {
|
||||
height: _searchHitSize,
|
||||
),
|
||||
iconSize: _giftIconSize,
|
||||
icon: Image.asset(
|
||||
'assets/icon/gift.png',
|
||||
width: _giftIconSize,
|
||||
height: _giftIconSize,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
icon: GiftGlyph(size: _giftIconSize),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -200,6 +196,7 @@ class _RecommendChannelNavState extends State<RecommendChannelNav> {
|
||||
fontSize: _tabFont,
|
||||
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
|
||||
letterSpacing: 0,
|
||||
height: 1.0,
|
||||
shadows: const [
|
||||
Shadow(color: AppColors.mediaBlack, blurRadius: 6),
|
||||
],
|
||||
|
||||
@@ -92,7 +92,7 @@ class RecommendSectionsView extends StatelessWidget {
|
||||
dramas: hits.take(_sectionPreviewCount).toList(),
|
||||
onMore: () => onMore(RecommendSectionKind.hits),
|
||||
),
|
||||
const SizedBox(height: AppDimens.spaceXl),
|
||||
const SizedBox(height: AppDimens.recommendSectionGap),
|
||||
_section(
|
||||
context,
|
||||
l10n,
|
||||
@@ -102,7 +102,7 @@ class RecommendSectionsView extends StatelessWidget {
|
||||
dramas: newest.take(_sectionPreviewCount).toList(),
|
||||
onMore: () => onMore(RecommendSectionKind.newReleases),
|
||||
),
|
||||
const SizedBox(height: AppDimens.spaceXl),
|
||||
const SizedBox(height: AppDimens.recommendSectionGap),
|
||||
_section(
|
||||
context,
|
||||
l10n,
|
||||
@@ -150,7 +150,7 @@ class RecommendSectionsView extends StatelessWidget {
|
||||
final sections = <Widget>[];
|
||||
for (final playlist in remote.items) {
|
||||
if (sections.isNotEmpty) {
|
||||
sections.add(const SizedBox(height: AppDimens.spaceXl));
|
||||
sections.add(const SizedBox(height: AppDimens.recommendSectionGap));
|
||||
}
|
||||
sections.add(
|
||||
_section(
|
||||
@@ -238,7 +238,7 @@ class RecommendSectionsView extends StatelessWidget {
|
||||
style: const TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: AppDimens.recommendSectionTitle,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
@@ -277,7 +277,7 @@ class RecommendSectionsView extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimens.spaceMd),
|
||||
const SizedBox(height: AppDimens.spaceXs),
|
||||
SizedBox(
|
||||
height: AppDimens.recommendRowHeight,
|
||||
child: ListView.separated(
|
||||
|
||||
@@ -5,13 +5,14 @@ import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../recommend_types.dart';
|
||||
import 'search_gift_glyphs.dart';
|
||||
|
||||
const double _navHeight = 56;
|
||||
const double _tabGap = 20;
|
||||
const double _tabFont = 17;
|
||||
const double _searchHitSize = 40;
|
||||
const double _searchIconSize = 26;
|
||||
const double _giftIconSize = 30;
|
||||
const double _giftIconSize = 26;
|
||||
const double _underlineWidth = 26;
|
||||
const double _underlineHeight = 3;
|
||||
|
||||
@@ -87,7 +88,7 @@ class RecommendTopNav extends StatelessWidget {
|
||||
),
|
||||
iconSize: _searchIconSize,
|
||||
color: AppColors.textPrimary,
|
||||
icon: const Icon(Icons.search_rounded),
|
||||
icon: SearchGlyph(size: _searchIconSize, color: AppColors.textPrimary),
|
||||
),
|
||||
),
|
||||
if (onGift != null)
|
||||
@@ -103,12 +104,7 @@ class RecommendTopNav extends StatelessWidget {
|
||||
height: _searchHitSize,
|
||||
),
|
||||
iconSize: _giftIconSize,
|
||||
icon: Image.asset(
|
||||
'assets/icon/gift.png',
|
||||
width: _giftIconSize,
|
||||
height: _giftIconSize,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
icon: GiftGlyph(size: _giftIconSize),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -142,6 +138,7 @@ class RecommendTopNav extends StatelessWidget {
|
||||
fontSize: _tabFont,
|
||||
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
|
||||
letterSpacing: 0,
|
||||
height: 1.0,
|
||||
shadows: shadows,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 首页右上角搜索/礼物图标的自定义样式:
|
||||
/// 搜索为粗线条放大镜(Material 图标无法调线条粗细),礼物为 emoji 红金礼盒。
|
||||
|
||||
/// 粗线条白色放大镜,圆环在左上、手柄朝右下。
|
||||
class SearchGlyph extends StatelessWidget {
|
||||
const SearchGlyph({super.key, required this.size, required this.color});
|
||||
|
||||
final double size;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
size: Size.square(size),
|
||||
painter: _SearchPainter(color: color),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchPainter extends CustomPainter {
|
||||
const _SearchPainter({required this.color});
|
||||
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final stroke = size.width * 0.09;
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = stroke
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
final circleDiameter = size.width * 0.7;
|
||||
final direction = math.pi / 4;
|
||||
// 图形只占画布左上角,先平移让它视觉居中,否则会比文字基线偏高。
|
||||
final drawnExtent =
|
||||
circleDiameter / 2 +
|
||||
(circleDiameter / 2 + size.width * 0.12) * math.sin(direction) +
|
||||
stroke / 2;
|
||||
final centering = (size.width - drawnExtent) / 2;
|
||||
canvas.translate(centering, centering);
|
||||
|
||||
final center = Offset(circleDiameter / 2, circleDiameter / 2);
|
||||
canvas.drawCircle(center, circleDiameter / 2 - stroke / 2, paint);
|
||||
|
||||
final start = center + Offset.fromDirection(direction, circleDiameter / 2 - stroke * 0.2);
|
||||
final end = center + Offset.fromDirection(direction, circleDiameter / 2 + size.width * 0.12);
|
||||
canvas.drawLine(start, end, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _SearchPainter oldDelegate) =>
|
||||
oldDelegate.color != color;
|
||||
}
|
||||
|
||||
/// emoji 礼盒(红盒金丝带,随系统 emoji 风格渲染)。
|
||||
class GiftGlyph extends StatelessWidget {
|
||||
const GiftGlyph({super.key, required this.size});
|
||||
|
||||
final double size;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox.square(
|
||||
dimension: size,
|
||||
child: FittedBox(
|
||||
child: Text('🎁', style: TextStyle(fontSize: size, height: 1)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user