图像、选择器与自定义组件


1 图像组件(Image)

<Image
    ohos:id="$+id:img"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:center_in_parent="true"
    ohos:image_src="$media:HarmonyOS"
    />
Image image = (Image)findComponentById(ResourceTable.Id_img);
//image.setScaleMode(Image.ScaleMode.CENTER);
image.setScaleMode(Image.ScaleMode.INSIDE);

image.setClickedListener(new ClickedListener() {
    private int minWidth = 0, maxWidth, deltaWidth, deltaHeight;

    @Override
    public void onClick(Component component) {
        int curWidth = image.getWidth();
        int curHeight = image.getHeight();

        if (minWidth == 0) {
            minWidth = curWidth / 5;
            maxWidth = curWidth;
            deltaWidth = -curWidth / 5;
            deltaHeight = -curHeight / 5;
        }

        for (;;) {
            int newWidth = curWidth + deltaWidth;
            int newHeight = curHeight + deltaHeight;

            if (minWidth <= newWidth && newWidth <= maxWidth) {
                image.setWidth(newWidth);
                image.setHeight(newHeight);
                break;
            }
            else {
                deltaWidth *= -1;
                deltaHeight *= -1;
            }
        }
    }
});

例程:Image

...\Image\entry\src\main\resources\base\media\HarmonyOS.png

...\Image\entry\src\main\resources\base\layout\ability_main.xml

<?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:alignment="center"
    ohos:orientation="vertical"
    ohos:background_element="#000000">

    <Image
        ohos:id="$+id:img"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:image_src="$media:HarmonyOS"
        />

</DirectionalLayout>

...\Image\entry\src\main\java\com\minwei\image\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    @Override
    public void onStart(Intent intent) {
        ...
        Image image = (Image)findComponentById(ResourceTable.Id_img);
        //image.setScaleMode(Image.ScaleMode.CENTER);
        image.setScaleMode(Image.ScaleMode.INSIDE);

        image.setClickedListener(new ClickedListener() {
            private int minWidth = 0, maxWidth, deltaWidth, deltaHeight;

            @Override
            public void onClick(Component component) {
                int curWidth = image.getWidth();
                int curHeight = image.getHeight();

                if (minWidth == 0) {
                    minWidth = curWidth / 5;
                    maxWidth = curWidth;
                    deltaWidth = -curWidth / 5;
                    deltaHeight = -curHeight / 5;
                }

                for (;;) {
                    int newWidth = curWidth + deltaWidth;
                    int newHeight = curHeight + deltaHeight;

                    if (minWidth <= newWidth && newWidth <= maxWidth) {
                        image.setWidth(newWidth);
                        image.setHeight(newHeight);
                        break;
                    }
                    else {
                        deltaWidth *= -1;
                        deltaHeight *= -1;
                    }
                }
            }
        });
    }
    ...
}

运行效果如下图所示:

2 选择器组件(Picker)

2.1 属性

selector_item_num                 - 显示的项目数量
normal_text_size                  - 未选中文本大小
normal_text_color                 - 未选中文本颜色
selected_text_size                - 被选中文本大小
selected_text_color               - 被选中文本颜色
selected_normal_text_margin_radio - 被选中文本和未选中文本的边距比例
shader_color                      - 着色器颜色
top_line_element                  - 被选中项的顶线
bottom_line_element               - 被选中项的底线
wheel_mode_enabled                - 是否轮转显示
max_value                         - 最大值
min_value                         - 最小值
value                             - 当前值
element_padding                   - 文本和Element之间的间距
                                    Element必须通过setElementFormatter
                                    接口配置

2.2 方法

picker.setMinValue(...);
picker.setMaxValue(...);
picker.setDisplayedData(...);

2.3 事件

picker.setValueChangedListener(
    (picker, last, next) -> {
        ...
    });

例程:Picker

...\Picker\entry\src\main\resources\base\layout\ability_main.xml

<?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:alignment="center"
    ohos:orientation="vertical">

    <DirectionalLayout
        ohos:height="0vp"
        ohos:weight="1"
        ohos:width="match_parent">

        <Text
            ohos:id="$+id:txtNumber"
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:text_size="26fp"
            ohos:text_color="#ff0000"
            ohos:text_alignment="horizontal_center"
            />

        <Picker
            ohos:id="$+id:pckNumber"
            ohos:height="match_parent"
            ohos:weight="1"
            ohos:width="match_parent"
            ohos:background_element="#ffe1ff"
            ohos:normal_text_size="20fp"
            ohos:selected_text_size="26fp"
            ohos:min_value="100"
            ohos:max_value="500"
            ohos:value="300"
            />

    </DirectionalLayout>

    <DirectionalLayout
        ohos:height="0vp"
        ohos:weight="1"
        ohos:width="match_parent"
        ohos:orientation="horizontal">

        <DirectionalLayout
            ohos:height="match_parent"
            ohos:width="0vp"
            ohos:weight="1">

            <Picker
                ohos:id="$+id:pckWeek"
                ohos:height="match_parent"
                ohos:weight="1"
                ohos:width="match_parent"
                ohos:background_element="#00a2e8"
                ohos:normal_text_size="20fp"
                ohos:normal_text_color="#ffffff"
                ohos:selected_text_size="26fp"
                ohos:selected_text_color="#ff7f27"
                ohos:wheel_mode_enabled="true"
                />

            <Text
                ohos:id="$+id:txtWeek"
                ohos:height="match_content"
                ohos:width="match_parent"
                ohos:text_size="26fp"
                ohos:text_color="#ff0000"
                ohos:text_alignment="horizontal_center"
                />

        </DirectionalLayout>

        <DirectionalLayout
            ohos:height="match_parent"
            ohos:width="0vp"
            ohos:weight="1">

            <Picker
                ohos:id="$+id:pckHour"
                ohos:height="match_parent"
                ohos:weight="1"
                ohos:width="match_parent"
                ohos:background_element="#22b14c"
                ohos:normal_text_size="20fp"
                ohos:normal_text_color="#ffffff"
                ohos:selected_text_size="26fp"
                ohos:selected_text_color="#ffff00"
                ohos:top_line_element="#ffff00"
                ohos:bottom_line_element="#ffff00"
                ohos:wheel_mode_enabled="true"
                />

            <Text
                ohos:id="$+id:txtHour"
                ohos:height="match_content"
                ohos:width="match_parent"
                ohos:text_size="26fp"
                ohos:text_color="#ff0000"
                ohos:text_alignment="horizontal_center"
                />

        </DirectionalLayout>

        <DirectionalLayout
            ohos:height="match_parent"
            ohos:width="0vp"
            ohos:weight="1">

            <Picker
                ohos:id="$+id:pckMinute"
                ohos:height="match_parent"
                ohos:weight="1"
                ohos:width="match_parent"
                ohos:background_element="#ff7f27"
                ohos:normal_text_size="20fp"
                ohos:normal_text_color="#ffffff"
                ohos:selected_text_size="26fp"
                ohos:selected_text_color="#00a2e8"
                ohos:wheel_mode_enabled="true"
                />

            <Text
                ohos:id="$+id:txtMinute"
                ohos:height="match_content"
                ohos:width="match_parent"
                ohos:text_size="26fp"
                ohos:text_color="#ff0000"
                ohos:text_alignment="horizontal_center"
                />

        </DirectionalLayout>

    </DirectionalLayout>

</DirectionalLayout>

...\Picker\entry\src\main\java\com\minwei\picker\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private Picker pckNumber, pckWeek, pckHour, pckMinute;
    private Text txtNumber, txtWeek, txtHour, txtMinute;

    final private String[] weeks = {
        "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
    private String[] hours = new String[24], minutes = new String[60];

    @Override
    public void onStart(Intent intent) {
        ...
        pckNumber = (Picker)findComponentById(ResourceTable.Id_pckNumber);
        pckWeek = (Picker)findComponentById(ResourceTable.Id_pckWeek);
        pckHour = (Picker)findComponentById(ResourceTable.Id_pckHour);
        pckMinute = (Picker)findComponentById(ResourceTable.Id_pckMinute);

        txtNumber = (Text)findComponentById(ResourceTable.Id_txtNumber);
        txtWeek = (Text)findComponentById(ResourceTable.Id_txtWeek);
        txtHour = (Text)findComponentById(ResourceTable.Id_txtHour);
        txtMinute = (Text)findComponentById(ResourceTable.Id_txtMinute);

        pckNumber.setValueChangedListener((picker, last, next) -> {
            txtNumber.setText(String.valueOf(next));
        });
        txtNumber.setText(String.valueOf(pckNumber.getValue()));

        pckWeek.setMinValue(0);
        pckWeek.setMaxValue(weeks.length - 1);
        pckWeek.setDisplayedData(weeks);
        pckWeek.setValueChangedListener((picker, last, next) -> {
            txtWeek.setText(weeks[next]);
        });
        pckWeek.setValue(0);

        for (int i = 0; i < hours.length; ++i)
            hours[i] = String.format("%d时", i);
        pckHour.setMinValue(0);
        pckHour.setMaxValue(hours.length - 1);
        pckHour.setDisplayedData(hours);
        pckHour.setValueChangedListener((picker, last, next) -> {
            txtHour.setText(hours[next]);
        });
        pckHour.setValue(0);

        for (int i = 0; i < minutes.length; ++i)
            minutes[i] = String.format("%d分", i);
        pckMinute.setMinValue(0);
        pckMinute.setMaxValue(minutes.length - 1);
        pckMinute.setDisplayedData(minutes);
        pckMinute.setValueChangedListener((picker, last, next) -> {
            txtMinute.setText(minutes[next]);
        });
        pckMinute.setValue(0);
    }
    ...
}

运行效果如下图所示:

3 日期选择器组件(DatePicker)

3.1 属性

date_order - 显示格式

3.2 方法

datePicker.setMinDate(...);
datePicker.setMaxDate(...);

3.3 事件

datePicker.setValueChangedListener(
    (datePicker, year, month, dayOfMonth) -> {
        ...
    });

例程:DatePicker

...\DatePicker\entry\src\main\resources\base\layout\ability_main.xml

<?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:alignment="center"
    ohos:orientation="vertical"
    ohos:background_element="#000000">

    <Text
        ohos:id="$+id:txt"
        ohos:height="0vp"
        ohos:weight="1"
        ohos:width="match_parent"
        ohos:text_size="28fp"
        ohos:text_color="#ffffff"
        ohos:text_alignment="center"
        />

    <DatePicker
        ohos:id="$+id:dp"
        ohos:height="0vp"
        ohos:weight="1"
        ohos:width="match_parent"
        ohos:background_element="#00a2e8"
        ohos:normal_text_size="20fp"
        ohos:normal_text_color="#ffffff"
        ohos:selected_text_size="26fp"
        ohos:selected_text_color="#ff7f27"
        ohos:top_line_element="#ff7f27"
        ohos:bottom_line_element="#ff7f27"
        ohos:wheel_mode_enabled="true"
        ohos:date_order="year-month-day"
        />

</DirectionalLayout>

...\DatePicker\entry\src\main\java\com\minwei\datepicker\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private Text text;
    private DatePicker datePicker;

    @Override
    public void onStart(Intent intent) {
        ...
        text = (Text)findComponentById(ResourceTable.Id_txt);
        datePicker = (DatePicker)findComponentById(ResourceTable.Id_dp);

        text.setText(
            datePicker.getYear() + "年" +
            datePicker.getMonth() + "月" +
            datePicker.getDayOfMonth() + "日");

        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
        try {
            datePicker.setMinDate(formatter.parse("19000101").getTime() / 1000);
            datePicker.setMaxDate(formatter.parse("22001231").getTime() / 1000);
        }
        catch (ParseException exception) {
            exception.printStackTrace();
        }

        datePicker.setValueChangedListener(
            (datePicker, year, month, dayOfMonth) -> {
                text.setText(year + "年" + month + "月" + dayOfMonth + "日");
            });
    }
    ...
}

运行效果如下图所示:

4 自定义组件

4.1 从Component类继承

任何自定义的组件类都必须是Component类的子类,并在其四个版本的构造方法中将构造参数透传给基类。对于高度定制(即直接从Component类继承)的组件,还需要通过基类的addDrawTask()方法,添加一个实现了DrawTask接口的对象,该对象的onDraw()方法负责自定义组件的绘制。

public class CustomComponent extends Component implements DrawTask {
    public CustomComponent(Context context) {
        super(context);
        addDrawTask(this);
    }

    public CustomComponent(Context context, AttrSet attrSet) {
        super(context, attrSet);
        addDrawTask(this);
    }

    public CustomComponent(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        addDrawTask(this);
    }

    public CustomComponent(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        addDrawTask(this);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        // 绘制组件
        ...
    }
}

构造方法包含如下参数:

4.2 绘制组件

组件的绘制需要用到画布(Canvas)和画笔(Paint),它们隶属于鸿蒙操作系统高级图形平台(Advanced Graphic Platform, AGP)核心类库。该类库位于ohos.agp.render包内。

Canvas类提供了许多与图形绘制有关的方法:

方法 描述
drawPoint()
drawPoints()
绘制点
drawLine()
drawLines()
绘制线
drawPath() 绘制路径
drawRect() 绘制矩形
drawRoundRect() 绘制圆角矩形
drawCircle() 绘制圆形
drawOval() 绘制椭圆
drawChars()
drawCharSequence()
drawText()
drawTextOnPath()
绘制文本
drawDeformedPixelMap()
drawPixelMapHolder()
drawPixelMapHolderRect()
drawPixelMapHolderCircleShape()
绘制PixelMapHolder

Canvas类的许多绘图方法都需要以Paint类型的画笔对象作为参数,画笔中包含了颜色、粗细、透明度、样式、字体等信息,这些信息通过Paint类的方法进行设置:

方法 描述
setColor() 设置颜色
setAlpha() 设置透明度
setCornerPathEffectRadius() 设置圆角半径
setStrokeCap() 设置线端样式
setStrokeJoin() 设置线肘样式
setStrokeMiter() 设置尖肘截断长度
setStrokeWidth() 设置线宽
setDashPathEffectPhase() 设置虚线的起始偏移量
setDashPathEffectIntervals() 设置虚线的划长和间隔
setStyle() 设置样式
setFont() 设置字体
setTextSize() 设置文本大小
setFakeBoldText() 设置文本加粗
setTextAlign() 设置文本对齐方式
setPosition() 设置文本起始位置
setLetterSpacing() 设置字符间距
setMultipleLine() 是否绘制多行文本
setUnderLine() 是否绘制下划线
setStrikeThrough() 是否绘制删除线

4.3 绘制阴影

4.3.1 着色器(Shader)

着色器用于控制图形的着色样式:

着色器 ShaderType
线性着色器 LinearShader LINEAR_SHADER
径向着色器 RadialShader RADIAL_SHADER
扫描着色器 SweepShader SWEEP_SHADER
位图着色器 PixelMapShader PIXELMAP_SHADER
组合着色器 GroupShader GROUP_SHADER

通过Paint对象的setShader()方法为画笔设置着色器:

LinearShader shader = new LinearShader(...);
paint.setShader(shader, ShaderType.LINEAR_SHADER);

4.3.2 掩码过滤器(MaskFilter)

掩码过滤器用于控制图形的阴影样式,其值取自Blur类型的枚举:

掩码过滤器 Blur
正常阴影 NORMAL
实体阴影 SOLID
内部阴影 INNER
外部阴影 OUTER

通过Paint对象的setMaskFilter()方法为画笔设置掩码过滤器:

MaskFilter maskFilter = new MaskFilter(... Blur.OUTER ...);
paint.setMaskFilter(maskFilter);

4.4 布局描述

在布局描述文件中,以类似下面这样的方式,引用自定义组件:

<com.minwei.custom.components.CustomComponent
    ohos:height="340px"
    ohos:width="340px"
    />

例程:Custom

...\Custom\entry\src\main\java\com\minwei\custom\components\CustomComponent.java

package com.minwei.custom.components;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.Component.DrawTask;
import ohos.agp.render.Canvas;
import ohos.agp.render.LinearShader;
import ohos.agp.render.MaskFilter;
import ohos.agp.render.MaskFilter.Blur;
import ohos.agp.render.Paint;
import ohos.agp.render.Paint.ShaderType;
import ohos.agp.render.Shader.TileMode;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.app.Context;

public class CustomComponent extends Component implements DrawTask {
    public CustomComponent(Context context) {
        super(context);
        addDrawTask(this);
    }

    public CustomComponent(Context context, AttrSet attrSet) {
        super(context, attrSet);
        addDrawTask(this);
    }

    public CustomComponent(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        addDrawTask(this);
    }

    public CustomComponent(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        addDrawTask(this);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        Paint paint = new Paint();

        Point[] points = new Point[] {new Point(0, 0), new Point(300, 300)};
        float[] stops = new float[] {0, 1};
        Color[] colors = new Color[] {Color.WHITE, Color.BLACK};
        TileMode tileMode = TileMode.REPEAT_TILEMODE;
        LinearShader shader = new LinearShader(
            points, stops, colors, tileMode);

        paint.setShader(shader, ShaderType.LINEAR_SHADER);

        MaskFilter maskFilter = new MaskFilter(20, Blur.OUTER);
        paint.setMaskFilter(maskFilter);

        canvas.drawCircle(170, 170, 150, paint);
    }
}

...\Custom\entry\src\main\resources\base\layout\ability_main.xml

<?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:alignment="center"
    ohos:orientation="vertical">

    <com.minwei.custom.components.CustomComponent
        ohos:height="340px"
        ohos:width="340px"
        />

</DirectionalLayout>

运行效果如下图所示:

更多精彩,敬请期待……


达内集团C++教学部 2021年9月17日