文件结构

  • java: java代码,运行逻辑
  • element: 元素资源,存放一些json格式文件,主要是颜色,字符串,bool值等
  • graphic: 表示可绘制资源。例如按钮的样式
  • layout: 表示布局资源, 控制控件摆放位置和控件的一些属性
  • media: 表示媒体资源,如图片、视频等
  • build: 存放最终编译后的包

项目配置文件

每个项目下都有一个config文件,它包含了该HAP的一些配置信息。

{
"app": {
"bundleName": "com.example.learning",//同一个应用需要保持一致
"vendor": "example",//开发商
"version": {//版本
"code": 1000000,//内部版本号,用户不可见
"name": "1.0.0"//呈现的版本号
},
"apiVersion"://api版本
{
"compatible": 4,//最低
"target": 5,//运行需要
"releaseType": "Beta1"//使用正式版(Release)还是Beta版还是受限版(Canary)
}
},
"deviceConfig": {},
"module": {
"package": "com.example.learning",
"name": ".MyApplication",//HAP类名
"mainAbility": "com.example.learning.MainAbility",
"deviceType": [ //运行设备
"phone"
],
"distro": { //具体描述
"deliveryWithInstall": true, //随应用安装
"moduleName": "entry", //当前HAP名称
"moduleType": "entry", //当前HAP类型,有entry和feature两种
"installationFree": true
},
"abilities": [
{
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
],
"orientation": "unspecified", //显示方式
"name": "com.example.learning.MainAbility",
"icon": "$media:icon",//Ability图表资源的索引
"description": "$string:mainability_description",
"label": "$string:entry_MainAbility",//安装后显示的名称
"type": "page",//表示Ability的Type类型,可以为page、service和data
"launchType": "standard" //启动模式,singleton是单例模式
}
]
}
}

资源使用

resources/base目录下的资源在编译时除了profile目录外都会被编译成二进制文件,并赋予ResourceId,可以使用ResourceTable引用。

resources下另外一大类目录是rawfile目录,这个目录下的文件需要自行创建,并且目录名需要遵循一定的规则,否则无法识别

类型 含义
语言 表示设备使用的语言类型,由2个小写祖母组成。例如zh,en
文字 由一个大写和三个小写组成,例如Hans,Hant(繁体中文)
国家或地区 如CN,GB(英国)
横竖屏 vertical、horizontal
设备类型 car、tv(电视)、wearable
屏幕密度 sdpi: dpi[0, 120]
mdpi: dpi[120, 160]
ldpi: dpi[160, 240]
xldip: dpi[240, 320]
xxldpi: dpi[320, 480]
xxxldpi: [480, 640]

使用要求:

  • 组合顺序为: 语言文字国家或地区-横竖屏-设备类型-屏幕密度
  • 取值范围: 每类限定词取指必须符合条件,否则将无法匹配目录中的资源文件

rawfile目录可以创建多个子目录,并且可以自由创建资源文件,系统会自动根据名字匹配当前使用哪种资源文件。目录中的资源文件会直接打包进入应用,不经过编译,不可以用ResourceTable引用

引用资源的方法

  • 普通资源: ResourceTable.type_name

  • 系统资源: ohos:global.systemres.ReousrceTable.type_name

ResourceManager resourceManager = getResourceManager();
resourceManager.getElement(ResourceTable.String_app_name).getString();
resourceManager.getElement(ResourceTable.Color_red).getColor()

xml文件引用:

  • 资源文件: $type:name

  • 系统文件: $ohos:type:name

<Text obos:text = ""$string.app_anme"/>

rawfile文件引用:制定文件路径和文件名引用

String rawfileUrl = "resources/rawfile/example.js";//引用这个文件
Resource resource = null;
try
{
resource = getResourceManager().getRawFileEntry(rawfileUrl).openRawFile();
InputStreamReader inputStreamReader = new InputStreamReader(resource, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String text = "";
while((text = bufferedReader.readLine()) != null)
{
System.out.println(text);
}
}
catch(Exception e){}

布局

通过xml创建布局

布局在layout下创建,格式为

<?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">

<Text
ohos:id="$+id:text"
ohos:height="match_content"
ohos:width="match_content"
ohos:layout_alignment="horizontal_center"
ohos:text="1"
ohos:text_size="60fp"
/>

<Button
ohos:height="match_content"
ohos:width="match_content"
ohos:text_size="18fp"
ohos:id = "$+id:btn_add"
ohos:layout_alignment="horizontal_center"
ohos:background_element="$graphic:background_ability_main"
ohos:text_color="white"
ohos:padding="15vp"
ohos:text="单击加1"/>

</DirectionalLayout>

宽和高的布局方式有三种

  • 具体的值: 例如10, 10vp

  • match_parent: 表示组件将扩展到父组件允许的最大值

  • match_content: 和控件需要的大笑相适应

加载布局

super.setUIContent(ResourceTable.Layout_ability_main);//加载xml布局
Text text = (Text)findComponentById(ResourceTable.Id_text);
Button button = (Button)findComponentById(ResourceTable.Id_btn_add);

此外,我们还可以读取xml到代码中

(DirectionalLayout)LayoutScatter.getInstance(this).parse(ResourceTable.Layout_layout_toast, null, false);

通过编码创建布局

首先创建一个方向布局

DirectionalLayout layout = new DirectionalLayout(getContext());
layout.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT);
layout.setHeight(ComponentContainer.LayoutConfig.MATCH_PARENT);
layout.setOrientation(Component.VERTICAL);

Button button = new Button(getContext());
button.setWidth(...);
button.setHeight(...);
button.setText("button");
button.setTextSize(50);

layout.addComponent(button);
setUIContent(layout);

常见布局方式

DirectionLayout

DirectionLayout有两种方向: horizontal和vertical,代表横向和纵向布局。如果垂直或横向长度总和超过父元素将会被截取。

DirectionalLayout layout = new DirectionalLayout(getContext());
layout.setOrientation(Component.VERTICAL);

Text text = new Text(getContext());
text.setText("1111111111111111111111111111111111111111111111111111111111");
layout.addComponent(text);
layout.setHeight(ComponentContainer.LayoutConfig.MATCH_CONTENT);
setUIContent(layout);

DependentLayout

在依赖布局中每个组件可以指定相对于其他同级组件或父组件的位置

相对于同级组件的位置有

位置布局 描述
above 同级组件上侧
below
start_of 位于同级组件起始侧
end_of 同级组件结束侧
left_of 同级组件左侧
right_of 同级组件右侧

相对于父组件有

布局 描述
align_parent_left 父组件左侧(还是在父组件内)
align_parent_right
align_parent_start
align_aprent_end
align_parent_end
align_parent_top
align_parent_bottom
center_in_parent
<?xml version="1.0" encoding="utf-8"?>
<DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:orientation="vertical">

<Text
ohos:height="match_content"
ohos:width="match_parent"
ohos:id="$+id:text1"
ohos:text_size="25fp"
ohos:top_margin="15vp"
ohos:left_margin="15vp"
ohos:right_margin="15vp"
ohos:background_element="#ccc"
ohos:text="Title"
ohos:text_weight="1000"
ohos:text_alignment="horizontal_center"/>

<Text
ohos:id="$+id:text2"
ohos:height="120vp"
ohos:width="match_content"
ohos:text_size="10fp"
ohos:background_element="#ccc"
ohos:text="Catalog"
ohos:top_margin="15vp"
ohos:left_margin="15vp"
ohos:right_margin="15vp"
ohos:bottom_margin="15vp"
ohos:align_parent_left="true"
/>

<Text
ohos:height="120vp"
ohos:width="match_parent"
ohos:text_size="25fp"
ohos:background_element="#ccc"
ohos:text="Content"
ohos:top_margin="15vp"
ohos:right_margin="15vp"
ohos:bottom_margin="15vp"
ohos:text_alignment="center"
ohos:below="$id:text1"
ohos:end_of="$id:text2"
ohos:text_font="sans-serif"/>

</DependentLayout>

TableLayout

TableLayout也就是表格布局,它设定几行几列然后向这些行列中填充控件。

如果使用xml需要ohos:row_count设置行的数量和ohos:column_count设置列的数量

<TableLayout
xmlns:ohos = "http://schemas.huawei.com/res/ohos"
obos:height = "match_parent"
ohos:width = "match_parent"
ohos:background_element = "#87CEEB"
ohos:layout_alignment = "horizontal_center"
ohos:row_count = "2"
ohos: column_count = "3"

PositionLayout

位置布局也就是绝对布局,但是这种布局难以维护,一般不使用

StackLayout

<?xml version="1.0" encoding="utf-8"?>
<StackLayout
ohos:id="$+id:stack_layout"
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:orientation="vertical">
<Text
ohos:id="$+id:text_blue"
ohos:height="400vp"
ohos:width="400vp"
ohos:text_alignment="center"
ohos:text_size="24fp"
ohos:text="第一层"
ohos:background_element="#3f56ea"/>

<Text
ohos:id="$+id:text_light_purple"
ohos:height="300vp"
ohos:width="300vp"
ohos:text_alignment="horizontal_center"
ohos:text_size="24fp"
ohos:text="第二层"
ohos:background_element="#00aaee"/>

<Text
ohos:id="$+id:text_orange"
ohos:height="80vp"
ohos:width="80vp"
ohos:text_alignment="center"
ohos:text_size="24fp"
ohos:text="第三层"
ohos:background_element="#00bfc9"/>

<Button
ohos:height="match_content"
ohos:width="match_content"
ohos:id="$+id:button"
ohos:bubble_height="40vp"
ohos:bubble_width="80vp"
ohos:layout_alignment="right"
ohos:background_element="#3399ff"/>
</StackLayout>

如图所示,第一个添加的位于最底层,最后一个添加的位于最上层。并且上一层视图会覆盖下一层。

如果想切换视图,可以使用

textFirst.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
stackLayout.moveChildToFront(component);
}
});

AdaptiveBoxLayout

自适应布局类似于swing中的GridBagLayout,相当于把整个layout划分成若干个块,每个块有若干个控件,只有在前一行填满之后才会开始下一行。它可以很方便的切换竖直方向和垂直方向

  • 每个块的宽度取决于布局的宽度和每行控件数量
  • 每一行的高度有最高控件的高度决定
  • 布局的宽度只能是MATCH_PARENT或固定值

组件

Text

显示字符串的组件,他支持根据文本长度自动调整字体大小和换行,他也可以通过background_element设置背景。常用的背景又文本背景、按钮背景、可以使用xml格式放置在graphic目录下

graphic写法如

<shape xmlns=... ohos:shape="rectangle">
<cornersohos:radius = "20"/> //矩形填充色
<solid ohos:color = "#878787"/> //边框宽度、虚线长度和颜色
<stroke ohos:width="10" ohos:color="red"/> //
</shape>

自动换行和自动调整字体大小效果

ohos:multiple_lines = "true"
ohos:max_text_lines = "2"
ohos:auto_font_size = "true"

跑马灯效果

Text text = (Text)findComponentById(ResourceTable.Id_text);
text.setTruncationMode(Text.TruncationMode.AUTO_SCROLLING);
text.setAutoScrollingCount(Text.AUTO_SCROLLING_FOREVER);
text.startAutoScrolling();//启动跑马灯效果

Button

button是一种特殊的Text,点击后会触发事件。并且他有多种样式,如普通按钮,椭圆按钮,圆形按钮等。这些样式可以通过background_element属性中设置。

如果想要设置按钮为图片,可以将svg转为xml文件,然后再通过ohos:element_left 、ohos:element_right 、 element_top 、 element_buttom来部署图片居于文字的位置

ohos:element_right="$graphic:arrow"

TextField

textField是文本输入框,他可以通过text_input_type来设置不同类型的输入值

  • text_input_type = “pattern_password”: 密码类型
  • “pattern_text”: 文本类型
  • “pattern_number”: 数字类型
  • “pattern_null”: 空类型

一些设置:

  • hint: 提示文字
  • element_cursor_bubble: 光标气泡,如ohos:element_cursor_bubble = "$graphic:ele_cursor_bubble"/>
  • multiple_lines: 多行显示,true或false

Image

Image一般放在resource的media目录下,可以使用图片Image显示本地图片

xml显示

<Image
ohos:id = "$ + id:image"
ohos:width = "match_content"
ohos:height = "match_content"
ohos:layout_alignment = "center"
ohos:image_src = "$media:plant"
/>

代码显示

Image image = new Image(getContext());
image.setPixelMap(ResourceTable.Media_plant);
缩放方式
按比例将原图变换到Image的宽度,居中显示 zoom_center
…,显示在上部 zoom_start
…,显示在Image的下部 zoom_end
不按比例将图片变换到Image大小并显示 stretch
保持原图大小,显示在Image的中心 center
按比例将原图缩小到Image的宽度,将图片的内容完整居中显示 inside
按比例将原图缩放到Image中宽度和高度较大的值,
如当设置的高度值较大时,在垂直方向上完整显示
clip_center

如果想要加载网络图片,步骤如下:

  1. 在config.json中添加

    {
    "module": {
    "reqPermission": [
    {
    "name": "ohos:permission.INTERNET"
    }
    ]
    }
    }
  2. 配置网络访问白名单

    "deviceConfig":{
    "default":{
    "network":{
    "usesCleartext": true,//是否允许明文传输
    "securityConfig": {
    "cleartextPermitted": true,
    "domains":{
    [
    "subDomains": true,
    "name": "blog.51itoto.com"
    ]
    }
    }
    }
    }
    }
  3. 然后使用加载图片的方法

    public void loadNetImageURL()
    {
    String URLImage = "";
    HttpURLConnection connection = null;
    try
    {
    URL URL = new URL(URLImage);
    URLConnection URLConnection = URL.openConnection();
    if(URLConnection instanceof HttpURLConnection)
    {
    connection = (HttpURLConnection)URLConnection;
    }
    if(connection != null)
    {
    connection.connect();
    InputStream inputStream = URLConnection.getInputStream();
    ImageSource imageSource = ImageSource.create(inputStream, new ImageSource.SourceOptions());
    ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
    decodingOptions.desiredPixelFormat = PixelFormat.ARGB_8888;
    PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);
    getUITaskDispatcher().syncDispatch(()->{
    new ToastDialog(MainAbilitySlice.this)
    .setText("333")
    .show();
    img.setPixelMap(pixelMap);
    img.release();
    });
    }
    }
    catch(Exception e)
    {
    System.out.println(e.fillInStackTrace());
    }
    }

    //在onStart中开启新线程方便加载
    img = (Image)findComponentById(ResourceTable.Id_photo);
    new Thread(new Runnable(){
    @Override
    public void run()
    {
    loadNetImageURL();
    }
    }).start();

    TabList

TabList是标签页,他和一些图形框架中不同的是它只有标签页,没有内容区域,内容区域的切换需要我们自己管理

<TabList
ohos:height="36vp"
ohos:width="match_parent"
ohos:id="$+id:tab_list"
ohos:top_margin="10vp"
ohos:tab_margin="26vp"
ohos:tab_length="120vp"
ohos:text_size="36vp"
ohos:layout_alignment="center"
ohos:orientation="horizontal"
ohos:text_alignment="center"
ohos:normal_text_color="#999999"/>

<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text_alignment="center"
ohos:layout_alignment="horizontal_center"
ohos:text_size="60fp"
ohos:top_margin="100vp"
ohos:id="$+id:content"
/>

Text content = (Text)findComponentById(ResourceTable.Id_content);
TabList tabList = (TabList)findComponentById(ResourceTable.Id_tab_list);
tabList.setFixedMode(true);
if(tabList != null)
{
String str[] = {"相册", "视频", "音乐", "电视", "新闻"};
for(int i=0; i<str.length; i++)
{
TabList.Tab tab = tabList.new Tab(getContext());
tab.setText(str[i]);
tabList.addTab(tab);
}
tabList.selectTabAt(1);
content.setText(tabList.getSelectedTab().getText());
}
tabList.addTabSelectedListener(new TabList.TabSelectedListener() {
@Override
public void onSelected(TabList.Tab tab) {

}

@Override
public void onUnselected(TabList.Tab tab) {

}

@Override
public void onReselected(TabList.Tab tab) {

}
});

效果为

Picker

Picker是滑动选择器,允许用户在预定义范围内选择

设置取值范围

picker.setMinValue(0);
picker.setMaxValue(6);

设置显示字符串(默认会显示取值的数字)
picker.setFormat(i->{
String value;//要显示的字符串
switch(i)//第几个序号
{
case ...: value = "...";break;
}
return value;

要响应选择器的变化可以使用

picker.setValueChangedListener((picker1, oldVal, newVal)->{
});

DirectionalLayout layout = new DirectionalLayout(getContext());
layout.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT);
layout.setHeight(ComponentContainer.LayoutConfig.MATCH_PARENT);
layout.setOrientation(Component.VERTICAL);
layout.setAlignment(LayoutAlignment.CENTER);
Picker picker = new Picker(getContext());
DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig();
layoutConfig.alignment = LayoutAlignment.HORIZONTAL_CENTER;
picker.setLayoutConfig(layoutConfig);
picker.setNormalTextSize(50);
picker.setSelectedTextSize(50);
picker.setMinValue(0);//设置最小值
picker.setMaxValue(6);//设置最大值
picker.setFormatter(i->{
String value;
switch (i)
{
case 0:
value = "星期一";break;
case 1:
value = "星期二";break;
case 2:
value = "星期三";break;
case 3:
value = "星期四";break;
case 4:
value = "星期五";break;
case 5:
value = "星期六";break;
case 6:
value = "星期日";break;
default: value = "无";
}
return value;
});
layout.addComponent(picker);
super.setUIContent(layout);

结果为

DatePicker

DatePicker是日期选择器,他会默认选择当前日期

CheckBox

checkBox是复选框,和spring框架中类似

响应是否选中的事件为

checkbox.setCheckedStateChangedListener((component, state)->{//选中或没选中
});

RaidoButton

单选按钮,如果和RaidoContainer使用,可以实现在多个按钮中只能选择一个

ToastDialog

ToastDialog是窗口上方弹出的对话框,可以反馈消息,他会在一段时间后消失,在此期间,还可以操作其他组件。

button.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
DirectionalLayout toastLayout = new DirectionalLayout(getContext());
Text text = new Text(getContext());
text.setText("显示");
text.setTextSize(70);
toastLayout.setAlignment(LayoutAlignment.CENTER);
toastLayout.setHeight(ComponentContainer.LayoutConfig.MATCH_CONTENT);
toastLayout.setWidth(ComponentContainer.LayoutConfig.MATCH_CONTENT);
toastLayout.addComponent(text);
new ToastDialog(getContext())
.setComponent(toastLayout)
.setSize(DirectionalLayout.LayoutConfig.MATCH_CONTENT, DirectionalLayout.LayoutConfig.MATCH_CONTENT)
.setAlignment(LayoutAlignment.CENTER)
.show();
}
});

如图,点击是一个按钮,显示是点击按钮后出现的提示文字

CommonDialog

上面的提示框就是以CommonDialog为基类的,它可以根据需求自定义提示框

  • commonDialog.setContentCustomComponent(rootComponent): 重新设置根控件
  • commonDialog.setTransparent(true); 设置背景透明
  • commonDialog.setAlignment(LayoutAlignment.CENTER)
  • commonDialog.setCornerRadius(15): 设置圆角
  • commonDialog.show(): 显示对话框

ProgressBar

ProgressBar是进度条,他有如下函数

  • setProgressValue(value): 设置当前进度值
  • setMaxValue(maxValue)
  • setMinValue(minValue)
  • enableDividerLines(true): 设置分割条
  • setDividerLinesNumber(5): 将进度条划分成6等份,每隔一段有一个垂直的分隔线

Slider

滑块

ScrollView

ScrollView是滚动组件,它继承自StackLayout,也可以将它作为一种布局使用

一般来讲,先在ScrollLayout下放置一个DirectionLayout,然后再再这个layout下放置组件

函数:

  • scrollView.fluentScrollByY(amount): 平滑滚动若干距离
  • scrollView.fluentScrollYTo(loc): 平滑滚动到某个位置
  • scrollView.setReboundEffect(true): 设置回弹效果
  • scrollView.setMatchViewportEnabled(true): 设置匹配缩放效果

ListContainer

ListContainer也就是列表组件,他可以自定义列表数据的类型和样式

自定义数据

public class SampleItem
{
private String name;
public SampleItem(String name)
{
this.name = name;
}
public String getName()
{
return name;
}

public void setName(String name)
{
this.name = name;
}
}

然后创建一个类继承BaseItemProvider,并且里面包含的数据时SampleItem
public class SampleItemProvider extends BaseItemProvider
{
private List<SampleItem> list;
private AbilitySlice slice;
public SampleItemProvider(List<SampleItem> list, AbilitySlice slice)
{
this.list = list;
this.slice = slice;
}

@Override
public int getCount()
{
return list == null ? 0 : list.size();
}

@Override
public Object getItem(int position)
{
if(list != null && position > 0 && position < list.size())
{
return list.get(position);
}
return null;
}

@Override
public long getItemId(int position)
{
return position;
}

@Override
public Component getComponent(int position, Component convertComponent, ComponentContainer componentContainer)
{
final Component cpt;
if(convertComponent == null)
{
cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_sample, null, false);
}
else
{
cpt = convertComponent;
}
SampleItem sampleItem = list.get(position);
Text text = (Text) cpt.findComponentById(ResourceTable.Id_item_index);
text.setText(sampleItem.getName());
return cpt;
}
}

然后向ListContainer添加数据

@Override
public void onStart(Intent intent)
{
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_page_listcontainer);
ListContainer listContainer = (ListContainer)findComponentById(ReosurceTable.Id_list_container);
List<SampleItem> list = getData();
SampleItemProvider sampleItemProvider = new SampleItemProvider(list, this);
listContainer.setItemProvider(sampleItemProvider);
}

private ArrayList<SampleItem> getData()
{
ArrayList<SampleItem> list = new ArrayList<>();
for(int i=0; i<=8; i++)
{
list.add(new SampleItem("Item" + i));
}
return list;
}

他可以响应单击事件和长按事件:

  • setItemClickedListener
  • setItemLongClickedListener

PageSlider

它也提供页面的切换效果,提供上下及左右滑动的功能

监听器:

  • addPageChangedListener: 可以监视滑动和滑动到页面切换时的事件

想要实现切换效果,可以进行如下步骤:

  1. 添加PageSlider和RadioContainer,设置PageSlider的高度为0vp,权重weight=1,这样pageSlider就会占据除RadioContainer外的全部空间
  2. 创建不同页面的布局文件
  3. 添加页面切换时的数据源
  4. 监听页面切换事件
<?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:background_element="cyan"
ohos:orientation="vertical">
<PageSlider
ohos:height="0vp"
ohos:width="match_parent"
ohos:id="$+id:page_slider"
ohos:background_element="#fff"
ohos:weight="1"/>
<RadioContainer
ohos:height="60vp"
ohos:width="match_parent"
ohos:id="$+id:page_slider_radio"
ohos:alignment="horizontal_center"
ohos:orientation="horizontal">
<RadioButton
ohos:height="match_parent"
ohos:width="match_content"
ohos:text_size="20fp"/>
<RadioButton
ohos:height="match_parent"
ohos:width="match_content"
ohos:text_size="20fp"/>
<RadioButton
ohos:height="match_parent"
ohos:width="match_content"
ohos:text_size="20fp"/>
</RadioContainer>
</DirectionalLayout>

主程序

@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_page_slider);
pageSlider = (PageSlider)findComponentById(ResourceTable.Id_page_slider);
radioContainer = (RadioContainer)findComponentById(ResourceTable.Id_page_slider_radio);
((RadioButton)radioContainer.getComponentAt(0)).setChecked(true);
LayoutScatter scatter = LayoutScatter.getInstance(getContext());
DirectionalLayout page0 = (DirectionalLayout)scatter.parse(ResourceTable.Layout_slider_page0, null, false);
DirectionalLayout page1 = (DirectionalLayout)scatter.parse(ResourceTable.Layout_slider_page1, null, false);
DirectionalLayout page2 = (DirectionalLayout)scatter.parse(ResourceTable.Layout_slider_page2, null, false);
List<Component> pageArr = new ArrayList<>();
pageArr.add(page0);
pageArr.add(page1);
pageArr.add(page2);
pageSlider.setProvider(new PageSliderProvider() {
@Override
public int getCount() {
return pageArr.size();
}

@Override
public Object createPageInContainer(ComponentContainer componentContainer, int i) {
componentContainer.addComponent(pageArr.get(i));
return pageArr.get(i);
}

@Override
public void destroyPageFromContainer(ComponentContainer componentContainer, int i, Object o) {
((PageSlider)componentContainer).removeComponent(pageArr.get(i));
}

@Override
public boolean isPageMatchToObject(Component component, Object o) {
return component == o;
}
});

pageSlider.addPageChangedListener(new PageSlider.PageChangedListener() {
@Override
public void onPageSliding(int i, float v, int i1) {

}

@Override
public void onPageSlideStateChanged(int i) {

}

@Override
public void onPageChosen(int i) {
((RadioButton)radioContainer.getComponentAt(i)).setChecked(true);
}
});

radioContainer.setMarkChangedListener((radioContainer, i) -> new ToastDialog(MainAbilitySlice.this)
.setText(i + "")
.show());
}
}

剪切板

剪切板可以实现两个程序之间的数据共享,使用时需要注意:

  1. 只有在前台获得焦点的应用才有权限读取剪切板,系统默认输入法除外
  2. 写入剪切板的数据不会随应用结束而销毁
  3. 写入剪切板数据会被覆盖
  4. 同一设备内,剪切板单次内容不应超过800kb

接口:

接口 描述
getSystemPastedboard(Context context) 获得剪切板实例
getPasteData() 获得剪切板的数据
hasPasteData() 剪切板是否有数据
setPasteData(PasteData data)
clear()
addPasteDataChangedListener(IPasteDataChangedListener listener)
removePasteDataChangedListener(IPasteDataChangedListenre listener

线程

线程派发

不同的线程有不同的优先级,一般让UI线程有最高的优先级,如果一个任务无需等待结果,则可以使用低优先级。共有三种优先级HIGH、DEFAULT、LOW

  • GlobalTaskDispatcher: 全局并发分发器,他会在程序结束后才销毁,一个应用应该只有一个全局分发器。获得方式为TaskDispatcher dispatcher = getGlobalTaskDispatcher(TaskProiority.DEFAULT);
  • ParallelTaskDispatcher: 他可以随自己需要创建和销毁
    String dispatcherName = "parallelTaskDispatcher";
    TaskDispatcher dispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
  • SerailTaskDispatcher: 串行分发器。所有任务都是按照顺序执行的,但是执行这些任务的线程不一定固定。如果想要并行,则使用 前面两种
    String dispatcherName = "abc";
    TaskDispatcher dispatcher = createSerialTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
  • SpecTaskDispatcher: 专有任务分发器,绑定到专有线程上进行处理。目前专有线程是主线程,UITaskDispatcher和MainTaskDispatcher都是S赔偿TaskDispatcher,并且建议使用UITaskDispatcher.TaskDispatcher dispatcher = getUITaskDispatcher()

任务派发

  • syncDispatch: 同步派发任务,派发任务并在当前线程等待任务完成,返回前线程将被阻塞

     TaskDispatcher dispatcher = getGlobalTaskDispatcher(TaskPriority.DEFUALT);
    globalTaskDispatcher.syncDispatch(new Runnable(){
    @Override
    public void run()
    {
    HiLog.info(label, "sync task1 run");
    }
    });
    HiLog.info(label, "after sync task1");
    globalTaskDispatcher.syncDispatch(new Runnable(){
    @Override
    public void run()
    {
    HiLog.info(label, "sync task2 run");
    }
    });
    HiLog.info(label, "after sync task2");
    执行结果为
    sync task1 run
    after sync task1
    sync task2 run
    sync task2 run

    syncDispatch使用不当可能会导致死锁,例如下列情况会导致死锁

    • 在专有线程上使用该专有线程分发器进行syncDispatch
    • 在串行任务分发器中再次利用同一个分发器分发任务
    • a串行分发器在任务中会用b分发器,b在任务中又使用a
  • asyncDispatch: 异步分发器。派发任务并立刻返回,返回值是一个可用于取消任务的接口
    TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
    Revocable revocable = globalTaskDispatcher.asyncDispatch(new Runnable(){
    @Override
    public void run()
    {
    HiLog.info(label, "async task1 run");
    }
    });
    HiLog.info(label, "after async task1");
    结果为:
    after async task1
    async task1 run
  • delayDispatch: 也是异步执行,但是会在指定时间后才派发到相应任务处理队列中,再等待一段时间后才会执行
    Revocable revocable = globalTaskDispatcher.delayDispatch(new Runnable(){
    @Override
    public void run()
    {
    HiLog.info(label, "delayDispatch task1 run");
    final long actualDelayMs = System.currentTimeMillis() - callTime;
    HiLog.info(label, actualDelayMs);
    }
    }, delayTime);
  • Group: 任务组表示一组任务,由createDispatchGroup创建,他可以同时控制多个任务

    String name = "dispatcher";
    TaskDispatcher dispatcher = createParallelTaskDispatcher(name, TaskPriority.DEFAULT);
    Group group = dispatcher.createDispatcherGroup();
    dispatcher.asyncGroupDispatch(group, new Runnable(){...});

    此外还可以设置屏障,只有任务组中所有任务都执行完成之后才会执行屏障任务,GlobalTaskDispatcher设置屏障无效.

    屏障也有同步屏障syncGroupBarrier和异步屏障asyncDispatcherBarrier

    Group group = dispatcher.createDispatchGroup();
    dispatcher.asyncGroupDispatch(group, new Runnable(){
    @Override
    public void run()
    {
    HiLog.info(label, "task1 is running");
    }
    });
    dispatcher.asyncGroupDispatch(group, new Runnable(){
    @Override
    public void run()
    {
    HiLog.info(label, "task2 is running");
    }
    });
    dispatcher.syncDispatchBarier(new Runnable(){//只有前两任务结束才会执行这个
    @Override
    public void run()
    {
    HiLog.info(label, "barrier");
    }
    });
  • Revocable: 取消任务可以使用revocable.revoke();
  • applyDispatch: 执行多次任务

    final int total = 10;
    //在total减到0才会放开,否则阻塞当前线程
    final CountDownLatch latch = new CountDownLatch(total);
    final ArrayList<Long> indexList = new ArrayList<>(total);
    TaskDispatcher dispatcher = getGlobalTaskDispatcher(taskPriority.DEFAULT);
    dispatcher.applyDispatch((index)->{
    indexList.add(index);
    latch.countDown();
    }, total);
    try
    {
    latch.await();//total没减到0阻塞
    }
    catch(InterruptedException e)
    {
    HiLog.error(label, "latch exception");
    }

    线程间通信

    EventRunner是事件的执行者,每次遇到投递过来的事件时会在新线程进行处理。而EventHandler是事件的派发者,他负责派发事件。一个EventHandler对应一个EventRunner,一个EventRunner可以有多个EventHandler。

EventRunner分为手动和托管两种模式: 托管模式不需要我们调用run()和stop()。他会在线程创建时自动调用run(),并且在没有引用时自动调用stop()

例如:

private class MyEventHandler extends EventHanlder
{
private MyEventHandler(EventRunner runner)
{
super(runner);
}

//如果发过来的是事件在EventRunner会调用这个函数
@Override
public void processEvent(InnerEvent event)
{
super.processEvent(event);
if(event == null)
{
return;
}

int eventId = event.eventId;
switch(eventId)
{
case CASE1:
//操作
break;
...
case CASE2:
//在原线程中执行的方法
if(object instanceof EventRunner)
{
EventRunner runner2 = (EventRunner)object;
EventHandler myHandler2 = new EventHandler(runner2){
@Override
public void processEvent(InnerEvent event)
{
//需要在原线程中执行的任务
}};
int eventId2 = 1;
long param2 = 0L;
Object object2 = null;
InnerEvent event2 = InnerEvent.get(eventId2, param2, object2);
myHandler2.sendEvent(event2);
}
}
}
}

//运行示例
EventRunner runner = EventRunner.create(true);
MyEventHandler myHandler = new MyEventHandler(runner);
int eventId= 0;
long param = 0L;
Object object = null;
InnerEvent event = Innerevent.get(eventId, param, object);
myHandler.sendEvent(event, 0, EventHandler.Priority.MMEDIATE);//0是延时

//或者
Runnable task = new Runnable(){...};
myHandler.postTask(task1, 0, EventHandler.Priority.IMMEDIATE);

网络和设备

网络

访问网络大致流程为:

  1. 调用NetManager.getInstance(Context)获得网络管理的实例对象
  2. 调用NetManager.getDefaultNet()获得默认数据网络
  3. 调用NetHandle.openConnection()打开URL
  4. 通过URL链接实例访问网站
NetManager netManager = NetManager.getInstance(null);
if(!netManager.hasDefaultNet())
{
return;
}
NetHandle netHandle = netManger.getDefaultNet();
NetStatusCallback callback = new NetStatusCallback(){
//网络变化时调用的函数
};
netManger.addDefaultNetStatusCallback(callback);
HttpURLConnection connection = null;
try
{
String urlstr = "https://www.baidu.com/";
URL url = new URL(urlstr);
URLConnection URLConnection = newHandle.openConnection(URL, java.net.Proxy.NO_PROXY);
if(URLConnection instanceof HTTPURLConnection)
{
connection = (HttpURLConnection) URLConnection;
}
connection.setRequestMethod("GET");
connection.connect();
}
catch(IOException e){}
finally{
connection.disconnect();
}

位置

接口 说明
Locator(Context context) 创建Locator
RequestParam(int scenario) 根据定位场景类型创建定位请求的RequestParam
onLocationReport(Location location) 获取定位结果
startLocating(RequestParam request, LocatorCallback callback) 向系统发起定位请求
requestOnce(RequestParam request, LocatorCallback callback) 单次请求
stopLocating(LocatorCallback callback) 结束定位
getCachedLocation() 获取系统缓存的位置信息

过程:

  1. 首先需要获得权限,定位权限有ohos.permission.LOCATIONohos.permission.LOCATION_IN_BACKGROUND
  2. 开启定位并写回调函数

    启动定位函数为locator.startLocating(requestParam, locatorCallback)

    然后我们就可以开始获得定位了,如果我们不需要经常获得,则调用locator.requestOnce(requestParam, locatorCallback).其中requestParam表示定位精度,locatorCallback是回调函数

    locatorCallback如下

    LocatorCallback locatorCallback = new LocatorCallback() {
    @Override
    public void onLocationReport(Location location) {

    }

    @Override
    public void onStatusChanged(int i) {

    }

    @Override
    public void onErrorReport(int i) {

    }
    }

    requestParam构建如:

    RequestParam requestParam = new RequestParam(ReuqestParam.PRIORITY_ACCURACY, 0, 0);//上报最短时间间隔和最短距离间隔
常量 说明
SCENE_NAVIGATION 导航场景,适用于户外设备实时位置
SCENE_TRAJECTORY_TRACKING 轨迹跟踪场景,主要是运动记录
SCENE_CAR_HAILING 打车时定位当前位置
SCENEN_DAILY_LIFE_SERVICE 生活场景,精度较低,只提供大致位置
SCENE_NO_POWER 系统在其他应用启动定位时会顺带发给该程序,不产生额外功耗
PRIORITY_ACCURACY 在室外精确,在室内不精确
PRIORITY_FAST_FIRST_FIX 快速定位,会使用多种技术定位并挑选最好的结果,功耗大
PRIORITY_LOW_POWER 低功耗,主要依赖基站和WLAN、蓝牙定位。定位精度波动较大

地理编码转化为位置信息

地理编码非常精确但并不直观,可以通过编码转化能力转化为位置信息,或者通过地名转化为经纬度。

  1. 获得实例: GeoConvert geoConvert = new GeoConvert();
  2. 获得转化结果: getConvert.getAddressFromLocation(纬度, 经度, 1)或者geoConvert.getAddressFromLocationName("北京大兴国际机场", 1)

视频

播放视频过程

Player mPlayer = new Player(VideoAbilitySlice, this);
private void playLocalFile(Surface surface)
{
try{
RawFileDescriptor fileDescriptor = getResourceManager().getRawFileEntry("resources/rawfile/test.mp4").openRawFileDescriptor();
//如果是网络视频可以用new Source(url)
Source srouce = new Source(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartPosition(), fileDescriptor.getFileSize());
mPlayer.setSource(source);//设置播放源
mPlayer.setVideoSurface(surface);//设置播放界面
mPlayer.setPlayerCallback(new VideoPlayerCallback());
mPlayer.prepare();
mPlayer.play();
catch(Exception e)
{
System.out.println("playURL Exception:" + e.getMessage());
}
}

相机

概念:

  • 物理相机: 物理相机是单个摄像头,用一个物理ID标识
  • 逻辑相机: 逻辑相机是多个相机的组合抽象。拥有大光圈、变焦等功能
  • 帧捕获: 相机启动后捕获图像。可以只捕获一帧(照相),也可以捕获多帧,也可以一直捕获(录像)。

使用流程:

  1. 获取权限:相机相关的权限有
    • ohos.permission.CAMERA: 相机权限,必选
    • ohos.permission.MICROPHONE: 录音权限,在录像时需要
    • ohos.permission.WRITE_USR_STORAGE: 存储权限,保存图像是需要
    • ohos.permission.LOCATION: 保存图像位置时需要
  2. 相机设备创建:

    CamreraKit cameraKit = CameraKit.getInstance(context);
    if(cameraKit == null)
    {
    //相机获取失败
    }

    相机有时候会被其他设备占用导致获取失败,可以使用getCameraIds()获得当前设备可用的逻辑相机ID列表。如果列表为空则代表当前没有可用相机

    当查询到有可用相机后,可以制定获取某个相机

    EventHandler eventHandler = new EventHandler(EventRunner.create("CameraCb");
    cameraKit.createCamera(cameraIds[0], cameraStateCallback, eventHandler)

    获得相机信息的函数

    • getFacingType(): 获得相机朝向信息
    • getDeviceLinkType(String pyhsicalId): 获得物理相机连接方式
    • getLogicalId(): 获得逻辑相机Id
    • getPhysicalIdList(): 获得物理相机Id列表
    • getSupportedSizes(int format): 根据格式查询输出图像分辨率列表
  3. 相机设备配置
    相机配置主要是设置预览,拍照、录像所用到的Surface,如果没有配置则这些功能无法使用。

    此外,为了获得拍照结果数据,需要配置setFrameStateCallback(FrameStateCallback, EventHandler)设置帧回调

    在前面创建相机的回调函数中
    cameraConfigBuilder = camera.getCameraConfigBuilder();
    cameraConfigBuilder.addSurface(previewSurface);
    cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
    cameraConfigBuilder.setFrameStateCallback(frameStateCallbackImpl, handler);
    try
    {
    camera.configure(cameraConfigBuilder.build());
    }
    catch(IllegalArgumentException e)
    {
    HiLog.error(LABEL, "State Exception");
    }
  4. 相机帧捕获
    使用相机的一些操作类开始进行捕获

    • triggerSingleCapture(FrameConfig frameConfig): 启动单帧捕获
    • triggerMultiCapture(List\ frameConfigs): 启动多帧捕获
    • flushCaptures():停止并清除捕获
    • triggerLoppingCapture(FrameConfig frameConfig): 启动循环捕获(录像)

声频

使用声频采集之前,需要申请话筒权限ohos.permission.MICROPHONE


public class PlaySoundUtil
{
private AudioStreamInfo audioStreamInfo = null;
private AudioRendererInfo audioRendererInfo = null;
private AudioRenderer.PlayMode playMode = AudioRenderer.PlayMode.MODE_STREAM;
private AudioRenderer audioRenderer = null;
private AudioManager audioMnaager = null;
private InputStream soundInputStream = null;
private String fileNmae = null;

public PlaySoundUtil()throws IOException
{
audioStreamInfo = new AudioStreamInfo.Builder().sampleRate(44100)
.audioStreamFlag(AudioStreamInfo.AudioStreamFlag.AUDIO_STREAM_FLAG_MY_DUCK);//混音
.channelMask(AudioStreamInfo.ChannelMask.CHANNEL_OUT_STEREO);//双声道
.streamUsage(AudioStreamInfo.StreamUsage.STREAM_USAGE_MEDIA);//媒体类声频
.build();
audioRendererInfo = new AudioRendererInfo.Builder().audioStreamInfo(audioStreamInfo)
.audioStreamOutputFlag(AudioRendererInfo.AudioStreamOutputFlag.AUDIO_STREAM_OUTPUT_FLAG_DIRECT_PCM);//pcm格式输出流
.bufferSizeInBytes(1024)
.isOffload(false)//表示分段传输buffer并播放,true表示将整个声频一次传输
.build();

audioRenderer = new AudioRenderer(audioRendererInfo.playMode);
}

public void loadSound(String fileName)
{
this.fileName = fileName;
String filePath = String.format("assets/entry/resources/rawfile/music/%s", fileName);
soundInputStream = this.getClass().getClassLoader().getResourceAsStream(filePath);
int bufSize = audioRenderer.getBufferFrameSize();
Byte[] buffer = new Byte[1024];
int len;
try
{
audioRenderer.start();
while(len = soundInputStream.read(buffer, 0, buffer, length)) != -1)
{
audioRenderer.write(buffer, 0, buffer.length);
}
soundInputStream.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}

public AudioRenderer getAudioRenderer()
{
return this.audioRenderer;
}

private TaskDispatcher globalTaskDispatcher = null;

public void asyncLoadSound(String fileName, AbilityContext cxt)
{
globalTaskDispatcher = cxt.getGlobalTaskDispatcher(TaskPriority.DEFAULT);
if(audioRenderer.getState() == AudioRenderer.State.STATE_PLAYING)
{
audioRenderer.pause();
return;
}
Revocable revocable = globalTaskDispatcher.asyncDispatch(new Runnable(){
@Override
public void run()
{
laodSound(fileName);
}
});
}
}