style: 调整搜索结果两列布局

This commit is contained in:
Zhou Weidong committed 2026-10-22 22:50:47 +08:00
1 parent 0319f8a6a8
commit 8637f5da21
6 files changed
+124 -9

No files matched your search

+1
View File
@@ -112,6 +112,7 @@ abstract final class AppColors {
static const textTertiary = Color(0xFF8E8E93);
static const textTabInactive = Color(0xFFB0B0B0);
static const textFilterIcon = Color(0xFF7A7A7E);
static const searchResultMeta = Color(0xFF9E9E9E);
/// 原生开屏与冷启动过渡底色。
static const splashInk = ink;
+6
View File
@@ -62,6 +62,12 @@ abstract final class AppDimens {
static const double searchHeight = 40;
static const double searchRadius = 8;
static const double searchPlaceholderFont = 13;
static const double searchResultGap = 12;
static const double searchResultRowGap = 16;
static const double searchResultTitleGap = 8;
static const double searchResultMetaGap = 6;
static const double searchResultMetaFont = 12;
static const double searchResultTextExtent = 68;
static const double navDot = 3;
static const double glassBlur = 24;
+8 -8
View File
@@ -13,10 +13,10 @@ import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/empty_state.dart';
import '../../l10n/app_localizations.dart';
import '../home/widgets/home_feed_card.dart';
import '../player/open_player.dart';
import 'discover_binding.dart';
import 'discover_logic.dart';
import 'widgets/search_result_card.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@@ -222,11 +222,11 @@ class _SearchPageState extends State<SearchPage> {
final width =
(constraints.maxWidth -
AppDimens.feedPageHorizontal * 2 -
AppDimens.homeGridGap * 2) /
3;
AppDimens.searchResultGap) /
2;
final itemExtent =
width / AppDimens.cardCoverAspectRatio +
AppDimens.homeCardBodyExtent;
AppDimens.searchResultTextExtent;
return GridView.builder(
key: const Key('search-results-grid'),
padding: const EdgeInsets.fromLTRB(
@@ -236,15 +236,15 @@ class _SearchPageState extends State<SearchPage> {
AppDimens.spaceXl,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: AppDimens.homeGridGap,
mainAxisSpacing: AppDimens.homeGridRowGap,
crossAxisCount: 2,
crossAxisSpacing: AppDimens.searchResultGap,
mainAxisSpacing: AppDimens.searchResultRowGap,
mainAxisExtent: itemExtent,
),
itemCount: _logic.searchResults.length,
itemBuilder: (context, index) {
final drama = _logic.searchResults[index];
return HomeFeedCard(
return SearchResultCard(
drama: drama,
onTap: () => unawaited(playDramaFromCatalog(context, drama)),
);
@@ -0,0 +1,104 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/data/models/drama.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/cover_play_count.dart';
import '../../../core/widgets/remote_image.dart';
class SearchResultCard extends StatelessWidget {
const SearchResultCard({super.key, required this.drama, required this.onTap});
final Drama drama;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final tags = _tags();
return Material(
key: Key('search-result-card-${drama.id}'),
color: AppColors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(AppDimens.cardCoverRadius),
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(AppDimens.cardCoverRadius),
child: AspectRatio(
key: Key('search-result-cover-${drama.id}'),
aspectRatio: AppDimens.cardCoverAspectRatio,
child: Stack(
fit: StackFit.expand,
children: [
RemoteImage(
drama.posterUrl,
fit: BoxFit.cover,
showFallbackIcon: true,
fallbackColor: AppColors.contentSurface,
),
const Positioned(
left: 0,
right: 0,
bottom: 0,
child: CoverBottomScrim(),
),
Positioned(
right: AppDimens.cardPlayCountRight,
bottom: AppDimens.cardPlayCountBottom,
child: CoverPlayCount(
key: Key('search-result-views-${drama.id}'),
count: drama.viewCount,
),
),
],
),
),
),
const SizedBox(height: AppDimens.searchResultTitleGap),
Text(
drama.title,
key: Key('search-result-title-${drama.id}'),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.cardTitleFont,
fontWeight: FontWeight.w500,
height: AppDimens.cardTitleLineHeight,
),
),
if (tags.isNotEmpty) ...[
const SizedBox(height: AppDimens.searchResultMetaGap),
Text(
tags.join(' | '),
key: Key('search-result-tags-${drama.id}'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: const TextStyle(
color: AppColors.searchResultMeta,
fontSize: AppDimens.searchResultMetaFont,
),
),
],
],
),
),
);
}
List<String> _tags() {
final seen = <String>{};
return [drama.genre, drama.category]
.expand((value) => value.split(RegExp(r'[,/、·]')))
.map((value) => value.trim())
.where((value) => value.isNotEmpty && seen.add(value))
.take(3)
.toList(growable: false);
}
}