鸿蒙arkui仿WPF实现ViewBox缩放容器

使用方法

Row(){
Stack(){
ViewBox({targetWidth:100}){//设置宽度100,高度默认父容器100%
Rect().width(50).height(25)
}
}.layoutWeight(1)
Stack(){
ViewBox({targetWidth:50,targetHeight:0}){//设置宽度50,高度设置0,则高根据宽度对应比例计算
Rect().width(50).height(25)
}
}.layoutWeight(1)
Stack(){
ViewBox({targetWidth:'50%'}){//宽度为父容器50%,高度默认100%
Rect().width(50).height(25)
}
}.layoutWeight(1)
Stack(){
ViewBox({targetWidth:'50%',targetHeight:'60%'}){
Rect().width(50).height(25)
}
}.layoutWeight(1)
Stack(){
ViewBox({targetWidth:'50%',targetHeight:20}){
Rect().width(50).height(25)
}
}.layoutWeight(1)
Stack(){
ViewBox({targetWidth:50,targetHeight:20}){
Rect().width(50).height(25)
}
}.layoutWeight(1)
Stack(){
ViewBox({targetHeight:100,allowZoomIn:false}){//不允许放大
Rect().width(50).height(25)
}
}.layoutWeight(1)
Stack(){
ViewBox({targetWidth:'120',alignContent:Alignment.End}){//ViewBox内容在ViewBox中右对齐
Rect().width(50).height(25)
}
}.layoutWeight(1)

}.width('100%').height(50).padding(5).backgroundColor(0xaaaaaa)

效果

image

 

单次内容变化不能超过100vp,超过时需要设置measurePadding

w和h为targetWidth和targetheight缩写

w=0 且 h=0:不缩放
w>0 且 h=0:按 w 单边等比缩放
w=0 且 h>0:按 h 单边等比缩放
w>0 且 h>0:按两边范围取较小比例缩放
w='100%' / h='100%' 或 1%~99%:先按父容器尺寸解析出实际目标值,再参与上面的规则0
0% 视为 0

完整代码



@Component
export struct ViewBox {
@BuilderParam content: () => void

@State contentWidth: number = 0
@State contentHeight: number = 0
@State parentWidth: number = 0
@State parentHeight: number = 0

@Prop targetWidth: Length = '100%'
@Prop targetHeight: Length = '100%'
@Prop allowZoomIn: boolean = true // 允许放大
@Prop alignContent: Alignment = Alignment.Center
@Prop measurePadding: number = 100 // 当内容单次变化尺寸不超过这个范围时,可以正常测量缩放

@State tScale: number = 1

private calcScale(
cw: number,
ch: number,
tw: number,
th: number
): number {
// 参数异常时,返回默认缩放
if (
isNaN(cw) ||
isNaN(ch) ||
isNaN(tw) ||
isNaN(th)
) {
return 1
}


// 防止设计尺寸为 0 或非法值导致除 0
if (cw <= 0 || ch <= 0) {
return 1
}

let scale: number = 1

// 两个目标都为 0:不缩放
if (tw === 0 && th === 0) {
scale = 1
}
// 只有宽度为 0:按高度缩放
else if (tw === 0) {
scale = th / ch
}
// 只有高度为 0:按宽度缩放
else if (th === 0) {
scale = tw / cw
}
// 两个都不为 0:确保不超出范围
else {
scale = Math.min(tw / cw, th / ch)
}

// 不允许放大时,最大只能为 1
if (!this.allowZoomIn && scale > 1) {
scale = 1
}

// 防御性兜底,避免出现无效比例
if (!isFinite(scale) || scale <= 0) {
return 1
}

return scale
}

private nearlyEqual(a: number, b: number, epsilon: number = 0.001): boolean {
return Math.abs(a - b) < epsilon
}

private updateScale(
cw: number,
ch: number,
tw: Length,
th: Length,
pw: number,
ph: number
): void {
let tw_number:number = 0
let th_number:number = 0
if (this.isPercentLength(tw))
{
tw_number = pw
if(tw=='0%')
{
tw_number = 0
}
}
else{
tw_number = Number(tw)
}


if (this.isPercentLength(th))
{
th_number = ph
if(th=='0%')
{
th_number = 0
}
}
else {
th_number = Number(th)
}



let newScale = this.calcScale(cw, ch, tw_number, th_number)
if (!this.nearlyEqual(this.tScale, newScale)) {
this.tScale = newScale
}
}
private isPercentLength(value: Length): boolean {
return typeof value === 'string' && value.trim().endsWith('%')
}
private getContainerWidth(): Length {
if(this.targetWidth == 0||this.targetWidth == '0%')
{
return this.contentWidth * this.tScale
}
return this.targetWidth
}
private getContainerHeight(): Length {
if(this.targetHeight == 0||this.targetHeight == '0%')
{
return this.contentHeight * this.tScale
}
return this.targetHeight

}

build() {
Stack({ alignContent: this.alignContent }) {
Stack({ alignContent: Alignment.Center }) {
Stack() {
Stack() {
this.content()
}
.scale({ x: this.tScale, y: this.tScale })
.onAreaChange((oldValue: Area, newValue: Area) => {
let w = Number(newValue.width)
let h = Number(newValue.height)

if (!this.nearlyEqual(this.contentWidth, w)) {
this.contentWidth = w
}
if (!this.nearlyEqual(this.contentHeight, h)) {
this.contentHeight = h
}

this.updateScale(
w,
h,
this.targetWidth,
this.targetHeight,
this.parentWidth,
this.parentHeight
)
})
}
.width(this.contentWidth + this.measurePadding)
.height(this.contentHeight + this.measurePadding)
}
.width(this.contentWidth * this.tScale)
.height(this.contentHeight * this.tScale)
.border({ width: 1, color: Color.Red })
}
.width(this.getContainerWidth())
.height(this.getContainerHeight())
.border({ width: 1, color: Color.Blue })
.onAreaChange((oldValue: Area, newValue: Area) => {
let w = Number(newValue.width)
let h = Number(newValue.height)

if (!this.nearlyEqual(this.parentWidth, w)) {
this.parentWidth = w
}
if (!this.nearlyEqual(this.parentHeight, h)) {
this.parentHeight = h
}

this.updateScale(
this.contentWidth,
this.contentHeight,
this.targetWidth,
this.targetHeight,
w,
h
)
})
}
}
posted @ 2026-03-13 13:44  奇迹之耀  阅读(1)  评论(0)    收藏  举报