--- /dev/null
+/**
+ * 运动倒计时 Slice(独立):仅 3 秒倒计时,时间到返回原流程(如运动界面)
+ * 不依赖 saveele,类名/文件名与 saveele 的 SaveBat* 区分,避免链接多重定义
+ */
+#ifndef _COUNTDOWN_VIEW_H
+#define _COUNTDOWN_VIEW_H
+
+#include "View.h"
+#include "components/ui_view_group.h"
+#include "components/ui_label.h"
+#include "components/ui_canvas.h"
+#include "components/ui_canvas_ext.h"
+#include "sport_countdown/CountdownPresenter.h"
+#include "hal_tick.h"
+#include "common/screen.h"
+
+namespace OHOS {
+
+enum class SportCountdownStage {
+ STAGE_GRADUAL_CIRCLE = 0,
+ STAGE_COUNTING_DOWN,
+ STAGE_FADE_OUT,
+ STAGE_NONE
+};
+
+class SportCountdownAnimatorCallback : public AnimatorCallback {
+ public:
+ explicit SportCountdownAnimatorCallback(UICanvasExt *canvas) : canvas_(canvas) {}
+ ~SportCountdownAnimatorCallback() override {}
+ void Callback(UIView *view) override;
+ void NotifyStart();
+ void CancelCountdown();
+ void SetInfo(UILabel *label, int counts);
+
+ private:
+ void CountingDown();
+ void UpdatePath(int16_t start, int16_t end, bool needDrawCircle);
+ void UpdateLable(uint32_t curTime);
+ void CircleFadeOut();
+ void GradualCircle();
+ UICanvasExt *canvas_ = nullptr;
+ UILabel *label_ = nullptr;
+ bool updateText_ = false;
+ bool firstArc_ = false;
+ SportCountdownStage stage_ = SportCountdownStage::STAGE_NONE;
+ int16_t endAngle_ = 0;
+ int16_t easeDegree_ = 0;
+ uint16_t idleTime_ = 0;
+ int16_t counts_ = 0;
+ uint32_t lastTime_ = 0;
+ uint32_t degreePerSecond_ = 0;
+ uint32_t surplusDegree = 0;
+ uint16_t lastFontSize_ = 0; // 用于节流 SetFont,减少卡顿
+};
+
+class CountdownPresenter;
+class CountdownView : public View<CountdownPresenter> {
+ public:
+ CountdownView();
+ virtual ~CountdownView();
+ static CountdownView *GetInstance(void);
+
+ void OnStart() override;
+
+ void InitCountdownView();
+ void StartCanvas();
+ void StopCanvas();
+
+ private:
+ CountdownView(const CountdownView &) = delete;
+ CountdownView &operator=(const CountdownView &) = delete;
+
+ UIViewGroup *group_ = nullptr;
+ SportCountdownAnimatorCallback *callBack_ = nullptr;
+ Animator *countdownAnimator_ = nullptr;
+ UICanvasExt *canvas_ = nullptr;
+ UILabel *label_ = nullptr;
+};
+
+} // namespace OHOS
+
+#endif
--- /dev/null
+/**
+ * 运动倒计时 Slice:仅 3 秒倒计时,时间到跳转到 SetReturnSlice 指定的 slice(如运动界面)
+ * 不依赖 saveele,完全独立在 sport_countdown 模块内
+ */
+#include "AppViewIDs.h"
+#include "key/include/KeyInputListener.h"
+#include "main/include/MainModel.h"
+#include "NativeAbility.h"
+#include "sport_countdown/CountdownPresenter.h"
+#include "sport_countdown/CountdownView.h"
+#include "wearable_log.h"
+#include "string.h"
+#include "NativeRegisterManager.h"
+
+namespace OHOS {
+
+REGIST_SLICE(VIEW_SPORT_COUNTDOWN, CountdownView, CountdownPresenter);
+
+static CountdownPresenter *g_pCountdownPresenter = nullptr;
+
+int16_t CountdownPresenter::s_returnSliceId = 0;
+bool CountdownPresenter::s_countdownFinished = false;
+
+CountdownPresenter::CountdownPresenter() {
+ g_pCountdownPresenter = this;
+}
+
+CountdownPresenter::~CountdownPresenter() {
+ g_pCountdownPresenter = nullptr;
+}
+
+CountdownPresenter *CountdownPresenter::GetInstance() {
+ return g_pCountdownPresenter;
+}
+
+void CountdownPresenter::SetReturnSlice(int16_t sliceId) {
+ s_returnSliceId = sliceId;
+}
+
+void CountdownPresenter::SetCountdownFinished(bool finished) {
+ s_countdownFinished = finished;
+}
+
+void CountdownPresenter::OnStart() {
+ Presenter<CountdownView>::OnStart();
+}
+
+void CountdownPresenter::OnResume() {
+ RootView::GetInstance()->SetOnKeyActListener(this);
+}
+
+void CountdownPresenter::ExitView(void) {
+ int16_t target = (s_returnSliceId != 0) ? s_returnSliceId : VIEW_MAIN;
+ s_returnSliceId = 0;
+ s_countdownFinished = false;
+ WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "运动倒计时结束,跳转 slice=%d", target);
+ NativeAbility::GetInstance().ChangeSlice(target);
+}
+
+void CountdownPresenter::OnTickEvent() {
+ if (s_countdownFinished) {
+ s_countdownFinished = false;
+ ExitView();
+ }
+}
+
+bool CountdownPresenter::OnKeyAct(UIView &view, const KeyEvent &event) {
+ (void)view;
+ (void)event;
+ return true;
+}
+
+} // namespace OHOS
--- /dev/null
+/**
+ * 运动倒计时 Slice:仅保留 3 秒倒计时 UI,进入即开始,结束通知 Presenter 跳转
+ * 不依赖 saveele,类名 CountdownView/SportCountdownAnimatorCallback 与 saveele 区分
+ */
+#include "sport_countdown/CountdownView.h"
+#include "sport_countdown/CountdownPresenter.h"
+#include "animator/easing_equation.h"
+#include "wearable_log.h"
+#include "common/screen.h"
+#include "custom_utils.h"
+#include "vibrator_service.h"
+#include <cstdio>
+
+namespace OHOS {
+
+const static int16_t CENTER_X = 233;
+const static int16_t CENTER_Y = 233;
+const static int16_t LABEL_X = 140;
+const static int16_t LABEL_Y = 170;
+const static int16_t RADIUS = 90;
+const static int16_t STROKE_WIDTH = 20;
+const static int16_t CIRCLE_BASE_WIDTH = 4;
+const static uint32_t LIGHT_GREEN = 0xff007700;
+const static uint16_t LABEL_FONT_SIZE = 80;
+const static uint16_t ANIMATE_TIME = 500;
+const static uint16_t SECOND_TO_MINISECOND = 1000;
+const static uint16_t CIRCLE_FADE_OUT = 200;
+const static uint16_t DEGREES_PER_CIRCLE = 360;
+
+void SportCountdownAnimatorCallback::Callback(UIView *view) {
+ (void)view;
+ switch (stage_) {
+ case SportCountdownStage::STAGE_GRADUAL_CIRCLE:
+ GradualCircle();
+ break;
+ case SportCountdownStage::STAGE_COUNTING_DOWN:
+ CountingDown();
+ break;
+ case SportCountdownStage::STAGE_FADE_OUT:
+ CircleFadeOut();
+ break;
+ default:
+ break;
+ }
+}
+
+void SportCountdownAnimatorCallback::CircleFadeOut() {
+ uint32_t diffTime = HALTick::GetInstance().GetTime() - lastTime_;
+ if (diffTime < idleTime_)
+ return;
+ canvas_->Clear();
+ uint32_t curTime = diffTime - idleTime_;
+ uint16_t width;
+ if (curTime >= CIRCLE_FADE_OUT) {
+ curTime = CIRCLE_FADE_OUT;
+ stage_ = SportCountdownStage::STAGE_NONE;
+ }
+ width = EasingEquation::QuadEaseInOut(STROKE_WIDTH, CIRCLE_BASE_WIDTH, curTime, CIRCLE_FADE_OUT);
+ Point center = {CENTER_X, CENTER_Y};
+ ColorType lightGreen;
+ lightGreen.full = LIGHT_GREEN;
+ PaintExt paint;
+ paint.SetStyle(Paint::PaintStyle::STROKE_STYLE);
+ paint.SetCapType(CAP_ROUND);
+ paint.SetAntialiased(true);
+ paint.SetStrokeColor(lightGreen);
+ paint.SetStrokeWidth(width);
+ canvas_->DrawCircle(center, RADIUS, paint);
+ uint16_t fontSize = LABEL_FONT_SIZE * width / STROKE_WIDTH;
+ uint16_t opa = (stage_ == SportCountdownStage::STAGE_NONE) ? OPA_TRANSPARENT : (OPA_OPAQUE * width / STROKE_WIDTH);
+ label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME_EN, fontSize);
+ label_->SetStyle(STYLE_TEXT_OPA, opa);
+}
+
+// 字号变化步长,避免每帧都 SetFont 触发重排/重绘,减轻卡顿
+const static uint16_t FONT_SIZE_STEP = 8;
+
+void SportCountdownAnimatorCallback::UpdateLable(uint32_t curTime) {
+ char buf[8] = {0};
+ uint16_t fontSize;
+ uint16_t opa;
+ if (curTime < ANIMATE_TIME / 2) {
+ fontSize = LABEL_FONT_SIZE - LABEL_FONT_SIZE * curTime * 2 / ANIMATE_TIME;
+ opa = OPA_OPAQUE * fontSize / LABEL_FONT_SIZE;
+ if (firstArc_)
+ fontSize = fontSize / 2;
+ } else {
+ fontSize = LABEL_FONT_SIZE * curTime * 2 / ANIMATE_TIME - LABEL_FONT_SIZE;
+ opa = OPA_OPAQUE * fontSize / LABEL_FONT_SIZE;
+ if (updateText_) {
+ int n = (endAngle_ + easeDegree_) / degreePerSecond_;
+ snprintf(buf, sizeof(buf), "%d", n > 0 ? n : 0);
+ label_->SetText(buf);
+ updateText_ = false;
+ }
+ }
+ // 仅当字号变化超过步长时再 SetFont,减少矢量字重算带来的卡顿
+ if (fontSize != lastFontSize_ &&
+ (fontSize >= lastFontSize_ + FONT_SIZE_STEP || fontSize + FONT_SIZE_STEP <= lastFontSize_ || lastFontSize_ == 0)) {
+ label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME_EN, fontSize);
+ lastFontSize_ = fontSize;
+ }
+ label_->SetStyle(STYLE_TEXT_OPA, opa);
+}
+
+void SportCountdownAnimatorCallback::UpdatePath(int16_t start, int16_t end, bool needDrawCircle) {
+ Point center = {CENTER_X, CENTER_Y};
+ ColorType lightGreen;
+ lightGreen.full = LIGHT_GREEN;
+ PaintExt paint;
+ paint.SetStyle(Paint::PaintStyle::STROKE_STYLE);
+ paint.SetStrokeWidth(STROKE_WIDTH);
+ paint.SetCapType(CAP_ROUND);
+ paint.SetAntialiased(true);
+ paint.SetStrokeColor(lightGreen);
+ canvas_->Clear();
+ if (needDrawCircle)
+ canvas_->DrawCircle(center, RADIUS, paint);
+ if (start != end) {
+ paint.SetStrokeColor(Color::Green());
+ if (end - start == DEGREES_PER_CIRCLE)
+ canvas_->DrawCircle(center, RADIUS, paint);
+ else
+ canvas_->DrawArc(center, RADIUS, start, end, paint);
+ }
+}
+
+void SportCountdownAnimatorCallback::CountingDown() {
+ uint32_t diffTime = HALTick::GetInstance().GetTime() - lastTime_;
+ if (diffTime >= SECOND_TO_MINISECOND) {
+ lastTime_ = HALTick::GetInstance().GetTime();
+ firstArc_ = (endAngle_ == DEGREES_PER_CIRCLE);
+ UpdatePath(0, endAngle_, true);
+ UpdateLable(0);
+ custom_VibratorFunc_int(0); // 每秒振动提醒(3、2、1 各一次)
+ if (endAngle_ == 0) {
+ stage_ = SportCountdownStage::STAGE_FADE_OUT;
+ idleTime_ = SECOND_TO_MINISECOND - CIRCLE_FADE_OUT;
+ WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "运动倒计时结束");
+ CountdownPresenter::SetCountdownFinished(true);
+ return;
+ }
+ //振动提醒
+ easeDegree_ = degreePerSecond_;
+ endAngle_ -= degreePerSecond_;
+ if (endAngle_ <= surplusDegree) {
+ endAngle_ = 0;
+ easeDegree_ += surplusDegree;
+ }
+ updateText_ = true;
+ } else if (diffTime >= idleTime_) {
+ uint32_t curTime = diffTime - idleTime_;
+ int16_t arcCurr = EasingEquation::QuadEaseInOut(0, easeDegree_, curTime, ANIMATE_TIME);
+ UpdatePath(0, endAngle_ + easeDegree_ - arcCurr, true);
+ UpdateLable(curTime);
+ }
+}
+
+void SportCountdownAnimatorCallback::GradualCircle() {
+ uint32_t diffTime = HALTick::GetInstance().GetTime() - lastTime_;
+ if (diffTime >= SECOND_TO_MINISECOND) {
+ stage_ = SportCountdownStage::STAGE_COUNTING_DOWN;
+ idleTime_ = SECOND_TO_MINISECOND - ANIMATE_TIME;
+ } else if (diffTime >= idleTime_) {
+ uint32_t curTime = diffTime - idleTime_;
+ int16_t arcCurr = EasingEquation::QuadEaseInOut(0, DEGREES_PER_CIRCLE, curTime, SECOND_TO_MINISECOND - idleTime_);
+ UpdatePath(180, arcCurr + 180, false);
+ }
+}
+
+void SportCountdownAnimatorCallback::SetInfo(UILabel *label, int counts) {
+ counts_ = counts;
+ label_ = label;
+}
+
+void SportCountdownAnimatorCallback::NotifyStart() {
+ stage_ = SportCountdownStage::STAGE_GRADUAL_CIRCLE;
+ endAngle_ = DEGREES_PER_CIRCLE;
+ idleTime_ = 200;
+ degreePerSecond_ = DEGREES_PER_CIRCLE / counts_;
+ surplusDegree = DEGREES_PER_CIRCLE - degreePerSecond_ * counts_;
+ lastTime_ = HALTick::GetInstance().GetTime();
+ lastFontSize_ = LABEL_FONT_SIZE / 2;
+ label_->SetText(" ");
+ label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME_EN, lastFontSize_);
+ label_->SetStyle(STYLE_TEXT_OPA, OPA_OPAQUE);
+}
+
+void SportCountdownAnimatorCallback::CancelCountdown() {
+ stage_ = SportCountdownStage::STAGE_FADE_OUT;
+ lastTime_ = HALTick::GetInstance().GetTime();
+ idleTime_ = 0;
+ endAngle_ = 0;
+}
+
+static CountdownView *g_pCountdownView = nullptr;
+
+CountdownView::CountdownView() {
+ g_pCountdownView = this;
+ ScreenAlwaysOnCtr(OPEN_SCREEN_ALWAYS_ON); // 运动倒计时期间常亮屏
+}
+
+CountdownView::~CountdownView() {
+ ScreenAlwaysOnCtr(CANCEL_SCREEN_ALWAYS_ON); // 退出运动倒计时取消常亮屏
+ if (countdownAnimator_) {
+ countdownAnimator_->Stop();
+ delete countdownAnimator_;
+ countdownAnimator_ = nullptr;
+ }
+ if (callBack_) {
+ delete callBack_;
+ callBack_ = nullptr;
+ }
+ if (canvas_) {
+ delete canvas_;
+ canvas_ = nullptr;
+ }
+ if (label_) {
+ delete label_;
+ label_ = nullptr;
+ }
+ if (group_) {
+ delete group_;
+ group_ = nullptr;
+ }
+ g_pCountdownView = nullptr;
+}
+
+CountdownView *CountdownView::GetInstance() {
+ return g_pCountdownView;
+}
+
+void CountdownView::InitCountdownView() {
+ canvas_ = new UICanvasExt();
+ if (!canvas_) return;
+ canvas_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+ canvas_->SetStyle(STYLE_BACKGROUND_OPA, OPA_TRANSPARENT);
+ group_->Add(canvas_);
+
+ label_ = new UILabel();
+ if (!label_) return;
+ label_->SetPosition(LABEL_X, LABEL_Y, 180, 120);
+ label_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+ group_->Add(label_);
+
+ PaintExt paint;
+ ColorType lightGreen;
+ lightGreen.full = LIGHT_GREEN;
+ paint.SetStyle(Paint::PaintStyle::STROKE_STYLE);
+ paint.SetStrokeWidth(CIRCLE_BASE_WIDTH);
+ paint.SetAntialiased(true);
+ paint.SetStrokeColor(lightGreen);
+ canvas_->DrawCircle({CENTER_X, CENTER_Y}, RADIUS, paint);
+
+ callBack_ = new SportCountdownAnimatorCallback(canvas_);
+ if (!callBack_) return;
+ countdownAnimator_ = new Animator(callBack_, canvas_, 0, true);
+ if (!countdownAnimator_) {
+ delete callBack_;
+ callBack_ = nullptr;
+ return;
+ }
+ countdownAnimator_->Start();
+ callBack_->SetInfo(label_, 3);
+}
+
+void CountdownView::StartCanvas() {
+ if (callBack_)
+ callBack_->NotifyStart();
+}
+
+void CountdownView::StopCanvas() {
+ if (callBack_)
+ callBack_->CancelCountdown();
+}
+
+void CountdownView::OnStart() {
+ group_ = new UIViewGroup();
+ if (!group_) return;
+ group_->SetPosition(0, 0);
+ group_->SetWidth(Screen::GetInstance().GetWidth());
+ group_->SetHeight(Screen::GetInstance().GetHeight());
+ group_->SetTouchable(true);
+ group_->SetDraggable(true);
+
+ InitCountdownView();
+ AddViewToRootContainer(group_);
+
+ if (canvas_)
+ canvas_->SetVisible(true);
+ if (label_)
+ label_->SetVisible(true);
+ StartCanvas();
+ // SetViewState(true);
+}
+
+} // namespace OHOS