HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

开发自定义键盘,HarmonyOS中有没有长按按键子项生成气泡效果的API

问题现象
按键子项生成气泡效果用什么组件实现好?

效果预览
点击放大

背景知识
使用自定义键盘实现官网示例代码进行自定义键盘调试,然后用LongPressGesture增加长按手势事件,最后使用Popup控制为组件绑定Popup气泡,使用Popup气泡,如果按键处于边缘位置,组件会自动偏移,完整的显示在屏幕中间。

解决方案
自定义键盘没有直接长按生成气泡的接口,需要自定义长按逻辑,手指在长按小键盘组件时,使用Popup生成一个气泡,在气泡生成之后,使用onTouch获取手指位置并且根据气泡组件的位置和横向大小,计算当前手指划过的距离从而标记手指当前选择的内容。

代码实现思路如下:

创建变量控制气泡显隐样式以及气泡在边缘的位置:
@State customPopup: boolean = false;
@State indexNum: number = 0;
// 手指位置1、2、3、4(长按气泡中显示的内容数量)四个参数,分别代指长按后出现的四个位置,参数值与实际业务有关。
@State fingerRect: string = '1';
// 组件宽度
@State widthRect: number = 0;
// 键盘宽度
@State inputWidthRect: number = 0;
// 判断是否在偏右侧位置
@State isRight: boolean = false;
增加LongPressGesture长按控制手势,在onAction事件中增加逻辑,让气泡仅显示在指定按键上方;长按手势默认最短长按时间为500毫秒,可配置duration参数控制最短长按时长:
.gesture(
// 绑定可以重复触发的LongPressGesture
LongPressGesture({ repeat: true })
.onAction(() => {
for (let i = 0; i < EnglishKeyboardData[0].length; i++) {
if (item.text === EnglishKeyboardData[0][i].text) {
this.indexNum = i;
break;
}
}
this.customPopup = true;
})
.onActionEnd(() => {
this.customPopup = !this.customPopup;
})
)
增加bindPopup气泡控制,设置placement: Placement.TopRight,让气泡显示在按钮上方,通过this.popupBuilder(item)给自定义气泡组件传参然后自定义气泡内容:
.bindPopup(this.customPopup && EnglishKeyboardData[0].indexOf(item) === this.indexNum, {
// CustomPopupOptions类型气泡的内容
builder: this.popupBuilder(item),
placement: Placement.TopLeft,
targetSpace: '15vp',
enableArrow: false,
onStateChange: (e) => {
if (!e.isVisible) {
this.customPopup = false;
}
}
})
@Builder
popupBuilder(item: Menu) {
Row() {
ForEach([1, 2, 3, 4], (num: number, index: number) => {
if (this.fingerRect === index + 1 + '') {
Row() {
Text(item.text + '' + num)
.fontColor(Color.White);
}
.globalFancy()
.backgroundColor(Color.Blue)
.justifyContent(FlexAlign.Center);
} else {
Row() {
Text(item.text + '' + num);
}
.globalFancy()
.justifyContent(FlexAlign.Center);
}
}, (num: number) => JSON.stringify(num));
}
.height(50)
.width(150)
.padding(5)
.justifyContent(FlexAlign.SpaceAround)
.onAreaChange((oldVal, newVal) => {
// 如果此时键盘在偏右侧位置,需要重新计算位置
this.isRight = this.inputWidthRect <= Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}
使用onAreaChange监听新生成的气泡组件的宽度,再使用onTouch事件获取当前手指的坐标,然后根据手指的坐标和气泡组件的宽度,获取当前手指在气泡组件中的位置:
.onTouch((event) => {
// 获取当前手指的坐标(相对于父容器,与组件rect的坐标系一致)
const fingerX = event.touches[0].x;
// 靠近右边屏幕时,需要特殊处理。
this.fingerRect =
this.isRight ? Math.ceil(((fingerX + event.touches[0].displayX) - 230) / this.widthRect) + '' :
Math.ceil(fingerX / this.widthRect) + '';
this.fingerRect =
(Number(this.fingerRect) <= 1) ? '1' : Number(this.fingerRect) >= 4 ? '4' : this.fingerRect;
})
.onAreaChange((oldVal, newVal) => {
this.widthRect = Number(newVal.width);
});
参考官网自定义键盘实现,将demo中的NumberKeyboard文件替换为以下示例代码:

// 该场景代码基于自定义键盘实现官网示例代码https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-custom-keyboard#section1680905412256进行调试;
// 修改demo中NumberKeyboard文件如下
@Component
export struct NumberKeyboard {
@State customPopup: boolean = false;
@State indexNum: number = 0;
// 手指位置1、2、3、4(长按气泡中显示的内容数量)四个参数,分别代指长按后出现的四个位置,参数值与实际业务有关。
@State fingerRect: string = '1';
// 组件宽度
@State widthRect: number = 0;
// 键盘宽度
@State inputWidthRect: number = 0;
// 判断是否在偏右侧位置
@State isRight: boolean = false;

build() {
Row() {
Scroll() {
Column() {
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[0], (item: Menu) => {
EnglishButton({ item: item })
.gesture(
// 绑定可以重复触发的LongPressGesture
LongPressGesture({ repeat: true })
.onAction(() => {
for (let i = 0; i < EnglishKeyboardData[0].length; i++) {
if (item.text === EnglishKeyboardData[0][i].text) {
this.indexNum = i;
break;
}
}
this.customPopup = true;
})
.onActionEnd(() => {
this.customPopup = !this.customPopup;
})
)
.bindPopup(this.customPopup && EnglishKeyboardData[0].indexOf(item) === this.indexNum, {
// CustomPopupOptions类型气泡的内容
builder: this.popupBuilder(item),
placement: Placement.TopLeft,
targetSpace: '15vp',
enableArrow: false,
onStateChange: (e) => {
if (!e.isVisible) {
this.customPopup = false;
}
}
})
.onTouch((event) => {
// 获取当前手指的坐标(相对于父容器,与组件rect的坐标系一致)
const fingerX = event.touches[0].x;
// 靠近右边屏幕时,需要特殊处理。
this.fingerRect =
this.isRight ? Math.ceil(((fingerX + event.touches[0].displayX) - 230) / this.widthRect) + '' :
Math.ceil(fingerX / this.widthRect) + '';
this.fingerRect =
(Number(this.fingerRect) <= 1) ? '1' : Number(this.fingerRect) >= 4 ? '4' : this.fingerRect;
})
.onAreaChange((oldVal, newVal) => {
this.widthRect = Number(newVal.width);
});
}, (item: Menu) => JSON.stringify(item));
};

      Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {  
        ForEach(EnglishKeyboardData[1], (item: Menu) => {  
          EnglishButton({ item: item });  
        }, (item: Menu) => JSON.stringify(item));  
      }  
      .padding({  
        left: 18,  
        right: 18,  
        top: 11  
      });  

      Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {  
        ForEach(EnglishKeyboardData[2], (item: Menu) => {  
          EnglishButton({ item: item });  
        }, (item: Menu) => JSON.stringify(item));  
      }  
      .padding({ top: 11 });  

      Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {  
        ForEach(EnglishKeyboardData[3], (item: Menu) => {  
          EnglishButton({ item: item });  
        }, (item: Menu) => JSON.stringify(item));  
      }  
      .padding({ top: 11 });  
    }  
    .onAreaChange((oldVal, newVal) => {  
      // 键盘宽度  
      this.inputWidthRect = Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));  
    });  
  }  
  .scrollable(ScrollDirection.Horizontal)  
  .scrollBar(BarState.Off);  
};  

}

@Builder
popupBuilder(item: Menu) {
Row() {
ForEach([1, 2, 3, 4], (num: number, index: number) => {
if (this.fingerRect === index + 1 + '') {
Row() {
Text(item.text + '' + num)
.fontColor(Color.White);
}
.globalFancy()
.backgroundColor(Color.Blue)
.justifyContent(FlexAlign.Center);
} else {
Row() {
Text(item.text + '' + num);
}
.globalFancy()
.justifyContent(FlexAlign.Center);
}
}, (num: number) => JSON.stringify(num));
}
.height(50)
.width(150)
.padding(5)
.justifyContent(FlexAlign.SpaceAround)
.onAreaChange((oldVal, newVal) => {
// 如果此时键盘在偏右侧位置,需要重新计算位置
this.isRight = this.inputWidthRect <= Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}

}

@Styles
function globalFancy() {
.borderRadius(10)
.width('25%')
.height('100%');
}

// [Start EnglishButton_start]
@Component
struct EnglishButton {
@Consume inputText: string;
// [StartExclude EnglishButton_start]
@Prop item: Menu;
@Consume keyboardController: KeyboardController;

getEnglishText(item: Menu): string | Resource {
if (this.keyboardController.isUpperCase && item.upperText) {
return item.upperText;
} else {
return item.text;
}
}

// [EndExclude EnglishButton_start]

build() {
Button({ type: ButtonType.Normal }) {
if (typeof this.getEnglishText(this.item) === 'string' ||
(this.getEnglishText(this.item) as Resource).type !== Constants.RESOURCE_TYPE_MEDIA) {
Text(this.getEnglishText(this.item));
} else {
Image(this.getEnglishText(this.item))
.width(Constants.KEYBOARD_BUTTON_FONTSIZE_18)
.height(Constants.KEYBOARD_BUTTON_FONTSIZE_18);
}
}
.fontColor(Color.Black)
.backgroundColor(this.item.backgroundColor)
.borderRadius(Constants.KEYBOARD_BUTTON_RADIUS)
.fontSize(Constants.KEYBOARD_BUTTON_FONTSIZE_18)
.padding(0)
.width(this.item.width)
.height(this.item.height)
.onClick(() => {
this.inputText = this.keyboardController.onInput(this.getEnglishText(this.item));
});
}
}

// [End EnglishButton_start]
https://coub.com/view/4b8l5y
https://coub.com/view/4b8l5w
https://coub.com/view/4b8l5v
https://coub.com/view/4b8l5t
https://coub.com/view/4b8l5r
https://coub.com/view/4b8l5p
https://coub.com/view/4b8l5l
https://coub.com/view/4b8l5h
https://coub.com/view/4b8l5e
https://coub.com/view/4b8l5c
https://coub.com/view/4b8l5b
https://coub.com/view/4b8l5a
https://coub.com/view/4b8l58
https://coub.com/view/4b8l57
https://coub.com/view/4b8l54
https://coub.com/view/4b8l51
https://coub.com/view/4b8l50
https://coub.com/view/4b8l4z
https://coub.com/view/4b8l4w
https://coub.com/view/4b8l4t
https://coub.com/view/4b8l4r
https://coub.com/view/4b8l4o
https://coub.com/view/4b8l4l
https://coub.com/view/4b8l4j
https://coub.com/view/4b8l4f
https://coub.com/view/4b8l4d
https://coub.com/view/4b8l4b
https://coub.com/view/4b8l47
https://coub.com/view/4b8l46
https://coub.com/view/4b8l41
https://coub.com/view/4b8l3y
https://coub.com/view/4b8l3v
https://coub.com/view/4b8l3s
https://coub.com/view/4b8l3r
https://coub.com/view/4b8l3q
https://coub.com/view/4b8l3h
https://coub.com/view/4b8l3f
https://coub.com/view/4b8l3c
https://coub.com/view/4b8l39
https://coub.com/view/4b8l36
https://coub.com/view/4b8l30
https://coub.com/view/4b8l2y
https://coub.com/view/4b8l2v
https://coub.com/view/4b8l2t
https://coub.com/view/4b8l2q
https://coub.com/view/4b8l2n
https://coub.com/view/4b8l2j
https://coub.com/view/4b8l2f
https://coub.com/view/4b8l2c
https://coub.com/view/4b8l2b
https://coub.com/view/4b8l28
https://coub.com/view/4b8l27
https://coub.com/view/4b8l23
https://coub.com/view/4b8l20
https://coub.com/view/4b8l1v
https://coub.com/view/4b8l16
https://coub.com/view/4b8l12
https://coub.com/view/4b8l0w
https://coub.com/view/4b8l0o
https://coub.com/view/4b8l0j
https://coub.com/view/4b8l0g
https://coub.com/view/4b8l0f
https://coub.com/view/4b8l0d
https://coub.com/view/4b8l08
https://coub.com/view/4b8l06
https://coub.com/view/4b8l04
https://coub.com/view/4b8l01
https://coub.com/view/4b8kzx
https://coub.com/view/4b8kzv
https://coub.com/view/4b8kzr
https://coub.com/view/4b8kzl
https://coub.com/view/4b8kzi
https://coub.com/view/4b8kzg
https://coub.com/view/4b8kze
https://coub.com/view/4b8kzc
https://coub.com/view/4b8kpg
https://coub.com/view/4b8kpf
https://coub.com/view/4b8kpe
https://coub.com/view/4b8kpc
https://coub.com/view/4b8kpa
https://coub.com/view/4b8kp7
https://coub.com/view/4b8kp5
https://coub.com/view/4b8kp1
https://coub.com/view/4b8kox
https://coub.com/view/4b8kov
https://coub.com/view/4b8kot
https://coub.com/view/4b8kor
https://coub.com/view/4b8kop
https://coub.com/view/4b8kol
https://coub.com/view/4b8koj
https://coub.com/view/4b8kog
https://coub.com/view/4b8koe
https://coub.com/view/4b8koc
https://coub.com/view/4b8knx
https://coub.com/view/4b8knw
https://coub.com/view/4b8knv
https://coub.com/view/4b8knu
https://coub.com/view/4b8knt
https://coub.com/view/4b8kns
https://coub.com/view/4b8knq
https://coub.com/view/4b8knp
https://coub.com/view/4b8kno
https://coub.com/view/4b8knn
https://coub.com/view/4b8knm
https://coub.com/view/4b8knl
https://coub.com/view/4b8knk
https://coub.com/view/4b8knj
https://coub.com/view/4b8kni
https://coub.com/view/4b8knd
https://coub.com/view/4b8knb
https://coub.com/view/4b8kna
https://coub.com/view/4b8kn9
https://coub.com/view/4b8kn8

继续阅读 »

问题现象
按键子项生成气泡效果用什么组件实现好?

效果预览
点击放大

背景知识
使用自定义键盘实现官网示例代码进行自定义键盘调试,然后用LongPressGesture增加长按手势事件,最后使用Popup控制为组件绑定Popup气泡,使用Popup气泡,如果按键处于边缘位置,组件会自动偏移,完整的显示在屏幕中间。

解决方案
自定义键盘没有直接长按生成气泡的接口,需要自定义长按逻辑,手指在长按小键盘组件时,使用Popup生成一个气泡,在气泡生成之后,使用onTouch获取手指位置并且根据气泡组件的位置和横向大小,计算当前手指划过的距离从而标记手指当前选择的内容。

代码实现思路如下:

创建变量控制气泡显隐样式以及气泡在边缘的位置:
@State customPopup: boolean = false;
@State indexNum: number = 0;
// 手指位置1、2、3、4(长按气泡中显示的内容数量)四个参数,分别代指长按后出现的四个位置,参数值与实际业务有关。
@State fingerRect: string = '1';
// 组件宽度
@State widthRect: number = 0;
// 键盘宽度
@State inputWidthRect: number = 0;
// 判断是否在偏右侧位置
@State isRight: boolean = false;
增加LongPressGesture长按控制手势,在onAction事件中增加逻辑,让气泡仅显示在指定按键上方;长按手势默认最短长按时间为500毫秒,可配置duration参数控制最短长按时长:
.gesture(
// 绑定可以重复触发的LongPressGesture
LongPressGesture({ repeat: true })
.onAction(() => {
for (let i = 0; i < EnglishKeyboardData[0].length; i++) {
if (item.text === EnglishKeyboardData[0][i].text) {
this.indexNum = i;
break;
}
}
this.customPopup = true;
})
.onActionEnd(() => {
this.customPopup = !this.customPopup;
})
)
增加bindPopup气泡控制,设置placement: Placement.TopRight,让气泡显示在按钮上方,通过this.popupBuilder(item)给自定义气泡组件传参然后自定义气泡内容:
.bindPopup(this.customPopup && EnglishKeyboardData[0].indexOf(item) === this.indexNum, {
// CustomPopupOptions类型气泡的内容
builder: this.popupBuilder(item),
placement: Placement.TopLeft,
targetSpace: '15vp',
enableArrow: false,
onStateChange: (e) => {
if (!e.isVisible) {
this.customPopup = false;
}
}
})
@Builder
popupBuilder(item: Menu) {
Row() {
ForEach([1, 2, 3, 4], (num: number, index: number) => {
if (this.fingerRect === index + 1 + '') {
Row() {
Text(item.text + '' + num)
.fontColor(Color.White);
}
.globalFancy()
.backgroundColor(Color.Blue)
.justifyContent(FlexAlign.Center);
} else {
Row() {
Text(item.text + '' + num);
}
.globalFancy()
.justifyContent(FlexAlign.Center);
}
}, (num: number) => JSON.stringify(num));
}
.height(50)
.width(150)
.padding(5)
.justifyContent(FlexAlign.SpaceAround)
.onAreaChange((oldVal, newVal) => {
// 如果此时键盘在偏右侧位置,需要重新计算位置
this.isRight = this.inputWidthRect <= Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}
使用onAreaChange监听新生成的气泡组件的宽度,再使用onTouch事件获取当前手指的坐标,然后根据手指的坐标和气泡组件的宽度,获取当前手指在气泡组件中的位置:
.onTouch((event) => {
// 获取当前手指的坐标(相对于父容器,与组件rect的坐标系一致)
const fingerX = event.touches[0].x;
// 靠近右边屏幕时,需要特殊处理。
this.fingerRect =
this.isRight ? Math.ceil(((fingerX + event.touches[0].displayX) - 230) / this.widthRect) + '' :
Math.ceil(fingerX / this.widthRect) + '';
this.fingerRect =
(Number(this.fingerRect) <= 1) ? '1' : Number(this.fingerRect) >= 4 ? '4' : this.fingerRect;
})
.onAreaChange((oldVal, newVal) => {
this.widthRect = Number(newVal.width);
});
参考官网自定义键盘实现,将demo中的NumberKeyboard文件替换为以下示例代码:

// 该场景代码基于自定义键盘实现官网示例代码https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-custom-keyboard#section1680905412256进行调试;
// 修改demo中NumberKeyboard文件如下
@Component
export struct NumberKeyboard {
@State customPopup: boolean = false;
@State indexNum: number = 0;
// 手指位置1、2、3、4(长按气泡中显示的内容数量)四个参数,分别代指长按后出现的四个位置,参数值与实际业务有关。
@State fingerRect: string = '1';
// 组件宽度
@State widthRect: number = 0;
// 键盘宽度
@State inputWidthRect: number = 0;
// 判断是否在偏右侧位置
@State isRight: boolean = false;

build() {
Row() {
Scroll() {
Column() {
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[0], (item: Menu) => {
EnglishButton({ item: item })
.gesture(
// 绑定可以重复触发的LongPressGesture
LongPressGesture({ repeat: true })
.onAction(() => {
for (let i = 0; i < EnglishKeyboardData[0].length; i++) {
if (item.text === EnglishKeyboardData[0][i].text) {
this.indexNum = i;
break;
}
}
this.customPopup = true;
})
.onActionEnd(() => {
this.customPopup = !this.customPopup;
})
)
.bindPopup(this.customPopup && EnglishKeyboardData[0].indexOf(item) === this.indexNum, {
// CustomPopupOptions类型气泡的内容
builder: this.popupBuilder(item),
placement: Placement.TopLeft,
targetSpace: '15vp',
enableArrow: false,
onStateChange: (e) => {
if (!e.isVisible) {
this.customPopup = false;
}
}
})
.onTouch((event) => {
// 获取当前手指的坐标(相对于父容器,与组件rect的坐标系一致)
const fingerX = event.touches[0].x;
// 靠近右边屏幕时,需要特殊处理。
this.fingerRect =
this.isRight ? Math.ceil(((fingerX + event.touches[0].displayX) - 230) / this.widthRect) + '' :
Math.ceil(fingerX / this.widthRect) + '';
this.fingerRect =
(Number(this.fingerRect) <= 1) ? '1' : Number(this.fingerRect) >= 4 ? '4' : this.fingerRect;
})
.onAreaChange((oldVal, newVal) => {
this.widthRect = Number(newVal.width);
});
}, (item: Menu) => JSON.stringify(item));
};

      Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {  
        ForEach(EnglishKeyboardData[1], (item: Menu) => {  
          EnglishButton({ item: item });  
        }, (item: Menu) => JSON.stringify(item));  
      }  
      .padding({  
        left: 18,  
        right: 18,  
        top: 11  
      });  

      Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {  
        ForEach(EnglishKeyboardData[2], (item: Menu) => {  
          EnglishButton({ item: item });  
        }, (item: Menu) => JSON.stringify(item));  
      }  
      .padding({ top: 11 });  

      Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {  
        ForEach(EnglishKeyboardData[3], (item: Menu) => {  
          EnglishButton({ item: item });  
        }, (item: Menu) => JSON.stringify(item));  
      }  
      .padding({ top: 11 });  
    }  
    .onAreaChange((oldVal, newVal) => {  
      // 键盘宽度  
      this.inputWidthRect = Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));  
    });  
  }  
  .scrollable(ScrollDirection.Horizontal)  
  .scrollBar(BarState.Off);  
};  

}

@Builder
popupBuilder(item: Menu) {
Row() {
ForEach([1, 2, 3, 4], (num: number, index: number) => {
if (this.fingerRect === index + 1 + '') {
Row() {
Text(item.text + '' + num)
.fontColor(Color.White);
}
.globalFancy()
.backgroundColor(Color.Blue)
.justifyContent(FlexAlign.Center);
} else {
Row() {
Text(item.text + '' + num);
}
.globalFancy()
.justifyContent(FlexAlign.Center);
}
}, (num: number) => JSON.stringify(num));
}
.height(50)
.width(150)
.padding(5)
.justifyContent(FlexAlign.SpaceAround)
.onAreaChange((oldVal, newVal) => {
// 如果此时键盘在偏右侧位置,需要重新计算位置
this.isRight = this.inputWidthRect <= Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}

}

@Styles
function globalFancy() {
.borderRadius(10)
.width('25%')
.height('100%');
}

// [Start EnglishButton_start]
@Component
struct EnglishButton {
@Consume inputText: string;
// [StartExclude EnglishButton_start]
@Prop item: Menu;
@Consume keyboardController: KeyboardController;

getEnglishText(item: Menu): string | Resource {
if (this.keyboardController.isUpperCase && item.upperText) {
return item.upperText;
} else {
return item.text;
}
}

// [EndExclude EnglishButton_start]

build() {
Button({ type: ButtonType.Normal }) {
if (typeof this.getEnglishText(this.item) === 'string' ||
(this.getEnglishText(this.item) as Resource).type !== Constants.RESOURCE_TYPE_MEDIA) {
Text(this.getEnglishText(this.item));
} else {
Image(this.getEnglishText(this.item))
.width(Constants.KEYBOARD_BUTTON_FONTSIZE_18)
.height(Constants.KEYBOARD_BUTTON_FONTSIZE_18);
}
}
.fontColor(Color.Black)
.backgroundColor(this.item.backgroundColor)
.borderRadius(Constants.KEYBOARD_BUTTON_RADIUS)
.fontSize(Constants.KEYBOARD_BUTTON_FONTSIZE_18)
.padding(0)
.width(this.item.width)
.height(this.item.height)
.onClick(() => {
this.inputText = this.keyboardController.onInput(this.getEnglishText(this.item));
});
}
}

// [End EnglishButton_start]
https://coub.com/view/4b8l5y
https://coub.com/view/4b8l5w
https://coub.com/view/4b8l5v
https://coub.com/view/4b8l5t
https://coub.com/view/4b8l5r
https://coub.com/view/4b8l5p
https://coub.com/view/4b8l5l
https://coub.com/view/4b8l5h
https://coub.com/view/4b8l5e
https://coub.com/view/4b8l5c
https://coub.com/view/4b8l5b
https://coub.com/view/4b8l5a
https://coub.com/view/4b8l58
https://coub.com/view/4b8l57
https://coub.com/view/4b8l54
https://coub.com/view/4b8l51
https://coub.com/view/4b8l50
https://coub.com/view/4b8l4z
https://coub.com/view/4b8l4w
https://coub.com/view/4b8l4t
https://coub.com/view/4b8l4r
https://coub.com/view/4b8l4o
https://coub.com/view/4b8l4l
https://coub.com/view/4b8l4j
https://coub.com/view/4b8l4f
https://coub.com/view/4b8l4d
https://coub.com/view/4b8l4b
https://coub.com/view/4b8l47
https://coub.com/view/4b8l46
https://coub.com/view/4b8l41
https://coub.com/view/4b8l3y
https://coub.com/view/4b8l3v
https://coub.com/view/4b8l3s
https://coub.com/view/4b8l3r
https://coub.com/view/4b8l3q
https://coub.com/view/4b8l3h
https://coub.com/view/4b8l3f
https://coub.com/view/4b8l3c
https://coub.com/view/4b8l39
https://coub.com/view/4b8l36
https://coub.com/view/4b8l30
https://coub.com/view/4b8l2y
https://coub.com/view/4b8l2v
https://coub.com/view/4b8l2t
https://coub.com/view/4b8l2q
https://coub.com/view/4b8l2n
https://coub.com/view/4b8l2j
https://coub.com/view/4b8l2f
https://coub.com/view/4b8l2c
https://coub.com/view/4b8l2b
https://coub.com/view/4b8l28
https://coub.com/view/4b8l27
https://coub.com/view/4b8l23
https://coub.com/view/4b8l20
https://coub.com/view/4b8l1v
https://coub.com/view/4b8l16
https://coub.com/view/4b8l12
https://coub.com/view/4b8l0w
https://coub.com/view/4b8l0o
https://coub.com/view/4b8l0j
https://coub.com/view/4b8l0g
https://coub.com/view/4b8l0f
https://coub.com/view/4b8l0d
https://coub.com/view/4b8l08
https://coub.com/view/4b8l06
https://coub.com/view/4b8l04
https://coub.com/view/4b8l01
https://coub.com/view/4b8kzx
https://coub.com/view/4b8kzv
https://coub.com/view/4b8kzr
https://coub.com/view/4b8kzl
https://coub.com/view/4b8kzi
https://coub.com/view/4b8kzg
https://coub.com/view/4b8kze
https://coub.com/view/4b8kzc
https://coub.com/view/4b8kpg
https://coub.com/view/4b8kpf
https://coub.com/view/4b8kpe
https://coub.com/view/4b8kpc
https://coub.com/view/4b8kpa
https://coub.com/view/4b8kp7
https://coub.com/view/4b8kp5
https://coub.com/view/4b8kp1
https://coub.com/view/4b8kox
https://coub.com/view/4b8kov
https://coub.com/view/4b8kot
https://coub.com/view/4b8kor
https://coub.com/view/4b8kop
https://coub.com/view/4b8kol
https://coub.com/view/4b8koj
https://coub.com/view/4b8kog
https://coub.com/view/4b8koe
https://coub.com/view/4b8koc
https://coub.com/view/4b8knx
https://coub.com/view/4b8knw
https://coub.com/view/4b8knv
https://coub.com/view/4b8knu
https://coub.com/view/4b8knt
https://coub.com/view/4b8kns
https://coub.com/view/4b8knq
https://coub.com/view/4b8knp
https://coub.com/view/4b8kno
https://coub.com/view/4b8knn
https://coub.com/view/4b8knm
https://coub.com/view/4b8knl
https://coub.com/view/4b8knk
https://coub.com/view/4b8knj
https://coub.com/view/4b8kni
https://coub.com/view/4b8knd
https://coub.com/view/4b8knb
https://coub.com/view/4b8kna
https://coub.com/view/4b8kn9
https://coub.com/view/4b8kn8

收起阅读 »

如何获取ArkTS状态管理框架代理前的原始对象

使用getTarget接口获取状态管理框架代理前的原始对象。

参考示例如下:

import { UIUtils } from '@kit.ArkUI';

@Observed
class UserInfo {
name: string = 'Tom';
}

@Entry
@Component
struct GetTargetDemo {
@State info: UserInfo = new UserInfo();

build() {
Column() {
Text(info.name: ${this.info.name})
Button('Change the properties of the proxy object')
.onClick(() => {
this.info.name = 'Alice'; // The Text component can refresh
})
Button('更改原始对象的属性')
.onClick(() => {
let rawInfo: UserInfo = UIUtils.getTarget(this.info);
if (rawInfo) {
rawInfo.name = 'Bob'; // The Text component cannot be refreshed
}
})
}
}
}
参考链接

getTarget接口:获取状态管理框架代理前的原始对象
https://coub.com/view/4b8gt3
https://coub.com/view/4b8gt2
https://coub.com/view/4b8gt0
https://coub.com/view/4b8gsz
https://coub.com/view/4b8gsy
https://coub.com/view/4b8gsx
https://coub.com/view/4b8gsw
https://coub.com/view/4b8gsv
https://coub.com/view/4b8gsu
https://coub.com/view/4b8gst
https://coub.com/view/4b8gss
https://coub.com/view/4b8gsr
https://coub.com/view/4b8gsq
https://coub.com/view/4b8gsp
https://coub.com/view/4b8gsl
https://coub.com/view/4b8gsk
https://coub.com/view/4b8gsh
https://coub.com/view/4b8gsg
https://coub.com/view/4b8gsf
https://coub.com/view/4b8gse
https://coub.com/view/4b8gsa
https://coub.com/view/4b8gs9
https://coub.com/view/4b8gs4
https://coub.com/view/4b8gs3
https://coub.com/view/4b8gs2
https://coub.com/view/4b8grz
https://coub.com/view/4b8grv
https://coub.com/view/4b8gru
https://coub.com/view/4b8grs
https://coub.com/view/4b8grq
https://coub.com/view/4b8gro
https://coub.com/view/4b8grn
https://coub.com/view/4b8grj
https://coub.com/view/4b8gre
https://coub.com/view/4b8grb
https://coub.com/view/4b8gr9
https://coub.com/view/4b8gr8
https://coub.com/view/4b8gr7
https://coub.com/view/4b8gr4
https://coub.com/view/4b8gqz
https://coub.com/view/4b8gqv
https://coub.com/view/4b8gqu
https://coub.com/view/4b8gqs
https://coub.com/view/4b8gqr
https://coub.com/view/4b8gqp
https://coub.com/view/4b8gqo
https://coub.com/view/4b8gqn
https://coub.com/view/4b8gqm
https://coub.com/view/4b8gql
https://coub.com/view/4b8gqi
https://coub.com/view/4b8gqf
https://coub.com/view/4b8gqc
https://coub.com/view/4b8gqa
https://coub.com/view/4b8gq8
https://coub.com/view/4b8gq6
https://coub.com/view/4b8gq4
https://coub.com/view/4b8gq2
https://coub.com/view/4b8gq0
https://coub.com/view/4b8gpx
https://coub.com/view/4b8gpp
https://coub.com/view/4b8gpn
https://coub.com/view/4b8gpm
https://coub.com/view/4b8gpl
https://coub.com/view/4b8gpi
https://coub.com/view/4b8gpf
https://coub.com/view/4b8gpa
https://coub.com/view/4b8gp9
https://coub.com/view/4b8gp6
https://coub.com/view/4b8gp5
https://coub.com/view/4b8gp3
https://coub.com/view/4b8gp1
https://coub.com/view/4b8goz
https://coub.com/view/4b8goy
https://coub.com/view/4b8gox
https://coub.com/view/4b8gov
https://coub.com/view/4b8gou
https://coub.com/view/4b8got
https://coub.com/view/4b8gos
https://coub.com/view/4b8gop
https://coub.com/view/4b8goe
https://coub.com/view/4b8god
https://coub.com/view/4b8goa
https://coub.com/view/4b8go8
https://coub.com/view/4b8go3
https://coub.com/view/4b8go0
https://coub.com/view/4b8gny
https://coub.com/view/4b8gnw
https://coub.com/view/4b8gnv
https://coub.com/view/4b8gnu
https://coub.com/view/4b8gns
https://coub.com/view/4b8gnk
https://coub.com/view/4b8gnj
https://coub.com/view/4b8gni
https://coub.com/view/4b8gnh
https://coub.com/view/4b8gnb
https://coub.com/view/4b8gna
https://coub.com/view/4b8gn9
https://coub.com/view/4b8gn7
https://coub.com/view/4b8gn2
https://coub.com/view/4b8cf1
https://coub.com/view/4b8cez
https://coub.com/view/4b8cev
https://coub.com/view/4b8ceu
https://coub.com/view/4b8cep
https://coub.com/view/4b8cen
https://coub.com/view/4b8cel
https://coub.com/view/4b8ceh
https://coub.com/view/4b8ceg
https://coub.com/view/4b8cee
https://coub.com/view/4b8ced
https://coub.com/view/4b8ceb
https://coub.com/view/4b8cea
https://coub.com/view/4b8ce8
https://coub.com/view/4b8ce6
https://coub.com/view/4b8ce5
https://coub.com/view/4b8ce4
https://coub.com/view/4b8ce2
https://coub.com/view/4b8ce1
https://coub.com/view/4b8cdz
https://coub.com/view/4b8cdx
https://coub.com/view/4b8cdw
https://coub.com/view/4b8cdv
https://coub.com/view/4b8cdu
https://coub.com/view/4b8cdt
https://coub.com/view/4b8cdr
https://coub.com/view/4b8cdq
https://coub.com/view/4b8cdp
https://coub.com/view/4b8cdo
https://coub.com/view/4b8cdn
https://coub.com/view/4b8cdm
https://coub.com/view/4b8cdl
https://coub.com/view/4b8cdk
https://coub.com/view/4b8cdj
https://coub.com/view/4b8cdi
https://coub.com/view/4b8cdh
https://coub.com/view/4b8cdf
https://coub.com/view/4b8cde
https://coub.com/view/4b8cdc
https://coub.com/view/4b8cda
https://coub.com/view/4b8cd5
https://coub.com/view/4b8cd4
https://coub.com/view/4b8cd2
https://coub.com/view/4b8cd0
https://coub.com/view/4b8ccx
https://coub.com/view/4b8ccw
https://coub.com/view/4b8ccv
https://coub.com/view/4b8ccs
https://coub.com/view/4b8ccq
https://coub.com/view/4b8ccn
https://coub.com/view/4b8ccj
https://coub.com/view/4b8cci
https://coub.com/view/4b8cca
https://coub.com/view/4b8cc3
https://coub.com/view/4b8cc1
https://coub.com/view/4b8cbz
https://coub.com/view/4b8cbw
https://coub.com/view/4b8cbt
https://coub.com/view/4b8cbl
https://coub.com/view/4b8cbk
https://coub.com/view/4b8cbh
https://coub.com/view/4b8cbf
https://coub.com/view/4b8cbe
https://coub.com/view/4b8cbd
https://coub.com/view/4b8cbb
https://coub.com/view/4b8cba
https://coub.com/view/4b8cb9
https://coub.com/view/4b8cb7
https://coub.com/view/4b8cb5
https://coub.com/view/4b8cb4
https://coub.com/view/4b8cb3
https://coub.com/view/4b8cb0
https://coub.com/view/4b8cax
https://coub.com/view/4b8caw
https://coub.com/view/4b8cau
https://coub.com/view/4b8cat
https://coub.com/view/4b8car
https://coub.com/view/4b8caq
https://coub.com/view/4b8cai
https://coub.com/view/4b8cah
https://coub.com/view/4b8caf
https://coub.com/view/4b8cae
https://coub.com/view/4b8cac
https://coub.com/view/4b8cab
https://coub.com/view/4b8caa
https://coub.com/view/4b8ca8
https://coub.com/view/4b8ca7
https://coub.com/view/4b8ca6
https://coub.com/view/4b8ca4
https://coub.com/view/4b8ca3
https://coub.com/view/4b8ca2
https://coub.com/view/4b8ca1
https://coub.com/view/4b8ca0
https://coub.com/view/4b8c9y
https://coub.com/view/4b8c9x
https://coub.com/view/4b8c9w
https://coub.com/view/4b8c9v
https://coub.com/view/4b8c9u

继续阅读 »

使用getTarget接口获取状态管理框架代理前的原始对象。

参考示例如下:

import { UIUtils } from '@kit.ArkUI';

@Observed
class UserInfo {
name: string = 'Tom';
}

@Entry
@Component
struct GetTargetDemo {
@State info: UserInfo = new UserInfo();

build() {
Column() {
Text(info.name: ${this.info.name})
Button('Change the properties of the proxy object')
.onClick(() => {
this.info.name = 'Alice'; // The Text component can refresh
})
Button('更改原始对象的属性')
.onClick(() => {
let rawInfo: UserInfo = UIUtils.getTarget(this.info);
if (rawInfo) {
rawInfo.name = 'Bob'; // The Text component cannot be refreshed
}
})
}
}
}
参考链接

getTarget接口:获取状态管理框架代理前的原始对象
https://coub.com/view/4b8gt3
https://coub.com/view/4b8gt2
https://coub.com/view/4b8gt0
https://coub.com/view/4b8gsz
https://coub.com/view/4b8gsy
https://coub.com/view/4b8gsx
https://coub.com/view/4b8gsw
https://coub.com/view/4b8gsv
https://coub.com/view/4b8gsu
https://coub.com/view/4b8gst
https://coub.com/view/4b8gss
https://coub.com/view/4b8gsr
https://coub.com/view/4b8gsq
https://coub.com/view/4b8gsp
https://coub.com/view/4b8gsl
https://coub.com/view/4b8gsk
https://coub.com/view/4b8gsh
https://coub.com/view/4b8gsg
https://coub.com/view/4b8gsf
https://coub.com/view/4b8gse
https://coub.com/view/4b8gsa
https://coub.com/view/4b8gs9
https://coub.com/view/4b8gs4
https://coub.com/view/4b8gs3
https://coub.com/view/4b8gs2
https://coub.com/view/4b8grz
https://coub.com/view/4b8grv
https://coub.com/view/4b8gru
https://coub.com/view/4b8grs
https://coub.com/view/4b8grq
https://coub.com/view/4b8gro
https://coub.com/view/4b8grn
https://coub.com/view/4b8grj
https://coub.com/view/4b8gre
https://coub.com/view/4b8grb
https://coub.com/view/4b8gr9
https://coub.com/view/4b8gr8
https://coub.com/view/4b8gr7
https://coub.com/view/4b8gr4
https://coub.com/view/4b8gqz
https://coub.com/view/4b8gqv
https://coub.com/view/4b8gqu
https://coub.com/view/4b8gqs
https://coub.com/view/4b8gqr
https://coub.com/view/4b8gqp
https://coub.com/view/4b8gqo
https://coub.com/view/4b8gqn
https://coub.com/view/4b8gqm
https://coub.com/view/4b8gql
https://coub.com/view/4b8gqi
https://coub.com/view/4b8gqf
https://coub.com/view/4b8gqc
https://coub.com/view/4b8gqa
https://coub.com/view/4b8gq8
https://coub.com/view/4b8gq6
https://coub.com/view/4b8gq4
https://coub.com/view/4b8gq2
https://coub.com/view/4b8gq0
https://coub.com/view/4b8gpx
https://coub.com/view/4b8gpp
https://coub.com/view/4b8gpn
https://coub.com/view/4b8gpm
https://coub.com/view/4b8gpl
https://coub.com/view/4b8gpi
https://coub.com/view/4b8gpf
https://coub.com/view/4b8gpa
https://coub.com/view/4b8gp9
https://coub.com/view/4b8gp6
https://coub.com/view/4b8gp5
https://coub.com/view/4b8gp3
https://coub.com/view/4b8gp1
https://coub.com/view/4b8goz
https://coub.com/view/4b8goy
https://coub.com/view/4b8gox
https://coub.com/view/4b8gov
https://coub.com/view/4b8gou
https://coub.com/view/4b8got
https://coub.com/view/4b8gos
https://coub.com/view/4b8gop
https://coub.com/view/4b8goe
https://coub.com/view/4b8god
https://coub.com/view/4b8goa
https://coub.com/view/4b8go8
https://coub.com/view/4b8go3
https://coub.com/view/4b8go0
https://coub.com/view/4b8gny
https://coub.com/view/4b8gnw
https://coub.com/view/4b8gnv
https://coub.com/view/4b8gnu
https://coub.com/view/4b8gns
https://coub.com/view/4b8gnk
https://coub.com/view/4b8gnj
https://coub.com/view/4b8gni
https://coub.com/view/4b8gnh
https://coub.com/view/4b8gnb
https://coub.com/view/4b8gna
https://coub.com/view/4b8gn9
https://coub.com/view/4b8gn7
https://coub.com/view/4b8gn2
https://coub.com/view/4b8cf1
https://coub.com/view/4b8cez
https://coub.com/view/4b8cev
https://coub.com/view/4b8ceu
https://coub.com/view/4b8cep
https://coub.com/view/4b8cen
https://coub.com/view/4b8cel
https://coub.com/view/4b8ceh
https://coub.com/view/4b8ceg
https://coub.com/view/4b8cee
https://coub.com/view/4b8ced
https://coub.com/view/4b8ceb
https://coub.com/view/4b8cea
https://coub.com/view/4b8ce8
https://coub.com/view/4b8ce6
https://coub.com/view/4b8ce5
https://coub.com/view/4b8ce4
https://coub.com/view/4b8ce2
https://coub.com/view/4b8ce1
https://coub.com/view/4b8cdz
https://coub.com/view/4b8cdx
https://coub.com/view/4b8cdw
https://coub.com/view/4b8cdv
https://coub.com/view/4b8cdu
https://coub.com/view/4b8cdt
https://coub.com/view/4b8cdr
https://coub.com/view/4b8cdq
https://coub.com/view/4b8cdp
https://coub.com/view/4b8cdo
https://coub.com/view/4b8cdn
https://coub.com/view/4b8cdm
https://coub.com/view/4b8cdl
https://coub.com/view/4b8cdk
https://coub.com/view/4b8cdj
https://coub.com/view/4b8cdi
https://coub.com/view/4b8cdh
https://coub.com/view/4b8cdf
https://coub.com/view/4b8cde
https://coub.com/view/4b8cdc
https://coub.com/view/4b8cda
https://coub.com/view/4b8cd5
https://coub.com/view/4b8cd4
https://coub.com/view/4b8cd2
https://coub.com/view/4b8cd0
https://coub.com/view/4b8ccx
https://coub.com/view/4b8ccw
https://coub.com/view/4b8ccv
https://coub.com/view/4b8ccs
https://coub.com/view/4b8ccq
https://coub.com/view/4b8ccn
https://coub.com/view/4b8ccj
https://coub.com/view/4b8cci
https://coub.com/view/4b8cca
https://coub.com/view/4b8cc3
https://coub.com/view/4b8cc1
https://coub.com/view/4b8cbz
https://coub.com/view/4b8cbw
https://coub.com/view/4b8cbt
https://coub.com/view/4b8cbl
https://coub.com/view/4b8cbk
https://coub.com/view/4b8cbh
https://coub.com/view/4b8cbf
https://coub.com/view/4b8cbe
https://coub.com/view/4b8cbd
https://coub.com/view/4b8cbb
https://coub.com/view/4b8cba
https://coub.com/view/4b8cb9
https://coub.com/view/4b8cb7
https://coub.com/view/4b8cb5
https://coub.com/view/4b8cb4
https://coub.com/view/4b8cb3
https://coub.com/view/4b8cb0
https://coub.com/view/4b8cax
https://coub.com/view/4b8caw
https://coub.com/view/4b8cau
https://coub.com/view/4b8cat
https://coub.com/view/4b8car
https://coub.com/view/4b8caq
https://coub.com/view/4b8cai
https://coub.com/view/4b8cah
https://coub.com/view/4b8caf
https://coub.com/view/4b8cae
https://coub.com/view/4b8cac
https://coub.com/view/4b8cab
https://coub.com/view/4b8caa
https://coub.com/view/4b8ca8
https://coub.com/view/4b8ca7
https://coub.com/view/4b8ca6
https://coub.com/view/4b8ca4
https://coub.com/view/4b8ca3
https://coub.com/view/4b8ca2
https://coub.com/view/4b8ca1
https://coub.com/view/4b8ca0
https://coub.com/view/4b8c9y
https://coub.com/view/4b8c9x
https://coub.com/view/4b8c9w
https://coub.com/view/4b8c9v
https://coub.com/view/4b8c9u

收起阅读 »

如何解决组件消失动画偏移闪烁

demo

问题描述

运行下面demo,点击使色块消失时,会突然出现在左上角闪烁。

@Entry
@ComponentV2
struct Index {
@Local isShow: boolean = true;

build() {
Stack() {
Stack() {
if (this.isShow) {
Row()
.width(100)
.height(100)
.backgroundColor(Color.Red)
.transition(TransitionEffect.OPACITY.animation({ duration: 150 }));
}
}
.backgroundColor(Color.Green)
}
.width('100%')
.height('100%')
.onClick(() => {
this.isShow = !this.isShow;
});
}
}
解决措施

原因是当变量置为false时,组件会从视图树中移除,导致内层Stack失去内容后位置重置到左上角,可以固定内层Stack组件的宽高来避免,示例如下:

@Entry
@ComponentV2
struct DisappearanceAnimationOffsetFlicker {
@Local isShow: boolean = true;

build() {
Stack() {
Stack() {
if (this.isShow) {
Row()
.width(100)
.height(100)
.backgroundColor(Color.Red)
.transition(TransitionEffect.OPACITY.animation({ duration: 150 }))
}
}
.width('100%')
.height('100%')
.onClick(() => {
this.isShow = !this.isShow
})
}
}
}
https://coub.com/view/4b8cf1
https://coub.com/view/4b8cez
https://coub.com/view/4b8cev
https://coub.com/view/4b8ceu
https://coub.com/view/4b8cep
https://coub.com/view/4b8cen
https://coub.com/view/4b8cel
https://coub.com/view/4b8ceh
https://coub.com/view/4b8ceg
https://coub.com/view/4b8cee
https://coub.com/view/4b8ced
https://coub.com/view/4b8ceb
https://coub.com/view/4b8cea
https://coub.com/view/4b8ce8
https://coub.com/view/4b8ce6
https://coub.com/view/4b8ce5
https://coub.com/view/4b8ce4
https://coub.com/view/4b8ce2
https://coub.com/view/4b8ce1
https://coub.com/view/4b8cdz
https://coub.com/view/4b8cdx
https://coub.com/view/4b8cdw
https://coub.com/view/4b8cdv
https://coub.com/view/4b8cdu
https://coub.com/view/4b8cdt
https://coub.com/view/4b8cdr
https://coub.com/view/4b8cdq
https://coub.com/view/4b8cdp
https://coub.com/view/4b8cdo
https://coub.com/view/4b8cdn
https://coub.com/view/4b8cdm
https://coub.com/view/4b8cdl
https://coub.com/view/4b8cdk
https://coub.com/view/4b8cdj
https://coub.com/view/4b8cdi
https://coub.com/view/4b8cdh
https://coub.com/view/4b8cdf
https://coub.com/view/4b8cde
https://coub.com/view/4b8cdc
https://coub.com/view/4b8cda
https://coub.com/view/4b8cd5
https://coub.com/view/4b8cd4
https://coub.com/view/4b8cd2
https://coub.com/view/4b8cd0
https://coub.com/view/4b8ccx
https://coub.com/view/4b8ccw
https://coub.com/view/4b8ccv
https://coub.com/view/4b8ccs
https://coub.com/view/4b8ccq
https://coub.com/view/4b8ccn
https://coub.com/view/4b8ccj
https://coub.com/view/4b8cci
https://coub.com/view/4b8cca
https://coub.com/view/4b8cc3
https://coub.com/view/4b8cc1
https://coub.com/view/4b8cbz
https://coub.com/view/4b8cbw
https://coub.com/view/4b8cbt
https://coub.com/view/4b8cbl
https://coub.com/view/4b8cbk
https://coub.com/view/4b8cbh
https://coub.com/view/4b8cbf
https://coub.com/view/4b8cbe
https://coub.com/view/4b8cbd
https://coub.com/view/4b8cbb
https://coub.com/view/4b8cba
https://coub.com/view/4b8cb9
https://coub.com/view/4b8cb7
https://coub.com/view/4b8cb5
https://coub.com/view/4b8cb4
https://coub.com/view/4b8cb3
https://coub.com/view/4b8cb0
https://coub.com/view/4b8cax
https://coub.com/view/4b8caw
https://coub.com/view/4b8cau
https://coub.com/view/4b8cat
https://coub.com/view/4b8car
https://coub.com/view/4b8caq
https://coub.com/view/4b8cai
https://coub.com/view/4b8cah
https://coub.com/view/4b8caf
https://coub.com/view/4b8cae
https://coub.com/view/4b8cac
https://coub.com/view/4b8cab
https://coub.com/view/4b8caa
https://coub.com/view/4b8ca8
https://coub.com/view/4b8ca7
https://coub.com/view/4b8ca6
https://coub.com/view/4b8ca4
https://coub.com/view/4b8ca3
https://coub.com/view/4b8ca2
https://coub.com/view/4b8ca1
https://coub.com/view/4b8ca0
https://coub.com/view/4b8c9y
https://coub.com/view/4b8c9x
https://coub.com/view/4b8c9w
https://coub.com/view/4b8c9v
https://coub.com/view/4b8c9u

https://coub.com/view/4b87sb
https://coub.com/view/4b7yjg
https://coub.com/view/4b7yjf
https://coub.com/view/4b7yjd
https://coub.com/view/4b7yiw
https://coub.com/view/4b7yis
https://coub.com/view/4b7yip
https://coub.com/view/4b7yik
https://coub.com/view/4b7yij
https://coub.com/view/4b7yig
https://coub.com/view/4b7yie
https://coub.com/view/4b7yic
https://coub.com/view/4b7yia
https://coub.com/view/4b7yi7
https://coub.com/view/4b7yi5
https://coub.com/view/4b7yi3
https://coub.com/view/4b7yhy
https://coub.com/view/4b7yht
https://coub.com/view/4b7yhr
https://coub.com/view/4b7yhp
https://coub.com/view/4b7yhh
https://coub.com/view/4b7yhg
https://coub.com/view/4b7yhf
https://coub.com/view/4b7yhb
https://coub.com/view/4b7yh9
https://coub.com/view/4b7yh6
https://coub.com/view/4b7yh4
https://coub.com/view/4b7ygz
https://coub.com/view/4b7ygx
https://coub.com/view/4b7ygu
https://coub.com/view/4b7ygr
https://coub.com/view/4b7ygn
https://coub.com/view/4b7ygk
https://coub.com/view/4b7ygh
https://coub.com/view/4b7ygf
https://coub.com/view/4b7ygd
https://coub.com/view/4b7ygb
https://coub.com/view/4b7yg8
https://coub.com/view/4b7yg5
https://coub.com/view/4b7yg2
https://coub.com/view/4b7yfr
https://coub.com/view/4b7yfo
https://coub.com/view/4b7yfk
https://coub.com/view/4b7yfh
https://coub.com/view/4b7yfe
https://coub.com/view/4b7yfc
https://coub.com/view/4b7yfa
https://coub.com/view/4b7yf7
https://coub.com/view/4b7yf5
https://coub.com/view/4b7yf1
https://coub.com/view/4b7yez
https://coub.com/view/4b7yet
https://coub.com/view/4b7yer
https://coub.com/view/4b7yep
https://coub.com/view/4b7yeo
https://coub.com/view/4b7ub4
https://coub.com/view/4b7ub3
https://coub.com/view/4b7ub2
https://coub.com/view/4b7ub0
https://coub.com/view/4b7uay
https://coub.com/view/4b7uax
https://coub.com/view/4b7uav
https://coub.com/view/4b7uap
https://coub.com/view/4b7uao
https://coub.com/view/4b7ual
https://coub.com/view/4b7uad
https://coub.com/view/4b7uaa
https://coub.com/view/4b7ua7
https://coub.com/view/4b7ua5
https://coub.com/view/4b7ua2
https://coub.com/view/4b7ua1
https://coub.com/view/4b7u9z
https://coub.com/view/4b7u9y
https://coub.com/view/4b7u9x
https://coub.com/view/4b7u9p
https://coub.com/view/4b7u9k
https://coub.com/view/4b7u9j
https://coub.com/view/4b7u9i
https://coub.com/view/4b7u9g
https://coub.com/view/4b7u9d
https://coub.com/view/4b7u9a
https://coub.com/view/4b7u98
https://coub.com/view/4b7u96
https://coub.com/view/4b7u94
https://coub.com/view/4b7u92
https://coub.com/view/4b7u91
https://coub.com/view/4b7u8z
https://coub.com/view/4b7u8t
https://coub.com/view/4b7u8q
https://coub.com/view/4b7u8p
https://coub.com/view/4b7u8n
https://coub.com/view/4b7u8j
https://coub.com/view/4b7u8i
https://coub.com/view/4b7u78
https://coub.com/view/4b7u76
https://coub.com/view/4b7u72
https://coub.com/view/4b7u6v
https://coub.com/view/4b7u6t
https://coub.com/view/4b7u6r
https://coub.com/view/4b7u6o
https://coub.com/view/4b7u6m
https://coub.com/view/4b7u6l
https://coub.com/view/4b7u6j
https://coub.com/view/4b7u6h
https://coub.com/view/4b7u6f
https://coub.com/view/4b7u6d
https://coub.com/view/4b7u6a
https://coub.com/view/4b7u67
https://coub.com/view/4b7u65
https://coub.com/view/4b7u60
https://coub.com/view/4b7u5t
https://coub.com/view/4b7u5p
https://coub.com/view/4b7u5i
https://coub.com/view/4b7u4t
https://coub.com/view/4b7u4p
https://coub.com/view/4b7u4l
https://coub.com/view/4b7u46
https://coub.com/view/4b7u3v
https://coub.com/view/4b7u3u
https://coub.com/view/4b7u3t
https://coub.com/view/4b7u3r
https://coub.com/view/4b7u3o
https://coub.com/view/4b7u3l
https://coub.com/view/4b7u3k
https://coub.com/view/4b7u3h
https://coub.com/view/4b7u3a
https://coub.com/view/4b7u38
https://coub.com/view/4b7u35
https://coub.com/view/4b7u34
https://coub.com/view/4b7u33
https://coub.com/view/4b7u30
https://coub.com/view/4b7u2z
https://coub.com/view/4b7u2w

继续阅读 »

问题描述

运行下面demo,点击使色块消失时,会突然出现在左上角闪烁。

@Entry
@ComponentV2
struct Index {
@Local isShow: boolean = true;

build() {
Stack() {
Stack() {
if (this.isShow) {
Row()
.width(100)
.height(100)
.backgroundColor(Color.Red)
.transition(TransitionEffect.OPACITY.animation({ duration: 150 }));
}
}
.backgroundColor(Color.Green)
}
.width('100%')
.height('100%')
.onClick(() => {
this.isShow = !this.isShow;
});
}
}
解决措施

原因是当变量置为false时,组件会从视图树中移除,导致内层Stack失去内容后位置重置到左上角,可以固定内层Stack组件的宽高来避免,示例如下:

@Entry
@ComponentV2
struct DisappearanceAnimationOffsetFlicker {
@Local isShow: boolean = true;

build() {
Stack() {
Stack() {
if (this.isShow) {
Row()
.width(100)
.height(100)
.backgroundColor(Color.Red)
.transition(TransitionEffect.OPACITY.animation({ duration: 150 }))
}
}
.width('100%')
.height('100%')
.onClick(() => {
this.isShow = !this.isShow
})
}
}
}
https://coub.com/view/4b8cf1
https://coub.com/view/4b8cez
https://coub.com/view/4b8cev
https://coub.com/view/4b8ceu
https://coub.com/view/4b8cep
https://coub.com/view/4b8cen
https://coub.com/view/4b8cel
https://coub.com/view/4b8ceh
https://coub.com/view/4b8ceg
https://coub.com/view/4b8cee
https://coub.com/view/4b8ced
https://coub.com/view/4b8ceb
https://coub.com/view/4b8cea
https://coub.com/view/4b8ce8
https://coub.com/view/4b8ce6
https://coub.com/view/4b8ce5
https://coub.com/view/4b8ce4
https://coub.com/view/4b8ce2
https://coub.com/view/4b8ce1
https://coub.com/view/4b8cdz
https://coub.com/view/4b8cdx
https://coub.com/view/4b8cdw
https://coub.com/view/4b8cdv
https://coub.com/view/4b8cdu
https://coub.com/view/4b8cdt
https://coub.com/view/4b8cdr
https://coub.com/view/4b8cdq
https://coub.com/view/4b8cdp
https://coub.com/view/4b8cdo
https://coub.com/view/4b8cdn
https://coub.com/view/4b8cdm
https://coub.com/view/4b8cdl
https://coub.com/view/4b8cdk
https://coub.com/view/4b8cdj
https://coub.com/view/4b8cdi
https://coub.com/view/4b8cdh
https://coub.com/view/4b8cdf
https://coub.com/view/4b8cde
https://coub.com/view/4b8cdc
https://coub.com/view/4b8cda
https://coub.com/view/4b8cd5
https://coub.com/view/4b8cd4
https://coub.com/view/4b8cd2
https://coub.com/view/4b8cd0
https://coub.com/view/4b8ccx
https://coub.com/view/4b8ccw
https://coub.com/view/4b8ccv
https://coub.com/view/4b8ccs
https://coub.com/view/4b8ccq
https://coub.com/view/4b8ccn
https://coub.com/view/4b8ccj
https://coub.com/view/4b8cci
https://coub.com/view/4b8cca
https://coub.com/view/4b8cc3
https://coub.com/view/4b8cc1
https://coub.com/view/4b8cbz
https://coub.com/view/4b8cbw
https://coub.com/view/4b8cbt
https://coub.com/view/4b8cbl
https://coub.com/view/4b8cbk
https://coub.com/view/4b8cbh
https://coub.com/view/4b8cbf
https://coub.com/view/4b8cbe
https://coub.com/view/4b8cbd
https://coub.com/view/4b8cbb
https://coub.com/view/4b8cba
https://coub.com/view/4b8cb9
https://coub.com/view/4b8cb7
https://coub.com/view/4b8cb5
https://coub.com/view/4b8cb4
https://coub.com/view/4b8cb3
https://coub.com/view/4b8cb0
https://coub.com/view/4b8cax
https://coub.com/view/4b8caw
https://coub.com/view/4b8cau
https://coub.com/view/4b8cat
https://coub.com/view/4b8car
https://coub.com/view/4b8caq
https://coub.com/view/4b8cai
https://coub.com/view/4b8cah
https://coub.com/view/4b8caf
https://coub.com/view/4b8cae
https://coub.com/view/4b8cac
https://coub.com/view/4b8cab
https://coub.com/view/4b8caa
https://coub.com/view/4b8ca8
https://coub.com/view/4b8ca7
https://coub.com/view/4b8ca6
https://coub.com/view/4b8ca4
https://coub.com/view/4b8ca3
https://coub.com/view/4b8ca2
https://coub.com/view/4b8ca1
https://coub.com/view/4b8ca0
https://coub.com/view/4b8c9y
https://coub.com/view/4b8c9x
https://coub.com/view/4b8c9w
https://coub.com/view/4b8c9v
https://coub.com/view/4b8c9u

https://coub.com/view/4b87sb
https://coub.com/view/4b7yjg
https://coub.com/view/4b7yjf
https://coub.com/view/4b7yjd
https://coub.com/view/4b7yiw
https://coub.com/view/4b7yis
https://coub.com/view/4b7yip
https://coub.com/view/4b7yik
https://coub.com/view/4b7yij
https://coub.com/view/4b7yig
https://coub.com/view/4b7yie
https://coub.com/view/4b7yic
https://coub.com/view/4b7yia
https://coub.com/view/4b7yi7
https://coub.com/view/4b7yi5
https://coub.com/view/4b7yi3
https://coub.com/view/4b7yhy
https://coub.com/view/4b7yht
https://coub.com/view/4b7yhr
https://coub.com/view/4b7yhp
https://coub.com/view/4b7yhh
https://coub.com/view/4b7yhg
https://coub.com/view/4b7yhf
https://coub.com/view/4b7yhb
https://coub.com/view/4b7yh9
https://coub.com/view/4b7yh6
https://coub.com/view/4b7yh4
https://coub.com/view/4b7ygz
https://coub.com/view/4b7ygx
https://coub.com/view/4b7ygu
https://coub.com/view/4b7ygr
https://coub.com/view/4b7ygn
https://coub.com/view/4b7ygk
https://coub.com/view/4b7ygh
https://coub.com/view/4b7ygf
https://coub.com/view/4b7ygd
https://coub.com/view/4b7ygb
https://coub.com/view/4b7yg8
https://coub.com/view/4b7yg5
https://coub.com/view/4b7yg2
https://coub.com/view/4b7yfr
https://coub.com/view/4b7yfo
https://coub.com/view/4b7yfk
https://coub.com/view/4b7yfh
https://coub.com/view/4b7yfe
https://coub.com/view/4b7yfc
https://coub.com/view/4b7yfa
https://coub.com/view/4b7yf7
https://coub.com/view/4b7yf5
https://coub.com/view/4b7yf1
https://coub.com/view/4b7yez
https://coub.com/view/4b7yet
https://coub.com/view/4b7yer
https://coub.com/view/4b7yep
https://coub.com/view/4b7yeo
https://coub.com/view/4b7ub4
https://coub.com/view/4b7ub3
https://coub.com/view/4b7ub2
https://coub.com/view/4b7ub0
https://coub.com/view/4b7uay
https://coub.com/view/4b7uax
https://coub.com/view/4b7uav
https://coub.com/view/4b7uap
https://coub.com/view/4b7uao
https://coub.com/view/4b7ual
https://coub.com/view/4b7uad
https://coub.com/view/4b7uaa
https://coub.com/view/4b7ua7
https://coub.com/view/4b7ua5
https://coub.com/view/4b7ua2
https://coub.com/view/4b7ua1
https://coub.com/view/4b7u9z
https://coub.com/view/4b7u9y
https://coub.com/view/4b7u9x
https://coub.com/view/4b7u9p
https://coub.com/view/4b7u9k
https://coub.com/view/4b7u9j
https://coub.com/view/4b7u9i
https://coub.com/view/4b7u9g
https://coub.com/view/4b7u9d
https://coub.com/view/4b7u9a
https://coub.com/view/4b7u98
https://coub.com/view/4b7u96
https://coub.com/view/4b7u94
https://coub.com/view/4b7u92
https://coub.com/view/4b7u91
https://coub.com/view/4b7u8z
https://coub.com/view/4b7u8t
https://coub.com/view/4b7u8q
https://coub.com/view/4b7u8p
https://coub.com/view/4b7u8n
https://coub.com/view/4b7u8j
https://coub.com/view/4b7u8i
https://coub.com/view/4b7u78
https://coub.com/view/4b7u76
https://coub.com/view/4b7u72
https://coub.com/view/4b7u6v
https://coub.com/view/4b7u6t
https://coub.com/view/4b7u6r
https://coub.com/view/4b7u6o
https://coub.com/view/4b7u6m
https://coub.com/view/4b7u6l
https://coub.com/view/4b7u6j
https://coub.com/view/4b7u6h
https://coub.com/view/4b7u6f
https://coub.com/view/4b7u6d
https://coub.com/view/4b7u6a
https://coub.com/view/4b7u67
https://coub.com/view/4b7u65
https://coub.com/view/4b7u60
https://coub.com/view/4b7u5t
https://coub.com/view/4b7u5p
https://coub.com/view/4b7u5i
https://coub.com/view/4b7u4t
https://coub.com/view/4b7u4p
https://coub.com/view/4b7u4l
https://coub.com/view/4b7u46
https://coub.com/view/4b7u3v
https://coub.com/view/4b7u3u
https://coub.com/view/4b7u3t
https://coub.com/view/4b7u3r
https://coub.com/view/4b7u3o
https://coub.com/view/4b7u3l
https://coub.com/view/4b7u3k
https://coub.com/view/4b7u3h
https://coub.com/view/4b7u3a
https://coub.com/view/4b7u38
https://coub.com/view/4b7u35
https://coub.com/view/4b7u34
https://coub.com/view/4b7u33
https://coub.com/view/4b7u30
https://coub.com/view/4b7u2z
https://coub.com/view/4b7u2w

收起阅读 »

打包

打包

三点打包。提示还有一分钟开始打包,然后打到五点。 打包两小时。这是不让下班了

三点打包。提示还有一分钟开始打包,然后打到五点。 打包两小时。这是不让下班了

如何实现背景以圆弧形式向外扩展的翻页动画效果

问题现象
如何实现一种翻页动画效果,其中背景以圆弧形式逐步扩大,同时文字向左下方放大并移动?

背景知识
Stack:堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。
createAnimator:定义Animator类,可以设置动画播放时长、插值曲线等参数。
解决方案
可通过帧动画实现页面转场效果,具体步骤如下:

定义两个Page组件,分别用于展示不同的页面内容。
class PageContent {
content: string;
style: TextStyle;

constructor(content: string, style: TextStyle = {}) {
this.content = content;
this.style = style;
}
}
配置页面切换时的动画。
this.animator = this.getUIContext().createAnimator({
duration: 500,
easing: 'ease',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 0, //动画插值起点
end: 500 //动画插值终点
});
this.animator.onFrame = progress => {
this.pageController[0].onFrame?.(progress / 500, this.currentIndex === 1);
this.pageController[1].onFrame?.(progress / 500, this.currentIndex === 0);
};
this.animator.onFinish = () => {
let lastIndex = this.currentIndex;
this.currentIndex = 1 - this.currentIndex;
this.zIndexList[lastIndex] = 1;
this.zIndexList[this.currentIndex] = 0;
this.isAnimating = false;
};
使用Stack布局将两个页面组件堆叠显示。
Stack({ alignContent: Alignment.TopStart }) {
Page({
content: page1Content,
bgColor: '#000',
scaleValue: 1,
controller: this.pageController[0],
isShow: true
})
.zIndex(this.zIndexList[0])
.onClick(() => {
this.handleClick();
});
Page({
content: page2Content,
bgColor: '#fff',
scaleValue: 0.01,
controller: this.pageController[1]
})
.zIndex(this.zIndexList[1])
.onClick(() => {
this.handleClick();
});
}
.width('100%')
.height('100%')
.id('root')
.clip(true);
在用户点击右上角圆形按钮时,触发动画切换。
handleClick() {
if (this.isAnimating) {
return;
}
this.isAnimating = true;
this.animator?.play();
}
完整示例代码如下:

import { AnimatorResult } from '@kit.ArkUI';

class PageContent {
content: string;
style: TextStyle;

constructor(content: string, style: TextStyle = {}) {
this.content = content;
this.style = style;
}
}

const page1Content: PageContent[] = [
new PageContent('Hello everyone, I`am very happy to tell you hello world ',
{ fontSize: 30, fontWeight: 700, fontColor: Color.White }),
new PageContent('The text component is used to display a piece of textual information.Support universal attributes and universal text attributes.',
{ fontColor: Color.White }),
new PageContent('This is the setting of textOverflow to Clip text content This is the setting of textOverflow to None text content. This is the setting of textOverflow to Clip text content This is the setting of textOverflow to None text content.',
{ fontColor: Color.White }),
];

const page2Content: PageContent[] = [
new PageContent('Hello everyone, Please say hello! ', { fontSize: 30 }),
new PageContent('Nice to meet you.', { fontSize: 30 }),
new PageContent('I`m fine thank you and you.', { fontSize: 30 }),
new PageContent('No problem.', { fontSize: 30 }),
];

interface PageInfo {
width: number,
height: number,
centerX: number,
centerY: number
}

class PageController {
push?: (onFinish: () => void) => void;
pop?: (onFinish: () => void) => void;
onFrame?: (progress: number, isPush: boolean) => void;
}

@Entry
@Component
export struct TransitionPage {
@Provide info: PageInfo = {
width: 0,
height: 0,
centerX: 0,
centerY: 0
};
pageController: PageController[] = [];
@State zIndexList: number[] = [];
private isAnimating: boolean = false;
private animator?: AnimatorResult;
private currentIndex: number = 0;

aboutToAppear(): void {
let observer = this.getUIContext().getUIInspector().createComponentObserver('root');
observer.on('layout', () => {
let node = this.getUIContext().getAttachedFrameNodeById('root');
if (node) {
this.info = {
width: this.getUIContext().px2vp(node.getMeasuredSize().width),
height: this.getUIContext().px2vp(node.getMeasuredSize().height),
centerY: 50,
centerX: this.getUIContext().px2vp(node.getMeasuredSize().width) - 40
};
}
});
for (let i = 0; i < 2; i++) {
this.pageController.push(new PageController());
this.zIndexList.push(i);
}
// 页面切换时的动画
this.animator = this.getUIContext().createAnimator({
duration: 500,
easing: 'ease',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 0, //动画插值起点
end: 500 //动画插值终点
});
this.animator.onFrame = progress => {
this.pageController[0].onFrame?.(progress / 500, this.currentIndex === 1);
this.pageController[1].onFrame?.(progress / 500, this.currentIndex === 0);
};
this.animator.onFinish = () => {
let lastIndex = this.currentIndex;
this.currentIndex = 1 - this.currentIndex;
this.zIndexList[lastIndex] = 1;
this.zIndexList[this.currentIndex] = 0;
this.isAnimating = false;
};
}

aboutToDisappear(): void {
this.animator?.finish();
this.animator = undefined;
}

push(index: number) {
let promise: Promise<void>[] = [];
promise.push(new Promise(resolve => {
this.pageController[index].push?.(() => {
this.zIndexList[index] = 0;
resolve();
});
}));
let lastIndex = 1 - index;
promise.push(new Promise(resolve => {
this.pageController[lastIndex].pop?.(() => {
this.zIndexList[lastIndex] = 1;
resolve();
});
}));
Promise.all(promise).then(() => this.isAnimating = false);
}
// 动画切换事件
handleClick() {
if (this.isAnimating) {
return;
}
this.isAnimating = true;
this.animator?.play();
}

build() {
// 两个页面组件堆叠显示
Stack({ alignContent: Alignment.TopStart }) {
Page({
content: page1Content,
bgColor: '#000',
scaleValue: 1,
controller: this.pageController[0],
isShow: true
})
.zIndex(this.zIndexList[0])
.onClick(() => {
this.handleClick();
});
Page({
content: page2Content,
bgColor: '#fff',
scaleValue: 0.01,
controller: this.pageController[1]
})
.zIndex(this.zIndexList[1])
.onClick(() => {
this.handleClick();
});
}
.width('100%')
.height('100%')
.id('root')
.clip(true);
}

onDidBuild(): void {
if (!this.animator) {
return;
}
}
}

@Component
struct Page {
@Require content: PageContent[] = [];
@Consume @Watch('onInit') info: PageInfo;
@State scaleValue: number = 0.01;
@State r: number = 0;
@State isShow: boolean = false;
bgColor: string = '#fff';
controller?: PageController;

onInit() {
this.r = Math.sqrt(this.info.centerX this.info.centerX +
(this.info.height - this.info.centerY)
(this.info.height - this.info.centerY));
}

aboutToAppear(): void {
if (this.controller) {
this.controller.push = (onFinish) => {
this.onPush(onFinish);
};
this.controller.pop = (onFinish) => {
this.onPop(onFinish);
};
this.controller.onFrame = (progress, isPush) => {
if (isPush) {
this.isShow = true;
this.scaleValue = 0.01 + (1 - 0.01) progress;
} else {
this.scaleValue = 1 + (4 - 1)
progress;
if (progress >= 1) {
this.isShow = false;
this.scaleValue = 0.01;
}
}
};
}
}

onPush(onFinish: () => void) {
this.isShow = true;
this.getUIContext().animateTo({ duration: 500, onFinish: onFinish }, () => {
this.scaleValue = 1;
});
}

onPop(onFinish: () => void) {
this.getUIContext().animateTo({
duration: 500,
onFinish: () => {
this.scaleValue = 0.01;
this.isShow = false;
onFinish();
}
}, () => {
this.scaleValue = 4;
});
}

build() {
Column() {
if (this.isShow) {
Column({ space: 20 }) {
ForEach(this.content, (item: PageContent) => {
Text(item.content)
.fontSize(item.style.fontSize)
.fontColor(item.style.fontColor)
.fontWeight(item.style.fontWeight)
.fontStyle(item.style.fontStyle)
.fontFamily(item.style.fontFamily);
});
}
.position({ x: this.r - this.info.centerX, y: this.r - this.info.centerY })
.padding({ left: 16, right:16 })
.width(this.info.width)
.height(this.info.height)
.justifyContent(FlexAlign.SpaceEvenly);
}
}
.width(this.r 2)
.height(this.r
2)
.constraintSize({ minWidth: this.r * 2 })
.scale({ x: this.scaleValue, y: this.scaleValue })
.offset({ x: this.info.centerX - this.r, y: this.info.centerY - this.r })
.borderRadius(this.r)
.clip(true)
.backgroundColor(this.bgColor);
}
}
https://coub.com/view/4b7yjg
https://coub.com/view/4b7yjf
https://coub.com/view/4b7yjd
https://coub.com/view/4b7yiw
https://coub.com/view/4b7yis
https://coub.com/view/4b7yip
https://coub.com/view/4b7yik
https://coub.com/view/4b7yij
https://coub.com/view/4b7yig
https://coub.com/view/4b7yie
https://coub.com/view/4b7yic
https://coub.com/view/4b7yia
https://coub.com/view/4b7yi7
https://coub.com/view/4b7yi5
https://coub.com/view/4b7yi3
https://coub.com/view/4b7yhy
https://coub.com/view/4b7yht
https://coub.com/view/4b7yhr
https://coub.com/view/4b7yhp
https://coub.com/view/4b7yhh
https://coub.com/view/4b7yhg
https://coub.com/view/4b7yhf
https://coub.com/view/4b7yhb
https://coub.com/view/4b7yh9
https://coub.com/view/4b7yh6
https://coub.com/view/4b7yh4
https://coub.com/view/4b7ygz
https://coub.com/view/4b7ygx
https://coub.com/view/4b7ygu
https://coub.com/view/4b7ygr
https://coub.com/view/4b7ygn
https://coub.com/view/4b7ygk
https://coub.com/view/4b7ygh
https://coub.com/view/4b7ygf
https://coub.com/view/4b7ygd
https://coub.com/view/4b7ygb
https://coub.com/view/4b7yg8
https://coub.com/view/4b7yg5
https://coub.com/view/4b7yg2
https://coub.com/view/4b7yfr
https://coub.com/view/4b7yfo
https://coub.com/view/4b7yfk
https://coub.com/view/4b7yfh
https://coub.com/view/4b7yfe
https://coub.com/view/4b7yfc
https://coub.com/view/4b7yfa
https://coub.com/view/4b7yf7
https://coub.com/view/4b7yf5
https://coub.com/view/4b7yf1
https://coub.com/view/4b7yez
https://coub.com/view/4b7yet
https://coub.com/view/4b7yer
https://coub.com/view/4b7yep
https://coub.com/view/4b7yeo
https://coub.com/view/4b7ub4
https://coub.com/view/4b7ub3
https://coub.com/view/4b7ub2
https://coub.com/view/4b7ub0
https://coub.com/view/4b7uay
https://coub.com/view/4b7uax
https://coub.com/view/4b7uav
https://coub.com/view/4b7uap
https://coub.com/view/4b7uao
https://coub.com/view/4b7ual
https://coub.com/view/4b7uad
https://coub.com/view/4b7uaa
https://coub.com/view/4b7ua7
https://coub.com/view/4b7ua5
https://coub.com/view/4b7ua2
https://coub.com/view/4b7ua1
https://coub.com/view/4b7u9z
https://coub.com/view/4b7u9y
https://coub.com/view/4b7u9x
https://coub.com/view/4b7u9p
https://coub.com/view/4b7u9k
https://coub.com/view/4b7u9j
https://coub.com/view/4b7u9i
https://coub.com/view/4b7u9g
https://coub.com/view/4b7u9d
https://coub.com/view/4b7u9a
https://coub.com/view/4b7u98
https://coub.com/view/4b7u96
https://coub.com/view/4b7u94
https://coub.com/view/4b7u92
https://coub.com/view/4b7u91
https://coub.com/view/4b7u8z
https://coub.com/view/4b7u8t
https://coub.com/view/4b7u8q
https://coub.com/view/4b7u8p
https://coub.com/view/4b7u8n
https://coub.com/view/4b7u8j
https://coub.com/view/4b7u8i
https://coub.com/view/4b7u78
https://coub.com/view/4b7u76
https://coub.com/view/4b7u72
https://coub.com/view/4b7u6v
https://coub.com/view/4b7u6t
https://coub.com/view/4b7u6r
https://coub.com/view/4b7u6o
https://coub.com/view/4b7u6m
https://coub.com/view/4b7u6l
https://coub.com/view/4b7u6j
https://coub.com/view/4b7u6h
https://coub.com/view/4b7u6f
https://coub.com/view/4b7u6d
https://coub.com/view/4b7u6a
https://coub.com/view/4b7u67
https://coub.com/view/4b7u65
https://coub.com/view/4b7u60
https://coub.com/view/4b7u5t
https://coub.com/view/4b7u5p
https://coub.com/view/4b7u5i
https://coub.com/view/4b7u4t
https://coub.com/view/4b7u4p
https://coub.com/view/4b7u4l
https://coub.com/view/4b7u46
https://coub.com/view/4b7u3v
https://coub.com/view/4b7u3u
https://coub.com/view/4b7u3t
https://coub.com/view/4b7u3r
https://coub.com/view/4b7u3o
https://coub.com/view/4b7u3l
https://coub.com/view/4b7u3k
https://coub.com/view/4b7u3h
https://coub.com/view/4b7u3a
https://coub.com/view/4b7u38
https://coub.com/view/4b7u35
https://coub.com/view/4b7u34
https://coub.com/view/4b7u33
https://coub.com/view/4b7u30
https://coub.com/view/4b7u2z
https://coub.com/view/4b7u2w

继续阅读 »

问题现象
如何实现一种翻页动画效果,其中背景以圆弧形式逐步扩大,同时文字向左下方放大并移动?

背景知识
Stack:堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。
createAnimator:定义Animator类,可以设置动画播放时长、插值曲线等参数。
解决方案
可通过帧动画实现页面转场效果,具体步骤如下:

定义两个Page组件,分别用于展示不同的页面内容。
class PageContent {
content: string;
style: TextStyle;

constructor(content: string, style: TextStyle = {}) {
this.content = content;
this.style = style;
}
}
配置页面切换时的动画。
this.animator = this.getUIContext().createAnimator({
duration: 500,
easing: 'ease',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 0, //动画插值起点
end: 500 //动画插值终点
});
this.animator.onFrame = progress => {
this.pageController[0].onFrame?.(progress / 500, this.currentIndex === 1);
this.pageController[1].onFrame?.(progress / 500, this.currentIndex === 0);
};
this.animator.onFinish = () => {
let lastIndex = this.currentIndex;
this.currentIndex = 1 - this.currentIndex;
this.zIndexList[lastIndex] = 1;
this.zIndexList[this.currentIndex] = 0;
this.isAnimating = false;
};
使用Stack布局将两个页面组件堆叠显示。
Stack({ alignContent: Alignment.TopStart }) {
Page({
content: page1Content,
bgColor: '#000',
scaleValue: 1,
controller: this.pageController[0],
isShow: true
})
.zIndex(this.zIndexList[0])
.onClick(() => {
this.handleClick();
});
Page({
content: page2Content,
bgColor: '#fff',
scaleValue: 0.01,
controller: this.pageController[1]
})
.zIndex(this.zIndexList[1])
.onClick(() => {
this.handleClick();
});
}
.width('100%')
.height('100%')
.id('root')
.clip(true);
在用户点击右上角圆形按钮时,触发动画切换。
handleClick() {
if (this.isAnimating) {
return;
}
this.isAnimating = true;
this.animator?.play();
}
完整示例代码如下:

import { AnimatorResult } from '@kit.ArkUI';

class PageContent {
content: string;
style: TextStyle;

constructor(content: string, style: TextStyle = {}) {
this.content = content;
this.style = style;
}
}

const page1Content: PageContent[] = [
new PageContent('Hello everyone, I`am very happy to tell you hello world ',
{ fontSize: 30, fontWeight: 700, fontColor: Color.White }),
new PageContent('The text component is used to display a piece of textual information.Support universal attributes and universal text attributes.',
{ fontColor: Color.White }),
new PageContent('This is the setting of textOverflow to Clip text content This is the setting of textOverflow to None text content. This is the setting of textOverflow to Clip text content This is the setting of textOverflow to None text content.',
{ fontColor: Color.White }),
];

const page2Content: PageContent[] = [
new PageContent('Hello everyone, Please say hello! ', { fontSize: 30 }),
new PageContent('Nice to meet you.', { fontSize: 30 }),
new PageContent('I`m fine thank you and you.', { fontSize: 30 }),
new PageContent('No problem.', { fontSize: 30 }),
];

interface PageInfo {
width: number,
height: number,
centerX: number,
centerY: number
}

class PageController {
push?: (onFinish: () => void) => void;
pop?: (onFinish: () => void) => void;
onFrame?: (progress: number, isPush: boolean) => void;
}

@Entry
@Component
export struct TransitionPage {
@Provide info: PageInfo = {
width: 0,
height: 0,
centerX: 0,
centerY: 0
};
pageController: PageController[] = [];
@State zIndexList: number[] = [];
private isAnimating: boolean = false;
private animator?: AnimatorResult;
private currentIndex: number = 0;

aboutToAppear(): void {
let observer = this.getUIContext().getUIInspector().createComponentObserver('root');
observer.on('layout', () => {
let node = this.getUIContext().getAttachedFrameNodeById('root');
if (node) {
this.info = {
width: this.getUIContext().px2vp(node.getMeasuredSize().width),
height: this.getUIContext().px2vp(node.getMeasuredSize().height),
centerY: 50,
centerX: this.getUIContext().px2vp(node.getMeasuredSize().width) - 40
};
}
});
for (let i = 0; i < 2; i++) {
this.pageController.push(new PageController());
this.zIndexList.push(i);
}
// 页面切换时的动画
this.animator = this.getUIContext().createAnimator({
duration: 500,
easing: 'ease',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 0, //动画插值起点
end: 500 //动画插值终点
});
this.animator.onFrame = progress => {
this.pageController[0].onFrame?.(progress / 500, this.currentIndex === 1);
this.pageController[1].onFrame?.(progress / 500, this.currentIndex === 0);
};
this.animator.onFinish = () => {
let lastIndex = this.currentIndex;
this.currentIndex = 1 - this.currentIndex;
this.zIndexList[lastIndex] = 1;
this.zIndexList[this.currentIndex] = 0;
this.isAnimating = false;
};
}

aboutToDisappear(): void {
this.animator?.finish();
this.animator = undefined;
}

push(index: number) {
let promise: Promise<void>[] = [];
promise.push(new Promise(resolve => {
this.pageController[index].push?.(() => {
this.zIndexList[index] = 0;
resolve();
});
}));
let lastIndex = 1 - index;
promise.push(new Promise(resolve => {
this.pageController[lastIndex].pop?.(() => {
this.zIndexList[lastIndex] = 1;
resolve();
});
}));
Promise.all(promise).then(() => this.isAnimating = false);
}
// 动画切换事件
handleClick() {
if (this.isAnimating) {
return;
}
this.isAnimating = true;
this.animator?.play();
}

build() {
// 两个页面组件堆叠显示
Stack({ alignContent: Alignment.TopStart }) {
Page({
content: page1Content,
bgColor: '#000',
scaleValue: 1,
controller: this.pageController[0],
isShow: true
})
.zIndex(this.zIndexList[0])
.onClick(() => {
this.handleClick();
});
Page({
content: page2Content,
bgColor: '#fff',
scaleValue: 0.01,
controller: this.pageController[1]
})
.zIndex(this.zIndexList[1])
.onClick(() => {
this.handleClick();
});
}
.width('100%')
.height('100%')
.id('root')
.clip(true);
}

onDidBuild(): void {
if (!this.animator) {
return;
}
}
}

@Component
struct Page {
@Require content: PageContent[] = [];
@Consume @Watch('onInit') info: PageInfo;
@State scaleValue: number = 0.01;
@State r: number = 0;
@State isShow: boolean = false;
bgColor: string = '#fff';
controller?: PageController;

onInit() {
this.r = Math.sqrt(this.info.centerX this.info.centerX +
(this.info.height - this.info.centerY)
(this.info.height - this.info.centerY));
}

aboutToAppear(): void {
if (this.controller) {
this.controller.push = (onFinish) => {
this.onPush(onFinish);
};
this.controller.pop = (onFinish) => {
this.onPop(onFinish);
};
this.controller.onFrame = (progress, isPush) => {
if (isPush) {
this.isShow = true;
this.scaleValue = 0.01 + (1 - 0.01) progress;
} else {
this.scaleValue = 1 + (4 - 1)
progress;
if (progress >= 1) {
this.isShow = false;
this.scaleValue = 0.01;
}
}
};
}
}

onPush(onFinish: () => void) {
this.isShow = true;
this.getUIContext().animateTo({ duration: 500, onFinish: onFinish }, () => {
this.scaleValue = 1;
});
}

onPop(onFinish: () => void) {
this.getUIContext().animateTo({
duration: 500,
onFinish: () => {
this.scaleValue = 0.01;
this.isShow = false;
onFinish();
}
}, () => {
this.scaleValue = 4;
});
}

build() {
Column() {
if (this.isShow) {
Column({ space: 20 }) {
ForEach(this.content, (item: PageContent) => {
Text(item.content)
.fontSize(item.style.fontSize)
.fontColor(item.style.fontColor)
.fontWeight(item.style.fontWeight)
.fontStyle(item.style.fontStyle)
.fontFamily(item.style.fontFamily);
});
}
.position({ x: this.r - this.info.centerX, y: this.r - this.info.centerY })
.padding({ left: 16, right:16 })
.width(this.info.width)
.height(this.info.height)
.justifyContent(FlexAlign.SpaceEvenly);
}
}
.width(this.r 2)
.height(this.r
2)
.constraintSize({ minWidth: this.r * 2 })
.scale({ x: this.scaleValue, y: this.scaleValue })
.offset({ x: this.info.centerX - this.r, y: this.info.centerY - this.r })
.borderRadius(this.r)
.clip(true)
.backgroundColor(this.bgColor);
}
}
https://coub.com/view/4b7yjg
https://coub.com/view/4b7yjf
https://coub.com/view/4b7yjd
https://coub.com/view/4b7yiw
https://coub.com/view/4b7yis
https://coub.com/view/4b7yip
https://coub.com/view/4b7yik
https://coub.com/view/4b7yij
https://coub.com/view/4b7yig
https://coub.com/view/4b7yie
https://coub.com/view/4b7yic
https://coub.com/view/4b7yia
https://coub.com/view/4b7yi7
https://coub.com/view/4b7yi5
https://coub.com/view/4b7yi3
https://coub.com/view/4b7yhy
https://coub.com/view/4b7yht
https://coub.com/view/4b7yhr
https://coub.com/view/4b7yhp
https://coub.com/view/4b7yhh
https://coub.com/view/4b7yhg
https://coub.com/view/4b7yhf
https://coub.com/view/4b7yhb
https://coub.com/view/4b7yh9
https://coub.com/view/4b7yh6
https://coub.com/view/4b7yh4
https://coub.com/view/4b7ygz
https://coub.com/view/4b7ygx
https://coub.com/view/4b7ygu
https://coub.com/view/4b7ygr
https://coub.com/view/4b7ygn
https://coub.com/view/4b7ygk
https://coub.com/view/4b7ygh
https://coub.com/view/4b7ygf
https://coub.com/view/4b7ygd
https://coub.com/view/4b7ygb
https://coub.com/view/4b7yg8
https://coub.com/view/4b7yg5
https://coub.com/view/4b7yg2
https://coub.com/view/4b7yfr
https://coub.com/view/4b7yfo
https://coub.com/view/4b7yfk
https://coub.com/view/4b7yfh
https://coub.com/view/4b7yfe
https://coub.com/view/4b7yfc
https://coub.com/view/4b7yfa
https://coub.com/view/4b7yf7
https://coub.com/view/4b7yf5
https://coub.com/view/4b7yf1
https://coub.com/view/4b7yez
https://coub.com/view/4b7yet
https://coub.com/view/4b7yer
https://coub.com/view/4b7yep
https://coub.com/view/4b7yeo
https://coub.com/view/4b7ub4
https://coub.com/view/4b7ub3
https://coub.com/view/4b7ub2
https://coub.com/view/4b7ub0
https://coub.com/view/4b7uay
https://coub.com/view/4b7uax
https://coub.com/view/4b7uav
https://coub.com/view/4b7uap
https://coub.com/view/4b7uao
https://coub.com/view/4b7ual
https://coub.com/view/4b7uad
https://coub.com/view/4b7uaa
https://coub.com/view/4b7ua7
https://coub.com/view/4b7ua5
https://coub.com/view/4b7ua2
https://coub.com/view/4b7ua1
https://coub.com/view/4b7u9z
https://coub.com/view/4b7u9y
https://coub.com/view/4b7u9x
https://coub.com/view/4b7u9p
https://coub.com/view/4b7u9k
https://coub.com/view/4b7u9j
https://coub.com/view/4b7u9i
https://coub.com/view/4b7u9g
https://coub.com/view/4b7u9d
https://coub.com/view/4b7u9a
https://coub.com/view/4b7u98
https://coub.com/view/4b7u96
https://coub.com/view/4b7u94
https://coub.com/view/4b7u92
https://coub.com/view/4b7u91
https://coub.com/view/4b7u8z
https://coub.com/view/4b7u8t
https://coub.com/view/4b7u8q
https://coub.com/view/4b7u8p
https://coub.com/view/4b7u8n
https://coub.com/view/4b7u8j
https://coub.com/view/4b7u8i
https://coub.com/view/4b7u78
https://coub.com/view/4b7u76
https://coub.com/view/4b7u72
https://coub.com/view/4b7u6v
https://coub.com/view/4b7u6t
https://coub.com/view/4b7u6r
https://coub.com/view/4b7u6o
https://coub.com/view/4b7u6m
https://coub.com/view/4b7u6l
https://coub.com/view/4b7u6j
https://coub.com/view/4b7u6h
https://coub.com/view/4b7u6f
https://coub.com/view/4b7u6d
https://coub.com/view/4b7u6a
https://coub.com/view/4b7u67
https://coub.com/view/4b7u65
https://coub.com/view/4b7u60
https://coub.com/view/4b7u5t
https://coub.com/view/4b7u5p
https://coub.com/view/4b7u5i
https://coub.com/view/4b7u4t
https://coub.com/view/4b7u4p
https://coub.com/view/4b7u4l
https://coub.com/view/4b7u46
https://coub.com/view/4b7u3v
https://coub.com/view/4b7u3u
https://coub.com/view/4b7u3t
https://coub.com/view/4b7u3r
https://coub.com/view/4b7u3o
https://coub.com/view/4b7u3l
https://coub.com/view/4b7u3k
https://coub.com/view/4b7u3h
https://coub.com/view/4b7u3a
https://coub.com/view/4b7u38
https://coub.com/view/4b7u35
https://coub.com/view/4b7u34
https://coub.com/view/4b7u33
https://coub.com/view/4b7u30
https://coub.com/view/4b7u2z
https://coub.com/view/4b7u2w

收起阅读 »

用CodeBuddy做了几个药店小程序的功能,电子处方,用药安全,药品追溯码

微信小程序

随着线上售药监管规则持续细化,线上药店小程序不再只是简单的下单工具,电子处方、用药安全确认、药品追溯码是满足药监核查的三大核心基础功能,也是线上经营必备合规模块。
元岳科技在原电子处方的基础上,新增了用药安全日志和本地药品追溯的功能。
一、电子处方闭环功能:规范处方药线上流转
根据《处方药网络零售合规指南》要求,线上售卖处方药必须形成完整审方流程,该模块核心作用是搭建标准化处方留存链路。
使用流程:用户选购处方药后,需完成实名登记,上传复诊处方;系统仅开放执业药师实名账号审核,全程记录操作人、操作时间,日志不可篡改。

//创建处方  
    public function createRxinfo($tmpId,$name,$phone,$age,$sex){  

        $url = $this->url."createRxinfo";  

        list($s1, $s2) = explode(' ', microtime());  
        $timestamp = (float)sprintf('%.0f',((float)$s1 + (float)$s2) * 1000);  

        $disease_json = $this->queryIcdPage();  
        $rx_item_json = $this->get_drug();  

        $data = [  
            "ywid" => "xz".$timestamp,//自定义的id  
            "status" => -1,  
            "patient_name" => $name,  
            "patient_age" => $age,  
            "patient_sex" => $sex,  
            "patient_phone" => $phone,  
            "rx_item_json" => $rx_item_json,  
            "disease_json" => $disease_json,  
            "timestamp" =>$timestamp,  
            "api_code"=>$this->api_code,  
            "temp_id"=>$tmpId  
        ];  

        $sign_str = $this->getSign($data);  
        $b_md5 = $sign_str . md5( $this->api_s);  
        $api_sign = strtolower( md5($b_md5) );  
        $data['api_sign'] = $api_sign;  

        $Data = $this->request($url,"POST",$data);  
        //var_dump($Data);die;  
        $Arr = json_decode($Data,1);  
        //var_dump($Arr);  

        return $Arr;  
    }  

风控机制:系统自动拦截重复复用处方、超剂量购药、禁忌药搭配等异常订单,杜绝先购药后补处方的违规行为。
留存规范:问诊记录、处方原件、审方记录自动加密存储,保存周期满足法规 5 年留存要求,监管抽查可一键导出审计台账。
市面上简易线上工具普遍缺少实名留痕、订单熔断机制,一旦核查无法提供完整凭证,极易产生处罚风险。
二、用药安全确认功能:完善线上药学服务凭证
《药品经营质量管理规范》明确,药品销售需留存用药风险告知记录,线上渠道必须设置标准化确认环节。
分层风险提示:系统自动识别药品类目,针对处方药、特殊人群用药,弹窗展示过敏禁忌、肝肾负担、孕期慎用等专业提示。
强制确认机制:用户必须勾选阅读知情同意、确认知晓用药风险,未完成确认无法提交订单,自动留存电子确认凭证。
配套咨询通道:订单页面内置药师咨询入口,购药前后用药沟通记录自动归档,若出现用药不良反应纠纷,可作为门店履行药学服务的有效依据。
传统线上商城仅设置付款流程,缺少该环节会存在民事赔偿与行政处罚双重隐患。

 /**  
     * 用药安全日志详情  
     * 通过 Service.getLogPage 获取数据(含 user/order 关联,含 nickname、phone)  
     * @param int $id  
     * @return mixed  
     */  
    public function info($id)  
    {  
        if (!$id) {  
            return app('json')->fail('参数错误');  
        }  

        try {  
            // 使用 Service 层 getLogPage + id 搜索器(利用新增 searchIdAttr)获取含关联数据的记录  
            $result = $this->service->getLogPage(['id' => (int)$id]);  
            $list   = $result['list'] ?? [];  
            if (empty($list)) {  
                return app('json')->fail('日志不存在');  
            }  
            $log = is_object($list[0]) ? $list[0]->toArray() : (array)$list[0];  
        } catch (\Throwable $e) {  
            Log::error('查看用药安全日志详情失败:' . $e->getMessage() . ',id=' . $id);  
            return app('json')->fail('查询失败');  
        }  

        // 格式化  
        $log['create_time_text'] = isset($log['create_time']) && $log['create_time']  
            ? date('Y-m-d H:i:s', (int)$log['create_time'])  
            : '—';  
        $log['pop_confirm_type_text'] = self::POP_CONFIRM_TYPE_MAP[$log['pop_confirm_type'] ?? 'none'] ?? '无弹窗';  
        $log['nickname'] = $log['nickname'] ?? '—';  
        $log['phone']    = $log['phone'] ?? '—';  
        $log['patient_name'] = $log['patient_name'] ?? '—';  
        if ($log['patient_name'] === '—' && !empty($log['patient_id'])) {  
            $p = UserInquiry::find((int)$log['patient_id']);  
            $log['patient_name'] = $p ? ($p['patient_name'] ?? '—') : '—';  
        }  
        if ($log['patient_name'] === '—') {  
            $log['patient_name'] = $log['order_real_name'] ?? '—';  
        }  

        // 4项安全字段中文标注  
        $log['safe_fields_text'] = [];  
        foreach (self::SAFE_FIELD_MAP as $field => $label) {  
            $log['safe_fields_text'][] = [  
                'field' => $field,  
                'label' => $label,  
                'value' => isset($log[$field]) ? ($log[$field] ? '是' : '否') : '—',  
            ];  
        }  

        return app('json')->success($log);  
    }

三、国标药品追溯码功能:实现药品一盒一码可查 依据《药品管理法》,药品经营主体需搭建全链路追溯体系,统一采用 Code128-C 20 位标准条码。

   // 按商品分组获取可用追溯码,只获取所需数量  
        Db::startTrans();  
        try {  
            $allBoundCodes = [];  

            foreach ($orderGoods as $product) {  
                $productId = $product['product_id'];  
                $cartId = $product['cart_id'];  
                $needNum = $product['cart_num'] ?? 1;  

                if ($needNum <= 0) {  
                    continue;  
                }  

                // 获取该商品的可用追溯码(在库且未删除),限制数量  
                $traceCodes = TraceStock::where('product_id', $productId)  
                    ->where('trace_status', TraceStock::TRACE_STATUS_IN_STOCK)  
                    ->where('is_deleted', 0)  
                    ->limit($needNum)  
                    ->select();  

                if ($traceCodes->isEmpty()) {  
                    Log::warning('订单' . $orderId . ' 商品' . $productId . '无可用追溯码库存');  
                    continue;  
                }  

                $boundCodes = [];  
                foreach ($traceCodes as $code) {  
                    $success = TraceStock::lockSale($code['id'], $orderId);  
                    if ($success) {  
                        $boundCodes[] = $code['id'];  
                    }  
                }  

                if (!empty($boundCodes)) {  
                    $allBoundCodes = array_merge($allBoundCodes, $boundCodes);  
                    $codeIdsStr = implode(',', $boundCodes);  

                    // 更新该订单明细的追溯码ID字段  
                    StoreOrderCartInfo::where('id', $cartId)  
                        ->update([  
                            'trace_code_ids' => Db::raw("CONCAT(trace_code_ids, '" . $codeIdsStr . "')")  
                        ]);  
                }  
            }  

            Db::commit();  

            Log::info('订单' . $orderId . ' 溯源绑定完成,绑定码数:' . count($allBoundCodes));  

        } catch (\Exception $e) {  
            Db::rollback();  
            Log::error('订单' . $orderId . ' 溯源绑定事务失败:' . $e->getMessage());  
            return false;  
        }  

    } catch (\Throwable $e) {  
        Log::error('订单' . $orderId . ' 溯源绑定异常:' . $e->getMessage());  
        // 仅记录日志,不阻断订单流程  
        return false;  
    }  
}

商家后台使用逻辑
双录入模式:支持扫码枪批量录入、文本手动导入追溯码,自动匹配对应药品规格;
双重校验规则:录入码数量与订单库存比对,货码不符、库存不足时锁定发货按钮;
商品分类适配:药品强制绑定追溯码,保健品、器械等非药品可跳过录入,提升发货效率。
用户前端查询逻辑
订单详情内置追溯码查看入口,弹窗展示标准条码,支持微信、支付宝扫码,一键调取药品生产、流通、销售全链条信息,满足公众溯源查询法定要求。
四、三大功能整体行业价值

作者:元岳ossign

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

继续阅读 »

随着线上售药监管规则持续细化,线上药店小程序不再只是简单的下单工具,电子处方、用药安全确认、药品追溯码是满足药监核查的三大核心基础功能,也是线上经营必备合规模块。
元岳科技在原电子处方的基础上,新增了用药安全日志和本地药品追溯的功能。
一、电子处方闭环功能:规范处方药线上流转
根据《处方药网络零售合规指南》要求,线上售卖处方药必须形成完整审方流程,该模块核心作用是搭建标准化处方留存链路。
使用流程:用户选购处方药后,需完成实名登记,上传复诊处方;系统仅开放执业药师实名账号审核,全程记录操作人、操作时间,日志不可篡改。

//创建处方  
    public function createRxinfo($tmpId,$name,$phone,$age,$sex){  

        $url = $this->url."createRxinfo";  

        list($s1, $s2) = explode(' ', microtime());  
        $timestamp = (float)sprintf('%.0f',((float)$s1 + (float)$s2) * 1000);  

        $disease_json = $this->queryIcdPage();  
        $rx_item_json = $this->get_drug();  

        $data = [  
            "ywid" => "xz".$timestamp,//自定义的id  
            "status" => -1,  
            "patient_name" => $name,  
            "patient_age" => $age,  
            "patient_sex" => $sex,  
            "patient_phone" => $phone,  
            "rx_item_json" => $rx_item_json,  
            "disease_json" => $disease_json,  
            "timestamp" =>$timestamp,  
            "api_code"=>$this->api_code,  
            "temp_id"=>$tmpId  
        ];  

        $sign_str = $this->getSign($data);  
        $b_md5 = $sign_str . md5( $this->api_s);  
        $api_sign = strtolower( md5($b_md5) );  
        $data['api_sign'] = $api_sign;  

        $Data = $this->request($url,"POST",$data);  
        //var_dump($Data);die;  
        $Arr = json_decode($Data,1);  
        //var_dump($Arr);  

        return $Arr;  
    }  

风控机制:系统自动拦截重复复用处方、超剂量购药、禁忌药搭配等异常订单,杜绝先购药后补处方的违规行为。
留存规范:问诊记录、处方原件、审方记录自动加密存储,保存周期满足法规 5 年留存要求,监管抽查可一键导出审计台账。
市面上简易线上工具普遍缺少实名留痕、订单熔断机制,一旦核查无法提供完整凭证,极易产生处罚风险。
二、用药安全确认功能:完善线上药学服务凭证
《药品经营质量管理规范》明确,药品销售需留存用药风险告知记录,线上渠道必须设置标准化确认环节。
分层风险提示:系统自动识别药品类目,针对处方药、特殊人群用药,弹窗展示过敏禁忌、肝肾负担、孕期慎用等专业提示。
强制确认机制:用户必须勾选阅读知情同意、确认知晓用药风险,未完成确认无法提交订单,自动留存电子确认凭证。
配套咨询通道:订单页面内置药师咨询入口,购药前后用药沟通记录自动归档,若出现用药不良反应纠纷,可作为门店履行药学服务的有效依据。
传统线上商城仅设置付款流程,缺少该环节会存在民事赔偿与行政处罚双重隐患。

 /**  
     * 用药安全日志详情  
     * 通过 Service.getLogPage 获取数据(含 user/order 关联,含 nickname、phone)  
     * @param int $id  
     * @return mixed  
     */  
    public function info($id)  
    {  
        if (!$id) {  
            return app('json')->fail('参数错误');  
        }  

        try {  
            // 使用 Service 层 getLogPage + id 搜索器(利用新增 searchIdAttr)获取含关联数据的记录  
            $result = $this->service->getLogPage(['id' => (int)$id]);  
            $list   = $result['list'] ?? [];  
            if (empty($list)) {  
                return app('json')->fail('日志不存在');  
            }  
            $log = is_object($list[0]) ? $list[0]->toArray() : (array)$list[0];  
        } catch (\Throwable $e) {  
            Log::error('查看用药安全日志详情失败:' . $e->getMessage() . ',id=' . $id);  
            return app('json')->fail('查询失败');  
        }  

        // 格式化  
        $log['create_time_text'] = isset($log['create_time']) && $log['create_time']  
            ? date('Y-m-d H:i:s', (int)$log['create_time'])  
            : '—';  
        $log['pop_confirm_type_text'] = self::POP_CONFIRM_TYPE_MAP[$log['pop_confirm_type'] ?? 'none'] ?? '无弹窗';  
        $log['nickname'] = $log['nickname'] ?? '—';  
        $log['phone']    = $log['phone'] ?? '—';  
        $log['patient_name'] = $log['patient_name'] ?? '—';  
        if ($log['patient_name'] === '—' && !empty($log['patient_id'])) {  
            $p = UserInquiry::find((int)$log['patient_id']);  
            $log['patient_name'] = $p ? ($p['patient_name'] ?? '—') : '—';  
        }  
        if ($log['patient_name'] === '—') {  
            $log['patient_name'] = $log['order_real_name'] ?? '—';  
        }  

        // 4项安全字段中文标注  
        $log['safe_fields_text'] = [];  
        foreach (self::SAFE_FIELD_MAP as $field => $label) {  
            $log['safe_fields_text'][] = [  
                'field' => $field,  
                'label' => $label,  
                'value' => isset($log[$field]) ? ($log[$field] ? '是' : '否') : '—',  
            ];  
        }  

        return app('json')->success($log);  
    }

三、国标药品追溯码功能:实现药品一盒一码可查 依据《药品管理法》,药品经营主体需搭建全链路追溯体系,统一采用 Code128-C 20 位标准条码。

   // 按商品分组获取可用追溯码,只获取所需数量  
        Db::startTrans();  
        try {  
            $allBoundCodes = [];  

            foreach ($orderGoods as $product) {  
                $productId = $product['product_id'];  
                $cartId = $product['cart_id'];  
                $needNum = $product['cart_num'] ?? 1;  

                if ($needNum <= 0) {  
                    continue;  
                }  

                // 获取该商品的可用追溯码(在库且未删除),限制数量  
                $traceCodes = TraceStock::where('product_id', $productId)  
                    ->where('trace_status', TraceStock::TRACE_STATUS_IN_STOCK)  
                    ->where('is_deleted', 0)  
                    ->limit($needNum)  
                    ->select();  

                if ($traceCodes->isEmpty()) {  
                    Log::warning('订单' . $orderId . ' 商品' . $productId . '无可用追溯码库存');  
                    continue;  
                }  

                $boundCodes = [];  
                foreach ($traceCodes as $code) {  
                    $success = TraceStock::lockSale($code['id'], $orderId);  
                    if ($success) {  
                        $boundCodes[] = $code['id'];  
                    }  
                }  

                if (!empty($boundCodes)) {  
                    $allBoundCodes = array_merge($allBoundCodes, $boundCodes);  
                    $codeIdsStr = implode(',', $boundCodes);  

                    // 更新该订单明细的追溯码ID字段  
                    StoreOrderCartInfo::where('id', $cartId)  
                        ->update([  
                            'trace_code_ids' => Db::raw("CONCAT(trace_code_ids, '" . $codeIdsStr . "')")  
                        ]);  
                }  
            }  

            Db::commit();  

            Log::info('订单' . $orderId . ' 溯源绑定完成,绑定码数:' . count($allBoundCodes));  

        } catch (\Exception $e) {  
            Db::rollback();  
            Log::error('订单' . $orderId . ' 溯源绑定事务失败:' . $e->getMessage());  
            return false;  
        }  

    } catch (\Throwable $e) {  
        Log::error('订单' . $orderId . ' 溯源绑定异常:' . $e->getMessage());  
        // 仅记录日志,不阻断订单流程  
        return false;  
    }  
}

商家后台使用逻辑
双录入模式:支持扫码枪批量录入、文本手动导入追溯码,自动匹配对应药品规格;
双重校验规则:录入码数量与订单库存比对,货码不符、库存不足时锁定发货按钮;
商品分类适配:药品强制绑定追溯码,保健品、器械等非药品可跳过录入,提升发货效率。
用户前端查询逻辑
订单详情内置追溯码查看入口,弹窗展示标准条码,支持微信、支付宝扫码,一键调取药品生产、流通、销售全链条信息,满足公众溯源查询法定要求。
四、三大功能整体行业价值

作者:元岳ossign

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收起阅读 »

java.lang.SecurityException: Shell does not have permission to access user 100 运行真机报错终于解决了

真机运行

报错原文

2026-08-06 15:22:08.448 [WARNING:] [PluginHost] Error: Command failed: E:\HBuilderX\plugins\launcher-tools\tools\adbs\adb.exe -s adb-AN2FVB1731016322-t2CsiT._adb-tls-connect._tcp shell pm list packages io.dcloud.HBuilder  
Exception occurred while executing 'list':  
java.lang.SecurityException: Shell does not have permission to access user 100  
com.android.server.am.ActivityManagerService.handleIncomingUser:14620 android.app.ActivityManager.handleIncomingUser:4753 com.android.server.pm.PackageManagerShellCommand.translateUserId:3853  
at com.android.server.am.UserController.handleIncomingUser(UserController.java:2745)  
at com.android.server.am.ActivityManagerService.handleIncomingUser(ActivityManagerService.java:14620)  
at android.app.ActivityManager.handleIncomingUser(ActivityManager.java:4753)  
at com.android.server.pm.PackageManagerShellCommand.translateUserId(PackageManagerShellCommand.java:3853)  
at com.android.server.pm.PackageManagerShellCommand.runListPackages(PackageManagerShellCommand.java:1060)  
at com.android.server.pm.PackageManagerShellCommand.runListPackages(PackageManagerShellCommand.java:956)  
at com.android.server.pm.PackageManagerShellCommand.runList(PackageManagerShellCommand.java:792)  
at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:230)  
at com.android.modules.utils.BasicShellCommandHandler.exec(BasicShellCommandHandler.java:97)  
at android.os.ShellCommand.exec(ShellCommand.java:38)  
at com.android.server.pm.PackageManagerService$IPackageManagerImpl.onShellCommand(PackageManagerService.java:7086)  
at android.os.Binder.shellCommand(Binder.java:1087)  
at android.os.Binder.onTransact(Binder.java:904)  
at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:4368)  
at com.android.server.pm.PackageManagerService$IPackageManagerImpl.onTransact(PackageManagerService.java:7062)  
at android.os.Binder.execTransactInternal(Binder.java:1371)  
at android.os.Binder.execTransact(Binder.java:1310)  
at genericNodeError (node:internal/errors:983:15)  
at wrappedFn (node:internal/errors:537:14)  
at ChildProcess.exithandler (node:child_process:417:12)  
at ChildProcess.emit (node:events:519:28)  
at maybeClose (node:internal/child_process:1101:16)  
at ChildProcess._handle.onexit (node:internal/child_process:304:5) {  
code: 255,  
killed: false,  
signal: null,  
cmd: 'E:\HBuilderX\plugins\launcher-tools\tools\adbs\adb.exe -s adb-AN2FVB1731016322-t2CsiT._adb-tls-connect._tcp shell pm list packages io.dcloud.HBuilder'  
}

解决方法

在 adb 安卓目录 E:\HBuilderX\plugins\launcher-tools\tools\adbs 输入 cmd 打开命令行 输入 adb devices

确保手机已经成功链接
然后输入 adb shell pm list users 查看系统底层真实用户

如果有 这个 UserInfo{100:平行空间:20000410} 说明你手机开启了平行空间 可以在手机设置里搜索 平行空间 直接销毁就行了

继续阅读 »

报错原文

2026-08-06 15:22:08.448 [WARNING:] [PluginHost] Error: Command failed: E:\HBuilderX\plugins\launcher-tools\tools\adbs\adb.exe -s adb-AN2FVB1731016322-t2CsiT._adb-tls-connect._tcp shell pm list packages io.dcloud.HBuilder  
Exception occurred while executing 'list':  
java.lang.SecurityException: Shell does not have permission to access user 100  
com.android.server.am.ActivityManagerService.handleIncomingUser:14620 android.app.ActivityManager.handleIncomingUser:4753 com.android.server.pm.PackageManagerShellCommand.translateUserId:3853  
at com.android.server.am.UserController.handleIncomingUser(UserController.java:2745)  
at com.android.server.am.ActivityManagerService.handleIncomingUser(ActivityManagerService.java:14620)  
at android.app.ActivityManager.handleIncomingUser(ActivityManager.java:4753)  
at com.android.server.pm.PackageManagerShellCommand.translateUserId(PackageManagerShellCommand.java:3853)  
at com.android.server.pm.PackageManagerShellCommand.runListPackages(PackageManagerShellCommand.java:1060)  
at com.android.server.pm.PackageManagerShellCommand.runListPackages(PackageManagerShellCommand.java:956)  
at com.android.server.pm.PackageManagerShellCommand.runList(PackageManagerShellCommand.java:792)  
at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:230)  
at com.android.modules.utils.BasicShellCommandHandler.exec(BasicShellCommandHandler.java:97)  
at android.os.ShellCommand.exec(ShellCommand.java:38)  
at com.android.server.pm.PackageManagerService$IPackageManagerImpl.onShellCommand(PackageManagerService.java:7086)  
at android.os.Binder.shellCommand(Binder.java:1087)  
at android.os.Binder.onTransact(Binder.java:904)  
at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:4368)  
at com.android.server.pm.PackageManagerService$IPackageManagerImpl.onTransact(PackageManagerService.java:7062)  
at android.os.Binder.execTransactInternal(Binder.java:1371)  
at android.os.Binder.execTransact(Binder.java:1310)  
at genericNodeError (node:internal/errors:983:15)  
at wrappedFn (node:internal/errors:537:14)  
at ChildProcess.exithandler (node:child_process:417:12)  
at ChildProcess.emit (node:events:519:28)  
at maybeClose (node:internal/child_process:1101:16)  
at ChildProcess._handle.onexit (node:internal/child_process:304:5) {  
code: 255,  
killed: false,  
signal: null,  
cmd: 'E:\HBuilderX\plugins\launcher-tools\tools\adbs\adb.exe -s adb-AN2FVB1731016322-t2CsiT._adb-tls-connect._tcp shell pm list packages io.dcloud.HBuilder'  
}

解决方法

在 adb 安卓目录 E:\HBuilderX\plugins\launcher-tools\tools\adbs 输入 cmd 打开命令行 输入 adb devices

确保手机已经成功链接
然后输入 adb shell pm list users 查看系统底层真实用户

如果有 这个 UserInfo{100:平行空间:20000410} 说明你手机开启了平行空间 可以在手机设置里搜索 平行空间 直接销毁就行了

收起阅读 »

如何实现全局可调用弹窗

问题现象
应用/元服务如何实现一个全局可调用的弹窗?

背景知识
windowStage为窗口管理器,管理各个基本窗口单元。
通过CustomDialogController类显示自定义弹窗,需要使用@CustomDialog定义弹窗内容。调用弹窗时,需要new CustomDialogController(),并将@CustomDialog作为CustomDialogControllerOptions中的builder参数传入。
openCustomDialog用于创建并弹出dialogContent对应的自定义弹窗。
解决方案
实现一个全局可调用弹窗,可通过如下三种方式实现:

方案一:实现全局可调用弹窗推荐使用:不依赖UI组件的全局自定义弹出框 (openCustomDialog),示例请参考完整示例。
方案二:可使用子窗口方式实现全局可调用弹窗,实现方式如下:
在EntryAbility.ets中通过AppStorage获取窗口管理器用于后续获取Window实例实现子窗口的创建与销毁,代码如下:
import { ConfigurationConstant, UIAbility } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
onCreate(): void {
try {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}

onDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
}

onWindowStageCreate(windowStage: window.WindowStage): void {
AppStorage.setOrCreate('windowStage', windowStage);
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}

onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}

onForeground(): void {
// Ability has brought to foreground
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
}

onBackground(): void {
// Ability has back to background
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
}
};
SubWindow.ets页面封装打开弹窗createMySubWindowFun方法,通过createSubWindow实现弹窗的弹出,使用setUIContent设置SubPage页面为子窗口加载的具体页面内容,通过moveWindowTo、resize等方法设置子窗口样式。封装deleteMySubWindowFun方法通过destroyWindow实现弹窗的关闭。代码如下:
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

// 获取windowStage
let windowStage = AppStorage.get('windowStage') as window.WindowStage;
let windowClass: window.Window | undefined = undefined;

export class mySubWindow {
createMySubWindowFun(subWindowName: string) {
// 创建子窗口
windowStage.createSubWindow(subWindowName, (err: BusinessError, data) => {
windowClass = data;
data.setUIContent('pages/SubPage', () => {
// 设置子窗口左上角坐标
data.moveWindowTo(150, 300);
// 设置子窗口大小
data.resize(1000, 1000);
// 展示子窗口
data.showWindow();
});
});
}

deleteMySubWindowFun() {
if (windowClass) {
windowClass.destroyWindow();
}
}
}
SubPage.ets页面用于展示子窗页面内容,代码如下:
// 子窗口显示页面
import { mySubWindow } from '../utils/SubWindow';

@Entry
@Component
struct SubPage {
mySubWindow: mySubWindow = new mySubWindow();

build() {
Column() {
Text('子窗口页面')
.fontSize(40)
.margin({ top: 30 });
Button('点击关闭')
.onClick(() => {
this.mySubWindow.deleteMySubWindowFun();
})
.margin({ top: 100 });
}
.width('100%')
.height('100%');
}
}
Index.ets页面使用方式通过引入SubWindow页面,通过调用封装的createMySubWindowFun方法实现弹窗的打开,代码如下:
import { mySubWindow } from '../utils/SubWindow';

@Entry
@Component
struct Index {
mySubWindow: mySubWindow = new mySubWindow();

build() {
RelativeContainer() {
Text('点击打开弹窗')
.id('HelloWorld')
.fontSize(30)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
})
.onClick(() => {
this.mySubWindow.createMySubWindowFun('mySubWindow');
});
}
.height('100%')
.width('100%')
.backgroundColor('#F1F3F5');
}
}
运行效果图如下:

点击放大

方案三:通过自定义弹窗(CustomDialog)封装成自定义组件的方式:
封装弹窗控制方法Dialog.ets,使用@watch监听外部传入的变量变化,通过CustomDialogController控制弹窗的显示隐藏,代码如下:
import { CustomDialogView } from './CustomDialogView';

@Component
export struct Dialog {
// 监听外部传入的visible变量,visible值发生变化时触发onChange回调函数
@Watch('onChange') @Link visible: boolean;
onCancel?: () => void;
onConfirm?: () => void;
// 通过CustomDialogController的builder参数绑定弹窗组件CustomDialogView
private controller = new CustomDialogController({
builder: CustomDialogView({
visible: $visible,
onCancel: this.onCancel,
onConfirm: this.onConfirm,
}),
autoCancel: false,
alignment: DialogAlignment.Center,
});

// 当visible的值变化时触发回调
onChange(): void {
if (this.visible) {
this.controller.open();
} else {
this.controller.close();
}
}

// 二次封装的Dialog组件主要通过控制器控制弹窗,不需要任何界面
build() {
}
}
封装弹窗视图页面CustomDialogView.ets,作用于弹窗中作为展示页面,代码如下:
@CustomDialog
export struct CustomDialogView {
@Link visible: boolean;
controller: CustomDialogController;
// 弹窗交互事件参数,点击确认和取消按钮时的回调函数
onCancel?: () => void;
onConfirm?: () => void;

build() {
Column() {
Text('自定义弹窗')
.margin({ top: 20, bottom: 40 });
Button('点击关闭弹窗').onClick(() => {
// 重置弹窗显隐参数
this.visible = false;
// 关闭弹窗
this.controller.close();
});
}
.width('100%')
.height(200)
.backgroundColor(Color.White);
}
}
使用弹窗页面CustomDialog.ets,通过给引入的Dialog组件传入visible控制弹窗的弹出,代码如下:
import { Dialog } from './Dialog';

@Entry
@Component
export struct CustomDialog {
// 外部定义visible变量作为弹窗组件入参,控制弹窗显隐
@State visible: boolean = false;

build() {
Column({ space: 20 }) {
Button('点击打开弹窗')
// 点击修改visible变量后,visible的值可以被Dialog组件监听并响应
.onClick(() => this.visible = !this.visible)
.margin({ top: 40 });
// 通过双向绑定visible变量,实现外部控制弹窗
Dialog({
visible: $visible,
});
}
.width('100%');
}
}
运行效果图如下:

点击放大

总结
实现全局可调用弹窗可通过openCustomDialog、子窗口与自定义弹窗方式实现,openCustomDialog通过ComponentContent封装内容可以与UI界面解耦,调用更加灵活,可以满足开发者的封装诉求,优先推荐使用。子窗口方式需要使用页面当作弹窗内容且需要获取窗口管理器,代码复杂性较高。自定义弹窗方式与调用UI界面耦合性较高也不建议使用。
https://coub.com/view/4b7ub4
https://coub.com/view/4b7ub3
https://coub.com/view/4b7ub2
https://coub.com/view/4b7ub0
https://coub.com/view/4b7uay
https://coub.com/view/4b7uax
https://coub.com/view/4b7uav
https://coub.com/view/4b7uap
https://coub.com/view/4b7uao
https://coub.com/view/4b7ual
https://coub.com/view/4b7uad
https://coub.com/view/4b7uaa
https://coub.com/view/4b7ua7
https://coub.com/view/4b7ua5
https://coub.com/view/4b7ua2
https://coub.com/view/4b7ua1
https://coub.com/view/4b7u9z
https://coub.com/view/4b7u9y
https://coub.com/view/4b7u9x
https://coub.com/view/4b7u9p
https://coub.com/view/4b7u9k
https://coub.com/view/4b7u9j
https://coub.com/view/4b7u9i
https://coub.com/view/4b7u9g
https://coub.com/view/4b7u9d
https://coub.com/view/4b7u9a
https://coub.com/view/4b7u98
https://coub.com/view/4b7u96
https://coub.com/view/4b7u94
https://coub.com/view/4b7u92
https://coub.com/view/4b7u91
https://coub.com/view/4b7u8z
https://coub.com/view/4b7u8t
https://coub.com/view/4b7u8q
https://coub.com/view/4b7u8p
https://coub.com/view/4b7u8n
https://coub.com/view/4b7u8j
https://coub.com/view/4b7u8i
https://coub.com/view/4b7u78
https://coub.com/view/4b7u76
https://coub.com/view/4b7u72
https://coub.com/view/4b7u6v
https://coub.com/view/4b7u6t
https://coub.com/view/4b7u6r
https://coub.com/view/4b7u6o
https://coub.com/view/4b7u6m
https://coub.com/view/4b7u6l
https://coub.com/view/4b7u6j
https://coub.com/view/4b7u6h
https://coub.com/view/4b7u6f
https://coub.com/view/4b7u6d
https://coub.com/view/4b7u6a
https://coub.com/view/4b7u67
https://coub.com/view/4b7u65
https://coub.com/view/4b7u60
https://coub.com/view/4b7u5t
https://coub.com/view/4b7u5p
https://coub.com/view/4b7u5i
https://coub.com/view/4b7u4t
https://coub.com/view/4b7u4p
https://coub.com/view/4b7u4l
https://coub.com/view/4b7u46
https://coub.com/view/4b7u3v
https://coub.com/view/4b7u3u
https://coub.com/view/4b7u3t
https://coub.com/view/4b7u3r
https://coub.com/view/4b7u3o
https://coub.com/view/4b7u3l
https://coub.com/view/4b7u3k
https://coub.com/view/4b7u3h
https://coub.com/view/4b7u3a
https://coub.com/view/4b7u38
https://coub.com/view/4b7u35
https://coub.com/view/4b7u34
https://coub.com/view/4b7u33
https://coub.com/view/4b7u30
https://coub.com/view/4b7u2z
https://coub.com/view/4b7u2w

https://coub.com/view/4b7pz5
https://coub.com/view/4b7pz2
https://coub.com/view/4b7pyu
https://coub.com/view/4b7pyr
https://coub.com/view/4b7pyp
https://coub.com/view/4b7pym
https://coub.com/view/4b7pyk
https://coub.com/view/4b7pya
https://coub.com/view/4b7py6
https://coub.com/view/4b7py5
https://coub.com/view/4b7py2
https://coub.com/view/4b7pxi
https://coub.com/view/4b7px8
https://coub.com/view/4b7pxd
https://coub.com/view/4b7px4
https://coub.com/view/4b7px2
https://coub.com/view/4b7pwy
https://coub.com/view/4b7pwt
https://coub.com/view/4b7pwi
https://coub.com/view/4b7pwb
https://coub.com/view/4b7pvo
https://coub.com/view/4b7pvk
https://coub.com/view/4b7pvf
https://coub.com/view/4b7pvd
https://coub.com/view/4b7pv9
https://coub.com/view/4b7pv7
https://coub.com/view/4b7puz
https://coub.com/view/4b7puu
https://coub.com/view/4b7pur
https://coub.com/view/4b7puj
https://coub.com/view/4b7puf
https://coub.com/view/4b7puc
https://coub.com/view/4b7pu5
https://coub.com/view/4b7pu3
https://coub.com/view/4b7pty
https://coub.com/view/4b7ptw
https://coub.com/view/4b7ptu
https://coub.com/view/4b7ptt
https://coub.com/view/4b7ptr
https://coub.com/view/4b7ptp
https://coub.com/view/4b7ptn
https://coub.com/view/4b7ptm
https://coub.com/view/4b7ptk
https://coub.com/view/4b7ptj
https://coub.com/view/4b7ptf
https://coub.com/view/4b7ptd
https://coub.com/view/4b7ptb
https://coub.com/view/4b7pt9
https://coub.com/view/4b7pt6
https://coub.com/view/4b7psx
https://coub.com/view/4b7pso
https://coub.com/view/4b7psl
https://coub.com/view/4b7psh
https://coub.com/view/4b7psc
https://coub.com/view/4b7ps7
https://coub.com/view/4b7ps2
https://coub.com/view/4b7prv
https://coub.com/view/4b7prm
https://coub.com/view/4b7prj
https://coub.com/view/4b7pri

https://coub.com/view/4b7lh7
https://coub.com/view/4b7lh5
https://coub.com/view/4b7lh4
https://coub.com/view/4b7lh1
https://coub.com/view/4b7lh0
https://coub.com/view/4b7lgx
https://coub.com/view/4b7lgu
https://coub.com/view/4b7lgr
https://coub.com/view/4b7lgo
https://coub.com/view/4b7lgj
https://coub.com/view/4b7lgh
https://coub.com/view/4b7lgd
https://coub.com/view/4b7lgb
https://coub.com/view/4b7lg6
https://coub.com/view/4b7lg2
https://coub.com/view/4b7lfw
https://coub.com/view/4b7lft
https://coub.com/view/4b7lfn
https://coub.com/view/4b7lfh
https://coub.com/view/4b7lfa
https://coub.com/view/4b7lek
https://coub.com/view/4b7leh
https://coub.com/view/4b7lef
https://coub.com/view/4b7lec
https://coub.com/view/4b7leb
https://coub.com/view/4b7le7
https://coub.com/view/4b7le1
https://coub.com/view/4b7ldz
https://coub.com/view/4b7ldx
https://coub.com/view/4b7ldw
https://coub.com/view/4b7ldt
https://coub.com/view/4b7ldq
https://coub.com/view/4b7ldn
https://coub.com/view/4b7lda
https://coub.com/view/4b7ld8
https://coub.com/view/4b7ld4
https://coub.com/view/4b7lcz
https://coub.com/view/4b7lcv
https://coub.com/view/4b7lcq
https://coub.com/view/4b7lco
https://coub.com/view/4b7lcm
https://coub.com/view/4b7lcj
https://coub.com/view/4b7lcd
https://coub.com/view/4b7lc9
https://coub.com/view/4b7lc0
https://coub.com/view/4b7lbx
https://coub.com/view/4b7lbw
https://coub.com/view/4b7lbv
https://coub.com/view/4b7lbu
https://coub.com/view/4b7lbt
https://coub.com/view/4b7lbc
https://coub.com/view/4b7lb5
https://coub.com/view/4b7lax
https://coub.com/view/4b7laj
https://coub.com/view/4b7laf
https://coub.com/view/4b7lae
https://coub.com/view/4b7lab
https://coub.com/view/4b7la7
https://coub.com/view/4b7l9u
https://coub.com/view/4b7l9s
https://coub.com/view/4b7l9q
https://coub.com/view/4b7l9n
https://coub.com/view/4b7l9k
https://coub.com/view/4b7l9j
https://coub.com/view/4b7l9i
https://coub.com/view/4b7l9h
https://coub.com/view/4b7l9g
https://coub.com/view/4b7l9f
https://coub.com/view/4b7l99
https://coub.com/view/4b7l95
https://coub.com/view/4b7l93
https://coub.com/view/4b7l90
https://coub.com/view/4b7l8u
https://coub.com/view/4b7l8r
https://coub.com/view/4b7l8q
https://coub.com/view/4b7l8p
https://coub.com/view/4b7l8o
https://coub.com/view/4b7l8m
https://coub.com/view/4b7l8k
https://coub.com/view/4b7l8g
https://coub.com/view/4b7l8f
https://coub.com/view/4b7l8e
https://coub.com/view/4b7l8b
https://coub.com/view/4b7l88
https://coub.com/view/4b7l86
https://coub.com/view/4b7l84
https://coub.com/view/4b7l81
https://coub.com/view/4b7l7z
https://coub.com/view/4b7l7x
https://coub.com/view/4b7l7w
https://coub.com/view/4b7l7v
https://coub.com/view/4b7l7u
https://coub.com/view/4b7l7s

继续阅读 »

问题现象
应用/元服务如何实现一个全局可调用的弹窗?

背景知识
windowStage为窗口管理器,管理各个基本窗口单元。
通过CustomDialogController类显示自定义弹窗,需要使用@CustomDialog定义弹窗内容。调用弹窗时,需要new CustomDialogController(),并将@CustomDialog作为CustomDialogControllerOptions中的builder参数传入。
openCustomDialog用于创建并弹出dialogContent对应的自定义弹窗。
解决方案
实现一个全局可调用弹窗,可通过如下三种方式实现:

方案一:实现全局可调用弹窗推荐使用:不依赖UI组件的全局自定义弹出框 (openCustomDialog),示例请参考完整示例。
方案二:可使用子窗口方式实现全局可调用弹窗,实现方式如下:
在EntryAbility.ets中通过AppStorage获取窗口管理器用于后续获取Window实例实现子窗口的创建与销毁,代码如下:
import { ConfigurationConstant, UIAbility } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
onCreate(): void {
try {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}

onDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
}

onWindowStageCreate(windowStage: window.WindowStage): void {
AppStorage.setOrCreate('windowStage', windowStage);
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}

onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}

onForeground(): void {
// Ability has brought to foreground
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
}

onBackground(): void {
// Ability has back to background
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
}
};
SubWindow.ets页面封装打开弹窗createMySubWindowFun方法,通过createSubWindow实现弹窗的弹出,使用setUIContent设置SubPage页面为子窗口加载的具体页面内容,通过moveWindowTo、resize等方法设置子窗口样式。封装deleteMySubWindowFun方法通过destroyWindow实现弹窗的关闭。代码如下:
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

// 获取windowStage
let windowStage = AppStorage.get('windowStage') as window.WindowStage;
let windowClass: window.Window | undefined = undefined;

export class mySubWindow {
createMySubWindowFun(subWindowName: string) {
// 创建子窗口
windowStage.createSubWindow(subWindowName, (err: BusinessError, data) => {
windowClass = data;
data.setUIContent('pages/SubPage', () => {
// 设置子窗口左上角坐标
data.moveWindowTo(150, 300);
// 设置子窗口大小
data.resize(1000, 1000);
// 展示子窗口
data.showWindow();
});
});
}

deleteMySubWindowFun() {
if (windowClass) {
windowClass.destroyWindow();
}
}
}
SubPage.ets页面用于展示子窗页面内容,代码如下:
// 子窗口显示页面
import { mySubWindow } from '../utils/SubWindow';

@Entry
@Component
struct SubPage {
mySubWindow: mySubWindow = new mySubWindow();

build() {
Column() {
Text('子窗口页面')
.fontSize(40)
.margin({ top: 30 });
Button('点击关闭')
.onClick(() => {
this.mySubWindow.deleteMySubWindowFun();
})
.margin({ top: 100 });
}
.width('100%')
.height('100%');
}
}
Index.ets页面使用方式通过引入SubWindow页面,通过调用封装的createMySubWindowFun方法实现弹窗的打开,代码如下:
import { mySubWindow } from '../utils/SubWindow';

@Entry
@Component
struct Index {
mySubWindow: mySubWindow = new mySubWindow();

build() {
RelativeContainer() {
Text('点击打开弹窗')
.id('HelloWorld')
.fontSize(30)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: 'container', align: VerticalAlign.Center },
middle: { anchor: 'container', align: HorizontalAlign.Center }
})
.onClick(() => {
this.mySubWindow.createMySubWindowFun('mySubWindow');
});
}
.height('100%')
.width('100%')
.backgroundColor('#F1F3F5');
}
}
运行效果图如下:

点击放大

方案三:通过自定义弹窗(CustomDialog)封装成自定义组件的方式:
封装弹窗控制方法Dialog.ets,使用@watch监听外部传入的变量变化,通过CustomDialogController控制弹窗的显示隐藏,代码如下:
import { CustomDialogView } from './CustomDialogView';

@Component
export struct Dialog {
// 监听外部传入的visible变量,visible值发生变化时触发onChange回调函数
@Watch('onChange') @Link visible: boolean;
onCancel?: () => void;
onConfirm?: () => void;
// 通过CustomDialogController的builder参数绑定弹窗组件CustomDialogView
private controller = new CustomDialogController({
builder: CustomDialogView({
visible: $visible,
onCancel: this.onCancel,
onConfirm: this.onConfirm,
}),
autoCancel: false,
alignment: DialogAlignment.Center,
});

// 当visible的值变化时触发回调
onChange(): void {
if (this.visible) {
this.controller.open();
} else {
this.controller.close();
}
}

// 二次封装的Dialog组件主要通过控制器控制弹窗,不需要任何界面
build() {
}
}
封装弹窗视图页面CustomDialogView.ets,作用于弹窗中作为展示页面,代码如下:
@CustomDialog
export struct CustomDialogView {
@Link visible: boolean;
controller: CustomDialogController;
// 弹窗交互事件参数,点击确认和取消按钮时的回调函数
onCancel?: () => void;
onConfirm?: () => void;

build() {
Column() {
Text('自定义弹窗')
.margin({ top: 20, bottom: 40 });
Button('点击关闭弹窗').onClick(() => {
// 重置弹窗显隐参数
this.visible = false;
// 关闭弹窗
this.controller.close();
});
}
.width('100%')
.height(200)
.backgroundColor(Color.White);
}
}
使用弹窗页面CustomDialog.ets,通过给引入的Dialog组件传入visible控制弹窗的弹出,代码如下:
import { Dialog } from './Dialog';

@Entry
@Component
export struct CustomDialog {
// 外部定义visible变量作为弹窗组件入参,控制弹窗显隐
@State visible: boolean = false;

build() {
Column({ space: 20 }) {
Button('点击打开弹窗')
// 点击修改visible变量后,visible的值可以被Dialog组件监听并响应
.onClick(() => this.visible = !this.visible)
.margin({ top: 40 });
// 通过双向绑定visible变量,实现外部控制弹窗
Dialog({
visible: $visible,
});
}
.width('100%');
}
}
运行效果图如下:

点击放大

总结
实现全局可调用弹窗可通过openCustomDialog、子窗口与自定义弹窗方式实现,openCustomDialog通过ComponentContent封装内容可以与UI界面解耦,调用更加灵活,可以满足开发者的封装诉求,优先推荐使用。子窗口方式需要使用页面当作弹窗内容且需要获取窗口管理器,代码复杂性较高。自定义弹窗方式与调用UI界面耦合性较高也不建议使用。
https://coub.com/view/4b7ub4
https://coub.com/view/4b7ub3
https://coub.com/view/4b7ub2
https://coub.com/view/4b7ub0
https://coub.com/view/4b7uay
https://coub.com/view/4b7uax
https://coub.com/view/4b7uav
https://coub.com/view/4b7uap
https://coub.com/view/4b7uao
https://coub.com/view/4b7ual
https://coub.com/view/4b7uad
https://coub.com/view/4b7uaa
https://coub.com/view/4b7ua7
https://coub.com/view/4b7ua5
https://coub.com/view/4b7ua2
https://coub.com/view/4b7ua1
https://coub.com/view/4b7u9z
https://coub.com/view/4b7u9y
https://coub.com/view/4b7u9x
https://coub.com/view/4b7u9p
https://coub.com/view/4b7u9k
https://coub.com/view/4b7u9j
https://coub.com/view/4b7u9i
https://coub.com/view/4b7u9g
https://coub.com/view/4b7u9d
https://coub.com/view/4b7u9a
https://coub.com/view/4b7u98
https://coub.com/view/4b7u96
https://coub.com/view/4b7u94
https://coub.com/view/4b7u92
https://coub.com/view/4b7u91
https://coub.com/view/4b7u8z
https://coub.com/view/4b7u8t
https://coub.com/view/4b7u8q
https://coub.com/view/4b7u8p
https://coub.com/view/4b7u8n
https://coub.com/view/4b7u8j
https://coub.com/view/4b7u8i
https://coub.com/view/4b7u78
https://coub.com/view/4b7u76
https://coub.com/view/4b7u72
https://coub.com/view/4b7u6v
https://coub.com/view/4b7u6t
https://coub.com/view/4b7u6r
https://coub.com/view/4b7u6o
https://coub.com/view/4b7u6m
https://coub.com/view/4b7u6l
https://coub.com/view/4b7u6j
https://coub.com/view/4b7u6h
https://coub.com/view/4b7u6f
https://coub.com/view/4b7u6d
https://coub.com/view/4b7u6a
https://coub.com/view/4b7u67
https://coub.com/view/4b7u65
https://coub.com/view/4b7u60
https://coub.com/view/4b7u5t
https://coub.com/view/4b7u5p
https://coub.com/view/4b7u5i
https://coub.com/view/4b7u4t
https://coub.com/view/4b7u4p
https://coub.com/view/4b7u4l
https://coub.com/view/4b7u46
https://coub.com/view/4b7u3v
https://coub.com/view/4b7u3u
https://coub.com/view/4b7u3t
https://coub.com/view/4b7u3r
https://coub.com/view/4b7u3o
https://coub.com/view/4b7u3l
https://coub.com/view/4b7u3k
https://coub.com/view/4b7u3h
https://coub.com/view/4b7u3a
https://coub.com/view/4b7u38
https://coub.com/view/4b7u35
https://coub.com/view/4b7u34
https://coub.com/view/4b7u33
https://coub.com/view/4b7u30
https://coub.com/view/4b7u2z
https://coub.com/view/4b7u2w

https://coub.com/view/4b7pz5
https://coub.com/view/4b7pz2
https://coub.com/view/4b7pyu
https://coub.com/view/4b7pyr
https://coub.com/view/4b7pyp
https://coub.com/view/4b7pym
https://coub.com/view/4b7pyk
https://coub.com/view/4b7pya
https://coub.com/view/4b7py6
https://coub.com/view/4b7py5
https://coub.com/view/4b7py2
https://coub.com/view/4b7pxi
https://coub.com/view/4b7px8
https://coub.com/view/4b7pxd
https://coub.com/view/4b7px4
https://coub.com/view/4b7px2
https://coub.com/view/4b7pwy
https://coub.com/view/4b7pwt
https://coub.com/view/4b7pwi
https://coub.com/view/4b7pwb
https://coub.com/view/4b7pvo
https://coub.com/view/4b7pvk
https://coub.com/view/4b7pvf
https://coub.com/view/4b7pvd
https://coub.com/view/4b7pv9
https://coub.com/view/4b7pv7
https://coub.com/view/4b7puz
https://coub.com/view/4b7puu
https://coub.com/view/4b7pur
https://coub.com/view/4b7puj
https://coub.com/view/4b7puf
https://coub.com/view/4b7puc
https://coub.com/view/4b7pu5
https://coub.com/view/4b7pu3
https://coub.com/view/4b7pty
https://coub.com/view/4b7ptw
https://coub.com/view/4b7ptu
https://coub.com/view/4b7ptt
https://coub.com/view/4b7ptr
https://coub.com/view/4b7ptp
https://coub.com/view/4b7ptn
https://coub.com/view/4b7ptm
https://coub.com/view/4b7ptk
https://coub.com/view/4b7ptj
https://coub.com/view/4b7ptf
https://coub.com/view/4b7ptd
https://coub.com/view/4b7ptb
https://coub.com/view/4b7pt9
https://coub.com/view/4b7pt6
https://coub.com/view/4b7psx
https://coub.com/view/4b7pso
https://coub.com/view/4b7psl
https://coub.com/view/4b7psh
https://coub.com/view/4b7psc
https://coub.com/view/4b7ps7
https://coub.com/view/4b7ps2
https://coub.com/view/4b7prv
https://coub.com/view/4b7prm
https://coub.com/view/4b7prj
https://coub.com/view/4b7pri

https://coub.com/view/4b7lh7
https://coub.com/view/4b7lh5
https://coub.com/view/4b7lh4
https://coub.com/view/4b7lh1
https://coub.com/view/4b7lh0
https://coub.com/view/4b7lgx
https://coub.com/view/4b7lgu
https://coub.com/view/4b7lgr
https://coub.com/view/4b7lgo
https://coub.com/view/4b7lgj
https://coub.com/view/4b7lgh
https://coub.com/view/4b7lgd
https://coub.com/view/4b7lgb
https://coub.com/view/4b7lg6
https://coub.com/view/4b7lg2
https://coub.com/view/4b7lfw
https://coub.com/view/4b7lft
https://coub.com/view/4b7lfn
https://coub.com/view/4b7lfh
https://coub.com/view/4b7lfa
https://coub.com/view/4b7lek
https://coub.com/view/4b7leh
https://coub.com/view/4b7lef
https://coub.com/view/4b7lec
https://coub.com/view/4b7leb
https://coub.com/view/4b7le7
https://coub.com/view/4b7le1
https://coub.com/view/4b7ldz
https://coub.com/view/4b7ldx
https://coub.com/view/4b7ldw
https://coub.com/view/4b7ldt
https://coub.com/view/4b7ldq
https://coub.com/view/4b7ldn
https://coub.com/view/4b7lda
https://coub.com/view/4b7ld8
https://coub.com/view/4b7ld4
https://coub.com/view/4b7lcz
https://coub.com/view/4b7lcv
https://coub.com/view/4b7lcq
https://coub.com/view/4b7lco
https://coub.com/view/4b7lcm
https://coub.com/view/4b7lcj
https://coub.com/view/4b7lcd
https://coub.com/view/4b7lc9
https://coub.com/view/4b7lc0
https://coub.com/view/4b7lbx
https://coub.com/view/4b7lbw
https://coub.com/view/4b7lbv
https://coub.com/view/4b7lbu
https://coub.com/view/4b7lbt
https://coub.com/view/4b7lbc
https://coub.com/view/4b7lb5
https://coub.com/view/4b7lax
https://coub.com/view/4b7laj
https://coub.com/view/4b7laf
https://coub.com/view/4b7lae
https://coub.com/view/4b7lab
https://coub.com/view/4b7la7
https://coub.com/view/4b7l9u
https://coub.com/view/4b7l9s
https://coub.com/view/4b7l9q
https://coub.com/view/4b7l9n
https://coub.com/view/4b7l9k
https://coub.com/view/4b7l9j
https://coub.com/view/4b7l9i
https://coub.com/view/4b7l9h
https://coub.com/view/4b7l9g
https://coub.com/view/4b7l9f
https://coub.com/view/4b7l99
https://coub.com/view/4b7l95
https://coub.com/view/4b7l93
https://coub.com/view/4b7l90
https://coub.com/view/4b7l8u
https://coub.com/view/4b7l8r
https://coub.com/view/4b7l8q
https://coub.com/view/4b7l8p
https://coub.com/view/4b7l8o
https://coub.com/view/4b7l8m
https://coub.com/view/4b7l8k
https://coub.com/view/4b7l8g
https://coub.com/view/4b7l8f
https://coub.com/view/4b7l8e
https://coub.com/view/4b7l8b
https://coub.com/view/4b7l88
https://coub.com/view/4b7l86
https://coub.com/view/4b7l84
https://coub.com/view/4b7l81
https://coub.com/view/4b7l7z
https://coub.com/view/4b7l7x
https://coub.com/view/4b7l7w
https://coub.com/view/4b7l7v
https://coub.com/view/4b7l7u
https://coub.com/view/4b7l7s

收起阅读 »

DCloud 内置引擎库libnative-imagetranscoder.so、libweexcore.so、libweexjss.so 中的检测报警

安全检测 安全漏洞

libjpeg,mbed_tls,zlib这三个组件分别依赖于:
xxxx.apk.extracted/lib/armeabi-v7a/libnative-imagetranscoder.so
xxxx.apk.extracted/lib/armeabi-v7a/libweexcore.so
xxxx.apk.extracted/lib/armeabi-v7a/libweexjss.so
xxxx.apk.extracted/lib/arm64-v8a/libnative-imagetranscoder.so
xxxx.apk.extracted/lib/arm64-v8a/libweexcore.so
xxxx.apk.extracted/lib/arm64-v8a/libweexjss.so
我们程序做第三方漏洞审计,这些组件版本均被检测出有漏洞修复更新

具体审计问题见附件详情,官方重视一下尽快给出解决方案并修复问题漏洞

继续阅读 »

libjpeg,mbed_tls,zlib这三个组件分别依赖于:
xxxx.apk.extracted/lib/armeabi-v7a/libnative-imagetranscoder.so
xxxx.apk.extracted/lib/armeabi-v7a/libweexcore.so
xxxx.apk.extracted/lib/armeabi-v7a/libweexjss.so
xxxx.apk.extracted/lib/arm64-v8a/libnative-imagetranscoder.so
xxxx.apk.extracted/lib/arm64-v8a/libweexcore.so
xxxx.apk.extracted/lib/arm64-v8a/libweexjss.so
我们程序做第三方漏洞审计,这些组件版本均被检测出有漏洞修复更新

具体审计问题见附件详情,官方重视一下尽快给出解决方案并修复问题漏洞

收起阅读 »