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

2021-06-09 10:03:14 浏览数 (1)

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

指针式秒表组件

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

代码语言:javascript复制
//指针式秒表组件类
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("d:d:d",
                        millisecond/1000/3600,   //hour
                        millisecond/1000/60`,  //minute
                        millisecond/1000`);     //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组件:

代码语言:javascript复制
代码语言:javascript复制
<?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>
代码语言:javascript复制

使用指针式秒表组件

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

代码语言:javascript复制
代码语言:javascript复制
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("Lapd d:d:d.d",
                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  ;
    }
}

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

执行结果如下:

参考代码

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

代码语言:javascript复制
https://github.com/xueweiguo/Harmony/tree/master/StopWatch

作者著作介绍

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

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

0 人点赞