一、页面级一多
1.自适应布局
- flex自动换行wrap(FlexWrap)、layoutWeight、justifyContext(FlexAlign)、alignItems(VerticalAlign)、
displayPriority显示优先级 - flexGrow(1)一般放在中间 flexShrink(1)Flex时默认1 拉伸能力指的是容器尺寸发生变化时:将变化的空间,分配给容器内的【指定区域】
- aspectRatio = width/height 等等这些都属于自适应布局
2.响应式布局(重点)
① 全局断点
typescript
export default class EntryAbility extends UIAbility {
private curBp: string = ''
// 目标页面
private targetPage: string = ''
// 根据当前窗口尺寸更新断点
private updateBreakpoint(windowWidth: number): void {
// 将长度的单位由px换算为vp
let windowWidthVp = px2vp(windowWidth)
let newBp: string = ''
if (windowWidthVp < 320) {
newBp = BreakpointConstants.XS
} else if (windowWidthVp < 600) {
newBp = BreakpointConstants.SM
} else if (windowWidthVp < 840) {
newBp = BreakpointConstants.MD
} else {
newBp = BreakpointConstants.LG
}
if (this.curBp !== newBp) {
this.curBp = newBp
// 使用状态变量记录当前断点值
AppStorage.setOrCreate(BreakpointConstants.BREAK_POINT_KEY, this.curBp)
}
}
...
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.getMainWindow()
.then((windowObj) => {
// 获取应用启动时的窗口尺寸
this.updateBreakpoint(windowObj.getWindowProperties()
.windowRect
.width)
// 注册回调函数,监听窗口尺寸变化
windowObj.on('windowSizeChange', (windowSize) => {
this.updateBreakpoint(windowSize.width)
})
});
- 核心:在
onWindowStageCreate中监听windowSizeChange窗口变化 windowStage.getMainWindow().then( windowObj.on('w..',s=>..s.width)
② 媒体查询
javascript
const XS:mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(0vp<=width<320vp)')
const SM:mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(320vp<=width<600vp)')
aboutToAppear(){
XS.on('change',(res:mediaquery.MediaQueryResult)=>{
//打印res,执行逻辑
})
SM.on('change',(res:mediaquery.MediaQueryResult)=>{
//打印res,执行逻辑
})
}
aboutToDisappear(){
//移除监听 避免性能浪费
XS.off('change')
SM.off('change')
}
③ 栅格布局
GridRow({columns:{sm:4...}})列数{ForEach..GridCol({span:2})}占2列
④横竖屏
"orientation":"auto_rotation_unspecified" //受开关控制的自动旋转模式src/main/module.json5
二、功能级一多
- 方法一:使用
canIUse接口判断设备是否支持某系统能力
scss
if(canIUse(系统能力)){
//正常调用
}
- 方法二:通过import动态导入,配合try/catch
javascript
import controller from '@kit.ConnectivityKit'
try{
controller.enableNfc()
}catch(busiError){
//打印
}
三、工程级一多
重点 : 若一个 HAR(2MB)被 3 个 HAP 引用,总冗余体积为 2MB × 3 = 6MB 。 若项目中存在 10 个此类 HAR,冗余体积可达 20MB ,占整体包体积的 10%-20%。所以使用 HSP(动态共享包)替代 HAR,将静态拷贝改为动态加载,冗余体积减少 80%-90%。(除非仓库需要HAR包)
四、LazyForEach
1. 首先我们测试一下LazyForEach和ForEach在表现上有哪些区别
-
- forEach 把数据全部都渲染出来,用户看不见的可视区域之外 也会渲染 数据量多 卡 内存占用大
-
- LazyForEach 懒加载 , 只会渲染 可视区域之内的组件,之外不会渲染
-
- 使用性能调试工具
profiler内存监听 工具 数据76条的时候 内存占用 1.2G 卡
- 使用性能调试工具
-
- LazyForEach 数据76条 内存占用 300M 流畅
2. 封装IDataSource接口实例
kotlin
export class LazyForEachDataSource <T> implements IDataSource {
private listeners : DataChangeListener [] = [];
// 数据源
private dataArray : T[] = [];
// 返回数据的长度
public totalCount (): number {
return this . dataArray . length ; }
// 返回数组指定指定索引的数据
public getData ( index: number ) {
return this . dataArray [index]; }
// 给数组增加一条数据
public pushData ( data : T): void {
this . dataArray . push (data); this . notifyDataAdd ( this . dataArray . length - 1 ); }
// 批量设置数组
public setArray ( arr: T[] ) {
this . dataArray = arr // 通知UI更新
this . notifyDataReload () }
// 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
registerDataChangeListener ( listener : DataChangeListener ): void {
if ( this . listeners . indexOf (listener) < 0 ) {
console . info ( 'add listener' );
this . listeners . push (listener); } }
// 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
unregisterDataChangeListener ( listener : DataChangeListener ): void {
const pos = this . listeners . indexOf (listener);
if (pos >= 0 ) { console . info ( 'remove listener' );
this . listeners . splice (pos, 1 ); } }
// 数据改变, 通知UI更新 所有数据
notifyDataReload (): void {
this . listeners . forEach ( listener => { listener. onDataReloaded (); }) }
// 添加数据了 顺便帮你更新
notifyDataAdd ( index : number ): void {
this . listeners . forEach ( listener => { listener. onDataAdd (index);
// 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]); }) }
// 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
notifyDataChange ( index : number ): void {
this . listeners . forEach ( listener => { listener. onDataChange (index);
// 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]); }) }
// 通知LazyForEach组件需要在index对应索引处删除该子组件
notifyDataDelete ( index : number ): void {
this . listeners . forEach ( listener => { listener. onDataDelete (index);
// 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]); }) }
// 通知LazyForEach组件将from索引和to索引处的子组件进行交换
notifyDataMove ( from : number , to : number ): void {
this . listeners . forEach ( listener => { listener. onDataMove ( from , to);
// 写法2:listener.onDatasetChange(
// [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]); }) }
notifyDatasetChange ( operations : DataOperation []): void { this . listeners . forEach ( listener => { listener. onDatasetChange (operations); }) } }
- 然后直接使用瀑布流渲染
typescript
RecommendData:LazyForEachDataSource<item> = new LazyForEachDataSource
...
//通过接口获得数据res
this.RecommendData.setArray(res)
...
WaterFlow(){
LazyForEach(this.RecommendData,(item:item,index:number)=>{
FlowItem(){
//放自定义组件
}},(item:item,index:number)=>item.id)
}
.cachedCount(6)
3. 实现原理:DataChangeListener数据变化监听器
-
LazyForEach接收数据源 不再是普通的数组, 而是一个 继承了IDataSource接口的实例
-
- 真正数据/数组 存放在 实例的成员上
-
- 除此之外还重要,还有很重要的成员 - 数据变化监听器
DataChangeListener
- 除此之外还重要,还有很重要的成员 - 数据变化监听器
-
- 要操作数据的时候 直接操作真正的数据源 -> 主动调用 数据变化监听器的一些方法(
add/delete/move)
- 要操作数据的时候 直接操作真正的数据源 -> 主动调用 数据变化监听器的一些方法(
五、Account Kit华为一键登录
1. 四签名、添加公钥指纹、Client ID、scope权限申请
- 这个需要了解的可以查看我之前的文章,有详细说明
- scope权限申请,如下图所示
2. 实现核心流程图
-
- 配置证书及权限
-
- 调用华为登录的 api 拉起登录界面
-
- 获取登录成功之后的 code(码)
-
- 提交给应用的后端接口
-
- 后端会将 code 提交给【华为服务器】验真
-
- 成功返回用户信息
-
- 本地拿到用户信息保存即可
3. 封装华为登录,核心API:authentication
javascript
import { authentication } from '@kit.AccountKit'
class HuaweiAuthPlugin {
async requestAuth() {
// 1. 创建一个Account Kit授权请求对象,可通过返回值设置请求参数。
const huaweiIdProvider = new authentication.HuaweiIDProvider()
const authCreateRequest = huaweiIdProvider.createAuthorizationWithHuaweiIDRequest()
// 2. 添加请求参数
authCreateRequest.scopes = ['phone']
authCreateRequest.permissions = ['serviceauthcode']
authCreateRequest.forceAuthorization = true
// 3. 执行授权请求,获取认证码
const authController = new authentication.AuthenticationController(getContext())
const authResponse: authentication.AuthorizationWithHuaweiIDResponse =
await authController.executeRequest(authCreateRequest)
const serviceauthcode = authResponse.data?.authorizationCode
return serviceauthcode
}
async cancelAuth() {
try {
// 1. 创建一个Account Kit授权请求对象,可通过返回值设置请求参数。
const huaweiIdProvider = new authentication.HuaweiIDProvider()
const authCancelRequest = huaweiIdProvider.createCancelAuthorizationRequest()
// 2. 取消授权
const authController = new authentication.AuthenticationController(getContext())
await authController.executeRequest(authCancelRequest)
return true
} catch (e) {
console.log('mk-logger', JSON.stringify(e))
return false
}
}
}
export const huaweiAuthPlugin = new HuaweiAuthPlugin()
4.将code、clientID、clientSecret传给后端
- 等服务器向华为服务器核实信息返回参数后登录
- 这里的Auth是我封装的一个token管理工具,使用的是持久化存储