在OpenHarmony生态中实现Flutter应用的开发一直是个有趣的技术挑战。这次我们要构建的是一个完整的幸运大转盘应用,而本篇章将重点聚焦在奖品详情模块的实现。这个模块看似简单,实则涉及数据管理、UI交互和状态维护等多个技术要点。
奖品详情作为用户中奖后的核心交互界面,需要实现以下核心功能:
我们采用以下技术组合:
dart复制class Prize {
final String id;
final String name;
final String description;
final String imageUrl;
final DateTime expireTime;
bool isClaimed;
Prize({
required this.id,
required this.name,
required this.description,
required this.imageUrl,
required this.expireTime,
this.isClaimed = false,
});
}
code复制PrizeDetailPage
├── PrizeHeader (图片展示区)
├── PrizeInfo (文字信息区)
├── CountdownTimer (倒计时组件)
└── ActionButton (操作按钮区)
针对OpenHarmony平台的网络图片加载,我们采用以下优化方案:
dart复制CachedNetworkImage(
imageUrl: prize.imageUrl,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
fadeInDuration: Duration(milliseconds: 300),
fit: BoxFit.cover,
width: double.infinity,
height: 200,
)
关键优化点:
奖品通常有领取时限,我们使用Stream实现精确倒计时:
dart复制StreamBuilder<Duration>(
stream: _countdownStream(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text("计算剩余时间...");
final duration = snapshot.data!;
return Text(
"${duration.inHours}:${duration.inMinutes.remainder(60)}:${duration.inSeconds.remainder(60)}",
style: TextStyle(fontSize: 18),
);
},
)
Stream<Duration> _countdownStream() async* {
while (prize.expireTime.isAfter(DateTime.now())) {
yield prize.expireTime.difference(DateTime.now());
await Future.delayed(Duration(seconds: 1));
}
yield Duration.zero;
}
使用Provider实现跨组件的状态共享:
dart复制class PrizeProvider with ChangeNotifier {
Prize _prize;
Prize get prize => _prize;
void claimPrize() {
_prize.isClaimed = true;
notifyListeners();
// 这里可以添加实际的领取逻辑
}
}
通过dart:ffi调用OpenHarmony原生能力:
dart复制final DynamicLibrary openHarmonyLib = DynamicLibrary.open("libopenharmony.so");
typedef OhosGetDeviceInfoNative = Pointer<Utf8> Function();
typedef OhosGetDeviceInfoDart = Pointer<Utf8> Function();
final OhosGetDeviceInfoDart _getDeviceInfo = openHarmonyLib
.lookup<NativeFunction<OhosGetDeviceInfoNative>>("getDeviceInfo")
.asFunction();
针对OpenHarmony多样的设备尺寸,我们采用以下适配策略:
dart复制LayoutBuilder(
builder: (context, constraints) {
final isTablet = constraints.maxWidth > 600;
return isTablet ? _buildTabletLayout() : _buildPhoneLayout();
},
)
以下是奖品详情页面的完整实现:
dart复制class PrizeDetailPage extends StatelessWidget {
final Prize prize;
const PrizeDetailPage({Key? key, required this.prize}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => PrizeProvider(prize),
child: Scaffold(
appBar: AppBar(title: Text('奖品详情')),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PrizeHeader(prize: prize),
SizedBox(height: 16),
PrizeInfo(prize: prize),
SizedBox(height: 24),
CountdownTimer(expireTime: prize.expireTime),
SizedBox(height: 32),
Consumer<PrizeProvider>(
builder: (context, provider, _) {
return ActionButton(
isClaimed: provider.prize.isClaimed,
onPressed: () => provider.claimPrize(),
);
},
),
],
),
),
),
);
}
}
dart复制CachedNetworkImage(
memCacheWidth: (MediaQuery.of(context).size.width * 2).toInt(),
)
dart复制@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
问题1:图片加载闪烁
问题2:倒计时不准确
问题3:状态丢失
dart复制void main() {
test('Prize claim status should update correctly', () {
final prize = Prize(...);
final provider = PrizeProvider(prize);
expect(provider.prize.isClaimed, false);
provider.claimPrize();
expect(provider.prize.isClaimed, true);
});
}
dart复制testWidgets('Prize detail should display correctly', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: PrizeDetailPage(prize: mockPrize),
),
);
expect(find.text(mockPrize.name), findsOneWidget);
expect(find.byType(CachedNetworkImage), findsOneWidget);
});
离线能力增强:
动画效果升级:
安全加固:
多平台适配:
在实际开发中,我发现奖品详情页虽然看似简单,但要做好用户体验需要关注很多细节。特别是状态管理和平台适配这两个方面,往往需要反复调试才能达到理想效果。建议在开发过程中尽早建立完整的测试用例,这能显著提高开发效率。