在这里插入图片描述

前言

倒计时是商城应用中营造紧迫感的重要组件,广泛应用于限时抢购、秒杀活动、优惠券过期提醒等场景。一个设计良好的倒计时组件需要精确显示剩余时间,并在视觉上吸引用户注意力。本文将详细介绍如何在Flutter和OpenHarmony平台上开发倒计时组件。

倒计时的设计需要考虑时间的精确性和显示的美观性。用户需要能够清晰地看到剩余的天、时、分、秒,同时倒计时的样式应该与活动的紧迫程度相匹配,营造出限时优惠的氛围。

Flutter倒计时基础实现

首先实现倒计时的核心逻辑:

class CountdownTimer extends StatefulWidget {
  final DateTime endTime;
  final VoidCallback? onEnd;
  final Widget Function(Duration remaining)? builder;

  const CountdownTimer({
    Key? key,
    required this.endTime,
    this.onEnd,
    this.builder,
  }) : super(key: key);

  
  State<CountdownTimer> createState() => _CountdownTimerState();
}

class _CountdownTimerState extends State<CountdownTimer> {
  late Timer _timer;
  late Duration _remaining;

  
  void initState() {
    super.initState();
    _calculateRemaining();
    _startTimer();
  }

  void _calculateRemaining() {
    final now = DateTime.now();
    _remaining = widget.endTime.difference(now);
    if (_remaining.isNegative) {
      _remaining = Duration.zero;
    }
  }

  void _startTimer() {
    _timer = Timer.periodic(const Duration(seconds: 1), (_) {
      setState(() {
        _calculateRemaining();
        if (_remaining == Duration.zero) {
          _timer.cancel();
          widget.onEnd?.call();
        }
      });
    });
  }

  
  void dispose() {
    _timer.cancel();
    super.dispose();
  }
}

CountdownTimer组件接收结束时间和结束回调。Timer.periodic每秒触发一次更新,计算当前时间与结束时间的差值。当剩余时间为零时取消定时器并触发onEnd回调。_calculateRemaining方法确保剩余时间不会为负值。dispose方法中取消定时器避免内存泄漏。

倒计时UI构建:


Widget build(BuildContext context) {
  if (widget.builder != null) {
    return widget.builder!(_remaining);
  }
  return _buildDefaultDisplay();
}

Widget _buildDefaultDisplay() {
  final days = _remaining.inDays;
  final hours = _remaining.inHours % 24;
  final minutes = _remaining.inMinutes % 60;
  final seconds = _remaining.inSeconds % 60;

  return Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      if (days > 0) ...[
        _buildTimeBlock(days.toString(), '天'),
        const SizedBox(width: 4),
      ],
      _buildTimeBlock(hours.toString().padLeft(2, '0'), '时'),
      _buildSeparator(),
      _buildTimeBlock(minutes.toString().padLeft(2, '0'), '分'),
      _buildSeparator(),
      _buildTimeBlock(seconds.toString().padLeft(2, '0'), '秒'),
    ],
  );
}

build方法支持自定义builder,也提供默认的显示样式。_buildDefaultDisplay方法将Duration拆分为天、时、分、秒。padLeft确保数字始终显示两位,如"05"而不是"5"。当天数大于0时才显示天数块。Row水平排列各个时间块和分隔符。这种设计既提供了默认样式,又支持完全自定义。

时间块组件

Widget _buildTimeBlock(String value, String unit) {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
    decoration: BoxDecoration(
      color: const Color(0xFFE53935),
      borderRadius: BorderRadius.circular(4),
    ),
    child: Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(
          value,
          style: const TextStyle(
            fontSize: 14,
            fontWeight: FontWeight.bold,
            color: Colors.white,
          ),
        ),
        const SizedBox(width: 2),
        Text(
          unit,
          style: const TextStyle(
            fontSize: 10,
            color: Colors.white,
          ),
        ),
      ],
    ),
  );
}

Widget _buildSeparator() {
  return const Padding(
    padding: EdgeInsets.symmetric(horizontal: 2),
    child: Text(
      ':',
      style: TextStyle(
        fontSize: 14,
        fontWeight: FontWeight.bold,
        color: Color(0xFFE53935),
      ),
    ),
  );
}

时间块使用红色背景和白色文字,在视觉上非常醒目。数字使用14像素粗体,单位使用10像素小字号。Container设置内边距和圆角,使时间块外观精致。分隔符使用红色冒号,与时间块颜色呼应。这种设计营造出限时抢购的紧迫感。

秒杀倒计时组件

class SeckillCountdown extends StatelessWidget {
  final DateTime endTime;
  final VoidCallback? onEnd;

  const SeckillCountdown({
    Key? key,
    required this.endTime,
    this.onEnd,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [Color(0xFFFF6B6B), Color(0xFFE53935)],
        ),
        borderRadius: BorderRadius.circular(20),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Icon(
            Icons.access_time,
            size: 16,
            color: Colors.white,
          ),
          const SizedBox(width: 6),
          const Text(
            '距结束',
            style: TextStyle(
              fontSize: 12,
              color: Colors.white,
            ),
          ),
          const SizedBox(width: 8),
          CountdownTimer(
            endTime: endTime,
            onEnd: onEnd,
          ),
        ],
      ),
    );
  }
}

SeckillCountdown组件专门用于秒杀活动场景。Container使用渐变红色背景和胶囊形状,非常醒目。Row水平排列时钟图标、提示文字和倒计时。"距结束"文字明确告知用户这是活动结束倒计时。这种设计在商品列表和详情页中都能有效吸引用户注意。

OpenHarmony倒计时实现

@Component
struct CountdownTimer {
  @State remaining: number = 0
  @Prop endTime: number = 0
  private onEnd: () => void = () => {}
  private timerId: number = -1

  aboutToAppear() {
    this.calculateRemaining()
    this.startTimer()
  }

  aboutToDisappear() {
    clearInterval(this.timerId)
  }

  calculateRemaining() {
    const now = Date.now()
    this.remaining = Math.max(0, this.endTime - now)
  }

  startTimer() {
    this.timerId = setInterval(() => {
      this.calculateRemaining()
      if (this.remaining <= 0) {
        clearInterval(this.timerId)
        this.onEnd()
      }
    }, 1000)
  }

  build() {
    Row() {
      this.TimeBlock(this.getHours(), '时')
      this.Separator()
      this.TimeBlock(this.getMinutes(), '分')
      this.Separator()
      this.TimeBlock(this.getSeconds(), '秒')
    }
  }
}

OpenHarmony的倒计时使用setInterval创建定时器。@State装饰的remaining存储剩余毫秒数,状态变化时UI自动更新。aboutToAppear生命周期方法初始化并启动定时器,aboutToDisappear方法清除定时器。endTime使用时间戳格式,便于计算。

时间计算方法:

getHours(): string {
  const hours = Math.floor(this.remaining / 3600000)
  return hours.toString().padStart(2, '0')
}

getMinutes(): string {
  const minutes = Math.floor((this.remaining % 3600000) / 60000)
  return minutes.toString().padStart(2, '0')
}

getSeconds(): string {
  const seconds = Math.floor((this.remaining % 60000) / 1000)
  return seconds.toString().padStart(2, '0')
}

三个方法分别计算剩余的小时、分钟和秒数。使用Math.floor向下取整,padStart确保始终显示两位数字。remaining以毫秒为单位存储,通过除法和取模运算得到各个时间单位的值。

时间块ArkUI实现

@Builder
TimeBlock(value: string, unit: string) {
  Row() {
    Text(value)
      .fontSize(14)
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
    
    Text(unit)
      .fontSize(10)
      .fontColor(Color.White)
      .margin({ left: 2 })
  }
  .padding({ left: 6, right: 6, top: 4, bottom: 4 })
  .backgroundColor('#E53935')
  .borderRadius(4)
}

@Builder
Separator() {
  Text(':')
    .fontSize(14)
    .fontWeight(FontWeight.Bold)
    .fontColor('#E53935')
    .margin({ left: 2, right: 2 })
}

@Builder装饰器定义了时间块和分隔符的构建方法。Row水平排列数字和单位,padding设置内边距,backgroundColor设置红色背景。分隔符使用红色冒号,与时间块颜色呼应。这种实现方式与Flutter版本的视觉效果一致。

优惠券倒计时

class CouponCountdown extends StatelessWidget {
  final DateTime expireTime;

  const CouponCountdown({
    Key? key,
    required this.expireTime,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return CountdownTimer(
      endTime: expireTime,
      builder: (remaining) {
        if (remaining == Duration.zero) {
          return const Text(
            '已过期',
            style: TextStyle(
              fontSize: 12,
              color: Color(0xFF999999),
            ),
          );
        }
        
        final days = remaining.inDays;
        final hours = remaining.inHours % 24;
        
        String text;
        if (days > 0) {
          text = '剩余$days天';
        } else if (hours > 0) {
          text = '剩余$hours小时';
        } else {
          text = '即将过期';
        }
        
        return Text(
          text,
          style: TextStyle(
            fontSize: 12,
            color: days > 3 
              ? const Color(0xFF999999) 
              : const Color(0xFFE53935),
          ),
        );
      },
    );
  }
}

CouponCountdown组件用于优惠券过期倒计时,使用自定义builder显示友好的剩余时间文字。当剩余时间为零时显示"已过期",天数大于0时显示"剩余X天",小时数大于0时显示"剩余X小时",否则显示"即将过期"。剩余天数小于等于3天时使用红色文字提醒用户。这种设计比精确的倒计时更适合优惠券场景。

总结

本文详细介绍了Flutter和OpenHarmony平台上倒计时组件的开发过程。倒计时作为营造紧迫感的重要组件,其设计质量直接影响用户的购买决策。通过基础倒计时、秒杀倒计时、优惠券倒计时等组件的合理设计,我们为不同场景提供了合适的倒计时展示方式。在实际项目中,还可以进一步添加倒计时动画、服务器时间同步等功能。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐