专栏首页C++核心准则原文翻译自学鸿蒙应用开发(44)- 秒表应用开发(2)

自学鸿蒙应用开发(44)- 秒表应用开发(2)

经过几天的开发,秒表应用终于初具规模了,先看执行效果:

指针式秒表组件

下面是自定义指针式模拟秒表组件的实现代码。具体内容参见注释。

//指针式秒表组件类
public class AnalogStopWatch extends Component implements Component.DrawTask {
    private long start_time = 0;    //计时开始时刻,毫秒单位
    private long millisecond = 0;   //计时时间,毫秒单位
    private boolean running = false;//执行状态
    //构造函数
    public AnalogStopWatch(Context context) {
        super(context);
        Initialize(null);
    }
    //构造函数
    public AnalogStopWatch(Context context, AttrSet attrSet) {
        super(context, attrSet);
        Initialize(attrSet);
    }
    //构造函数
    public AnalogStopWatch(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        Initialize(attrSet);
    }
    //构造函数
    public AnalogStopWatch(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        Initialize(attrSet);
    }

    //获取运行状态
    public boolean isRunning(){
        return running;
    }

    //获取当前计时时长
    public long getMiliseconds(){
        return millisecond;
    }

    //根据目前的运行状态,开始或停止计时
    public void start_stop(){
        if(!running) {
            start_time = Calendar.getInstance().getTimeInMillis();
            millisecond = 0;
            running = true;
            onRunTimer();
        }
        else{
            running = false;
        }
    }

    //计时Timer
    void onRunTimer(){
        final long delayTime = 100L;  //延时时间,毫秒单位
        millisecond = Calendar.getInstance().getTimeInMillis() - start_time;
        invalidate();  //更新画面表示
        if(running) {
            //如果处于运行状态,触发下一次延时执行
            TaskDispatcher uiTaskDispatcher = mContext.getUITaskDispatcher();
            Revocable revocable = uiTaskDispatcher.delayDispatch(new Runnable() {
                @Override
                public void run() {
                    onRunTimer();
                }
            }, delayTime);
        }
    }

    //归零
    public void reset(){
        if(!running && millisecond > 0) {
            onResetTimer();  //启动归零处理
        }
    }

    //归零处理
    void onResetTimer(){
        final long delayTime = 10L;
        long second_value = millisecond / 1000 % 60;
        long minute_value = millisecond / 1000 / 60;
        if(second_value > 0){
            millisecond -= 1000;   //秒针反转
        }
        if(minute_value > 0){
            millisecond -= 1000 * 60;  //分针反转
        }
        if(second_value > 0 || minute_value >0) {
            //如果反转未到位,触发下次延时处理
            TaskDispatcher uiTaskDispatcher = mContext.getUITaskDispatcher();
            Revocable revocable = uiTaskDispatcher.delayDispatch(new Runnable() {
                @Override
                public void run() {
                    onResetTimer();
                }
            }, delayTime);
        }
        else{
            millisecond = 0;
        }
        invalidate();
    }

    @Override
    public void onDraw(Component component, Canvas canvas)
{
        Paint paint = new Paint();
        RectFloat bound = getBoundRect();
        Point main_center = bound.getCenter();
        drawScale(canvas, paint, main_center, getRadius(), 1);
        drawScaleValue(canvas, paint, main_center, getRadius() * 0.8f,
                12, 5, (int)(getRadius() * 0.2f));

        Point center = new Point(main_center.getPointX(),
                                bound.top + getRadius() * 0.6f);
        float radius = getRadius() / 3;
        drawScale(canvas, paint, center, radius, 5);
        drawScaleValue(canvas, paint, center, radius * 0.7f,
                12, 5, (int)(radius * 0.25f));

        drawDigitalTime(canvas, paint, main_center.getPointX(),main_center.getPointY() + getRadius() * 0.4f);
        drawMinute(canvas, center, radius);
        drawSecond(canvas, bound);
    }

    //描画刻度线
    private void drawScale(Canvas canvas, Paint paint, Point center, float radius, int scale_interval){
        paint.setColor(Color.WHITE);
        float len5sec = radius / 8;
        float len1sec = radius / 12;
        float len02sec = radius / 20;
        paint.setColor(Color.BLACK);
        for(int i = 0; i < 300; i+= scale_interval){
            float insideRaduis = radius;
            if ((i % 25)==0){
                insideRaduis -= len5sec;
                paint.setStrokeWidth(radius / 60);
            }
            else if((i % 5)==0){
                insideRaduis -= len1sec;
                paint.setStrokeWidth(radius / 80);
            }
            else{
                insideRaduis -= len02sec;
                paint.setStrokeWidth(radius / 120);
            }
            drawRadius(canvas, paint, center, insideRaduis, radius, i * Math.PI / 150);
        }
    }

    //描画全部刻度值
    private void drawScaleValue(Canvas canvas, Paint paint, Point center, float radius, int count, int offset, int font_size){
        for(int i = 1; i <= count; i++){
            drawScaleText(canvas, paint, center, radius, Math.PI * 2 / count * i, offset * i, font_size);
        }
    }

    //描画秒针
    private void drawMinute(Canvas canvas, Point center, float radius){
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth(radius / 20);
        paint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        drawRadius(canvas, paint, center, 0, radius * 0.9f, millisecond / 60 * Math.PI * 2 / 60000);
        float oval_radius = radius / 10;
        canvas.drawOval(new RectFloat(center.getPointX() - oval_radius, center.getPointY() - oval_radius,
                        center.getPointX() + oval_radius, center.getPointY() + oval_radius),
                paint);
    }

    //描画分针
    private void drawSecond(Canvas canvas, RectFloat bound){
        float radius = getRadius();
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth(radius / 40);
        paint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        drawRadius(canvas, paint, bound.getCenter(), 0, radius * 0.9f, millisecond * Math.PI * 2 / 60000);
        float oval_radius = radius / 20;
        Point center = bound.getCenter();
        canvas.drawOval(new RectFloat(center.getPointX() - oval_radius, center.getPointY() - oval_radius,
                        center.getPointX() + oval_radius, center.getPointY() + oval_radius),
                paint);
    }

    //描画径向直线
    private void drawRadius(Canvas canvas, Paint paint, Point center, float from, float to, double angle){
        double sin = Math.sin(angle);
        double cos = Math.cos(angle);
        canvas.drawLine(new Point(center.getPointX() + (float)(from * sin),
                        center.getPointY() - (float)(from * cos)),
                        new Point(center.getPointX() + (float)(to * sin),
                        center.getPointY() - (float)(to * cos)),
                        paint);
    }

    //描画单一刻度值
    private void drawScaleText(Canvas canvas, Paint paint, Point center, float radius, double angle, int value, int font_size){
        double sin = Math.sin(angle);
        double cos = Math.cos(angle);
        Font.Builder builder = new Font.Builder("Arial");
        builder.setWeight(Font.REGULAR);
        paint.setFont(builder.build());
        paint.setTextSize(font_size);
        String text = String.format("%d", value);
        float width = paint.measureText(text);      //计算字符串显示宽度
        canvas.drawText(paint, text,
                center.getPointX() + (float)(radius * sin) - width / 2,
                center.getPointY() - (float)(radius * cos) + font_size / 3);
    }

    //描画数字时间
    private void drawDigitalTime(Canvas canvas, Paint paint, float x, float y){
        Font.Builder builder = new Font.Builder("Arial");
        builder.setWeight(Font.REGULAR);
        paint.setFont(builder.build());
        int font_size = (int)(getRadius() / 4);
        paint.setColor(Color.BLUE);
        paint.setTextSize(font_size);
        String now = String.format("%02d:%02d:%02d",
                        millisecond/1000/3600,   //hour
                        millisecond/1000/60%60,  //minute
                        millisecond/1000%60);     //second
        float width = paint.measureText(now);           //计算字符串显示宽度
        canvas.drawText(paint, now, x - width / 2 , y);
    }

    //获取秒表显示区域
    private RectFloat getBoundRect(){
        float width = getWidth();
        float height = getHeight();
        float size = Math.min(width, height);
        float x_padding = (width - size) / 2;
        float y_padding = (height - size) / 2;
        return new RectFloat(x_padding, y_padding, width - x_padding, height - y_padding);
    }

    //获取显示半径
    private float getRadius(){
        return getBoundRect().getWidth() / 2;
    }

    //获取圆心位置
    private Point getCenter(){
        return getBoundRect().getCenter();
    }

    //初始化
    private void Initialize(AttrSet attrSet){
        addDrawTask(this);
    }
}

在布局中布置指针式秒表组件

在本应用的布局中,我们使用了指针式秒表组件、两个操作按钮和一个表示计时结果的Text组件:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical"
    ohos:alignment="top">
    <xwg.harmony.stopwatch.AnalogStopWatch
        ohos:id="$+id:analog_stop_watch"
        ohos:height="300vp"
        ohos:width="match_parent"
        />
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:orientation="horizontal"
        ohos:alignment="center"
        ohos:top_margin="5vp">
        <Component
            ohos:height="match_parent"
            ohos:width="0"
            ohos:weight="1"/>
        <Button
            ohos:id="$+id:reset_lap"
            ohos:height="match_content"
            ohos:width="0"
            ohos:weight="5"
            ohos:text_size="27fp"
            ohos:text="$string:Reset"
            ohos:background_element="$graphic:background_button"
            />
        <Component
            ohos:height="match_parent"
            ohos:width="0"
            ohos:weight="1"/>
        <Button
            ohos:id="$+id:start_stop"
            ohos:height="match_content"
            ohos:width="0"
            ohos:weight="5"
            ohos:text_size="27fp"
            ohos:text="$string:Start"
            ohos:background_element="$graphic:background_button"
            />
        <Component
            ohos:height="match_parent"
            ohos:width="0"
            ohos:weight="1"/>
    </DirectionalLayout>
    <Text
        ohos:id="$+id:lap_times"
        ohos:width="match_parent"
        ohos:height="match_parent"
        ohos:text_alignment="horizontal_center"
        ohos:text_size="30fp"
        ohos:multiple_lines="true"
        ohos:scrollable="true"
        ohos:top_margin="5vp"
        ohos:left_margin="5vp"
        ohos:right_margin="5vp"
        />
</DirectionalLayout>

使用指针式秒表组件

以下是在AbilitySlice中使用指针式秒表组件的示例代码:

public class MainAbilitySlice extends AbilitySlice {
    AnalogStopWatch stopwatch = null;
    Text lap_time = null;
    int record_count = 0;
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);
        //秒表组件
        stopwatch = (AnalogStopWatch)findComponentById(ResourceTable.Id_analog_stop_watch);
        //计时结果Text组件
        lap_time = (Text)findComponentById(ResourceTable.Id_lap_times);
        //开始或停止按钮
        Button start_stop = (Button)findComponentById(ResourceTable.Id_start_stop);
        //清零或保存结果按钮
        Button reset_lap = (Button)findComponentById(ResourceTable.Id_reset_lap);
        start_stop.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                stopwatch.start_stop();
                if(stopwatch.isRunning()){
                    start_stop.setText(ResourceTable.String_Stop);
                    reset_lap.setText(ResourceTable.String_Lap);
                    clearTime();
                }
                else{
                    start_stop.setText(ResourceTable.String_Start);
                    reset_lap.setText(ResourceTable.String_Reset);
                    recordTime();
                }
            }
        });

        reset_lap.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                if (stopwatch.isRunning()){
                    recordTime();
                }
                else{
                    stopwatch.reset();
                    clearTime();
                }
            }
        });
    }
    //清除计时结果
    private void clearTime(){
        lap_time.setText("");
        record_count = 0;
    }

    //记录当前时间
    private void recordTime(){
        String lap_string = lap_time.getText();
        long milliseconds = stopwatch.getMiliseconds();
        String current_time = String.format("Lap%02d %02d:%02d:%02d.%03d",
                record_count,
                milliseconds / 1000 / 60 / 60 % 60,  //hour
                milliseconds / 1000 / 60 % 60,       //minute
                milliseconds / 1000 % 60,            //second
                milliseconds % 1000);                //milisecond
        lap_time.setText(lap_string + "\n" + current_time);
        record_count++;
    }
}

我们没有在指针式秒钟组件中处理按钮动作,其目的是为了给使用者提供更大的灵活性。

执行结果如下:

参考代码

完整代码可以从以下链接下载:

https://github.com/xueweiguo/Harmony/tree/master/StopWatch

作者著作介绍

《实战Python设计模式》是作者去年3月份出版的技术书籍,该书利用Python 的标准GUI 工具包tkinter,通过可执行的示例对23 个设计模式逐个进行说明。这样一方面可以使读者了解真实的软件开发工作中每个设计模式的运用场景和想要解决的问题;另一方面通过对这些问题的解决过程进行说明,让读者明白在编写代码时如何判断使用设计模式的利弊,并合理运用设计模式。

对设计模式感兴趣而且希望随学随用的读者通过本书可以快速跨越从理解到运用的门槛;希望学习Python GUI 编程的读者可以将本书中的示例作为设计和开发的参考;使用Python 语言进行图像分析、数据处理工作的读者可以直接以本书中的示例为基础,迅速构建自己的系统架构。

本文分享自微信公众号 - 面向对象思考(OOThinkingDalian),作者:面向对象思考

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-05-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 自学鸿蒙应用开发(43)- 秒表应用开发(1)

    从本篇文章开始,边学边练开发一个秒表应用,本文是本系列的第一篇:绘制表盘。先看演示视频:

    面向对象思考
  • 自学鸿蒙应用开发(12)- Checkbox

    下面代码中的第20行获取RadioContainer组件后,在第24行根据Checkbox的状态更新TimePicker的形式,然后在第42行~45行为Chec...

    面向对象思考
  • 自学鸿蒙应用开发(13)- ProgressBar

    下面代码中的第18行获取ProgressBar组件后,在第19行根据TimePicker的状态更新Progress的形式,然后在第26行TimerPicker的...

    面向对象思考
  • 自学鸿蒙应用开发(14)- RoundProgressBar

    如下代码中35行~44行所示,在布局中增加RoundProgressBar组件。

    面向对象思考
  • 自学鸿蒙应用开发(15)- ScrollView

    如下代码中第2行~第10行和第137行所示,可以很简单地在布局中增加ScrollView组件。

    面向对象思考
  • 自学鸿蒙应用开发(16)- ListContainer

    在layout目录下的xml文件中创建ListContainer布局,将其命名为page_listcontainer.xml。

    面向对象思考
  • 自学鸿蒙应用开发(9)- TimePicker组件

    如下面代码中21行~49行所示,在获取TimePicker组件后,一方面在button的动作响应中计算所选时刻和当前时刻的秒数差之后用小窗口表示出来;另一方面在...

    面向对象思考
  • 自学鸿蒙应用开发(10)- Switch组件

    如下面代码中20行和42行~49行所示,在获取Switch组件后,在Switch响应处理中根据Switch的当前状态为TimePicker设定是否像是为24小时...

    面向对象思考
  • 自学鸿蒙应用开发(11)- RadioButton和RadioContainer

    本文介绍在鸿蒙应用中RadioButton和RadioContainer组件的基本用法。

    面向对象思考
  • 自学鸿蒙应用开发(17)- TabList和Tab

    在layout目录下创建TabList布局,将其命名为ability_tablist.xml。

    面向对象思考
  • 自学鸿蒙应用开发(5)- button组件

    代码中按钮id被指定为hello_button,这个信息会在下面的响应代码中用到。

    面向对象思考
  • 自学鸿蒙应用开发(6)- TextField组件

    如下面代码中20行和22行所示,在按钮组件的响应代码中获取TextField组件后,获取输入的文字信息并弹窗显示。

    面向对象思考
  • 自学鸿蒙应用开发(7)- Picker组件

    如下面代码中30行和36行所示,在获取PIcker组件后,设定表示信息并在动作响应代码将最新表示信息设定到TextField组件上。

    面向对象思考
  • 自学鸿蒙应用开发(8)- DatePicker组件

    如下面代码中21行和50行所示,在获取DatePicker组件后,一方面在button的动作响应中计算所选日期和当前日期的差值之后用小窗口表示出来;另一方面在用...

    面向对象思考
  • 自学鸿蒙应用开发(37)- PageSlider组件

    PageSlider是用于页面之间切换的组件,它通过响应滑动事件完成页面间的切换。

    面向对象思考
  • 自学鸿蒙应用开发(26)- 自定义CommonDialog

    上一篇文章中说过,直接使用鸿蒙系统中的CommonDialog大致是下面的效果:

    面向对象思考
  • 自学鸿蒙应用开发(27)- 自定义ListDialog

    这个效果过于简陋,无法用于实际的产品开发。本文介绍如何定制自己的ListDialog。还是先看演示视频:

    面向对象思考
  • 鸿蒙应用开发环境

    DevEco Studio的编译构建依赖JDK,DevEco Studio预置了Open JDK,版本为1.8,安装过程中会自动安装JDK。

    乐心湖
  • “开辟鸿蒙 ” - 鸿蒙系统应用开发尝鲜

    第一次知道“鸿蒙”这个词,是在《红楼梦》的开篇引子。鸿蒙,在古典文化中,表示宇宙形成前的混沌状态,意为开天辟地之前。

    极客人

扫码关注云+社区

领取腾讯云代金券