一、一次开发多段部署
主要实现功能:
多个不同设备的应用,动态识别屏幕大小(响应布局),动态渲染不同效果。用 媒体查询 技术其是一点变化就会触发,触发要判断一下是否改变设备,是符合范围的要这种,不符合的要另一种。
代码:
BreakpointConstants.ets:
import BreakpointType from '../bean/BreanpointType';
export default class BreakpointConstants {
/**
* 小屏幕设备的 Breakpoints 标记.
*/
static readonly BREAKPOINT_SM: string = 'sm';
/**
* 中等屏幕设备的 Breakpoints 标记.
*/
static readonly BREAKPOINT_MD: string = 'md';
/**
* 大屏幕设备的 Breakpoints 标记.
*/
static readonly BREAKPOINT_LG: string = 'lg';
/**
* 当前设备的 breakpoints 存储key
*/
static readonly CURRENT_BREAKPOINT: string = 'currentBreakpoint';
/**
* 小屏幕设备宽度范围.
*/
static readonly RANGE_SM: string = '(320vp<=width<600vp)';
/**
* 中屏幕设备宽度范围.
*/
static readonly RANGE_MD: string = '(600vp<=width<840vp)';
/**
* 大屏幕设备宽度范围.
*/
static readonly RANGE_LG: string = '(840vp<=width)';
//用的比较多,定义为常量
static readonly BAR_POSITION: BreakpointType<BarPosition> = new BreakpointType({
sm: BarPosition.End,
md: BarPosition.Start,
lg: BarPosition.Start,
})
}
BreakpointSystem.ets:
import mediaQuery from '@ohos.mediaquery'
import BreakpointConstants from '../constants/BreakpointConstants'
export default class BreakpointSystem{
//定义监听器
//ets才能导入
private smListener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_SM)
private mdListener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_MD)
private lgListener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_LG)
//监听器的回调函数
smListenerCallback(result: mediaQuery.MediaQueryResult){
if(result.matches){
//匹配到了就存储
this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_SM)
}
}
mdListenerCallback(result: mediaQuery.MediaQueryResult){
if(result.matches){
this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_MD)
}
}
lgListenerCallback(result: mediaQuery.MediaQueryResult){
if(result.matches){
this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_LG)
}
}
//存储到全局变量的函数
updateCurrentBreakpoint(breakpoint: string){
AppStorage.SetOrCreate(BreakpointConstants.CURRENT_BREAKPOINT, breakpoint)
}
//注册回调函数
register(){
this.smListener.on('change', this.smListenerCallback.bind(this))
this.mdListener.on('change', this.mdListenerCallback.bind(this))
this.lgListener.on('change', this.lgListenerCallback.bind(this))
}
//在系统退出时,取消回调
unregister(){
this.smListener.off('change', this.smListenerCallback.bind(this))
this.mdListener.off('change', this.mdListenerCallback.bind(this))
this.lgListener.off('change', this.lgListenerCallback.bind(this))
}
}
在Index中,做初始化:
//再次进行初始化,欢迎界面之后
//因为不是静态的,要new出来
private breakpointSystem:BreakpointSystem=new BreakpointSystem()
//storageProp必须初始化
@StorageProp('currentBreakpoint') currentBreakpoint: string=BreakpointConstants.BREAKPOINT_SM
aboutToAppear(){
//多段部署
this.breakpointSystem.register()//完成回调函数注册
}
aboutToDisappear(){
this.breakpointSystem.unregister()//完成回调函数注册
}
//优雅化的选择
declare interface BreakpointTypeOptions<T>{
//泛型
sm?:T,
md?:T,
lg?:T
}
//这个类帮助我们从对象中取值
export default class BreakpointType<T>{
options: BreakpointTypeOptions<T>//对象
//构造函数
constructor(options: BreakpointTypeOptions<T>) {
//类内部拿到所要传的对象了
this.options = options
}
getValue(breakpoint: string): T{
return this.options[breakpoint]
}
}
Tabs:
.vertical(new BreakpointType({
sm:false,
md:true,
lg:true
}).getValue(this.currentBreakpoint))//布局模式
swiper:
.displayCount(new BreakpointType({
sm: 1,
md: 1,
lg: 2
}).getValue(this.currentBreakpoint))
效果:
相关知识:
1.媒体查询:
媒体查询条件
媒体查询条件由媒体类型,逻辑操作符,媒体特征组成,其中媒体类型可省略,逻辑操作符用于连接不同媒体类型与媒体特征,其中,媒体特征要使用()包裹且可以有多个。具体规则如下:
语法规则
[media-type] [and|not|only] [(media-feature)]
例如:
screen and (round-screen: true)
:当设备屏幕是圆形时条件成立。
(max-height: 800)
:当高度小于等于800时条件成立。
(height <= 800)
:当高度小于等于800时条件成立。
screen and (device-type: tv) or (resolution < 2)
:包含多个媒体特征的多条件复杂语句查询,当设备类型为tv或设备分辨率小于2时条件成立。
媒体类型(media-type)
类型 | 说明 |
---|---|
screen | 按屏幕相关参数进行媒体查询。 |
媒体逻辑操作(and|or|not|only)
媒体逻辑操作符:and、or、not、only用于构成复杂媒体查询,也可以通过comma(, )将其组合起来,详细解释说明如下表。
表1 媒体逻辑操作符
类型 | 说明 |
---|---|
and | 将多个媒体特征(Media Feature)以“与”的方式连接成一个媒体查询,只有当所有媒体特征都为true,查询条件成立。另外,它还可以将媒体类型和媒体功能结合起来。 例如:screen and (device-type: wearable) and (max-height: 600) 表示当设备类型是智能穿戴且应用的最大高度小于等于600个像素单位时成立。 |
or | 将多个媒体特征以“或”的方式连接成一个媒体查询,如果存在结果为true的媒体特征,则查询条件成立。 例如:screen and (max-height: 1000) or (round-screen:true)表示当应用高度小于等于1000个像素单位或者设备屏幕是圆形时,条件成立。 |
not | 取反媒体查询结果,媒体查询结果不成立时返回true,否则返回false。 例如:not screen and (min-height: 50) and (max-height: 600) 表示当应用高度小于50个像素单位或者大于600个像素单位时成立。 使用not运算符时必须指定媒体类型。 |
only | 当整个表达式都匹配时,才会应用选择的样式,可以应用在防止某些较早的版本的浏览器上产生歧义的场景。一些较早版本的浏览器对于同时包含了媒体类型和媒体特征的语句会产生歧义,比如: screen and (min-height: 50) 老版本浏览器会将这句话理解成screen,从而导致仅仅匹配到媒体类型(screen),就应用了指定样式,使用only可以很好地规避这种情况。 使用only时必须指定媒体类型。 |
, (comma) | 将多个媒体特征以“或”的方式连接成一个媒体查询,如果存在结果为true的媒体特征,则查询条件成立。其效果等同于or运算符。 例如:screen and (min-height: 1000), (round-screen:true) 表示当应用高度大于等于1000个像素单位或者设备屏幕是圆形时,条件成立。 |
在MediaQuery Level 4中引入了范围查询,使其能够使用max-,min-的同时,也支持了< =,> =,< ,> 操作符。
表2 媒体逻辑范围操作符
类型 | 说明 |
---|---|
< = | 小于等于,例如:screen and (height < = 50)。 |
> = | 大于等于,例如:screen and (height > = 600)。 |
< | 小于,例如:screen and (height < 50)。 |
> | 大于,例如:screen and (height > 600)。 |
媒体特征(media-feature)
类型 | 说明 |
---|---|
height | 应用页面显示区域的高度。 |
min-height | 应用页面显示区域的最小高度。 |
max-height | 应用页面显示区域的最大高度。 |
width | 应用页面显示区域的宽度。 |
min-width | 应用页面显示区域的最小宽度。 |
max-width | 应用页面显示区域的最大宽度。 |
resolution | 设备的分辨率,支持dpi,dppx和dpcm单位。其中: - dpi表示每英寸中物理像素个数,1dpi≈0.39dpcm; - dpcm表示每厘米上的物理像素个数,1dpcm ≈ 2.54dpi; - dppx表示每个px中的物理像素数(此单位按96px=1英寸为基准,与页面中的px单位计算方式不同),1dppx = 96dpi。 |
min-resolution | 设备的最小分辨率。 |
max-resolution | 设备的最大分辨率。 |
orientation | 屏幕的方向。 可选值: - orientation: portrait(设备竖屏) - orientation: landscape(设备横屏) |
device-height | 设备的高度。 |
min-device-height | 设备的最小高度。 |
max-device-height | 设备的最大高度。 |
device-width | 设备的宽度。 |
device-type | 设备的类型。 可选值: - default |
min-device-width | 设备的最小宽度。 |
max-device-width | 设备的最大宽度。 |
round-screen | 屏幕类型,圆形屏幕为true, 非圆形屏幕为 false。 |
dark-mode | 系统为深色模式时为true,否则为false。 |
2.declare
3.static readonly修饰符
初始化时机:运行时,是两个关键字的组合
值状态:允许修改,声明时赋值或者静态构造函数中赋值
修饰范围:字段
主要应用场景:第一,和const相比,它主要应用于引用性变量;而const,只能用于string类型的引用性变量;第二,类的只读公共属性
总结:
const是静态的、编译期变量,只能在声明变量的时候赋值。
readonly是运行时变量,可以在声明的时候或在构造函数内赋值。
static readonly变量就变成了静态的、编译期变量。只能静态构造函数中进行初始化。
同时static readonly可以理解为是最简单的一种单例模式实现方式。
const是静态常量,readonly是动态常量.const高效,readonly灵活!但实际开发中我们经常用static readonly 来代替const, 以平衡const在灵活性上的不足.
二、记录项
主要实现功能:
在饮食记录列表中点击添加,会出现各种食物,我们要去添加记录项,该记录项所用的是一些食物或运动的模型,我们要去设计,在这之前还应该有分类的模型。
代码:
ItemCategory.ets
/**
* 记录项类型
*/
export default class ItemCategory{
/**
* 类型id
*/
id: number
/**
* 类型名称
*/
name: ResourceStr
constructor(id: number, name: ResourceStr) {
this.id = id
this.name = name
}
}
RecordItem.ets
/**
* 饮食记录中的记录项,可以是食物或运动
*/
//数据模型只提供各式,不提供数据
export default class RecordItem{
//id
id: number
//* 名称
name: ResourceStr
//图片
image: ResourceStr
//分类id
categoryId: number
//包含的卡路里
calorie: number
//单位
unit: ResourceStr
//碳水含量,单位(克)
carbon: number
//蛋白质含量,单位(克)
protein: number
//脂肪含量,单位(克)
fat: number
constructor(id: number, name: ResourceStr, image: ResourceStr,
categoryId: number, unit: ResourceStr, calorie: number,
carbon: number = 0, protein: number = 0, fat: number = 0) {//设置营养素信息0,运动没有,可不传
this.id = id
this.name = name
this.image = image
this.categoryId = categoryId
this.unit = unit
this.calorie = calorie
this.protein = protein
this.fat = fat
this.carbon = carbon
}
}
ItemCategoryModel.ets
//记录想类型的操作接口
import ItemCategory from '../viewmodel/ItemCategory'
/**
* 食物类型的枚举
*/
enum FoodCategoryEnum{//因为食物就对应那几只,数组默认值0~
//只是声明有这几种类型,但不是真正的类型
/**
* 主食
*/
STAPLE,
/**
* 蔬果
*/
FRUIT,
/**
* 肉蛋奶
*/
MEAT,
/**
* 坚果
*/
NUT,
/**
* 其它
*/
OTHER,
}//起的名字
/**
* 食物类型数组
*/
let FoodCategories = [
//食物类型
new ItemCategory(0, $r('app.string.staple')),//名称用Resourse语法读取string,中英文都有
new ItemCategory(1, $r('app.string.fruit')),
new ItemCategory(2, $r('app.string.meat')),
new ItemCategory(3, $r('app.string.nut')),
new ItemCategory(4, $r('app.string.other_type')),
]
//可以拿枚举项值做脚标获取食物的对应类型
/**
* 运动类型枚举
*/
enum WorkoutCategoryEnum {
/**
* 走路
*/
WALKING,
/**
* 跑步
*/
RUNNING,
/**
* 骑行
*/
RIDING,
/**
* 跳操
*/
AEROBICS,
/**
* 游泳
*/
SWIMMING,
/**
* 打球
*/
BALLGAME,
/**
* 力量训练
*/
STRENGTH
}
/**
* 真正的运动类型数组
*/
let WorkoutCategories = [
new ItemCategory(0, $r('app.string.walking_type')),
new ItemCategory(1, $r('app.string.running')),
new ItemCategory(2, $r('app.string.riding')),
new ItemCategory(3, $r('app.string.aerobics')),
new ItemCategory(4, $r('app.string.swimming')),
new ItemCategory(5, $r('app.string.ballgame')),
new ItemCategory(6, $r('app.string.strength')),
]
export {FoodCategories , WorkoutCategories , FoodCategoryEnum, WorkoutCategoryEnum}//四个都导出可以进行使用
ItemModel.ets
//记录项的操作接口
import RecordItem from '../viewmodel/RecordItem'
import ItemCategory from '../viewmodel/ItemCategory'
import { FoodCategories, FoodCategoryEnum, WorkoutCategories, WorkoutCategoryEnum } from './ItemCategoryModel'
import GroupInfo from '../viewmodel/GroupInfo'
//这样定义不需要服务端做查询
const foods: RecordItem[] = [
//类型填的对应的枚举
new RecordItem(0, '米饭',$r('app.media.rice'),FoodCategoryEnum.STAPLE, '碗', 209, 46.6, 4.7, 0.5),
new RecordItem(1, '馒头',$r('app.media.steamed_bun'),FoodCategoryEnum.STAPLE, '个', 114, 24.0, 3.6, 0.6),
new RecordItem(2, '面包',$r('app.media.bun'),FoodCategoryEnum.STAPLE, '个', 188, 35.2, 5.0, 3.1),
new RecordItem(3, '全麦吐司',$r('app.media.toast'),FoodCategoryEnum.STAPLE, '片', 91, 15.5, 4.4, 1.3),
new RecordItem(4, '紫薯',$r('app.media.purple_potato'),FoodCategoryEnum.STAPLE, '个', 163, 42.0, 1.6, 0.4),
new RecordItem(5, '煮玉米',$r('app.media.corn'),FoodCategoryEnum.STAPLE, '根', 111, 22.6, 4.0, 1.2),
new RecordItem(6, '黄瓜',$r('app.media.cucumber'),FoodCategoryEnum.FRUIT, '根', 29, 5.3, 1.5, 0.4),
new RecordItem(7, '蓝莓',$r('app.media.blueberry'),FoodCategoryEnum.FRUIT, '盒', 71, 18.1, 0.9, 0.4),
new RecordItem(8, '草莓',$r('app.media.strawberry'),FoodCategoryEnum.FRUIT, '颗', 14, 3.1, 0.4, 0.1),
new RecordItem(9, '火龙果',$r('app.media.pitaya'),FoodCategoryEnum.FRUIT, '个', 100, 24.6, 2.2, 0.5),
new RecordItem(10, '奇异果',$r('app.media.kiwi'),FoodCategoryEnum.FRUIT, '个', 25, 8.4, 0.5, 0.3),
new RecordItem(11, '煮鸡蛋',$r('app.media.egg'),FoodCategoryEnum.MEAT, '个', 74, 0.1, 6.2, 5.4),
new RecordItem(12, '煮鸡胸肉',$r('app.media.chicken_breast'),FoodCategoryEnum.MEAT, '克', 1.15, 0.011, 0.236, 0.018),
new RecordItem(13, '煮鸡腿肉',$r('app.media.chicken_leg'),FoodCategoryEnum.MEAT, '克', 1.87, 0.0, 0.243, 0.092),
new RecordItem(14, '牛肉',$r('app.media.beef'),FoodCategoryEnum.MEAT, '克', 1.22, 0.0, 0.23, 0.033),
new RecordItem(15, '鱼肉',$r("app.media.fish"),FoodCategoryEnum.MEAT, '克', 1.04, 0.0, 0.206, 0.024),
new RecordItem(16, '牛奶',$r("app.media.milk"),FoodCategoryEnum.MEAT, '毫升', 0.66, 0.05, 0.03, 0.038),
new RecordItem(17, '酸奶',$r("app.media.yogurt"),FoodCategoryEnum.MEAT, '毫升', 0.7, 0.10, 0.032, 0.019),
new RecordItem(18, '核桃',$r("app.media.walnut"),FoodCategoryEnum.NUT, '颗', 42, 1.2, 1.0, 3.8),
new RecordItem(19, '花生',$r("app.media.peanut"),FoodCategoryEnum.NUT, '克', 3.13, 0.13, 0.12, 0.254),
new RecordItem(20, '腰果',$r("app.media.cashew"),FoodCategoryEnum.NUT, '克', 5.59, 0.416, 0.173, 0.367),
new RecordItem(21, '无糖拿铁',$r("app.media.coffee"),FoodCategoryEnum.OTHER, '毫升', 0.43, 0.044, 0.028, 0.016),
new RecordItem(22, '豆浆',$r("app.media.soybean_milk"),FoodCategoryEnum.OTHER, '毫升', 0.31, 0.012, 0.030, 0.016),
]
const workouts: RecordItem[] = [
new RecordItem(10000, '散步',$r('app.media.ic_walk'), WorkoutCategoryEnum.WALKING, '小时', 111),
new RecordItem(10001, '快走',$r('app.media.ic_walk'), WorkoutCategoryEnum.WALKING, '小时', 343),
new RecordItem(10002, '慢跑',$r('app.media.ic_running'), WorkoutCategoryEnum.RUNNING, '小时', 472),
new RecordItem(10003, '快跑',$r('app.media.ic_running'), WorkoutCategoryEnum.RUNNING, '小时', 652),
new RecordItem(10004, '自行车',$r('app.media.ic_ridding'), WorkoutCategoryEnum.RIDING, '小时', 497),
new RecordItem(10005, '动感单车',$r('app.media.ic_ridding'), WorkoutCategoryEnum.RIDING, '小时', 587),
new RecordItem(10006, '瑜伽',$r('app.media.ic_aerobics'), WorkoutCategoryEnum.AEROBICS, '小时', 172),
new RecordItem(10007, '健身操',$r('app.media.ic_aerobics'), WorkoutCategoryEnum.AEROBICS, '小时', 429),
new RecordItem(10008, '游泳',$r('app.media.ic_swimming'), WorkoutCategoryEnum.SWIMMING, '小时', 472),
new RecordItem(10009, '冲浪',$r('app.media.ic_swimming'), WorkoutCategoryEnum.SWIMMING, '小时', 429),
new RecordItem(10010, '篮球',$r('app.media.ic_basketball'), WorkoutCategoryEnum.BALLGAME, '小时', 472),
new RecordItem(10011, '足球',$r('app.media.ic_football'), WorkoutCategoryEnum.BALLGAME, '小时', 515),
new RecordItem(10012, '排球',$r("app.media.ic_volleyball"), WorkoutCategoryEnum.BALLGAME, '小时', 403),
new RecordItem(10013, '羽毛球',$r("app.media.ic_badminton"), WorkoutCategoryEnum.BALLGAME, '小时', 386),
new RecordItem(10014, '乒乓球',$r("app.media.ic_table_tennis"), WorkoutCategoryEnum.BALLGAME, '小时', 257),
new RecordItem(10015, '哑铃飞鸟',$r("app.media.ic_dumbbell"), WorkoutCategoryEnum.STRENGTH, '小时', 343),
new RecordItem(10016, '哑铃卧推',$r("app.media.ic_dumbbell"), WorkoutCategoryEnum.STRENGTH, '小时', 429),
new RecordItem(10017, '仰卧起坐',$r("app.media.ic_sit_up"), WorkoutCategoryEnum.STRENGTH, '小时', 515),
]
//渲染,需要我们提供查询的接口,提供出去
class ItemModel{
//什么样的接口与页面的需求有关
//两个接口,一个所有展示出来,另一个根据类型展示出来
getById(id: number, isFood: boolean = true){
return isFood ? foods[id] : workouts[id - 10000]//id与数组脚标一致
}
//查询全部的食物或者是运动
list(isFood: boolean = true): RecordItem[]{
return isFood ? foods : workouts
}
//详细的分类
listItemGroupByCategory(isFood: boolean = true){
// 1.判断要处理的是食物还是运动
let categories = isFood ? FoodCategories : WorkoutCategories
let items = isFood ? foods: workouts
// 2.创建空的分组
let groups = categories.map(itemCategory => new GroupInfo(itemCategory, []))//map做映射,把当前数组转成另一个数组Item转成Group的数组
// 3.遍历记录项列表,将食物添加到对应的分组
items.forEach(item => groups[item.categoryId].items.push(item))//通过类型枚举脚标,找到组。.是到这一组的Items,放进去
// 4.返回结果
return groups
}
}
let itemModel = new ItemModel()
export default itemModel as ItemModel
效果:
相关知识:
枚举:
定义一个枚举类型,需要使用 enum 关键字,后面跟着枚举类型的名称,以及用大括号 {} 括起来的一组枚举常量。每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。
枚举语法定义格式为:
enum 枚举名 {枚举元素1,枚举元素2,……};
总结:
一、一次开发多段部署
将状态条件统一传到全局中,AppStore.SetOrCreate键值存储,用@StorageProp获取值。
断点习惯命名方式,由于多段部署,bar的位置不在写死,是否将布局设置为垂直方向。如果当前屏幕是中等(md)或大(lg)尺寸,那么将应用垂直布局,因为md
和lg
的值都被设置为true
。如果是小屏幕(sm),则不会应用垂直布局,因为sm
的值设置为false
。
二、记录项
代构建了一个清晰、模块化的数据管理系统,用于处理食品和运动的记录项,支持基本的数据操作和复杂的数据展示需求。