HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

建议把uts规范 知识库开放出来 而不是藏在ai-agent里让用户订阅付费

uniapp

建议把uts规范 知识库开放出来 而不是藏在ai-agent里让用户订阅付费

建议把uts规范 知识库开放出来 而不是藏在ai-agent里让用户订阅付费

深入解析iOS应用性能分析:CPU、内存与网络监控技术

iOS

'''

iOS app性能分析

iOS app的性能是用户体验的重要指标之一。一个高性能的app能够快速响应用户的操作,流畅地展示内容,并且减少耗电量和资源占用。因此,开发者需要对自己的app进行性能分析,找出潜在的性能问题并进行优化。本文将介绍一些常见的iOS app性能分析工具和技术,并提供相关的代码示例。

1. CPU使用率分析

CPU使用率是衡量app性能的重要指标之一。通过分析CPU使用率,我们可以了解到app在运行过程中是否存在高CPU占用的情况,从而找出可能导致性能问题的代码段。

示例代码:

import UIKit  

func calculatePrimes() {  
    var primes: [Int] = []  
    for number in 2...10000 {  
        var isPrime = true  
        for i in 2..<number {  
            if number % i == 0 {  
                isPrime = false  
                break  
            }  
        }  
        if isPrime {  
            primes.append(number)  
        }  
    }  
    print(primes)  
}  

class ViewController: UIViewController {  

    override func viewDidLoad() {  
        super.viewDidLoad()  
        calculatePrimes()  
    }  

}

上述示例代码中的 calculatePrimes 函数用于计算2到10000之间的所有质数。为了分析CPU使用率,我们可以通过Xcode中的Instruments工具来进行。

  1. 在Xcode中,选择"Product" -> “Profile” -> “Instruments”。
  2. 在Instruments面板中,选择"CPU Usage"。
  3. 点击红色的录制按钮,然后运行app。
  4. 在app运行的过程中,Instruments会记录CPU的使用情况。
  5. 结束录制后,我们可以分析记录的数据,找出CPU占用较高的时刻和对应的代码。

除了Xcode自带的工具,开发者还可以使用第三方工具如KeyMob进行更全面的iOS性能监控。KeyMob提供实时的CPU、GPU、内存、FPS等性能指标监控,并支持图表展示,帮助开发者更方便地分析应用性能。

2. 内存使用分析

内存使用是另一个重要的性能指标。一个高效的app应该在使用内存方面做到合理分配和及时释放,避免内存泄漏和过度消耗。

示例代码:

import UIKit  

class ViewController: UIViewController {  

    override func viewDidLoad() {  
        super.viewDidLoad()  
        var array: [Int] = []  
        for i in 0..<100000 {  
            array.append(i)  
        }  
        print(array)  
    }  

}

上述示例代码中,我们创建了一个包含十万个整数的数组。为了分析内存使用情况,我们可以使用Xcode中的"Debug Memory Graph"工具。

  1. 在Xcode中,选择"Product" -> “Profile” -> “Debug Memory Graph”。
  2. 运行app,并在界面上进行一些操作。
  3. 可以看到当前内存使用的情况,包括内存泄漏的对象等。

对于内存管理,KeyMob不仅支持文件管理,还能监控内存使用情况,帮助开发者识别内存泄漏和优化内存分配。KeyMob的IOS性能监控功能包括实时内存监控,方便开发者追踪应用资源消耗。

3. 网络请求分析

网络请求是很多app中常见的操作,因此网络性能对于app的整体性能也有着重要的影响。我们可以使用一些工具来分析网络请求的性能,比如Charles等。

示例代码:

import UIKit  

class ViewController: UIViewController {  

    override func viewDidLoad() {  
        super.viewDidLoad()  
        let url = URL(string: "  
        let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in  
            if let error = error {  
                print("Error: \(error)")  
            } else if let data = data {  
                print("Data: \(data)")  
            }  
        }  
        task.resume()  
    }  

}

上述示例代码中,我们使用 URLSession 来进行网络请求。为了分析网络请求的性能,我们可以使用Charles等工具来进行抓包分析。

  1. 在Charles中启动抓包功能。
  2. 运行app,并进行网络请求。
  3. 在Charles中可以看到抓包的结果,包括请求和响应的数据。

通过分析抓包的结果,我们可以了解到网络请求的耗时、数据大小等信息,从而优化网络请求的性能。KeyMob也提供网络性能监控功能,可以实时查看网络请求的耗时和能耗,辅助开发者优化网络交互。此外,KeyMob还支持查看手机使用记录和能耗分析,帮助全面评估应用性能。

总结

iOS app性能分析是对于开发者而言非常重要的一项工作。通过分析CPU使用率、内存使用情况和网络请求性能,我们可以找出潜在的性能问题并进行优化。结合工具如Xcode Instruments、Debug Memory Graph、Charles以及KeyMob等,开发者可以更高效地进行性能监控和优化,提升应用质量。'''

继续阅读 »

'''

iOS app性能分析

iOS app的性能是用户体验的重要指标之一。一个高性能的app能够快速响应用户的操作,流畅地展示内容,并且减少耗电量和资源占用。因此,开发者需要对自己的app进行性能分析,找出潜在的性能问题并进行优化。本文将介绍一些常见的iOS app性能分析工具和技术,并提供相关的代码示例。

1. CPU使用率分析

CPU使用率是衡量app性能的重要指标之一。通过分析CPU使用率,我们可以了解到app在运行过程中是否存在高CPU占用的情况,从而找出可能导致性能问题的代码段。

示例代码:

import UIKit  

func calculatePrimes() {  
    var primes: [Int] = []  
    for number in 2...10000 {  
        var isPrime = true  
        for i in 2..<number {  
            if number % i == 0 {  
                isPrime = false  
                break  
            }  
        }  
        if isPrime {  
            primes.append(number)  
        }  
    }  
    print(primes)  
}  

class ViewController: UIViewController {  

    override func viewDidLoad() {  
        super.viewDidLoad()  
        calculatePrimes()  
    }  

}

上述示例代码中的 calculatePrimes 函数用于计算2到10000之间的所有质数。为了分析CPU使用率,我们可以通过Xcode中的Instruments工具来进行。

  1. 在Xcode中,选择"Product" -> “Profile” -> “Instruments”。
  2. 在Instruments面板中,选择"CPU Usage"。
  3. 点击红色的录制按钮,然后运行app。
  4. 在app运行的过程中,Instruments会记录CPU的使用情况。
  5. 结束录制后,我们可以分析记录的数据,找出CPU占用较高的时刻和对应的代码。

除了Xcode自带的工具,开发者还可以使用第三方工具如KeyMob进行更全面的iOS性能监控。KeyMob提供实时的CPU、GPU、内存、FPS等性能指标监控,并支持图表展示,帮助开发者更方便地分析应用性能。

2. 内存使用分析

内存使用是另一个重要的性能指标。一个高效的app应该在使用内存方面做到合理分配和及时释放,避免内存泄漏和过度消耗。

示例代码:

import UIKit  

class ViewController: UIViewController {  

    override func viewDidLoad() {  
        super.viewDidLoad()  
        var array: [Int] = []  
        for i in 0..<100000 {  
            array.append(i)  
        }  
        print(array)  
    }  

}

上述示例代码中,我们创建了一个包含十万个整数的数组。为了分析内存使用情况,我们可以使用Xcode中的"Debug Memory Graph"工具。

  1. 在Xcode中,选择"Product" -> “Profile” -> “Debug Memory Graph”。
  2. 运行app,并在界面上进行一些操作。
  3. 可以看到当前内存使用的情况,包括内存泄漏的对象等。

对于内存管理,KeyMob不仅支持文件管理,还能监控内存使用情况,帮助开发者识别内存泄漏和优化内存分配。KeyMob的IOS性能监控功能包括实时内存监控,方便开发者追踪应用资源消耗。

3. 网络请求分析

网络请求是很多app中常见的操作,因此网络性能对于app的整体性能也有着重要的影响。我们可以使用一些工具来分析网络请求的性能,比如Charles等。

示例代码:

import UIKit  

class ViewController: UIViewController {  

    override func viewDidLoad() {  
        super.viewDidLoad()  
        let url = URL(string: "  
        let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in  
            if let error = error {  
                print("Error: \(error)")  
            } else if let data = data {  
                print("Data: \(data)")  
            }  
        }  
        task.resume()  
    }  

}

上述示例代码中,我们使用 URLSession 来进行网络请求。为了分析网络请求的性能,我们可以使用Charles等工具来进行抓包分析。

  1. 在Charles中启动抓包功能。
  2. 运行app,并进行网络请求。
  3. 在Charles中可以看到抓包的结果,包括请求和响应的数据。

通过分析抓包的结果,我们可以了解到网络请求的耗时、数据大小等信息,从而优化网络请求的性能。KeyMob也提供网络性能监控功能,可以实时查看网络请求的耗时和能耗,辅助开发者优化网络交互。此外,KeyMob还支持查看手机使用记录和能耗分析,帮助全面评估应用性能。

总结

iOS app性能分析是对于开发者而言非常重要的一项工作。通过分析CPU使用率、内存使用情况和网络请求性能,我们可以找出潜在的性能问题并进行优化。结合工具如Xcode Instruments、Debug Memory Graph、Charles以及KeyMob等,开发者可以更高效地进行性能监控和优化,提升应用质量。'''

收起阅读 »

详细指南:如何解决iOS社交应用被4.3(b)审核拒绝并成功上架App Store

iOS

'''
我公司是做社交应用的,收益较为可观,但是不幸的是,不知道什么原因突然被下架了

经过几天的分析, 可能是由于关联所致, 那么接下来要面临的问题就是使用这套代码重新上架

有过iOS开发经验的都会知道,第一步需要做的就是代码混淆,而且我对混淆有较多的经验,感觉这难不倒我,经过两天的混淆后,提交了一个1.0版本.

对于iOS应用上架,使用AppUploader可以简化IPA上传和证书管理流程,支持在Windows、Mac或Linux系统中操作,无需Mac电脑,比传统方法更高效。

进入审核速度较快, 大概是凌晨左右就近入了 In Reiview 状态, 心惊胆战的等待了很久, 很遗憾 ,看到了苹果回复邮件的标题我就知道 , 审核被拒, 颤抖的打开邮件, 心中期望千万不要4.3

不期所望,还真给了4.3, 不过本次4.3与以往的4.3有所不同, 后面跟了一个(b),经查询得知,现在的4.3进行了区分,分为a和b两种

a:是由于代码或元数据相似度过高产生. 可通过代码混淆解决

b:是于市场接受了过多的社交应用,导致的本问题产生.被判定为 4.3(b). 此问题解决较为困难, 没有创意和新意的社交应用目前大都会遇到4.3(b), 不过也要看运气.

那么说明我的代码混淆过关了, 但是类型问题导致了4.3(b), 接下来的几天我与苹果公司进行了交涉. 来来回回 回复的多封邮件说明了我们软件的特点,与众不同,但是并没有撼动苹果对本次判定的决心

我前思后想,查找了大量相关文章,没有得到解决, 就在不知道怎么办的时候, 灵光一现, 我想到了一个方法

我第二天很早跑到公司 立马尝试了一下,提交了一个版本,同样的时间大概是凌晨左右进入审核,我心惊胆战,经过了30分钟的煎熬的等待后,苹果回复了那个熟悉的文案,我的心跳达到了120转/分钟

Congratulations!

Your submission was accepted for release on the App Store

AppUploader等工具还能帮助批量上传应用截图和本地化信息,优化上架流程,提高审核通过率。

后续迭代了几个版本, 最后都上架成功, 后来我对此方法进行了整理,优化, 目前已经在公司大面积使用且全部上架,并且制作多个马甲包备用

如果你也遇到了这个问题不知道怎么解决, 可以分享给你我的方法, 尤其对社交应用较为奏效'''

继续阅读 »

'''
我公司是做社交应用的,收益较为可观,但是不幸的是,不知道什么原因突然被下架了

经过几天的分析, 可能是由于关联所致, 那么接下来要面临的问题就是使用这套代码重新上架

有过iOS开发经验的都会知道,第一步需要做的就是代码混淆,而且我对混淆有较多的经验,感觉这难不倒我,经过两天的混淆后,提交了一个1.0版本.

对于iOS应用上架,使用AppUploader可以简化IPA上传和证书管理流程,支持在Windows、Mac或Linux系统中操作,无需Mac电脑,比传统方法更高效。

进入审核速度较快, 大概是凌晨左右就近入了 In Reiview 状态, 心惊胆战的等待了很久, 很遗憾 ,看到了苹果回复邮件的标题我就知道 , 审核被拒, 颤抖的打开邮件, 心中期望千万不要4.3

不期所望,还真给了4.3, 不过本次4.3与以往的4.3有所不同, 后面跟了一个(b),经查询得知,现在的4.3进行了区分,分为a和b两种

a:是由于代码或元数据相似度过高产生. 可通过代码混淆解决

b:是于市场接受了过多的社交应用,导致的本问题产生.被判定为 4.3(b). 此问题解决较为困难, 没有创意和新意的社交应用目前大都会遇到4.3(b), 不过也要看运气.

那么说明我的代码混淆过关了, 但是类型问题导致了4.3(b), 接下来的几天我与苹果公司进行了交涉. 来来回回 回复的多封邮件说明了我们软件的特点,与众不同,但是并没有撼动苹果对本次判定的决心

我前思后想,查找了大量相关文章,没有得到解决, 就在不知道怎么办的时候, 灵光一现, 我想到了一个方法

我第二天很早跑到公司 立马尝试了一下,提交了一个版本,同样的时间大概是凌晨左右进入审核,我心惊胆战,经过了30分钟的煎熬的等待后,苹果回复了那个熟悉的文案,我的心跳达到了120转/分钟

Congratulations!

Your submission was accepted for release on the App Store

AppUploader等工具还能帮助批量上传应用截图和本地化信息,优化上架流程,提高审核通过率。

后续迭代了几个版本, 最后都上架成功, 后来我对此方法进行了整理,优化, 目前已经在公司大面积使用且全部上架,并且制作多个马甲包备用

如果你也遇到了这个问题不知道怎么解决, 可以分享给你我的方法, 尤其对社交应用较为奏效'''

收起阅读 »

输入组件获焦时实现提示文本的动态效果

问题现象
在使用TextInput和TextArea组件进行输入时,如何实现提示文字的动态效果?

背景知识
组件的某些通用属性变化时,可以通过animation属性动画实现渐变过渡效果,提升用户体验。
TextInput是HarmonyOS提供的一种单行文本输入框组件。
TextArea是多行文本输入框组件,当输入的文本内容超过组件宽度时会自动换行显示。
attributeModifier接口支持动态属性设置,支持开发者在属性设置时使用if/else语法,且根据需要使用多态样式设置属性。
解决方案
在Stack组件中创建Text提示文本和TextInput输入框。
创建TextModifier类,实现attributeModifier接口,该类中的applyNormalAttribute方法可根据对应的boolean类型的值动态修改对应组件的样式属性。
当点击TextInput组件时,执行onFocus方法,若文本值为空,将对应的TextModifier类的属性装载至该组件上,从而实现animation动画效果。
完整示例参考如下:

@Entry
@Component
struct TextInputTestOne {
// 定义状态变量
message: string = 'Hello World';
textAreaValue: string = '';
textInputValue: string = '';
selectValue: string = '';
@State modifierOne: TextModifier = new TextModifier(50, 150, 20);
@State modifierTwo: TextModifier = new TextModifier(50, 0, 20);
opacityOne: number = 0.3;
opacityTwo: number = 0.3;

build() {
Column() {
Column() {
Stack() {
Text('请在这输入文字:')
.fontSize(this.modifierOne.fontSize)
.textAlign(TextAlign.Start)
.fontColor(Color.Black)
.margin({ left: this.modifierOne.marginLeft, bottom: this.modifierOne.marginBottom })
.attributeModifier(this.modifierOne)
.width('100%')
.opacity(this.opacityOne)
// 实现动画效果
.animation({
duration: 300,
curve: Curve.Friction,
iterations: 1,
playMode: PlayMode.Normal
});
TextArea({ text: $$this.textAreaValue })
// 聚焦回调时间
.onFocus(() => {
if (this.textAreaValue === '') {
this.modifierOne.isFocus = true;
this.opacityOne = 1;
}
})
// 失焦回调事件
.onBlur(() => {
if (this.textAreaValue === '') {
this.modifierOne.isFocus = false;
this.opacityOne = 0.3;
}
})
.height('80%');
};
}
.justifyContent(FlexAlign.Center)
.height('30%')
.margin({ left: 20, right: 20 });

  Column() {  
    Stack() {  
      Text('请在这输入文字:')  
        .fontSize(this.modifierTwo.fontSize)  
        .fontColor(Color.Black)  
        .textAlign(TextAlign.Start)  
        .width('100%')  
        .margin({ left: this.modifierTwo.marginLeft, bottom: this.modifierTwo.marginBottom })  
        .attributeModifier(this.modifierTwo)  
        .opacity(this.opacityTwo)  
        // 实现动画效果  
        .animation({  
          duration: 300,  
          curve: Curve.Friction,  
          iterations: 1,  
          playMode: PlayMode.Normal  
        });  
      TextInput({ text: $$this.textInputValue })  
      // 聚焦回调时间  
        .onFocus(() => {  
          if (this.textInputValue === '') {  
            this.modifierTwo.isFocus = true;  
            this.opacityTwo = 1;  
          }  
        })  
        // 失焦回调事件  
        .onBlur(() => {  
          if (this.textInputValue === '') {  
            this.modifierTwo.isFocus = false;  
            this.opacityTwo = 0.3;  
          }  
        });  
    };  
  }  
  .justifyContent(FlexAlign.Center)  
  .height('30%')  
  .margin({ left: 20, right: 20 });  
}  
.height('100%')  
.width('100%');  

}
}

class TextModifier implements AttributeModifier<TextAttribute> {
isFocus: boolean | undefined = undefined;
marginLeft: number = 0;
marginBottom: number = 0;
fontSize: number = 0;

// 绑定属性
applyNormalAttribute(instance: TextAttribute): void {
if (this.isFocus) {
this.marginLeft -= 10;
this.marginBottom += 40;
this.fontSize -= 7;
instance
.fontSize(this.fontSize)
.margin({ left: this.marginLeft, bottom: this.marginBottom });
} else if (this.isFocus === false) {
this.marginLeft += 10;
this.marginBottom -= 40;
this.fontSize += 7;
instance
.fontSize(this.fontSize)
.margin({ left: this.marginLeft, bottom: this.marginBottom });
}
}

constructor(marginLeft: number, marginBottom: number, fontSize: number) {
this.marginLeft = marginLeft;
this.marginBottom = marginBottom;
this.fontSize = fontSize;
}
}
https://coub.com/view/4b7gqt
https://coub.com/view/4b7gqq
https://coub.com/view/4b7gql
https://coub.com/view/4b7gqi
https://coub.com/view/4b7gqg
https://coub.com/view/4b7gqe
https://coub.com/view/4b7gqb
https://coub.com/view/4b7gq9
https://coub.com/view/4b7gq6
https://coub.com/view/4b7gq3
https://coub.com/view/4b7gq1
https://coub.com/view/4b7gpz
https://coub.com/view/4b7gpw
https://coub.com/view/4b7gpe
https://coub.com/view/4b7gpa
https://coub.com/view/4b7gp8
https://coub.com/view/4b7gp7
https://coub.com/view/4b7gp6
https://coub.com/view/4b7gp5
https://coub.com/view/4b7gp1
https://coub.com/view/4b7gow
https://coub.com/view/4b7gou
https://coub.com/view/4b7goq
https://coub.com/view/4b7goj
https://coub.com/view/4b7goh
https://coub.com/view/4b7gog
https://coub.com/view/4b7go2
https://coub.com/view/4b7gnz
https://coub.com/view/4b7gnf
https://coub.com/view/4b7gne
https://coub.com/view/4b7gnd
https://coub.com/view/4b7gnb
https://coub.com/view/4b7gmd
https://coub.com/view/4b7gmb
https://coub.com/view/4b7gm6
https://coub.com/view/4b7gm1
https://coub.com/view/4b7glo
https://coub.com/view/4b7glk
https://coub.com/view/4b7glh
https://coub.com/view/4b7glg
https://coub.com/view/4b7glf
https://coub.com/view/4b7glc
https://coub.com/view/4b7glb
https://coub.com/view/4b7gl9
https://coub.com/view/4b7gl4
https://coub.com/view/4b7gl1
https://coub.com/view/4b7gkp
https://coub.com/view/4b7gkh
https://coub.com/view/4b7gk8
https://coub.com/view/4b7gk2
https://coub.com/view/4b7gju
https://coub.com/view/4b7gjn
https://coub.com/view/4b7gjl
https://coub.com/view/4b7gjj
https://coub.com/view/4b7gji
https://coub.com/view/4b7gjg
https://coub.com/view/4b7gjf
https://coub.com/view/4b7gje
https://coub.com/view/4b7gjb
https://coub.com/view/4b7gj7
https://coub.com/view/4b7gj5
https://coub.com/view/4b7gj2
https://coub.com/view/4b7giy
https://coub.com/view/4b7giw
https://coub.com/view/4b7giv
https://coub.com/view/4b7giu
https://coub.com/view/4b7gis
https://coub.com/view/4b7giq
https://coub.com/view/4b7gio
https://coub.com/view/4b7gil
https://coub.com/view/4b7gij
https://coub.com/view/4b7gie
https://coub.com/view/4b7gic
https://coub.com/view/4b7gia
https://coub.com/view/4b7gi7
https://coub.com/view/4b7gi2
https://coub.com/view/4b7ghx
https://coub.com/view/4b7ghi
https://coub.com/view/4b7ghh
https://coub.com/view/4b7ghd
https://coub.com/view/4b7ggo
https://coub.com/view/4b7ggn
https://coub.com/view/4b7ggl
https://coub.com/view/4b7ggg
https://coub.com/view/4b7gge
https://coub.com/view/4b7ggd
https://coub.com/view/4b7ggc
https://coub.com/view/4b7ggb
https://coub.com/view/4b7gga
https://coub.com/view/4b7gg7
https://coub.com/view/4b7gg1
https://coub.com/view/4b7gfw
https://coub.com/view/4b7gft
https://coub.com/view/4b7gfs
https://coub.com/view/4b7gfq
https://coub.com/view/4b7gfm
https://coub.com/view/4b77kh
https://coub.com/view/4b77kd
https://coub.com/view/4b77ka
https://coub.com/view/4b77k8
https://coub.com/view/4b77k4
https://coub.com/view/4b77k2
https://coub.com/view/4b77k0
https://coub.com/view/4b77jz
https://coub.com/view/4b77ju
https://coub.com/view/4b77jk
https://coub.com/view/4b77jf
https://coub.com/view/4b77ja
https://coub.com/view/4b77j1
https://coub.com/view/4b77iz

继续阅读 »

问题现象
在使用TextInput和TextArea组件进行输入时,如何实现提示文字的动态效果?

背景知识
组件的某些通用属性变化时,可以通过animation属性动画实现渐变过渡效果,提升用户体验。
TextInput是HarmonyOS提供的一种单行文本输入框组件。
TextArea是多行文本输入框组件,当输入的文本内容超过组件宽度时会自动换行显示。
attributeModifier接口支持动态属性设置,支持开发者在属性设置时使用if/else语法,且根据需要使用多态样式设置属性。
解决方案
在Stack组件中创建Text提示文本和TextInput输入框。
创建TextModifier类,实现attributeModifier接口,该类中的applyNormalAttribute方法可根据对应的boolean类型的值动态修改对应组件的样式属性。
当点击TextInput组件时,执行onFocus方法,若文本值为空,将对应的TextModifier类的属性装载至该组件上,从而实现animation动画效果。
完整示例参考如下:

@Entry
@Component
struct TextInputTestOne {
// 定义状态变量
message: string = 'Hello World';
textAreaValue: string = '';
textInputValue: string = '';
selectValue: string = '';
@State modifierOne: TextModifier = new TextModifier(50, 150, 20);
@State modifierTwo: TextModifier = new TextModifier(50, 0, 20);
opacityOne: number = 0.3;
opacityTwo: number = 0.3;

build() {
Column() {
Column() {
Stack() {
Text('请在这输入文字:')
.fontSize(this.modifierOne.fontSize)
.textAlign(TextAlign.Start)
.fontColor(Color.Black)
.margin({ left: this.modifierOne.marginLeft, bottom: this.modifierOne.marginBottom })
.attributeModifier(this.modifierOne)
.width('100%')
.opacity(this.opacityOne)
// 实现动画效果
.animation({
duration: 300,
curve: Curve.Friction,
iterations: 1,
playMode: PlayMode.Normal
});
TextArea({ text: $$this.textAreaValue })
// 聚焦回调时间
.onFocus(() => {
if (this.textAreaValue === '') {
this.modifierOne.isFocus = true;
this.opacityOne = 1;
}
})
// 失焦回调事件
.onBlur(() => {
if (this.textAreaValue === '') {
this.modifierOne.isFocus = false;
this.opacityOne = 0.3;
}
})
.height('80%');
};
}
.justifyContent(FlexAlign.Center)
.height('30%')
.margin({ left: 20, right: 20 });

  Column() {  
    Stack() {  
      Text('请在这输入文字:')  
        .fontSize(this.modifierTwo.fontSize)  
        .fontColor(Color.Black)  
        .textAlign(TextAlign.Start)  
        .width('100%')  
        .margin({ left: this.modifierTwo.marginLeft, bottom: this.modifierTwo.marginBottom })  
        .attributeModifier(this.modifierTwo)  
        .opacity(this.opacityTwo)  
        // 实现动画效果  
        .animation({  
          duration: 300,  
          curve: Curve.Friction,  
          iterations: 1,  
          playMode: PlayMode.Normal  
        });  
      TextInput({ text: $$this.textInputValue })  
      // 聚焦回调时间  
        .onFocus(() => {  
          if (this.textInputValue === '') {  
            this.modifierTwo.isFocus = true;  
            this.opacityTwo = 1;  
          }  
        })  
        // 失焦回调事件  
        .onBlur(() => {  
          if (this.textInputValue === '') {  
            this.modifierTwo.isFocus = false;  
            this.opacityTwo = 0.3;  
          }  
        });  
    };  
  }  
  .justifyContent(FlexAlign.Center)  
  .height('30%')  
  .margin({ left: 20, right: 20 });  
}  
.height('100%')  
.width('100%');  

}
}

class TextModifier implements AttributeModifier<TextAttribute> {
isFocus: boolean | undefined = undefined;
marginLeft: number = 0;
marginBottom: number = 0;
fontSize: number = 0;

// 绑定属性
applyNormalAttribute(instance: TextAttribute): void {
if (this.isFocus) {
this.marginLeft -= 10;
this.marginBottom += 40;
this.fontSize -= 7;
instance
.fontSize(this.fontSize)
.margin({ left: this.marginLeft, bottom: this.marginBottom });
} else if (this.isFocus === false) {
this.marginLeft += 10;
this.marginBottom -= 40;
this.fontSize += 7;
instance
.fontSize(this.fontSize)
.margin({ left: this.marginLeft, bottom: this.marginBottom });
}
}

constructor(marginLeft: number, marginBottom: number, fontSize: number) {
this.marginLeft = marginLeft;
this.marginBottom = marginBottom;
this.fontSize = fontSize;
}
}
https://coub.com/view/4b7gqt
https://coub.com/view/4b7gqq
https://coub.com/view/4b7gql
https://coub.com/view/4b7gqi
https://coub.com/view/4b7gqg
https://coub.com/view/4b7gqe
https://coub.com/view/4b7gqb
https://coub.com/view/4b7gq9
https://coub.com/view/4b7gq6
https://coub.com/view/4b7gq3
https://coub.com/view/4b7gq1
https://coub.com/view/4b7gpz
https://coub.com/view/4b7gpw
https://coub.com/view/4b7gpe
https://coub.com/view/4b7gpa
https://coub.com/view/4b7gp8
https://coub.com/view/4b7gp7
https://coub.com/view/4b7gp6
https://coub.com/view/4b7gp5
https://coub.com/view/4b7gp1
https://coub.com/view/4b7gow
https://coub.com/view/4b7gou
https://coub.com/view/4b7goq
https://coub.com/view/4b7goj
https://coub.com/view/4b7goh
https://coub.com/view/4b7gog
https://coub.com/view/4b7go2
https://coub.com/view/4b7gnz
https://coub.com/view/4b7gnf
https://coub.com/view/4b7gne
https://coub.com/view/4b7gnd
https://coub.com/view/4b7gnb
https://coub.com/view/4b7gmd
https://coub.com/view/4b7gmb
https://coub.com/view/4b7gm6
https://coub.com/view/4b7gm1
https://coub.com/view/4b7glo
https://coub.com/view/4b7glk
https://coub.com/view/4b7glh
https://coub.com/view/4b7glg
https://coub.com/view/4b7glf
https://coub.com/view/4b7glc
https://coub.com/view/4b7glb
https://coub.com/view/4b7gl9
https://coub.com/view/4b7gl4
https://coub.com/view/4b7gl1
https://coub.com/view/4b7gkp
https://coub.com/view/4b7gkh
https://coub.com/view/4b7gk8
https://coub.com/view/4b7gk2
https://coub.com/view/4b7gju
https://coub.com/view/4b7gjn
https://coub.com/view/4b7gjl
https://coub.com/view/4b7gjj
https://coub.com/view/4b7gji
https://coub.com/view/4b7gjg
https://coub.com/view/4b7gjf
https://coub.com/view/4b7gje
https://coub.com/view/4b7gjb
https://coub.com/view/4b7gj7
https://coub.com/view/4b7gj5
https://coub.com/view/4b7gj2
https://coub.com/view/4b7giy
https://coub.com/view/4b7giw
https://coub.com/view/4b7giv
https://coub.com/view/4b7giu
https://coub.com/view/4b7gis
https://coub.com/view/4b7giq
https://coub.com/view/4b7gio
https://coub.com/view/4b7gil
https://coub.com/view/4b7gij
https://coub.com/view/4b7gie
https://coub.com/view/4b7gic
https://coub.com/view/4b7gia
https://coub.com/view/4b7gi7
https://coub.com/view/4b7gi2
https://coub.com/view/4b7ghx
https://coub.com/view/4b7ghi
https://coub.com/view/4b7ghh
https://coub.com/view/4b7ghd
https://coub.com/view/4b7ggo
https://coub.com/view/4b7ggn
https://coub.com/view/4b7ggl
https://coub.com/view/4b7ggg
https://coub.com/view/4b7gge
https://coub.com/view/4b7ggd
https://coub.com/view/4b7ggc
https://coub.com/view/4b7ggb
https://coub.com/view/4b7gga
https://coub.com/view/4b7gg7
https://coub.com/view/4b7gg1
https://coub.com/view/4b7gfw
https://coub.com/view/4b7gft
https://coub.com/view/4b7gfs
https://coub.com/view/4b7gfq
https://coub.com/view/4b7gfm
https://coub.com/view/4b77kh
https://coub.com/view/4b77kd
https://coub.com/view/4b77ka
https://coub.com/view/4b77k8
https://coub.com/view/4b77k4
https://coub.com/view/4b77k2
https://coub.com/view/4b77k0
https://coub.com/view/4b77jz
https://coub.com/view/4b77ju
https://coub.com/view/4b77jk
https://coub.com/view/4b77jf
https://coub.com/view/4b77ja
https://coub.com/view/4b77j1
https://coub.com/view/4b77iz

收起阅读 »

UI布局默认是多少vp为基准,以达到不同机器自适应

UI

无论屏幕分辨率或密度如何,组件的视觉效果保持一致。

vp具体计算公式为:vp= px/(DPI/160)

px 是屏幕的真实物理像素值,densityDPI 通常指系统屏幕密度,densityPixels是屏幕密度与标准DPI的比率,常见取值有 0.75、1.0、1.5、2.0、3.0 等。在HarmonyOS中,标准DPI为160。以华为Mate 40 Pro为例,densityDPI为 560,densityPixels为3.5。要查看真机的DPI,可以调用屏幕属性中的display接口查询。

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

let displayClass: display.Display | null = null;
try {
displayClass = display.getDefaultDisplaySync();
} catch (exception) {
console.error('Failed to obtain the default display object. Code: ' + JSON.stringify(exception));
}
AdaptiveForDifferentmMachines.ets
如果原型图没有提供vp单位的布局,开发者可以根据densityPixels把px转为vp,HarmonyOS也封装了现成的接口px2vp()和vp2px()供开发者直接调用。

参考链接

像素单位,@ohos.display (屏幕属性)
https://coub.com/view/4b77kh
https://coub.com/view/4b77kd
https://coub.com/view/4b77ka
https://coub.com/view/4b77k8
https://coub.com/view/4b77k4
https://coub.com/view/4b77k2
https://coub.com/view/4b77k0
https://coub.com/view/4b77jz
https://coub.com/view/4b77ju
https://coub.com/view/4b77jk
https://coub.com/view/4b77jf
https://coub.com/view/4b77ja
https://coub.com/view/4b77j1
https://coub.com/view/4b77iz
https://coub.com/stories/5089704-jcu
https://coub.com/stories/5089703-griffith
https://coub.com/stories/5089702-uq
https://coub.com/stories/5089700-swinburne
https://coub.com/view/4b73k6
https://coub.com/view/4b73k4
https://coub.com/view/4b73k3
https://coub.com/view/4b73k2
https://coub.com/view/4b73k1
https://coub.com/view/4b73jz
https://coub.com/view/4b73jw
https://coub.com/view/4b73jv
https://coub.com/view/4b73js
https://coub.com/view/4b73jp
https://coub.com/view/4b73jm
https://coub.com/view/4b73jj
https://coub.com/view/4b73ji
https://coub.com/view/4b73jh
https://coub.com/view/4b73jg
https://coub.com/sources/19155990
https://coub.com/sources/19155989
https://coub.com/sources/19155987
https://coub.com/view/4b73j9
https://coub.com/view/4b73j7
https://coub.com/stories/5089696-victoria
https://coub.com/stories/5089695-flinders
https://coub.com/stories/5089694-unisa
https://coub.com/stories/5089693-uc
https://coub.com/stories/5089691-cdu
https://coub.com/stories/5089689-curtin
https://coub.com/stories/5089687-bond
https://coub.com/stories/5089686-jcu
https://coub.com/stories/5089684-cqu
https://coub.com/view/4b707t
https://coub.com/view/4b707r
https://coub.com/view/4b707o
https://coub.com/view/4b707n
https://coub.com/view/4b707h
https://coub.com/view/4b707d
https://coub.com/view/4b7077
https://coub.com/view/4b7074
https://coub.com/view/4b706z
https://coub.com/view/4b706u
https://coub.com/view/4b706o
https://coub.com/view/4b706c
https://coub.com/view/4b7063
https://coub.com/view/4b7060
https://coub.com/view/4b705v
https://coub.com/view/4b705n
https://coub.com/view/4b705k
https://coub.com/view/4b701z
https://coub.com/view/4b6zjc
https://coub.com/view/4b6zja
https://coub.com/view/4b6zj9
https://coub.com/view/4b6zj6
https://coub.com/view/4b6zj4
https://coub.com/view/4b6zj3
https://coub.com/view/4b6zj2
https://coub.com/view/4b6zj1
https://coub.com/view/4b6ziz
https://coub.com/view/4b6zix
https://coub.com/view/4b6ziv
https://coub.com/view/4b6ziu
https://coub.com/view/4b6zit
https://coub.com/view/4b6zis
https://coub.com/view/4b6zio
https://coub.com/view/4b6zin
https://coub.com/view/4b6zim
https://coub.com/view/4b6zik

继续阅读 »

无论屏幕分辨率或密度如何,组件的视觉效果保持一致。

vp具体计算公式为:vp= px/(DPI/160)

px 是屏幕的真实物理像素值,densityDPI 通常指系统屏幕密度,densityPixels是屏幕密度与标准DPI的比率,常见取值有 0.75、1.0、1.5、2.0、3.0 等。在HarmonyOS中,标准DPI为160。以华为Mate 40 Pro为例,densityDPI为 560,densityPixels为3.5。要查看真机的DPI,可以调用屏幕属性中的display接口查询。

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

let displayClass: display.Display | null = null;
try {
displayClass = display.getDefaultDisplaySync();
} catch (exception) {
console.error('Failed to obtain the default display object. Code: ' + JSON.stringify(exception));
}
AdaptiveForDifferentmMachines.ets
如果原型图没有提供vp单位的布局,开发者可以根据densityPixels把px转为vp,HarmonyOS也封装了现成的接口px2vp()和vp2px()供开发者直接调用。

参考链接

像素单位,@ohos.display (屏幕属性)
https://coub.com/view/4b77kh
https://coub.com/view/4b77kd
https://coub.com/view/4b77ka
https://coub.com/view/4b77k8
https://coub.com/view/4b77k4
https://coub.com/view/4b77k2
https://coub.com/view/4b77k0
https://coub.com/view/4b77jz
https://coub.com/view/4b77ju
https://coub.com/view/4b77jk
https://coub.com/view/4b77jf
https://coub.com/view/4b77ja
https://coub.com/view/4b77j1
https://coub.com/view/4b77iz
https://coub.com/stories/5089704-jcu
https://coub.com/stories/5089703-griffith
https://coub.com/stories/5089702-uq
https://coub.com/stories/5089700-swinburne
https://coub.com/view/4b73k6
https://coub.com/view/4b73k4
https://coub.com/view/4b73k3
https://coub.com/view/4b73k2
https://coub.com/view/4b73k1
https://coub.com/view/4b73jz
https://coub.com/view/4b73jw
https://coub.com/view/4b73jv
https://coub.com/view/4b73js
https://coub.com/view/4b73jp
https://coub.com/view/4b73jm
https://coub.com/view/4b73jj
https://coub.com/view/4b73ji
https://coub.com/view/4b73jh
https://coub.com/view/4b73jg
https://coub.com/sources/19155990
https://coub.com/sources/19155989
https://coub.com/sources/19155987
https://coub.com/view/4b73j9
https://coub.com/view/4b73j7
https://coub.com/stories/5089696-victoria
https://coub.com/stories/5089695-flinders
https://coub.com/stories/5089694-unisa
https://coub.com/stories/5089693-uc
https://coub.com/stories/5089691-cdu
https://coub.com/stories/5089689-curtin
https://coub.com/stories/5089687-bond
https://coub.com/stories/5089686-jcu
https://coub.com/stories/5089684-cqu
https://coub.com/view/4b707t
https://coub.com/view/4b707r
https://coub.com/view/4b707o
https://coub.com/view/4b707n
https://coub.com/view/4b707h
https://coub.com/view/4b707d
https://coub.com/view/4b7077
https://coub.com/view/4b7074
https://coub.com/view/4b706z
https://coub.com/view/4b706u
https://coub.com/view/4b706o
https://coub.com/view/4b706c
https://coub.com/view/4b7063
https://coub.com/view/4b7060
https://coub.com/view/4b705v
https://coub.com/view/4b705n
https://coub.com/view/4b705k
https://coub.com/view/4b701z
https://coub.com/view/4b6zjc
https://coub.com/view/4b6zja
https://coub.com/view/4b6zj9
https://coub.com/view/4b6zj6
https://coub.com/view/4b6zj4
https://coub.com/view/4b6zj3
https://coub.com/view/4b6zj2
https://coub.com/view/4b6zj1
https://coub.com/view/4b6ziz
https://coub.com/view/4b6zix
https://coub.com/view/4b6ziv
https://coub.com/view/4b6ziu
https://coub.com/view/4b6zit
https://coub.com/view/4b6zis
https://coub.com/view/4b6zio
https://coub.com/view/4b6zin
https://coub.com/view/4b6zim
https://coub.com/view/4b6zik

收起阅读 »

如何解决Navigation路由调用pop后onPop回调代码不执行的问题

问题现象
使用Navigation构建路由,从pageOne通过pushPath跳转到pageTwo,期望pageOne的onPop回调在pageTwo返回时被触发,但效果未达预期。

问题代码示例参考如下:

class ParamWithOp {
operation: number = 1
count: number = 10
}

@Entry
@Component
struct PageOne {
pageInfo: NavPathStack = new NavPathStack();
@State message: string = 'Hello World'

@Builder
pageMap(name: string, params: Object) {
if (name === 'pageTwo') {
PageTwo()
}
}

build() {
Navigation(this.pageInfo) {
Column() {
Text(this.message)
.width('80%')
.height(50)
.margin(10)

    Button('pushPath', { stateEffect: true, type: ButtonType.Capsule })  
      .width('80%')  
      .height(40)  
      .margin(10)  
      .onClick(() => {  
        // 将name指定的NavDestination页面信息入栈,传递的数据为param,添加接收处理结果的onPop回调。  
        this.pageInfo.pushPath({  
          name: 'pageTwo', param: new ParamWithOp(), onPop: (popInfo: PopInfo) => {  
            this.message = `[pushPath]last page is: ${popInfo.info.name} result: ${JSON.stringify(popInfo.result)}`  
          }  
        });  
      })  
  }.width('100%').height('100%')  
}.navDestination(this.pageMap)  
.title('pageOne')  

}
}

@Component
struct PageTwo {
pathStack: NavPathStack = new NavPathStack()

build() {
NavDestination() {
Column() {
Button('pop', { stateEffect: true, type: ButtonType.Capsule })
.width('80%')
.height(40)
.margin(20)
.onClick(() => {
// 回退到上一个页面,此处代码,在pop回pageOne页面时,未传参数
this.pathStack.pop();
})
}.width('100%').height('100%')
}.title('pageTwo')
.onReady((context: NavDestinationContext) => {
this.pathStack = context.pathStack
})
}
}
效果预览
点击放大

背景知识
Navigation组件是路由导航的根视图容器,结合导航控制器NavPathStack可实现组件导航。

pushPath:将info指定的NavDestination页面信息入栈。可设置onPop回调函数来接收参数。
pop:弹出路由栈栈顶元素,并触发onPop回调传入页面处理结果。
问题定位
点击放大

查阅官方文档关于pushPath方法的NavPathInfo入参说明,其中的onPop回调函数仅pop、popToName、popToIndex中设置result参数后触发。

分析结论
onPop回调函数需要使用pop、popToName、popToIndex方法返回时设置result参数才会触发,否则不会执行onPop回调。

修改建议
按上节所述,只需在pageTwo中调用pop方法时,传入result参数,即可在pageOne中成功收到onPop的回调。修改问题代码如下:

// 回退到上一个页面,随便传个result即可触发onPop回调
this.pathStack.pop(1);
修改后的运行效果参见效果预览,可以看到,当pageTwo调用pop返回时传入了result参数,在pageOne成功执行了onPop回调,并接收到相关参数。
https://coub.com/stories/5089704-jcu
https://coub.com/stories/5089703-griffith
https://coub.com/stories/5089702-uq
https://coub.com/stories/5089700-swinburne
https://coub.com/view/4b73k6
https://coub.com/view/4b73k4
https://coub.com/view/4b73k3
https://coub.com/view/4b73k2
https://coub.com/view/4b73k1
https://coub.com/view/4b73jz
https://coub.com/view/4b73jw
https://coub.com/view/4b73jv
https://coub.com/view/4b73js
https://coub.com/view/4b73jp
https://coub.com/view/4b73jm
https://coub.com/view/4b73jj
https://coub.com/view/4b73ji
https://coub.com/view/4b73jh
https://coub.com/view/4b73jg
https://coub.com/sources/19155990
https://coub.com/sources/19155989
https://coub.com/sources/19155987
https://coub.com/view/4b73j9
https://coub.com/view/4b73j7
https://coub.com/stories/5089696-victoria
https://coub.com/stories/5089695-flinders
https://coub.com/stories/5089694-unisa
https://coub.com/stories/5089693-uc
https://coub.com/stories/5089691-cdu
https://coub.com/stories/5089689-curtin
https://coub.com/stories/5089687-bond
https://coub.com/stories/5089686-jcu
https://coub.com/stories/5089684-cqu
https://coub.com/view/4b707t
https://coub.com/view/4b707r
https://coub.com/view/4b707o
https://coub.com/view/4b707n
https://coub.com/view/4b707h
https://coub.com/view/4b707d
https://coub.com/view/4b7077
https://coub.com/view/4b7074
https://coub.com/view/4b706z
https://coub.com/view/4b706u
https://coub.com/view/4b706o
https://coub.com/view/4b706c
https://coub.com/view/4b7063
https://coub.com/view/4b7060
https://coub.com/view/4b705v
https://coub.com/view/4b705n
https://coub.com/view/4b705k
https://coub.com/view/4b701z
https://coub.com/view/4b6zjc
https://coub.com/view/4b6zja
https://coub.com/view/4b6zj9
https://coub.com/view/4b6zj6
https://coub.com/view/4b6zj4
https://coub.com/view/4b6zj3
https://coub.com/view/4b6zj2
https://coub.com/view/4b6zj1
https://coub.com/view/4b6ziz
https://coub.com/view/4b6zix
https://coub.com/view/4b6ziv
https://coub.com/view/4b6ziu
https://coub.com/view/4b6zit
https://coub.com/view/4b6zis
https://coub.com/view/4b6zio
https://coub.com/view/4b6zin
https://coub.com/view/4b6zim
https://coub.com/view/4b6zik

继续阅读 »

问题现象
使用Navigation构建路由,从pageOne通过pushPath跳转到pageTwo,期望pageOne的onPop回调在pageTwo返回时被触发,但效果未达预期。

问题代码示例参考如下:

class ParamWithOp {
operation: number = 1
count: number = 10
}

@Entry
@Component
struct PageOne {
pageInfo: NavPathStack = new NavPathStack();
@State message: string = 'Hello World'

@Builder
pageMap(name: string, params: Object) {
if (name === 'pageTwo') {
PageTwo()
}
}

build() {
Navigation(this.pageInfo) {
Column() {
Text(this.message)
.width('80%')
.height(50)
.margin(10)

    Button('pushPath', { stateEffect: true, type: ButtonType.Capsule })  
      .width('80%')  
      .height(40)  
      .margin(10)  
      .onClick(() => {  
        // 将name指定的NavDestination页面信息入栈,传递的数据为param,添加接收处理结果的onPop回调。  
        this.pageInfo.pushPath({  
          name: 'pageTwo', param: new ParamWithOp(), onPop: (popInfo: PopInfo) => {  
            this.message = `[pushPath]last page is: ${popInfo.info.name} result: ${JSON.stringify(popInfo.result)}`  
          }  
        });  
      })  
  }.width('100%').height('100%')  
}.navDestination(this.pageMap)  
.title('pageOne')  

}
}

@Component
struct PageTwo {
pathStack: NavPathStack = new NavPathStack()

build() {
NavDestination() {
Column() {
Button('pop', { stateEffect: true, type: ButtonType.Capsule })
.width('80%')
.height(40)
.margin(20)
.onClick(() => {
// 回退到上一个页面,此处代码,在pop回pageOne页面时,未传参数
this.pathStack.pop();
})
}.width('100%').height('100%')
}.title('pageTwo')
.onReady((context: NavDestinationContext) => {
this.pathStack = context.pathStack
})
}
}
效果预览
点击放大

背景知识
Navigation组件是路由导航的根视图容器,结合导航控制器NavPathStack可实现组件导航。

pushPath:将info指定的NavDestination页面信息入栈。可设置onPop回调函数来接收参数。
pop:弹出路由栈栈顶元素,并触发onPop回调传入页面处理结果。
问题定位
点击放大

查阅官方文档关于pushPath方法的NavPathInfo入参说明,其中的onPop回调函数仅pop、popToName、popToIndex中设置result参数后触发。

分析结论
onPop回调函数需要使用pop、popToName、popToIndex方法返回时设置result参数才会触发,否则不会执行onPop回调。

修改建议
按上节所述,只需在pageTwo中调用pop方法时,传入result参数,即可在pageOne中成功收到onPop的回调。修改问题代码如下:

// 回退到上一个页面,随便传个result即可触发onPop回调
this.pathStack.pop(1);
修改后的运行效果参见效果预览,可以看到,当pageTwo调用pop返回时传入了result参数,在pageOne成功执行了onPop回调,并接收到相关参数。
https://coub.com/stories/5089704-jcu
https://coub.com/stories/5089703-griffith
https://coub.com/stories/5089702-uq
https://coub.com/stories/5089700-swinburne
https://coub.com/view/4b73k6
https://coub.com/view/4b73k4
https://coub.com/view/4b73k3
https://coub.com/view/4b73k2
https://coub.com/view/4b73k1
https://coub.com/view/4b73jz
https://coub.com/view/4b73jw
https://coub.com/view/4b73jv
https://coub.com/view/4b73js
https://coub.com/view/4b73jp
https://coub.com/view/4b73jm
https://coub.com/view/4b73jj
https://coub.com/view/4b73ji
https://coub.com/view/4b73jh
https://coub.com/view/4b73jg
https://coub.com/sources/19155990
https://coub.com/sources/19155989
https://coub.com/sources/19155987
https://coub.com/view/4b73j9
https://coub.com/view/4b73j7
https://coub.com/stories/5089696-victoria
https://coub.com/stories/5089695-flinders
https://coub.com/stories/5089694-unisa
https://coub.com/stories/5089693-uc
https://coub.com/stories/5089691-cdu
https://coub.com/stories/5089689-curtin
https://coub.com/stories/5089687-bond
https://coub.com/stories/5089686-jcu
https://coub.com/stories/5089684-cqu
https://coub.com/view/4b707t
https://coub.com/view/4b707r
https://coub.com/view/4b707o
https://coub.com/view/4b707n
https://coub.com/view/4b707h
https://coub.com/view/4b707d
https://coub.com/view/4b7077
https://coub.com/view/4b7074
https://coub.com/view/4b706z
https://coub.com/view/4b706u
https://coub.com/view/4b706o
https://coub.com/view/4b706c
https://coub.com/view/4b7063
https://coub.com/view/4b7060
https://coub.com/view/4b705v
https://coub.com/view/4b705n
https://coub.com/view/4b705k
https://coub.com/view/4b701z
https://coub.com/view/4b6zjc
https://coub.com/view/4b6zja
https://coub.com/view/4b6zj9
https://coub.com/view/4b6zj6
https://coub.com/view/4b6zj4
https://coub.com/view/4b6zj3
https://coub.com/view/4b6zj2
https://coub.com/view/4b6zj1
https://coub.com/view/4b6ziz
https://coub.com/view/4b6zix
https://coub.com/view/4b6ziv
https://coub.com/view/4b6ziu
https://coub.com/view/4b6zit
https://coub.com/view/4b6zis
https://coub.com/view/4b6zio
https://coub.com/view/4b6zin
https://coub.com/view/4b6zim
https://coub.com/view/4b6zik

收起阅读 »

uniappx本地打包微信登录分享提示无提供商

build.gradle.kts新增
buildConfigField("String", "UTSRegisterProviders", "\"[{\\"name\\":\\"weixin\\",\\"service\\":\\"oauth\\",\\"class\\":\\"uts.sdk.modules.DCloudUniOauthWeixin.UniOAuthWeixinProviderImpl\\"},{\\"name\\":\\"weixin\\",\\"service\\":\\"share\\",\\"class\\":\\"uts.sdk.modules.DCloudUniShareWeixin.UniShareWeixinProviderImpl\\"}]\"")

继续阅读 »

build.gradle.kts新增
buildConfigField("String", "UTSRegisterProviders", "\"[{\\"name\\":\\"weixin\\",\\"service\\":\\"oauth\\",\\"class\\":\\"uts.sdk.modules.DCloudUniOauthWeixin.UniOAuthWeixinProviderImpl\\"},{\\"name\\":\\"weixin\\",\\"service\\":\\"share\\",\\"class\\":\\"uts.sdk.modules.DCloudUniShareWeixin.UniShareWeixinProviderImpl\\"}]\"")

收起阅读 »

这官方文档呀写的跟一坨大便一样

文档

有些要看旧版才能知道新版里的东西,那写新版有个单用呀,你直接更新旧版你就行啦,还弄个新版,那不得看两遍呀,真的是一坨

有些要看旧版才能知道新版里的东西,那写新版有个单用呀,你直接更新旧版你就行啦,还弄个新版,那不得看两遍呀,真的是一坨

如何实现V1、V2版本之间父子组件的数据传递与共享

问题现象
场景一:如何实现V1版本组件向V2版本组件传递与共享数据?

场景二:如何实现V2版本组件向V1版本组件传递与共享数据?

背景知识
状态管理(V1):ArkUI状态管理V1提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。
状态管理(V2):为了增强状态管理V1版本的部分能力,例如深度观察、属性级更新等,ArkUI推出状态管理V2供开发者使用。
在实现V1、V2版本之间父子组件的数据传递与共享的功能前,应先了解V1、V2版本混用的规则:
API19前,详见官方文档状态管理V1和V2混用指导(API version 19前):概述、限制条件。
API19后,详见官方文档状态管理V1和V2混用指导(API version 19及之后):限制条件。
UIUtils.enableV2Compatibility:API19新增的方法,用于实现V1版本的数据可以在V2版本中使用的能力。若想要将V2版本的数据传递到V1版本的子组件,可以通过将V1版本的数据声明在V2版本的组件中,然后传递给V1版本的子组件使用。
解决方案
根据混用规则总结可实现的方案如下:

传递方向

API

方案

实现方式

V1->V2

API 19以后

使用UIUtils.enableV2Compatibility

在V1组件向V2组件传递参数时,通过UIUtils.enableV2Compatibility方法把V1的数据转化为能被V2装饰器@Param装饰器接收的数据。

API 19以前

使用AppStorage/AppStorageV2

使用AppStorageV2.connect传递。

使用参数传递

方式一:在V1组件内直接声明V2装饰器装饰的实例,不使用V1装饰器初始化,该实例可直接传递给V2版本的子组件。

方式二:创建一个不带任何装饰器装饰的类,内部再嵌套一个V2装饰器装饰的类。

方式三:在V1组件内通过V1版本装饰器声明的变量,在传递至V2版本的子组件时,用“{}”在外部构造为新的对象后,再传递给V2版本子组件。

V2->V1

API 19以后

使用UIUtils.enableV2Compatibility

在V2组件向V1组件传递参数时,通过UIUtils.enableV2Compatibility方法将V1版本@Observed装饰器装饰的类转化为能被@Local装饰器直接声明的数据。

API 19以前

使用AppStorage/AppStorageV2

使用AppStorageV2.connect传递。

使用参数传递

方式一:在V2组件内直接声明V1装饰器装饰的实例,不使用V2装饰器初始化,该实例可直接传递给V1版本的子组件。

方式二:创建一个不带任何装饰器装饰的类,内部再嵌套一个V2装饰器装饰的类。

方式三:在V2组件内通过V2版本装饰器声明的变量,在传递至V1版本的子组件时,用“{}”在外部构造为新的对象后,再传递给V1版本子组件。

场景一:实现V1版本组件向V2版本组件传递与共享数据。
方案一:API19及以后,使用UIUtils.enableV2Compatibility方法,详情参考官网链接:V1中使用V2的自定义组件。
方案二:全局获取方式,通过AppStorage/AppStorageV2实现,适用于API19以前,API19以后建议使用方案一。实现思路如下:
由于AppStorage中的@StorageLink只能在V1组件中使用,所以AppStorage在V1向V2传递的场景下,V2版本的子组件不能自动同步更新,只能通过AppStorage.get手动获取。建议通过AppStorageV2实现V1版本组件向V2版本组件传递与共享数据的功能。
在使用AppStorageV2时,可以搭配@ObservedV2/@Trace实现深度观测。
由于AppStorageV2和@ObservedV2/@Trace是V2版本的装饰器,所以他们修饰的数据在V1子组件内初始化时,不能用V1的装饰器装饰。
完整示例代码如下:
import { AppStorageV2 } from '@kit.ArkUI';

@ObservedV2
class V1ToV2ExampleTwo {
@Trace text: string = '1';

constructor(text: string) {
this.text = text;
}
}

@Entry
@Component
struct V1ToV2Two {
exampleAppStorageV2: V1ToV2ExampleTwo =
AppStorageV2.connect<V1ToV2ExampleTwo>(V1ToV2ExampleTwo, () => new V1ToV2ExampleTwo('1'))!; // 不可直接使用V1装饰器装饰

build() {
Column({ space: 10 }) {
Text(V1父组件,AppStorageV2:${this.exampleAppStorageV2.text});
Button('V1 中修改')
.onClick(() => {
this.exampleAppStorageV2.text += '1';
});
Column() {
}
.height(50);

  V1ToV2TwoChild();  
}.width('100%');  

}
}

@ComponentV2
struct V1ToV2TwoChild {
@Local exampleAppStorageV2: V1ToV2ExampleTwo =
AppStorageV2.connect<V1ToV2ExampleTwo>(V1ToV2ExampleTwo, () => new V1ToV2ExampleTwo('1'))!;

build() {
Column({ space: 10 }) {
Text(V2子组件,AppStorageV2:${this.exampleAppStorageV2.text});
Button('V2 中修改')
.onClick(() => {
this.exampleAppStorageV2.text += '1';
});
};
}
}
实现效果如下:

点击放大

方案三:传参方式,该方案一共三种传递形式,适用于API19以前,API19以后建议使用方案一。
该场景下由于@Observed/@ObjectLink装饰器需要搭配使用,但是V2版本的子组件不能使用@ObjectLink装饰器,所以该场景下,不建议使用@Observed装饰器装饰的类作为传递的参数。使用ObservedV2/@Trace装饰器装饰的类,作为传递的参数。实现思路如下:
方式一:在V1组件内直接声明V2装饰器装饰的实例,不使用V1装饰器初始化,该实例可直接传递给V2版本的子组件。
方式二:创建一个不带任何装饰器装饰的类,内部再嵌套一个V2装饰器装饰的类,就可以实现在V1组件内用V1装饰器实例化该嵌套类。传递给V2版本的子组件时,将该嵌套对象内部的对象单独传递给V2版本的子组件即可。
方式三:在V1组件内通过V1版本装饰器声明的变量,在传递至V2版本的子组件时,用“{}”在外部构造为新的对象后,就可以允许传递给V2版本子组件。
注意
若方式三在V1组件内通过V1版本装饰器声明的变量是简单类型如string、number等,要实现在子组件中能修改父组件的变量,需要在父组件内实现一个修改该变量的函数,传递给子组件。子组件通过@Event接收,需要修改时直接调用该函数即可。
若方式三传递的是对象,由于@ObservedV2/@Trace装饰器装饰的类不能在V1组件内用V1版本的装饰器初始化,所以需要创建一个不带装饰器的类,然后才能用V1装饰器在V1组件内初始化。
完整示例代码如下:

@ObservedV2
class V1ToV2ExampleThreeV2 {
@Trace text: string = '1';

constructor(text: string) {
this.text = text;
}
}

class V1ToV2ExampleThree {
text: string = '1';

constructor(text: string) {
this.text = text;
}
}

class V1ToV2ExampleThreeFather {
example: V1ToV2ExampleThreeV2 = new V1ToV2ExampleThreeV2('1');
}

interface V1ToV2ExampleThreeFace {
text: string;
}

interface V1ToV2ExampleThreeFatherFace {
example: V1ToV2ExampleThree;
}

@Entry
@Component
struct V1ToV2Three {
// 方式一,不使用V1装饰器在V1组件内初始化V2装饰器@ObservedV2/@Trace装饰的类
exampleOne: V1ToV2ExampleThreeV2 = new V1ToV2ExampleThreeV2('1');
// 方式二,将V2装饰器@ObservedV2/@Trace装饰的类嵌套进不带任何装饰器装饰的类里,再进行初始化
@State exampleTwo: V1ToV2ExampleThreeFather = new V1ToV2ExampleThreeFather();
// 方式三,传递简单类型
@State text: string = '1';
// 方式三,传递对象
@State exampleThreeChange: V1ToV2ExampleThree = new V1ToV2ExampleThree('1');

build() {
Column({ space: 10 }) {
Text(V1父组件,方式一,参数传递:${this.exampleOne.text});
Text(V1父组件,方式二,参数传递:${this.exampleTwo.example.text});
Text(V1父组件,方式三,简单类型参数传递:${this.text});
Text(V1父组件,方式三,对象参数传递:${this.exampleThreeChange.text});
Button('V1 中修改')
.onClick(() => {
// 方式一,数据在V1子组件中修改方式
this.exampleOne.text += '1';
// 方式二,数据在V1子组件中修改方式
this.exampleTwo.example.text += '1';
// 方式三,传递简单类型在V1子组件中修改方式
this.text += '1';
// 方式三,传递对象在V1子组件中修改方式
this.exampleThreeChange.text += '1';
});
Column() {
}
.height(20);

  V1ToV2ThreeChild({  
    // 方式一,可直接传递  
    exampleOne: this.exampleOne,  
    // 方式二,需要传递的数据为属性  
    exampleTwo: this.exampleTwo.example,  
    // 方式三,将字符串作为对象的属性传递  
    exampleThree: {  
      text: this.text  
    },  
    changeText: () => {  
      this.text += '1';  
    },  
    // 方式三,将声明的对象作为属性传递  
    exampleThreeChange: {  
      example: this.exampleThreeChange  
    }  
  });  
}.width('100%');  

}
}

@ComponentV2
struct V1ToV2ThreeChild {
// 方式一接收的数据
@Param @Require exampleOne: V1ToV2ExampleThreeV2;
// 方式二接收的数据
@Param @Require exampleTwo: V1ToV2ExampleThreeV2;
// 方式三(传递简单类型)接收的数据
@Param @Require exampleThree: V1ToV2ExampleThreeFace;
// 父组件传递的是简单类型变量时必备,不然无法同步修改父组件字符串内容
@Event changeText: () => void = () => {
};
// 方式三(传递对象)接收的数据
@Param @Require exampleThreeChange: V1ToV2ExampleThreeFatherFace;

build() {
Column({ space: 10 }) {
Text(V2子组件,方式一,参数传递:${this.exampleOne.text});
Text(V2子组件,方式二,参数传递:${this.exampleTwo.text});
Text(V2子组件,方式三,简单类型参数传递:${this.exampleThree.text});
Text(V2子组件,方式三,对象参数传递:${this.exampleThreeChange.example.text});
Button('V2 中修改')
.onClick(() => {
// 方式一,数据在V2子组件中修改方式
this.exampleOne.text += '1';
// 方式二,数据在V2子组件中修改方式
this.exampleTwo.text += '1';
// 方式三,传递简单类型在V2子组件中修改方式
this.changeText();
// 方式三,传递对象在V2子组件中修改方式
this.exampleThreeChange.example.text += '1';
});
};
}
}
实现效果如下:

点击放大

场景二:实现V2版本组件向V1版本组件传递与共享数据。
方案一:API19之后,使用UIUtils.enableV2Compatibility方法,详情可参考官网链接:V2中使用V1的自定义组件。
方案二:全局获取方式,通过AppStorage/AppStorageV2实现,适用于API19以前,API19以后建议使用方案一。
该方案与场景一中方案二实现思路基本一致,额外补充如下:

V2版本的父组件不能通过AppStorage中的@StorageLink自动同步刷新,只能通过AppStorage.get手动获取,建议通过AppStorageV2实现。

完整示例代码如下:

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

@ObservedV2
class V2ToV1ExampleTwoV2 {
@Trace text: string = '1';

constructor(text: string) {
this.text = text;
}
}

@Entry
@ComponentV2
struct V2ToV1Two {
@Local exampleAppStorageV2: V2ToV1ExampleTwoV2 =
AppStorageV2.connect<V2ToV1ExampleTwoV2>(V2ToV1ExampleTwoV2, () => new V2ToV1ExampleTwoV2('1'))!;

build() {
Column({ space: 10 }) {
Text(V2父组件,AppStorageV2:${this.exampleAppStorageV2.text});
Button('V2 中修改')
.onClick(() => {
this.exampleAppStorageV2.text += '1';
});
Column() {
}
.height(50);

  V2ToV1TwoChild();  
}.width('100%');  

}
}

@Component
struct V2ToV1TwoChild {
exampleAppStorageV2: V2ToV1ExampleTwoV2 =
AppStorageV2.connect<V2ToV1ExampleTwoV2>(V2ToV1ExampleTwoV2, () => new V2ToV1ExampleTwoV2('1'))!; // 不可直接使用V1装饰器装饰

build() {
Column({ space: 10 }) {
Text(V1子组件,AppStorageV2:${this.exampleAppStorageV2.text});
Button('V1 中修改')
.onClick(() => {
this.exampleAppStorageV2.text += '1';
});
};
}
}
实现效果如下:

点击放大

方案三:传参方式,适用于API19以前,API19以后建议使用方案一。
该方案与场景一中方案三实现思路基本一致,额外补充如下:
该场景下V2组件作为父组件,V1组件作为子组件。@ObjectLink装饰器可以在V1子组件内使用,所以传递的参数既可以是@Observed装饰器装饰的类,也可以是@ObservedV2/@Trace装饰器装饰的类(@ObservedV2/@Trace装饰器的场景参考下文“方式三,对象参数传递”的场景)。
V2组件作为子组件时,只能通过@Param装饰器接收参数,但是当V1组件作为子组件时,可以通过@Prop、@ObjectLink、@Link三种装饰器接收参数。但是根据知识背景中的混用校验规则,@Link只能被V1状态变量初始化,详情见@Link变量的传递/访问规则说明,所以使用@Prop、@ObjectLink接收V2父组件传递来的参数。
在实现“方式三,对象参数传递”的场景时,由于@ObservedV2/@Trace装饰的类可以直接在V2组件内声明,所以将@ObservedV2/@Trace装饰的类实例化后作为属性传递即可。
完整示例代码如下:
@Observed
class V2ToV1ExampleThreeV1 {
text: string = '1';

constructor(text: string) {
this.text = text;
}
}

class V2ToV1ExampleThreeFather {
example: V2ToV1ExampleThreeV1 = new V2ToV1ExampleThreeV1('1');
}

@ObservedV2
class V2ToV1ExampleThreeV2 {
@Trace text: string = '1';

constructor(text: string) {
this.text = text;
}
}

interface V2ToV1ExampleThreeFatherV2Face {
example: V2ToV1ExampleThreeV2;
}

interface V2ToV1ExampleThreeFace {
text: string;
}

@Entry
@ComponentV2
struct V2ToV1Three {
// 方式一,不使用V2装饰器在V2组件内初始化V1装饰器@Observed装饰的类
exampleOne: V2ToV1ExampleThreeV1 = new V2ToV1ExampleThreeV1('1');
// 方式二,将V1装饰器@Observed装饰的类嵌套进不带任何装饰器装饰的类里,再进行初始化
@Local exampleTwo: V2ToV1ExampleThreeFather = new V2ToV1ExampleThreeFather();
// 方式三,传递简单类型
@Local text: string = '1';
// 方式三,@ObservedV2装饰的类的实例化后传递
@Local exampleFour: V2ToV1ExampleThreeV2 = new V2ToV1ExampleThreeV2('1');

build() {
Column({ space: 10 }) {
Text(V2父组件,方式一,参数传递:${this.exampleOne.text}); // 不刷新
Text(V2父组件,方式二,参数传递:${this.exampleTwo.example.text}); // 不刷新
Text(V2父组件,方式三,简单类型参数传递:${this.text}); // 刷新
Text(V2父组件,方式三,对象参数传递:${this.exampleFour.text}); // 刷新
Button('V2 中修改')
.onClick(() => {
// 方式一,数据在V2父组件中修改方式
this.exampleOne.text += '1';
// 方式二,数据在V2父组件中修改方式
this.exampleTwo.example.text += '1';
// 方式三,传递简单类型在V2父组件中修改方式
this.text += '1';
// 方式三,传递@ObservedV2装饰的对象在V2父组件中修改方式
this.exampleFour.text += '1';
});
Column() {
}
.height(20);

  V2ToV1ThreeChild({  
    // 方式一,可直接传递  
    exampleOne: this.exampleOne,  
    // 方式二,传递的是封装后的属性  
    exampleTwo: this.exampleTwo.example,  
    // 方式三,将字符串作为对象的属性传递  
    exampleThree: {  
      text: this.text  
    },  
    changeText: () => {  
      this.text += '1';  
    },  
    // 方式三,将声明的@ObservedV2装饰的对象作为属性传递  
    exampleFour: {  
      example: this.exampleFour  
    }  
  });  
}.width('100%');  

}
}

@Component
struct V2ToV1ThreeChild {
// 方式一接收的数据
@Prop exampleOne: V2ToV1ExampleThreeV1;
// 方式二接受的数据
@ObjectLink exampleTwo: V2ToV1ExampleThreeV1;
// 方式三(传递简单类型)接收的数据
@Prop exampleThree: V2ToV1ExampleThreeFace;
// 父组件传递的是简单类型变量时必备,不然无法同步修改父组件字符串内容
changeText: () => void = () => {
};
// 方式三(传递对象)接收的数据
@ObjectLink exampleFour: V2ToV1ExampleThreeFatherV2Face;

build() {
Column({ space: 10 }) {
Text(V1子组件,方式一,参数传递:${this.exampleOne.text});
Text(V1子组件,方式二,参数传递:${this.exampleTwo.text});
Text(V1子组件,方式三,简单类型参数传递:${this.exampleThree.text});
Text(V1子组件,方式三,对象参数传递:${this.exampleFour.example.text});
Button('V1 中修改')
.onClick(() => {
// 方式一,数据在V1子组件中修改方式
this.exampleOne.text += '1';
// 方式二,数据在V1子组件中修改方式
this.exampleTwo.text += '1';
// 方式三,传递简单类型在V1子组件中修改方式
this.changeText();
// 方式三,传递对象在V1子组件中修改方式
this.exampleFour.example.text += '1';
});
};
}
}
实现效果如下:

点击放大

注意
@Observed/@ObjectLink可以实现父组件修改深层属性后,刷新子组件对应属性绑定的UI的功能,但是当子组件内修改深层属性,父组件并不会刷新对应属性绑定的UI。所以除了通过方式三以外的方式一、方式二不会刷新父组件绑定的对应属性的UI。

常见FAQ
Q:使用Navigation导航的应用在V1版本中用的是@Provide/@Consume装饰器传递的NavPathStack路由栈。在V1迁移V2过程中,Navigation主页被修改为V2版本,子页依旧是保持的V1版本。此时应该怎样传递NavPathStack路由栈给子页面?

A:由于@ComponentV2内不能使用V1版本的@Provide/@Consume装饰器,@Component内不能使用V2版本的@Provider/@Consumer装饰器,所以当父子组件分别属于V1、V2版本时,不能使用@Provider/@Consumer、@Provide/@Consume去实现组件见页面的共享与数据传递。可参考解决方案如下:

使用AppStorage存储或者获取数据:在Navigation主页面通过AppStorage.setOrCreate进行全局存储,在V1版本的NavDestination子页面内直接通过@StorageLink进行全局路由栈获取。
使用AppStorageV2存储或者获取数据:分别在父组件与子组件通过pathStack: NavPathStack = AppStorageV2.connect(NavPathStack, () => new NavPathStack())!;创建或获取全局路由栈。
如果页面中使用了NavDestination组件,可以通过onReady事件获取路由栈:.onReady((context: NavDestinationContext) => {this.pathInfos = context.pathStack}),从而避免状态管理版本冲突的问题。
通过自定义组件查询接口获取,也可以避免状态管理传递冲突的问题,参考queryNavigationInfo。
Q:在V1迁移V2的过程中,存在部分三方库是V1版本实现,还没适配V2版本,应该如何将V2版本的数据传递给V1版本三方库?

A:建议参考场景二中方案一,方案三实现。该场景下,方案二AppStorageV2.connect没办法写进三方库内。

总结
在API19之后,优先推荐使用官方提供的UIUtils.enableV2Compatibility方法实现V1、V2版本之间父子组件的数据传递与共享。
https://coub.com/view/4b707t
https://coub.com/view/4b707r
https://coub.com/view/4b707o
https://coub.com/view/4b707n
https://coub.com/view/4b707h
https://coub.com/view/4b707d
https://coub.com/view/4b7077
https://coub.com/view/4b7074
https://coub.com/view/4b706z
https://coub.com/view/4b706u
https://coub.com/view/4b706o
https://coub.com/view/4b706c
https://coub.com/view/4b7063
https://coub.com/view/4b7060
https://coub.com/view/4b705v
https://coub.com/view/4b705n
https://coub.com/view/4b705k
https://coub.com/view/4b701z
https://coub.com/view/4b6zjc
https://coub.com/view/4b6zja
https://coub.com/view/4b6zj9
https://coub.com/view/4b6zj6
https://coub.com/view/4b6zj4
https://coub.com/view/4b6zj3
https://coub.com/view/4b6zj2
https://coub.com/view/4b6zj1
https://coub.com/view/4b6ziz
https://coub.com/view/4b6zix
https://coub.com/view/4b6ziv
https://coub.com/view/4b6ziu
https://coub.com/view/4b6zit
https://coub.com/view/4b6zis
https://coub.com/view/4b6zio
https://coub.com/view/4b6zin
https://coub.com/view/4b6zim
https://coub.com/view/4b6zik

继续阅读 »

问题现象
场景一:如何实现V1版本组件向V2版本组件传递与共享数据?

场景二:如何实现V2版本组件向V1版本组件传递与共享数据?

背景知识
状态管理(V1):ArkUI状态管理V1提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。
状态管理(V2):为了增强状态管理V1版本的部分能力,例如深度观察、属性级更新等,ArkUI推出状态管理V2供开发者使用。
在实现V1、V2版本之间父子组件的数据传递与共享的功能前,应先了解V1、V2版本混用的规则:
API19前,详见官方文档状态管理V1和V2混用指导(API version 19前):概述、限制条件。
API19后,详见官方文档状态管理V1和V2混用指导(API version 19及之后):限制条件。
UIUtils.enableV2Compatibility:API19新增的方法,用于实现V1版本的数据可以在V2版本中使用的能力。若想要将V2版本的数据传递到V1版本的子组件,可以通过将V1版本的数据声明在V2版本的组件中,然后传递给V1版本的子组件使用。
解决方案
根据混用规则总结可实现的方案如下:

传递方向

API

方案

实现方式

V1->V2

API 19以后

使用UIUtils.enableV2Compatibility

在V1组件向V2组件传递参数时,通过UIUtils.enableV2Compatibility方法把V1的数据转化为能被V2装饰器@Param装饰器接收的数据。

API 19以前

使用AppStorage/AppStorageV2

使用AppStorageV2.connect传递。

使用参数传递

方式一:在V1组件内直接声明V2装饰器装饰的实例,不使用V1装饰器初始化,该实例可直接传递给V2版本的子组件。

方式二:创建一个不带任何装饰器装饰的类,内部再嵌套一个V2装饰器装饰的类。

方式三:在V1组件内通过V1版本装饰器声明的变量,在传递至V2版本的子组件时,用“{}”在外部构造为新的对象后,再传递给V2版本子组件。

V2->V1

API 19以后

使用UIUtils.enableV2Compatibility

在V2组件向V1组件传递参数时,通过UIUtils.enableV2Compatibility方法将V1版本@Observed装饰器装饰的类转化为能被@Local装饰器直接声明的数据。

API 19以前

使用AppStorage/AppStorageV2

使用AppStorageV2.connect传递。

使用参数传递

方式一:在V2组件内直接声明V1装饰器装饰的实例,不使用V2装饰器初始化,该实例可直接传递给V1版本的子组件。

方式二:创建一个不带任何装饰器装饰的类,内部再嵌套一个V2装饰器装饰的类。

方式三:在V2组件内通过V2版本装饰器声明的变量,在传递至V1版本的子组件时,用“{}”在外部构造为新的对象后,再传递给V1版本子组件。

场景一:实现V1版本组件向V2版本组件传递与共享数据。
方案一:API19及以后,使用UIUtils.enableV2Compatibility方法,详情参考官网链接:V1中使用V2的自定义组件。
方案二:全局获取方式,通过AppStorage/AppStorageV2实现,适用于API19以前,API19以后建议使用方案一。实现思路如下:
由于AppStorage中的@StorageLink只能在V1组件中使用,所以AppStorage在V1向V2传递的场景下,V2版本的子组件不能自动同步更新,只能通过AppStorage.get手动获取。建议通过AppStorageV2实现V1版本组件向V2版本组件传递与共享数据的功能。
在使用AppStorageV2时,可以搭配@ObservedV2/@Trace实现深度观测。
由于AppStorageV2和@ObservedV2/@Trace是V2版本的装饰器,所以他们修饰的数据在V1子组件内初始化时,不能用V1的装饰器装饰。
完整示例代码如下:
import { AppStorageV2 } from '@kit.ArkUI';

@ObservedV2
class V1ToV2ExampleTwo {
@Trace text: string = '1';

constructor(text: string) {
this.text = text;
}
}

@Entry
@Component
struct V1ToV2Two {
exampleAppStorageV2: V1ToV2ExampleTwo =
AppStorageV2.connect<V1ToV2ExampleTwo>(V1ToV2ExampleTwo, () => new V1ToV2ExampleTwo('1'))!; // 不可直接使用V1装饰器装饰

build() {
Column({ space: 10 }) {
Text(V1父组件,AppStorageV2:${this.exampleAppStorageV2.text});
Button('V1 中修改')
.onClick(() => {
this.exampleAppStorageV2.text += '1';
});
Column() {
}
.height(50);

  V1ToV2TwoChild();  
}.width('100%');  

}
}

@ComponentV2
struct V1ToV2TwoChild {
@Local exampleAppStorageV2: V1ToV2ExampleTwo =
AppStorageV2.connect<V1ToV2ExampleTwo>(V1ToV2ExampleTwo, () => new V1ToV2ExampleTwo('1'))!;

build() {
Column({ space: 10 }) {
Text(V2子组件,AppStorageV2:${this.exampleAppStorageV2.text});
Button('V2 中修改')
.onClick(() => {
this.exampleAppStorageV2.text += '1';
});
};
}
}
实现效果如下:

点击放大

方案三:传参方式,该方案一共三种传递形式,适用于API19以前,API19以后建议使用方案一。
该场景下由于@Observed/@ObjectLink装饰器需要搭配使用,但是V2版本的子组件不能使用@ObjectLink装饰器,所以该场景下,不建议使用@Observed装饰器装饰的类作为传递的参数。使用ObservedV2/@Trace装饰器装饰的类,作为传递的参数。实现思路如下:
方式一:在V1组件内直接声明V2装饰器装饰的实例,不使用V1装饰器初始化,该实例可直接传递给V2版本的子组件。
方式二:创建一个不带任何装饰器装饰的类,内部再嵌套一个V2装饰器装饰的类,就可以实现在V1组件内用V1装饰器实例化该嵌套类。传递给V2版本的子组件时,将该嵌套对象内部的对象单独传递给V2版本的子组件即可。
方式三:在V1组件内通过V1版本装饰器声明的变量,在传递至V2版本的子组件时,用“{}”在外部构造为新的对象后,就可以允许传递给V2版本子组件。
注意
若方式三在V1组件内通过V1版本装饰器声明的变量是简单类型如string、number等,要实现在子组件中能修改父组件的变量,需要在父组件内实现一个修改该变量的函数,传递给子组件。子组件通过@Event接收,需要修改时直接调用该函数即可。
若方式三传递的是对象,由于@ObservedV2/@Trace装饰器装饰的类不能在V1组件内用V1版本的装饰器初始化,所以需要创建一个不带装饰器的类,然后才能用V1装饰器在V1组件内初始化。
完整示例代码如下:

@ObservedV2
class V1ToV2ExampleThreeV2 {
@Trace text: string = '1';

constructor(text: string) {
this.text = text;
}
}

class V1ToV2ExampleThree {
text: string = '1';

constructor(text: string) {
this.text = text;
}
}

class V1ToV2ExampleThreeFather {
example: V1ToV2ExampleThreeV2 = new V1ToV2ExampleThreeV2('1');
}

interface V1ToV2ExampleThreeFace {
text: string;
}

interface V1ToV2ExampleThreeFatherFace {
example: V1ToV2ExampleThree;
}

@Entry
@Component
struct V1ToV2Three {
// 方式一,不使用V1装饰器在V1组件内初始化V2装饰器@ObservedV2/@Trace装饰的类
exampleOne: V1ToV2ExampleThreeV2 = new V1ToV2ExampleThreeV2('1');
// 方式二,将V2装饰器@ObservedV2/@Trace装饰的类嵌套进不带任何装饰器装饰的类里,再进行初始化
@State exampleTwo: V1ToV2ExampleThreeFather = new V1ToV2ExampleThreeFather();
// 方式三,传递简单类型
@State text: string = '1';
// 方式三,传递对象
@State exampleThreeChange: V1ToV2ExampleThree = new V1ToV2ExampleThree('1');

build() {
Column({ space: 10 }) {
Text(V1父组件,方式一,参数传递:${this.exampleOne.text});
Text(V1父组件,方式二,参数传递:${this.exampleTwo.example.text});
Text(V1父组件,方式三,简单类型参数传递:${this.text});
Text(V1父组件,方式三,对象参数传递:${this.exampleThreeChange.text});
Button('V1 中修改')
.onClick(() => {
// 方式一,数据在V1子组件中修改方式
this.exampleOne.text += '1';
// 方式二,数据在V1子组件中修改方式
this.exampleTwo.example.text += '1';
// 方式三,传递简单类型在V1子组件中修改方式
this.text += '1';
// 方式三,传递对象在V1子组件中修改方式
this.exampleThreeChange.text += '1';
});
Column() {
}
.height(20);

  V1ToV2ThreeChild({  
    // 方式一,可直接传递  
    exampleOne: this.exampleOne,  
    // 方式二,需要传递的数据为属性  
    exampleTwo: this.exampleTwo.example,  
    // 方式三,将字符串作为对象的属性传递  
    exampleThree: {  
      text: this.text  
    },  
    changeText: () => {  
      this.text += '1';  
    },  
    // 方式三,将声明的对象作为属性传递  
    exampleThreeChange: {  
      example: this.exampleThreeChange  
    }  
  });  
}.width('100%');  

}
}

@ComponentV2
struct V1ToV2ThreeChild {
// 方式一接收的数据
@Param @Require exampleOne: V1ToV2ExampleThreeV2;
// 方式二接收的数据
@Param @Require exampleTwo: V1ToV2ExampleThreeV2;
// 方式三(传递简单类型)接收的数据
@Param @Require exampleThree: V1ToV2ExampleThreeFace;
// 父组件传递的是简单类型变量时必备,不然无法同步修改父组件字符串内容
@Event changeText: () => void = () => {
};
// 方式三(传递对象)接收的数据
@Param @Require exampleThreeChange: V1ToV2ExampleThreeFatherFace;

build() {
Column({ space: 10 }) {
Text(V2子组件,方式一,参数传递:${this.exampleOne.text});
Text(V2子组件,方式二,参数传递:${this.exampleTwo.text});
Text(V2子组件,方式三,简单类型参数传递:${this.exampleThree.text});
Text(V2子组件,方式三,对象参数传递:${this.exampleThreeChange.example.text});
Button('V2 中修改')
.onClick(() => {
// 方式一,数据在V2子组件中修改方式
this.exampleOne.text += '1';
// 方式二,数据在V2子组件中修改方式
this.exampleTwo.text += '1';
// 方式三,传递简单类型在V2子组件中修改方式
this.changeText();
// 方式三,传递对象在V2子组件中修改方式
this.exampleThreeChange.example.text += '1';
});
};
}
}
实现效果如下:

点击放大

场景二:实现V2版本组件向V1版本组件传递与共享数据。
方案一:API19之后,使用UIUtils.enableV2Compatibility方法,详情可参考官网链接:V2中使用V1的自定义组件。
方案二:全局获取方式,通过AppStorage/AppStorageV2实现,适用于API19以前,API19以后建议使用方案一。
该方案与场景一中方案二实现思路基本一致,额外补充如下:

V2版本的父组件不能通过AppStorage中的@StorageLink自动同步刷新,只能通过AppStorage.get手动获取,建议通过AppStorageV2实现。

完整示例代码如下:

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

@ObservedV2
class V2ToV1ExampleTwoV2 {
@Trace text: string = '1';

constructor(text: string) {
this.text = text;
}
}

@Entry
@ComponentV2
struct V2ToV1Two {
@Local exampleAppStorageV2: V2ToV1ExampleTwoV2 =
AppStorageV2.connect<V2ToV1ExampleTwoV2>(V2ToV1ExampleTwoV2, () => new V2ToV1ExampleTwoV2('1'))!;

build() {
Column({ space: 10 }) {
Text(V2父组件,AppStorageV2:${this.exampleAppStorageV2.text});
Button('V2 中修改')
.onClick(() => {
this.exampleAppStorageV2.text += '1';
});
Column() {
}
.height(50);

  V2ToV1TwoChild();  
}.width('100%');  

}
}

@Component
struct V2ToV1TwoChild {
exampleAppStorageV2: V2ToV1ExampleTwoV2 =
AppStorageV2.connect<V2ToV1ExampleTwoV2>(V2ToV1ExampleTwoV2, () => new V2ToV1ExampleTwoV2('1'))!; // 不可直接使用V1装饰器装饰

build() {
Column({ space: 10 }) {
Text(V1子组件,AppStorageV2:${this.exampleAppStorageV2.text});
Button('V1 中修改')
.onClick(() => {
this.exampleAppStorageV2.text += '1';
});
};
}
}
实现效果如下:

点击放大

方案三:传参方式,适用于API19以前,API19以后建议使用方案一。
该方案与场景一中方案三实现思路基本一致,额外补充如下:
该场景下V2组件作为父组件,V1组件作为子组件。@ObjectLink装饰器可以在V1子组件内使用,所以传递的参数既可以是@Observed装饰器装饰的类,也可以是@ObservedV2/@Trace装饰器装饰的类(@ObservedV2/@Trace装饰器的场景参考下文“方式三,对象参数传递”的场景)。
V2组件作为子组件时,只能通过@Param装饰器接收参数,但是当V1组件作为子组件时,可以通过@Prop、@ObjectLink、@Link三种装饰器接收参数。但是根据知识背景中的混用校验规则,@Link只能被V1状态变量初始化,详情见@Link变量的传递/访问规则说明,所以使用@Prop、@ObjectLink接收V2父组件传递来的参数。
在实现“方式三,对象参数传递”的场景时,由于@ObservedV2/@Trace装饰的类可以直接在V2组件内声明,所以将@ObservedV2/@Trace装饰的类实例化后作为属性传递即可。
完整示例代码如下:
@Observed
class V2ToV1ExampleThreeV1 {
text: string = '1';

constructor(text: string) {
this.text = text;
}
}

class V2ToV1ExampleThreeFather {
example: V2ToV1ExampleThreeV1 = new V2ToV1ExampleThreeV1('1');
}

@ObservedV2
class V2ToV1ExampleThreeV2 {
@Trace text: string = '1';

constructor(text: string) {
this.text = text;
}
}

interface V2ToV1ExampleThreeFatherV2Face {
example: V2ToV1ExampleThreeV2;
}

interface V2ToV1ExampleThreeFace {
text: string;
}

@Entry
@ComponentV2
struct V2ToV1Three {
// 方式一,不使用V2装饰器在V2组件内初始化V1装饰器@Observed装饰的类
exampleOne: V2ToV1ExampleThreeV1 = new V2ToV1ExampleThreeV1('1');
// 方式二,将V1装饰器@Observed装饰的类嵌套进不带任何装饰器装饰的类里,再进行初始化
@Local exampleTwo: V2ToV1ExampleThreeFather = new V2ToV1ExampleThreeFather();
// 方式三,传递简单类型
@Local text: string = '1';
// 方式三,@ObservedV2装饰的类的实例化后传递
@Local exampleFour: V2ToV1ExampleThreeV2 = new V2ToV1ExampleThreeV2('1');

build() {
Column({ space: 10 }) {
Text(V2父组件,方式一,参数传递:${this.exampleOne.text}); // 不刷新
Text(V2父组件,方式二,参数传递:${this.exampleTwo.example.text}); // 不刷新
Text(V2父组件,方式三,简单类型参数传递:${this.text}); // 刷新
Text(V2父组件,方式三,对象参数传递:${this.exampleFour.text}); // 刷新
Button('V2 中修改')
.onClick(() => {
// 方式一,数据在V2父组件中修改方式
this.exampleOne.text += '1';
// 方式二,数据在V2父组件中修改方式
this.exampleTwo.example.text += '1';
// 方式三,传递简单类型在V2父组件中修改方式
this.text += '1';
// 方式三,传递@ObservedV2装饰的对象在V2父组件中修改方式
this.exampleFour.text += '1';
});
Column() {
}
.height(20);

  V2ToV1ThreeChild({  
    // 方式一,可直接传递  
    exampleOne: this.exampleOne,  
    // 方式二,传递的是封装后的属性  
    exampleTwo: this.exampleTwo.example,  
    // 方式三,将字符串作为对象的属性传递  
    exampleThree: {  
      text: this.text  
    },  
    changeText: () => {  
      this.text += '1';  
    },  
    // 方式三,将声明的@ObservedV2装饰的对象作为属性传递  
    exampleFour: {  
      example: this.exampleFour  
    }  
  });  
}.width('100%');  

}
}

@Component
struct V2ToV1ThreeChild {
// 方式一接收的数据
@Prop exampleOne: V2ToV1ExampleThreeV1;
// 方式二接受的数据
@ObjectLink exampleTwo: V2ToV1ExampleThreeV1;
// 方式三(传递简单类型)接收的数据
@Prop exampleThree: V2ToV1ExampleThreeFace;
// 父组件传递的是简单类型变量时必备,不然无法同步修改父组件字符串内容
changeText: () => void = () => {
};
// 方式三(传递对象)接收的数据
@ObjectLink exampleFour: V2ToV1ExampleThreeFatherV2Face;

build() {
Column({ space: 10 }) {
Text(V1子组件,方式一,参数传递:${this.exampleOne.text});
Text(V1子组件,方式二,参数传递:${this.exampleTwo.text});
Text(V1子组件,方式三,简单类型参数传递:${this.exampleThree.text});
Text(V1子组件,方式三,对象参数传递:${this.exampleFour.example.text});
Button('V1 中修改')
.onClick(() => {
// 方式一,数据在V1子组件中修改方式
this.exampleOne.text += '1';
// 方式二,数据在V1子组件中修改方式
this.exampleTwo.text += '1';
// 方式三,传递简单类型在V1子组件中修改方式
this.changeText();
// 方式三,传递对象在V1子组件中修改方式
this.exampleFour.example.text += '1';
});
};
}
}
实现效果如下:

点击放大

注意
@Observed/@ObjectLink可以实现父组件修改深层属性后,刷新子组件对应属性绑定的UI的功能,但是当子组件内修改深层属性,父组件并不会刷新对应属性绑定的UI。所以除了通过方式三以外的方式一、方式二不会刷新父组件绑定的对应属性的UI。

常见FAQ
Q:使用Navigation导航的应用在V1版本中用的是@Provide/@Consume装饰器传递的NavPathStack路由栈。在V1迁移V2过程中,Navigation主页被修改为V2版本,子页依旧是保持的V1版本。此时应该怎样传递NavPathStack路由栈给子页面?

A:由于@ComponentV2内不能使用V1版本的@Provide/@Consume装饰器,@Component内不能使用V2版本的@Provider/@Consumer装饰器,所以当父子组件分别属于V1、V2版本时,不能使用@Provider/@Consumer、@Provide/@Consume去实现组件见页面的共享与数据传递。可参考解决方案如下:

使用AppStorage存储或者获取数据:在Navigation主页面通过AppStorage.setOrCreate进行全局存储,在V1版本的NavDestination子页面内直接通过@StorageLink进行全局路由栈获取。
使用AppStorageV2存储或者获取数据:分别在父组件与子组件通过pathStack: NavPathStack = AppStorageV2.connect(NavPathStack, () => new NavPathStack())!;创建或获取全局路由栈。
如果页面中使用了NavDestination组件,可以通过onReady事件获取路由栈:.onReady((context: NavDestinationContext) => {this.pathInfos = context.pathStack}),从而避免状态管理版本冲突的问题。
通过自定义组件查询接口获取,也可以避免状态管理传递冲突的问题,参考queryNavigationInfo。
Q:在V1迁移V2的过程中,存在部分三方库是V1版本实现,还没适配V2版本,应该如何将V2版本的数据传递给V1版本三方库?

A:建议参考场景二中方案一,方案三实现。该场景下,方案二AppStorageV2.connect没办法写进三方库内。

总结
在API19之后,优先推荐使用官方提供的UIUtils.enableV2Compatibility方法实现V1、V2版本之间父子组件的数据传递与共享。
https://coub.com/view/4b707t
https://coub.com/view/4b707r
https://coub.com/view/4b707o
https://coub.com/view/4b707n
https://coub.com/view/4b707h
https://coub.com/view/4b707d
https://coub.com/view/4b7077
https://coub.com/view/4b7074
https://coub.com/view/4b706z
https://coub.com/view/4b706u
https://coub.com/view/4b706o
https://coub.com/view/4b706c
https://coub.com/view/4b7063
https://coub.com/view/4b7060
https://coub.com/view/4b705v
https://coub.com/view/4b705n
https://coub.com/view/4b705k
https://coub.com/view/4b701z
https://coub.com/view/4b6zjc
https://coub.com/view/4b6zja
https://coub.com/view/4b6zj9
https://coub.com/view/4b6zj6
https://coub.com/view/4b6zj4
https://coub.com/view/4b6zj3
https://coub.com/view/4b6zj2
https://coub.com/view/4b6zj1
https://coub.com/view/4b6ziz
https://coub.com/view/4b6zix
https://coub.com/view/4b6ziv
https://coub.com/view/4b6ziu
https://coub.com/view/4b6zit
https://coub.com/view/4b6zis
https://coub.com/view/4b6zio
https://coub.com/view/4b6zin
https://coub.com/view/4b6zim
https://coub.com/view/4b6zik

收起阅读 »

推荐一款 护航跑刀电竞下单系统,仓库开源 前后端

项目模板 后端 开源

仓库地址

https://gitee.com/zhangshangshidai/dailianhuhang

电竞护航下单系统 | UniApp + ThinkPHP6
2026年最火的游戏模式,三角洲等游戏的护航陪玩模式,面向游戏工作室、代练工作室、电竞护航工作室打造的订单管理交易系统,一套完整前后端开源方案,快速搭建电竞护航、游戏代练线上下单平台。

🛠️ 技术栈
前端:UniApp,支持编译 H5、微信小程序、App 多端
后端:ThinkPHP6 RESTful API
数据库:MySQL,支持 Redis 缓存拓展
✨ 核心功能
后台管理端
游戏分类管理,自定义游戏列表
护航商品发布,支持多规格价格、多属性配置、多图片上传
订单统一管理,接单操作,录入打手信息,订单完结处理
用户管理、资金配置、支付渠道配置、退款审核
用户客户端
用户注册登录,个人中心
浏览护航商品,在线下单
支付方式:微信支付、账户余额支付
完整订单流程:待接单、进行中、已完成、退款申请
订单详情查询,提交退款申请
余额明细、消费记录查看
🎯 项目优势
专为电竞护航、游戏代练工作室业务场景定制,贴合行业流程
UniApp 多端适配,小程序 / H5/App 任选上线
商品支持多规格多价位,满足不同段位、时长护航套餐需求
订单闭环完整:下单→支付→后台接单→登记打手→完成 / 退款
双支付模式,余额体系便于工作室老客户复购结算
代码分层清晰,易于二次开发、自定义功能迭代
📌 适用主体
电竞护航工作室、游戏代练工作室、游戏陪玩工作室、线上游戏服务交易团队

🚀 部署简述
后端部署 PHP 环境,导入数据库,配置数据库与支付参数
HBuilderX 打开 UniApp 前端项目,修改后端接口地址
编译打包对应端(小程序 / H5/App)即可投入使用

继续阅读 »

仓库地址

https://gitee.com/zhangshangshidai/dailianhuhang

电竞护航下单系统 | UniApp + ThinkPHP6
2026年最火的游戏模式,三角洲等游戏的护航陪玩模式,面向游戏工作室、代练工作室、电竞护航工作室打造的订单管理交易系统,一套完整前后端开源方案,快速搭建电竞护航、游戏代练线上下单平台。

🛠️ 技术栈
前端:UniApp,支持编译 H5、微信小程序、App 多端
后端:ThinkPHP6 RESTful API
数据库:MySQL,支持 Redis 缓存拓展
✨ 核心功能
后台管理端
游戏分类管理,自定义游戏列表
护航商品发布,支持多规格价格、多属性配置、多图片上传
订单统一管理,接单操作,录入打手信息,订单完结处理
用户管理、资金配置、支付渠道配置、退款审核
用户客户端
用户注册登录,个人中心
浏览护航商品,在线下单
支付方式:微信支付、账户余额支付
完整订单流程:待接单、进行中、已完成、退款申请
订单详情查询,提交退款申请
余额明细、消费记录查看
🎯 项目优势
专为电竞护航、游戏代练工作室业务场景定制,贴合行业流程
UniApp 多端适配,小程序 / H5/App 任选上线
商品支持多规格多价位,满足不同段位、时长护航套餐需求
订单闭环完整:下单→支付→后台接单→登记打手→完成 / 退款
双支付模式,余额体系便于工作室老客户复购结算
代码分层清晰,易于二次开发、自定义功能迭代
📌 适用主体
电竞护航工作室、游戏代练工作室、游戏陪玩工作室、线上游戏服务交易团队

🚀 部署简述
后端部署 PHP 环境,导入数据库,配置数据库与支付参数
HBuilderX 打开 UniApp 前端项目,修改后端接口地址
编译打包对应端(小程序 / H5/App)即可投入使用

收起阅读 »

如何实现页面上滑tabBar和顶部标题的悬停以及属性动画效果

tabbar

问题现象
参照Scroll的示例3,实现了tabBar在页面顶部的悬停效果。

如果页面顶部还有一个标题栏(即下图中“首页”),现在要实现tab内容向上滚动时tabBar悬停到标题栏下方(即下图中“同城”,“推荐”,“活动”,“玩机”这一行),且可以实现标题栏在滚动过程中样式变化(如下图背景变化),该如何实现?

点击放大点击放大

背景知识
Stack:堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。

Scroll:可滚动的容器组件,当子组件的布局尺寸超过父组件的尺寸时,内容可以滚动。

nestedScroll:设置前后两个方向的嵌套滚动模式,实现与父组件的滚动联动。
onDidScroll:滚动事件回调,Scroll滚动时触发。
解决方案
使用Stack层叠布局,将标题栏悬浮展示在页面顶部。
考虑页面滚动以及tabContent里面的list滚动,就要考虑滚动嵌套问题,目前场景需要选择:
向上滚动时:父组件先滚动,父组件滚动到边缘以后自身滚动;
向下滚动时:自身先滚动,自身滚动到边缘以后父组件滚动。
示例代码如下:

.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
});

父组件滚动过程中,根据滚动偏移量线性修改标题栏样式(如字体大小、透明度、背景等),onDidScroll可以返回当前帧滚动的偏移量和当前滚动状态。
示例代码如下:
@Entry
@Component
struct StickyNestedScroll {
@State arr: number[] = [];
@State opacityNum: number = 0;
@State curYOffset: number = 0;
@State currentIndex: number = 0;

aboutToAppear() {
for (let i = 0; i < 30; i++) {
this.arr.push(i);
}
}

@Styles
listCard() {
.backgroundColor('#FFF')
.height(64)
.width('calc(100% - 32vp)')
.borderRadius(20)
.margin({
left: 16,
right: 16,
});
}

@Builder
tabBuilder(title: string, targetIndex: number) {
Column() {
Text(title)
.fontColor(this.currentIndex === targetIndex ? '#FFF' : '#000')
.fontSize(14)
.fontWeight(this.currentIndex === targetIndex ? 500 : 400);
}
.height(36)
.padding({
left: 16,
right: 16,
})
.margin({ left: 8 })
.borderRadius(20)
.backgroundColor(this.currentIndex === targetIndex ? '#0A59F7' : 'rgba(0, 0, 0, 0.05)')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center);
}

build() {
Stack() {
Scroll() {
Column() {
Image($r('app.media.scrollTopbg')) // 图片资源需自行替换
.width('100%')
.height(300);
Tabs({ barPosition: BarPosition.Start }) {
ForEach(this.arr.slice(0, 6),
(item: number, index: number) => {
TabContent() {
List({ space: 16 }) {
ForEach(this.arr, (item1: number) => {
ListItem() {
Text('item' + item1)
.fontSize(16).fontWeight(400);
}.listCard();
}, (item1: string) => item1);
}.width('100%')
.edgeEffect(EdgeEffect.None)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
});

            }.tabBar(this.tabBuilder(`标签${item + 1}`, index));  
          }, (item: number, index: number) => JSON.stringify(item) + index);  
      }  
      .onChange((index) => {  
        this.currentIndex = index;  
      })  
      .barMode(BarMode.Scrollable)  
      .barHeight(72)  
      .vertical(false)  
      .width('100%')  
      .height('calc(100% - 80vp)');  
    }.width('100%');  
  }  
  .friction(0.6)  
  .scrollBar(BarState.Off)  
  .width('100%')  
  .height('100%')  
  .onDidScroll((xOffset: number, yOffset: number, scrollState: ScrollState): void => {  
    // 累计计算当前父组件滚动在Y轴方向的偏移量  
    this.curYOffset += yOffset;  
    // 根据父组件一共可以滚动的距离计算当前每帧的当前透明度  
    let opacity = this.curYOffset / 220;  
    if (opacity >= 1) {  
      opacity = 1;  
    }  
    if (opacity <= 0) {  
      opacity = 0;  
    }  
    this.opacityNum = opacity;  
    console.info(`xOffset: ${xOffset},scrollState:${scrollState}`);  
  });  

  // 悬浮标题栏  
  Text('工作台')  
    .fontSize(24)  
    .fontColor('#000')  
    .fontWeight(FontWeight.Bold)  
    .backgroundColor(`rgba(255,255,255,${this.opacityNum})`)  
    .position({ x: 0, y: 0 })  
    .width('100%')  
    .height(80)  
    .padding({ left: 16, top: 44 });  
}.width('100%')  
.height('100%').background('#F1F3F5');  

}
}
效果如图:

点击放大

总结
悬浮类布局首先考虑Stack层叠布局。
嵌套滚动,考虑父子组件之间的先后滚动模式,选择合适的滚动模式。
滚动过程中,获取当前滚动每帧偏移量和滚动状态,做合适的UI更新。
https://pastebin.com/H9s5Prqc
https://pastebin.com/B3rdpE0P
https://pastebin.com/1DnHtpyd
https://pastebin.com/TEdBHtNb
https://pastebin.com/LJ0cQ7Uu
https://pastebin.com/bEc1mLCD
https://pastebin.com/kgSG3Gqx
https://pastebin.com/562Mtrb0
https://pastebin.com/TcJ1ucZM
https://pastebin.com/NdYhTKwU
https://pastebin.com/0kg2myHE
https://pastebin.com/tuXkKyRR
https://pastebin.com/JNaUA51y
https://pastebin.com/RqerbF2p
https://pastebin.com/jzeTmGu6
https://pastebin.com/bG1T2kG0
https://pastebin.com/2waPB0AK
https://pastebin.com/miLDPTFG
https://pastebin.com/2GVXUYr6
https://pastebin.com/RDfika4N
https://pastebin.com/yVTUxb9T
https://pastebin.com/8fpFzpMF
https://pastebin.com/dxrwgxZb
https://pastebin.com/WW5LdheP
https://pastebin.com/j9dwuCiX
https://pastebin.com/4pjqADUq
https://pastebin.com/RMeYiqCG
https://pastebin.com/jcEZ3mCw
https://pastebin.com/u0UKBHMc
https://pastebin.com/6AtALgcG
https://pastebin.com/yZZZgpRJ
https://pastebin.com/LbQCHdWp
https://pastebin.com/8uaaUxm3
https://pastebin.com/smKGwEkJ
https://pastebin.com/XZS8Lgc9
https://pastebin.com/PXPcxxu5
https://pastebin.com/AzHkpzSh
https://pastebin.com/kdDFUmsj
https://pastebin.com/ejETYeux
https://pastebin.com/s4Tj7hqX
https://pastebin.com/n8vn16hR
https://pastebin.com/cUBURDS5
https://pastebin.com/iHuTYWub
https://pastebin.com/it84K7XW
https://pastebin.com/dHhXdDc4
https://pastebin.com/btsEnW55
https://pastebin.com/VhQvwq6f
https://pastebin.com/prMTmsc5
https://pastebin.com/giVp1BpF
https://pastebin.com/fQyYpmGY
https://pastebin.com/gAg3F6UY
https://pastebin.com/2QWLCNvx
https://pastebin.com/2gxy4TKg
https://pastebin.com/xKYNRWMk
https://pastebin.com/1HxCD5DS
https://pastebin.com/GiAXtWj1
https://pastebin.com/m8afMq62
https://pastebin.com/55Xb4vqW
https://pastebin.com/zJcq0WY7
https://pastebin.com/WmVwfQrY
https://pastebin.com/91bnWbQF
https://pastebin.com/gvKhZ9Tc
https://pastebin.com/Nt21BqQ7
https://pastebin.com/wM2aTQ0w
https://pastebin.com/Vc7vvzKJ
https://pastebin.com/yaq33eZQ
https://pastebin.com/qZ5F9NYb
https://pastebin.com/i3E4FgCp
https://pastebin.com/AkTA5RZZ
https://pastebin.com/ELEG1B0m
https://pastebin.com/5JWdPFhR
https://pastebin.com/R0YTHUS4
https://pastebin.com/x9nRf1j0
https://pastebin.com/Aih0qsy2
https://pastebin.com/CpwvWE4e
https://pastebin.com/rr9rRU2E
https://pastebin.com/6Ye4Gwq7
https://pastebin.com/DAw6tKSy
https://pastebin.com/6gFkNdxP
https://pastebin.com/zMtnD7tM
https://pastebin.com/95F1ruf8
https://pastebin.com/ZXeMfgMs
https://pastebin.com/s8Xu4WDd
https://pastebin.com/cTfEk1wu
https://pastebin.com/Mjm4d4Rz
https://pastebin.com/gRAQUhkK
https://pastebin.com/YpNWSg6u
https://pastebin.com/51Edu6ba
https://pastebin.com/XkAuM8JZ
https://pastebin.com/vcnnDhb9
https://pastebin.com/hjhUzWgG
https://pastebin.com/Za2eji2y
https://pastebin.com/6HcM5ez2
https://pastebin.com/iKpg8iex
https://pastebin.com/9zDKuETK
https://pastebin.com/M4swhPLu
https://pastebin.com/BFs6X9UX
https://pastebin.com/GWjxjEgb
https://pastebin.com/E1LbLCgp
https://pastebin.com/Ze95QMAi
https://pastebin.com/ZY8zYNWn
https://pastebin.com/fTLHPLZZ
https://pastebin.com/6fDk9tYi
https://pastebin.com/GfuDHVfH
https://pastebin.com/KUq4BLce
https://pastebin.com/qY5k2ZxX
https://pastebin.com/8yXshjhD
https://pastebin.com/3BmddLGG
https://pastebin.com/6hgiPSQy
https://pastebin.com/pwCxCvBx
https://pastebin.com/rdgB20Hq
https://pastebin.com/fR1AXxN3
https://pastebin.com/XAyJJSh4
https://pastebin.com/Udz89NF9
https://pastebin.com/tRFNRTfq
https://pastebin.com/LYnbJkDX
https://pastebin.com/UrndZZ4r
https://pastebin.com/UJ0cJu7W
https://pastebin.com/VC9ftumY
https://pastebin.com/1mD57fxS
https://pastebin.com/4yCvSzPt
https://pastebin.com/Tds1KK8q
https://pastebin.com/cuTV0ANH
https://pastebin.com/tfvAj5zG
https://pastebin.com/YmTJJ9P4
https://pastebin.com/qr720c8a
https://pastebin.com/Qzx9z0jT
https://pastebin.com/phurPJTs
https://pastebin.com/gHHTkHD3
https://pastebin.com/ZwnPCDTj
https://pastebin.com/0DSjtHAt
https://pastebin.com/TftyJMxx
https://pastebin.com/vBBJvgLm
https://pastebin.com/NQYakApj
https://pastebin.com/meCbWSmb
https://pastebin.com/1VP7BDiy
https://pastebin.com/9dwnBKrH
https://pastebin.com/CfNxq9BD
https://pastebin.com/7SdTHhbB
https://pastebin.com/wjGatMb0
https://pastebin.com/Gv2kQv0K
https://pastebin.com/kqY5ZC2U
https://pastebin.com/NVQSYDC2
https://pastebin.com/p5cmmLTs
https://pastebin.com/ksUjWjEf
https://pastebin.com/iGqdf7JS
https://pastebin.com/1jPPW8SY
https://pastebin.com/zaiTAPGu
https://pastebin.com/ZyCEaVhf
https://pastebin.com/Z8nhRzGY
https://pastebin.com/4ANEqn0d
https://pastebin.com/B6aK9n6G
https://pastebin.com/0Tt4rGw1
https://pastebin.com/jV1WDDj1
https://pastebin.com/epsfQ9xR
https://pastebin.com/TDsuwVQq
https://pastebin.com/1sCfUZ1r
https://pastebin.com/rPZVVY9W
https://pastebin.com/YMAeatYs
https://pastebin.com/USZwiZLz
https://pastebin.com/0D4g5hpe
https://pastebin.com/aTgxSFAv
https://pastebin.com/d9yLduCs
https://pastebin.com/t2jGSt52
https://pastebin.com/ZCjZZ5cd
https://pastebin.com/fYBzqBQc
https://pastebin.com/rPSifbkZ
https://pastebin.com/r5hsmStB
https://pastebin.com/GBQJqUpA
https://pastebin.com/wi5TgU60
https://pastebin.com/17bmtrLm
https://pastebin.com/ymjx0ej6
https://pastebin.com/tnhPx5tx
https://pastebin.com/pKythJNa
https://pastebin.com/7rkBm8RR
https://pastebin.com/nKrgz0hx
https://pastebin.com/Ls4U30up
https://pastebin.com/GLrgZDKM
https://pastebin.com/QLenyQyb
https://pastebin.com/6kf7ctaD
https://pastebin.com/493xDX2E
https://pastebin.com/NNLBP1BA
https://pastebin.com/fQsBphT0
https://pastebin.com/iFUr5gqq
https://pastebin.com/YkZtGRGV
https://pastebin.com/CUgpDscE
https://pastebin.com/4BSRJRJ2
https://pastebin.com/rgaPPPXy
https://pastebin.com/e0MbXcLH
https://pastebin.com/0y661SPV
https://pastebin.com/WnhafHAL
https://pastebin.com/vdcuA6Wn
https://pastebin.com/0wP7iM13
https://pastebin.com/kSsbb7uN
https://pastebin.com/CKuHJUp0
https://pastebin.com/VFHuYY0w
https://pastebin.com/e57GMSyT
https://pastebin.com/JCJqt21y
https://pastebin.com/v03W6Wua
https://pastebin.com/Qh5cvmxD
https://pastebin.com/1n1rmrga
https://pastebin.com/EqpNrUd9
https://pastebin.com/hNzVc4VA
https://pastebin.com/wyd61PAX
https://pastebin.com/KUqAc0dA
https://pastebin.com/dAwvfQ3n
https://pastebin.com/tfhzjx0X
https://pastebin.com/qcry1nbc
https://pastebin.com/Xx1xKrr5
https://pastebin.com/z4RRXjbZ
https://pastebin.com/HFUs4bbZ
https://pastebin.com/7hFpzwBb
https://pastebin.com/kkxJEXkH
https://pastebin.com/8N244GbV
https://pastebin.com/6QgG0hC0
https://pastebin.com/SPfWYRk3
https://pastebin.com/CQQ6jn3J
https://pastebin.com/KHGYtVZu
https://pastebin.com/ibnJTTYm

继续阅读 »

问题现象
参照Scroll的示例3,实现了tabBar在页面顶部的悬停效果。

如果页面顶部还有一个标题栏(即下图中“首页”),现在要实现tab内容向上滚动时tabBar悬停到标题栏下方(即下图中“同城”,“推荐”,“活动”,“玩机”这一行),且可以实现标题栏在滚动过程中样式变化(如下图背景变化),该如何实现?

点击放大点击放大

背景知识
Stack:堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。

Scroll:可滚动的容器组件,当子组件的布局尺寸超过父组件的尺寸时,内容可以滚动。

nestedScroll:设置前后两个方向的嵌套滚动模式,实现与父组件的滚动联动。
onDidScroll:滚动事件回调,Scroll滚动时触发。
解决方案
使用Stack层叠布局,将标题栏悬浮展示在页面顶部。
考虑页面滚动以及tabContent里面的list滚动,就要考虑滚动嵌套问题,目前场景需要选择:
向上滚动时:父组件先滚动,父组件滚动到边缘以后自身滚动;
向下滚动时:自身先滚动,自身滚动到边缘以后父组件滚动。
示例代码如下:

.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
});

父组件滚动过程中,根据滚动偏移量线性修改标题栏样式(如字体大小、透明度、背景等),onDidScroll可以返回当前帧滚动的偏移量和当前滚动状态。
示例代码如下:
@Entry
@Component
struct StickyNestedScroll {
@State arr: number[] = [];
@State opacityNum: number = 0;
@State curYOffset: number = 0;
@State currentIndex: number = 0;

aboutToAppear() {
for (let i = 0; i < 30; i++) {
this.arr.push(i);
}
}

@Styles
listCard() {
.backgroundColor('#FFF')
.height(64)
.width('calc(100% - 32vp)')
.borderRadius(20)
.margin({
left: 16,
right: 16,
});
}

@Builder
tabBuilder(title: string, targetIndex: number) {
Column() {
Text(title)
.fontColor(this.currentIndex === targetIndex ? '#FFF' : '#000')
.fontSize(14)
.fontWeight(this.currentIndex === targetIndex ? 500 : 400);
}
.height(36)
.padding({
left: 16,
right: 16,
})
.margin({ left: 8 })
.borderRadius(20)
.backgroundColor(this.currentIndex === targetIndex ? '#0A59F7' : 'rgba(0, 0, 0, 0.05)')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center);
}

build() {
Stack() {
Scroll() {
Column() {
Image($r('app.media.scrollTopbg')) // 图片资源需自行替换
.width('100%')
.height(300);
Tabs({ barPosition: BarPosition.Start }) {
ForEach(this.arr.slice(0, 6),
(item: number, index: number) => {
TabContent() {
List({ space: 16 }) {
ForEach(this.arr, (item1: number) => {
ListItem() {
Text('item' + item1)
.fontSize(16).fontWeight(400);
}.listCard();
}, (item1: string) => item1);
}.width('100%')
.edgeEffect(EdgeEffect.None)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
});

            }.tabBar(this.tabBuilder(`标签${item + 1}`, index));  
          }, (item: number, index: number) => JSON.stringify(item) + index);  
      }  
      .onChange((index) => {  
        this.currentIndex = index;  
      })  
      .barMode(BarMode.Scrollable)  
      .barHeight(72)  
      .vertical(false)  
      .width('100%')  
      .height('calc(100% - 80vp)');  
    }.width('100%');  
  }  
  .friction(0.6)  
  .scrollBar(BarState.Off)  
  .width('100%')  
  .height('100%')  
  .onDidScroll((xOffset: number, yOffset: number, scrollState: ScrollState): void => {  
    // 累计计算当前父组件滚动在Y轴方向的偏移量  
    this.curYOffset += yOffset;  
    // 根据父组件一共可以滚动的距离计算当前每帧的当前透明度  
    let opacity = this.curYOffset / 220;  
    if (opacity >= 1) {  
      opacity = 1;  
    }  
    if (opacity <= 0) {  
      opacity = 0;  
    }  
    this.opacityNum = opacity;  
    console.info(`xOffset: ${xOffset},scrollState:${scrollState}`);  
  });  

  // 悬浮标题栏  
  Text('工作台')  
    .fontSize(24)  
    .fontColor('#000')  
    .fontWeight(FontWeight.Bold)  
    .backgroundColor(`rgba(255,255,255,${this.opacityNum})`)  
    .position({ x: 0, y: 0 })  
    .width('100%')  
    .height(80)  
    .padding({ left: 16, top: 44 });  
}.width('100%')  
.height('100%').background('#F1F3F5');  

}
}
效果如图:

点击放大

总结
悬浮类布局首先考虑Stack层叠布局。
嵌套滚动,考虑父子组件之间的先后滚动模式,选择合适的滚动模式。
滚动过程中,获取当前滚动每帧偏移量和滚动状态,做合适的UI更新。
https://pastebin.com/H9s5Prqc
https://pastebin.com/B3rdpE0P
https://pastebin.com/1DnHtpyd
https://pastebin.com/TEdBHtNb
https://pastebin.com/LJ0cQ7Uu
https://pastebin.com/bEc1mLCD
https://pastebin.com/kgSG3Gqx
https://pastebin.com/562Mtrb0
https://pastebin.com/TcJ1ucZM
https://pastebin.com/NdYhTKwU
https://pastebin.com/0kg2myHE
https://pastebin.com/tuXkKyRR
https://pastebin.com/JNaUA51y
https://pastebin.com/RqerbF2p
https://pastebin.com/jzeTmGu6
https://pastebin.com/bG1T2kG0
https://pastebin.com/2waPB0AK
https://pastebin.com/miLDPTFG
https://pastebin.com/2GVXUYr6
https://pastebin.com/RDfika4N
https://pastebin.com/yVTUxb9T
https://pastebin.com/8fpFzpMF
https://pastebin.com/dxrwgxZb
https://pastebin.com/WW5LdheP
https://pastebin.com/j9dwuCiX
https://pastebin.com/4pjqADUq
https://pastebin.com/RMeYiqCG
https://pastebin.com/jcEZ3mCw
https://pastebin.com/u0UKBHMc
https://pastebin.com/6AtALgcG
https://pastebin.com/yZZZgpRJ
https://pastebin.com/LbQCHdWp
https://pastebin.com/8uaaUxm3
https://pastebin.com/smKGwEkJ
https://pastebin.com/XZS8Lgc9
https://pastebin.com/PXPcxxu5
https://pastebin.com/AzHkpzSh
https://pastebin.com/kdDFUmsj
https://pastebin.com/ejETYeux
https://pastebin.com/s4Tj7hqX
https://pastebin.com/n8vn16hR
https://pastebin.com/cUBURDS5
https://pastebin.com/iHuTYWub
https://pastebin.com/it84K7XW
https://pastebin.com/dHhXdDc4
https://pastebin.com/btsEnW55
https://pastebin.com/VhQvwq6f
https://pastebin.com/prMTmsc5
https://pastebin.com/giVp1BpF
https://pastebin.com/fQyYpmGY
https://pastebin.com/gAg3F6UY
https://pastebin.com/2QWLCNvx
https://pastebin.com/2gxy4TKg
https://pastebin.com/xKYNRWMk
https://pastebin.com/1HxCD5DS
https://pastebin.com/GiAXtWj1
https://pastebin.com/m8afMq62
https://pastebin.com/55Xb4vqW
https://pastebin.com/zJcq0WY7
https://pastebin.com/WmVwfQrY
https://pastebin.com/91bnWbQF
https://pastebin.com/gvKhZ9Tc
https://pastebin.com/Nt21BqQ7
https://pastebin.com/wM2aTQ0w
https://pastebin.com/Vc7vvzKJ
https://pastebin.com/yaq33eZQ
https://pastebin.com/qZ5F9NYb
https://pastebin.com/i3E4FgCp
https://pastebin.com/AkTA5RZZ
https://pastebin.com/ELEG1B0m
https://pastebin.com/5JWdPFhR
https://pastebin.com/R0YTHUS4
https://pastebin.com/x9nRf1j0
https://pastebin.com/Aih0qsy2
https://pastebin.com/CpwvWE4e
https://pastebin.com/rr9rRU2E
https://pastebin.com/6Ye4Gwq7
https://pastebin.com/DAw6tKSy
https://pastebin.com/6gFkNdxP
https://pastebin.com/zMtnD7tM
https://pastebin.com/95F1ruf8
https://pastebin.com/ZXeMfgMs
https://pastebin.com/s8Xu4WDd
https://pastebin.com/cTfEk1wu
https://pastebin.com/Mjm4d4Rz
https://pastebin.com/gRAQUhkK
https://pastebin.com/YpNWSg6u
https://pastebin.com/51Edu6ba
https://pastebin.com/XkAuM8JZ
https://pastebin.com/vcnnDhb9
https://pastebin.com/hjhUzWgG
https://pastebin.com/Za2eji2y
https://pastebin.com/6HcM5ez2
https://pastebin.com/iKpg8iex
https://pastebin.com/9zDKuETK
https://pastebin.com/M4swhPLu
https://pastebin.com/BFs6X9UX
https://pastebin.com/GWjxjEgb
https://pastebin.com/E1LbLCgp
https://pastebin.com/Ze95QMAi
https://pastebin.com/ZY8zYNWn
https://pastebin.com/fTLHPLZZ
https://pastebin.com/6fDk9tYi
https://pastebin.com/GfuDHVfH
https://pastebin.com/KUq4BLce
https://pastebin.com/qY5k2ZxX
https://pastebin.com/8yXshjhD
https://pastebin.com/3BmddLGG
https://pastebin.com/6hgiPSQy
https://pastebin.com/pwCxCvBx
https://pastebin.com/rdgB20Hq
https://pastebin.com/fR1AXxN3
https://pastebin.com/XAyJJSh4
https://pastebin.com/Udz89NF9
https://pastebin.com/tRFNRTfq
https://pastebin.com/LYnbJkDX
https://pastebin.com/UrndZZ4r
https://pastebin.com/UJ0cJu7W
https://pastebin.com/VC9ftumY
https://pastebin.com/1mD57fxS
https://pastebin.com/4yCvSzPt
https://pastebin.com/Tds1KK8q
https://pastebin.com/cuTV0ANH
https://pastebin.com/tfvAj5zG
https://pastebin.com/YmTJJ9P4
https://pastebin.com/qr720c8a
https://pastebin.com/Qzx9z0jT
https://pastebin.com/phurPJTs
https://pastebin.com/gHHTkHD3
https://pastebin.com/ZwnPCDTj
https://pastebin.com/0DSjtHAt
https://pastebin.com/TftyJMxx
https://pastebin.com/vBBJvgLm
https://pastebin.com/NQYakApj
https://pastebin.com/meCbWSmb
https://pastebin.com/1VP7BDiy
https://pastebin.com/9dwnBKrH
https://pastebin.com/CfNxq9BD
https://pastebin.com/7SdTHhbB
https://pastebin.com/wjGatMb0
https://pastebin.com/Gv2kQv0K
https://pastebin.com/kqY5ZC2U
https://pastebin.com/NVQSYDC2
https://pastebin.com/p5cmmLTs
https://pastebin.com/ksUjWjEf
https://pastebin.com/iGqdf7JS
https://pastebin.com/1jPPW8SY
https://pastebin.com/zaiTAPGu
https://pastebin.com/ZyCEaVhf
https://pastebin.com/Z8nhRzGY
https://pastebin.com/4ANEqn0d
https://pastebin.com/B6aK9n6G
https://pastebin.com/0Tt4rGw1
https://pastebin.com/jV1WDDj1
https://pastebin.com/epsfQ9xR
https://pastebin.com/TDsuwVQq
https://pastebin.com/1sCfUZ1r
https://pastebin.com/rPZVVY9W
https://pastebin.com/YMAeatYs
https://pastebin.com/USZwiZLz
https://pastebin.com/0D4g5hpe
https://pastebin.com/aTgxSFAv
https://pastebin.com/d9yLduCs
https://pastebin.com/t2jGSt52
https://pastebin.com/ZCjZZ5cd
https://pastebin.com/fYBzqBQc
https://pastebin.com/rPSifbkZ
https://pastebin.com/r5hsmStB
https://pastebin.com/GBQJqUpA
https://pastebin.com/wi5TgU60
https://pastebin.com/17bmtrLm
https://pastebin.com/ymjx0ej6
https://pastebin.com/tnhPx5tx
https://pastebin.com/pKythJNa
https://pastebin.com/7rkBm8RR
https://pastebin.com/nKrgz0hx
https://pastebin.com/Ls4U30up
https://pastebin.com/GLrgZDKM
https://pastebin.com/QLenyQyb
https://pastebin.com/6kf7ctaD
https://pastebin.com/493xDX2E
https://pastebin.com/NNLBP1BA
https://pastebin.com/fQsBphT0
https://pastebin.com/iFUr5gqq
https://pastebin.com/YkZtGRGV
https://pastebin.com/CUgpDscE
https://pastebin.com/4BSRJRJ2
https://pastebin.com/rgaPPPXy
https://pastebin.com/e0MbXcLH
https://pastebin.com/0y661SPV
https://pastebin.com/WnhafHAL
https://pastebin.com/vdcuA6Wn
https://pastebin.com/0wP7iM13
https://pastebin.com/kSsbb7uN
https://pastebin.com/CKuHJUp0
https://pastebin.com/VFHuYY0w
https://pastebin.com/e57GMSyT
https://pastebin.com/JCJqt21y
https://pastebin.com/v03W6Wua
https://pastebin.com/Qh5cvmxD
https://pastebin.com/1n1rmrga
https://pastebin.com/EqpNrUd9
https://pastebin.com/hNzVc4VA
https://pastebin.com/wyd61PAX
https://pastebin.com/KUqAc0dA
https://pastebin.com/dAwvfQ3n
https://pastebin.com/tfhzjx0X
https://pastebin.com/qcry1nbc
https://pastebin.com/Xx1xKrr5
https://pastebin.com/z4RRXjbZ
https://pastebin.com/HFUs4bbZ
https://pastebin.com/7hFpzwBb
https://pastebin.com/kkxJEXkH
https://pastebin.com/8N244GbV
https://pastebin.com/6QgG0hC0
https://pastebin.com/SPfWYRk3
https://pastebin.com/CQQ6jn3J
https://pastebin.com/KHGYtVZu
https://pastebin.com/ibnJTTYm

收起阅读 »

如何实现Matrix2D图形在画布中心点放大并旋转角度

问题现象
如何在画布中心点实现Matrix2D图形指定倍数的放大和指定角度的旋转?

背景知识
Matrix2D组件的方法rotate,能够以旋转点为中心、对当前矩阵进行右乘旋转运算,调用公式为:rotate(degree: number, rx?: number, ry?: number),其中degree为旋转角度,rx和ry为旋转中心点相对[0,0]点的水平和垂直方向坐标,如果[0,0]点通过translate发生移动,则以新的点计算相对的rx和ry值。
translate方法能够对当前矩阵进行左乘平移运算,调用公式为:translate(tx?: number, ty?: number)。
scale方法能够对当前矩阵进行右乘缩放运算,调用公式为:scale(sx?: number, sy?: number),其中sx和sy为水平和垂直缩放比例系数。以[0,0]为基准进行缩放,如果[0,0]点通过translate发生移动,则以移动后的点为基准点进行缩放。
解决方案
使用Flex布局将Canvas组件居中显示,创建矩形。
使用rotate方法对矩阵进行旋转操作,旋转角度由angle决定,旋转中心为矩形的中心;使用scale方法对矩阵进行缩放操作,缩放倍数由scaleTimesX和scaleTimesY决定,以中心点为基准进行缩放。rotate和scale存在调用顺序,遵循就近原则,因此需要将scale方法的使用放在rotate上方。
使用translate方法对矩阵进行平移操作,将画布的原点移动到画布的中心,即可将图形移动至画布中心。将经过变换的矩阵应用到CanvasRenderingContext2D对象上。
用fillRect()方法在画布上绘制矩形。
完整示例参考如下:
@Entry
@Component
struct MatrixChange {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private matrix: Matrix2D = new Matrix2D();
x: number = 50; // 图形大小
y: number = 50; // 图形大小
canvasWidth: number = 200; // 画布大小
canvasHeight: number = 200; // 画布大小
scaleTimesX: number = 2; // 缩放设置
scaleTimesY: number = 2; // 缩放设置
angle: number = -30; // 旋转角度

build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Canvas(this.context)
.width(this.canvasWidth)
.height(this.canvasHeight)
.backgroundColor('#0D5AF5')
.onReady(() => {
this.matrix.scaleX = 1;
this.matrix.scaleY = 1;
this.matrix.scale(this.scaleTimesX, this.scaleTimesY);
this.matrix.rotate(this.angle Math.PI / 180, this.x / 2, this.y / 2);
this.matrix.translate(this.canvasWidth / 2, this.canvasHeight / 2);
this.matrix.translate(-this.x
this.scaleTimesX / 2, -this.y * this.scaleTimesY / 2);
this.context.setTransform(this.matrix);
this.context.fillRect(0, 0, this.x, this.y);
});
}
.width('100%')
.height('100%');
}
}
总结
Matrix2D图形能够在画布中心点放大指定倍数并旋转指定角度,该功能适用于需要展示图形并对其进行各种变换的场景,例如数据可视化、图形编辑器等。
https://pastebin.com/jYcy8DG4
https://pastebin.com/Hp1sbSaS
https://pastebin.com/hTxV2yyS
https://pastebin.com/TQCjtTcP
https://pastebin.com/JpF1LSct
https://pastebin.com/Fr3XnQdE
https://pastebin.com/yTvhcbNw
https://pastebin.com/hRe1DgNt
https://pastebin.com/M3y1HL1H
https://pastebin.com/VraVGXVf
https://pastebin.com/griS9fnH
https://pastebin.com/pwrdFgiY
https://pastebin.com/Pbn36K7e
https://pastebin.com/7DMANuaz
https://pastebin.com/rHKJdsZT
https://pastebin.com/NLD2BSd3
https://pastebin.com/95Shz0dA
https://pastebin.com/zhphtiyk
https://pastebin.com/3HY6p8t3
https://pastebin.com/XQEtXnKJ
https://pastebin.com/56Zk2nAE
https://pastebin.com/FHzZvcGp
https://pastebin.com/jvCBLRx8
https://pastebin.com/UQ8bqVpS
https://pastebin.com/H5XxuvFD
https://pastebin.com/ZTRtEnKD
https://pastebin.com/DXXEFpJ7
https://pastebin.com/Xz7is4PA
https://pastebin.com/ujgDH7CQ
https://pastebin.com/QXvsH58y
https://pastebin.com/Syp4LL5s
https://pastebin.com/CLgzB5B2
https://pastebin.com/B1P1eZiT
https://pastebin.com/XsyVXiCg
https://pastebin.com/1XCeaqAU
https://pastebin.com/Jw13Rebn
https://pastebin.com/DyCcgNsv
https://pastebin.com/7LEMiQZ2
https://pastebin.com/dS3rfyQ6
https://pastebin.com/wLwm6rp7
https://pastebin.com/4WGjDaH1
https://pastebin.com/zb1zD5aE
https://pastebin.com/c1zNryeR
https://pastebin.com/4s5BeWXh
https://pastebin.com/Ce3nxNXr
https://pastebin.com/cmj5c080
https://pastebin.com/1H5LCdEY
https://pastebin.com/zZkVFh7Q
https://pastebin.com/BBrYWefF
https://pastebin.com/U4vvzbtw
https://pastebin.com/BWKQJ9Bf
https://pastebin.com/B5CXLSym
https://pastebin.com/PebrcV9M
https://pastebin.com/Ee30dGS4
https://pastebin.com/beXkZr16
https://pastebin.com/qfWN2Ryj
https://pastebin.com/c7mAxDum
https://pastebin.com/MpP9YNFU
https://pastebin.com/SQ8jWZqx
https://pastebin.com/9VALda7W
https://pastebin.com/xZ8P3Ks8
https://pastebin.com/niR9ymit
https://pastebin.com/Rv0T6rUx
https://pastebin.com/jAh0PWYa
https://pastebin.com/3wv5Agv8
https://pastebin.com/EtNNfJmj
https://pastebin.com/a9j6TNjC
https://pastebin.com/x6xxPkPX
https://pastebin.com/az8dYRbQ
https://pastebin.com/husRdHb4
https://pastebin.com/ZQw2Pbhx
https://pastebin.com/C59eWK5e
https://pastebin.com/kzcn3KvK
https://pastebin.com/mT8ehVrP
https://pastebin.com/uv3DmK2r
https://pastebin.com/9A90NJRS
https://pastebin.com/nxKRJ6DM
https://pastebin.com/em4D4Psa
https://pastebin.com/sq1LkcWj
https://pastebin.com/X9Qr3LsF
https://pastebin.com/0yhb1G5u
https://pastebin.com/RNBk3wcp
https://pastebin.com/n21GrF8N
https://pastebin.com/BVGGULWt
https://pastebin.com/2LVYSav1
https://pastebin.com/37m2vAg8
https://pastebin.com/YALGJ9Dj
https://pastebin.com/MPrNKc2X
https://pastebin.com/WcQR7d4P
https://pastebin.com/frpzGFbV
https://pastebin.com/MTZN74bH
https://pastebin.com/4XFYvRhd
https://pastebin.com/XEnbVYfF
https://pastebin.com/iGNu6WyC
https://pastebin.com/iC4qCnDP
https://pastebin.com/SubVHQiU
https://pastebin.com/GtmFQfJ6
https://pastebin.com/PqcdaaqX
https://pastebin.com/wbpVhAa4
https://pastebin.com/Gs8c9UJM
https://pastebin.com/rvBJLy48
https://pastebin.com/qJhsAkBV
https://pastebin.com/GsUxwvRp
https://pastebin.com/xhtemkVi
https://pastebin.com/VpcarPwJ
https://pastebin.com/M2MZ4XjZ
https://pastebin.com/1hsmtBAh
https://pastebin.com/FBKiMKLM
https://pastebin.com/TFcszpgY
https://pastebin.com/wZyc488a
https://pastebin.com/eyzX1hF0
https://pastebin.com/pn2C5r5y
https://pastebin.com/dyHRSz7V
https://pastebin.com/qspZ7UVQ
https://pastebin.com/hXZ59vW5
https://pastebin.com/EBymVseu
https://pastebin.com/Pi2qAyiG
https://pastebin.com/r53FkZrX
https://pastebin.com/pBxp5x6P
https://pastebin.com/kRmwv8xX
https://pastebin.com/YPBtC4bD
https://pastebin.com/zaazK0qN
https://pastebin.com/MH9s5BWr
https://pastebin.com/kUWEmtQ3
https://pastebin.com/EiVfHUPQ
https://pastebin.com/F1zDdap8
https://pastebin.com/x6ninbWm
https://pastebin.com/kp5xBGss
https://pastebin.com/X8rLUdJN
https://pastebin.com/V1Zmj7FJ
https://pastebin.com/UheVuGRp
https://pastebin.com/yKHmpBX3
https://pastebin.com/GQaa3tDW
https://pastebin.com/D00GfaUn
https://pastebin.com/2bTz1JVU
https://pastebin.com/hhdmx2bc
https://pastebin.com/KaXVaXRZ
https://pastebin.com/R0R3JCNA
https://pastebin.com/zQGT96rX
https://pastebin.com/cbqQbPyf
https://pastebin.com/6PJN3M7N
https://pastebin.com/k1yeEBr9
https://pastebin.com/GAVHCfGW
https://pastebin.com/V9BDRArR
https://pastebin.com/Ms7giaW9
https://pastebin.com/ddfs4xn7
https://pastebin.com/bhtqrAfp
https://pastebin.com/hCwrGzXp
https://pastebin.com/Cf6NEq1t
https://pastebin.com/i2YZAQTR
https://pastebin.com/PBVaCkjV
https://pastebin.com/CZh9JdFZ
https://pastebin.com/Cf4LLKEd
https://pastebin.com/EiNK5had
https://pastebin.com/j2XSp6pu
https://pastebin.com/1EM6BSxm
https://pastebin.com/P2Zf7gHn
https://pastebin.com/tjjJFy7s
https://pastebin.com/jZ2QMP9d
https://pastebin.com/vtrnXw8f
https://pastebin.com/rKNy2k7F
https://pastebin.com/t792XJE7
https://pastebin.com/BBb77Pfy
https://pastebin.com/euTtybUX
https://pastebin.com/DDiRxvXw
https://pastebin.com/SpLWzDPF
https://pastebin.com/jVk5exQP
https://pastebin.com/FXCHxBMy
https://pastebin.com/WWUqnR5j
https://pastebin.com/fPQs5mLa
https://pastebin.com/RH5zBVEh
https://pastebin.com/C5w1KpPC
https://pastebin.com/1u6PSz3M
https://pastebin.com/hEWn5WFA
https://pastebin.com/8yH2QWxL
https://pastebin.com/TVdRsjMp
https://pastebin.com/rDbhUfwu
https://pastebin.com/drQEKtGE
https://pastebin.com/99Rmnksr
https://pastebin.com/BXZmvJNc
https://pastebin.com/WG4vFfa5
https://pastebin.com/mMuTtyhm
https://pastebin.com/24dfdEw2
https://pastebin.com/3TkPm4xm
https://pastebin.com/X32JiquQ
https://pastebin.com/j5e9PiRm
https://pastebin.com/cuDaup6h
https://pastebin.com/cwxTRY00
https://pastebin.com/J71r912Y
https://pastebin.com/CzD4M9uD
https://pastebin.com/7KBxgcqK
https://pastebin.com/7NfWAa0J
https://pastebin.com/zwkkUavP
https://pastebin.com/shhZgj5L
https://pastebin.com/H68TdJqL
https://pastebin.com/A5DUFX8G
https://pastebin.com/pbPakzr2
https://pastebin.com/XyVssEmP
https://pastebin.com/wtuvMrT4
https://pastebin.com/w24BpmWr
https://pastebin.com/aiG2D9nf
https://pastebin.com/maNzV2CK
https://pastebin.com/cfE0zNBy

继续阅读 »

问题现象
如何在画布中心点实现Matrix2D图形指定倍数的放大和指定角度的旋转?

背景知识
Matrix2D组件的方法rotate,能够以旋转点为中心、对当前矩阵进行右乘旋转运算,调用公式为:rotate(degree: number, rx?: number, ry?: number),其中degree为旋转角度,rx和ry为旋转中心点相对[0,0]点的水平和垂直方向坐标,如果[0,0]点通过translate发生移动,则以新的点计算相对的rx和ry值。
translate方法能够对当前矩阵进行左乘平移运算,调用公式为:translate(tx?: number, ty?: number)。
scale方法能够对当前矩阵进行右乘缩放运算,调用公式为:scale(sx?: number, sy?: number),其中sx和sy为水平和垂直缩放比例系数。以[0,0]为基准进行缩放,如果[0,0]点通过translate发生移动,则以移动后的点为基准点进行缩放。
解决方案
使用Flex布局将Canvas组件居中显示,创建矩形。
使用rotate方法对矩阵进行旋转操作,旋转角度由angle决定,旋转中心为矩形的中心;使用scale方法对矩阵进行缩放操作,缩放倍数由scaleTimesX和scaleTimesY决定,以中心点为基准进行缩放。rotate和scale存在调用顺序,遵循就近原则,因此需要将scale方法的使用放在rotate上方。
使用translate方法对矩阵进行平移操作,将画布的原点移动到画布的中心,即可将图形移动至画布中心。将经过变换的矩阵应用到CanvasRenderingContext2D对象上。
用fillRect()方法在画布上绘制矩形。
完整示例参考如下:
@Entry
@Component
struct MatrixChange {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private matrix: Matrix2D = new Matrix2D();
x: number = 50; // 图形大小
y: number = 50; // 图形大小
canvasWidth: number = 200; // 画布大小
canvasHeight: number = 200; // 画布大小
scaleTimesX: number = 2; // 缩放设置
scaleTimesY: number = 2; // 缩放设置
angle: number = -30; // 旋转角度

build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Canvas(this.context)
.width(this.canvasWidth)
.height(this.canvasHeight)
.backgroundColor('#0D5AF5')
.onReady(() => {
this.matrix.scaleX = 1;
this.matrix.scaleY = 1;
this.matrix.scale(this.scaleTimesX, this.scaleTimesY);
this.matrix.rotate(this.angle Math.PI / 180, this.x / 2, this.y / 2);
this.matrix.translate(this.canvasWidth / 2, this.canvasHeight / 2);
this.matrix.translate(-this.x
this.scaleTimesX / 2, -this.y * this.scaleTimesY / 2);
this.context.setTransform(this.matrix);
this.context.fillRect(0, 0, this.x, this.y);
});
}
.width('100%')
.height('100%');
}
}
总结
Matrix2D图形能够在画布中心点放大指定倍数并旋转指定角度,该功能适用于需要展示图形并对其进行各种变换的场景,例如数据可视化、图形编辑器等。
https://pastebin.com/jYcy8DG4
https://pastebin.com/Hp1sbSaS
https://pastebin.com/hTxV2yyS
https://pastebin.com/TQCjtTcP
https://pastebin.com/JpF1LSct
https://pastebin.com/Fr3XnQdE
https://pastebin.com/yTvhcbNw
https://pastebin.com/hRe1DgNt
https://pastebin.com/M3y1HL1H
https://pastebin.com/VraVGXVf
https://pastebin.com/griS9fnH
https://pastebin.com/pwrdFgiY
https://pastebin.com/Pbn36K7e
https://pastebin.com/7DMANuaz
https://pastebin.com/rHKJdsZT
https://pastebin.com/NLD2BSd3
https://pastebin.com/95Shz0dA
https://pastebin.com/zhphtiyk
https://pastebin.com/3HY6p8t3
https://pastebin.com/XQEtXnKJ
https://pastebin.com/56Zk2nAE
https://pastebin.com/FHzZvcGp
https://pastebin.com/jvCBLRx8
https://pastebin.com/UQ8bqVpS
https://pastebin.com/H5XxuvFD
https://pastebin.com/ZTRtEnKD
https://pastebin.com/DXXEFpJ7
https://pastebin.com/Xz7is4PA
https://pastebin.com/ujgDH7CQ
https://pastebin.com/QXvsH58y
https://pastebin.com/Syp4LL5s
https://pastebin.com/CLgzB5B2
https://pastebin.com/B1P1eZiT
https://pastebin.com/XsyVXiCg
https://pastebin.com/1XCeaqAU
https://pastebin.com/Jw13Rebn
https://pastebin.com/DyCcgNsv
https://pastebin.com/7LEMiQZ2
https://pastebin.com/dS3rfyQ6
https://pastebin.com/wLwm6rp7
https://pastebin.com/4WGjDaH1
https://pastebin.com/zb1zD5aE
https://pastebin.com/c1zNryeR
https://pastebin.com/4s5BeWXh
https://pastebin.com/Ce3nxNXr
https://pastebin.com/cmj5c080
https://pastebin.com/1H5LCdEY
https://pastebin.com/zZkVFh7Q
https://pastebin.com/BBrYWefF
https://pastebin.com/U4vvzbtw
https://pastebin.com/BWKQJ9Bf
https://pastebin.com/B5CXLSym
https://pastebin.com/PebrcV9M
https://pastebin.com/Ee30dGS4
https://pastebin.com/beXkZr16
https://pastebin.com/qfWN2Ryj
https://pastebin.com/c7mAxDum
https://pastebin.com/MpP9YNFU
https://pastebin.com/SQ8jWZqx
https://pastebin.com/9VALda7W
https://pastebin.com/xZ8P3Ks8
https://pastebin.com/niR9ymit
https://pastebin.com/Rv0T6rUx
https://pastebin.com/jAh0PWYa
https://pastebin.com/3wv5Agv8
https://pastebin.com/EtNNfJmj
https://pastebin.com/a9j6TNjC
https://pastebin.com/x6xxPkPX
https://pastebin.com/az8dYRbQ
https://pastebin.com/husRdHb4
https://pastebin.com/ZQw2Pbhx
https://pastebin.com/C59eWK5e
https://pastebin.com/kzcn3KvK
https://pastebin.com/mT8ehVrP
https://pastebin.com/uv3DmK2r
https://pastebin.com/9A90NJRS
https://pastebin.com/nxKRJ6DM
https://pastebin.com/em4D4Psa
https://pastebin.com/sq1LkcWj
https://pastebin.com/X9Qr3LsF
https://pastebin.com/0yhb1G5u
https://pastebin.com/RNBk3wcp
https://pastebin.com/n21GrF8N
https://pastebin.com/BVGGULWt
https://pastebin.com/2LVYSav1
https://pastebin.com/37m2vAg8
https://pastebin.com/YALGJ9Dj
https://pastebin.com/MPrNKc2X
https://pastebin.com/WcQR7d4P
https://pastebin.com/frpzGFbV
https://pastebin.com/MTZN74bH
https://pastebin.com/4XFYvRhd
https://pastebin.com/XEnbVYfF
https://pastebin.com/iGNu6WyC
https://pastebin.com/iC4qCnDP
https://pastebin.com/SubVHQiU
https://pastebin.com/GtmFQfJ6
https://pastebin.com/PqcdaaqX
https://pastebin.com/wbpVhAa4
https://pastebin.com/Gs8c9UJM
https://pastebin.com/rvBJLy48
https://pastebin.com/qJhsAkBV
https://pastebin.com/GsUxwvRp
https://pastebin.com/xhtemkVi
https://pastebin.com/VpcarPwJ
https://pastebin.com/M2MZ4XjZ
https://pastebin.com/1hsmtBAh
https://pastebin.com/FBKiMKLM
https://pastebin.com/TFcszpgY
https://pastebin.com/wZyc488a
https://pastebin.com/eyzX1hF0
https://pastebin.com/pn2C5r5y
https://pastebin.com/dyHRSz7V
https://pastebin.com/qspZ7UVQ
https://pastebin.com/hXZ59vW5
https://pastebin.com/EBymVseu
https://pastebin.com/Pi2qAyiG
https://pastebin.com/r53FkZrX
https://pastebin.com/pBxp5x6P
https://pastebin.com/kRmwv8xX
https://pastebin.com/YPBtC4bD
https://pastebin.com/zaazK0qN
https://pastebin.com/MH9s5BWr
https://pastebin.com/kUWEmtQ3
https://pastebin.com/EiVfHUPQ
https://pastebin.com/F1zDdap8
https://pastebin.com/x6ninbWm
https://pastebin.com/kp5xBGss
https://pastebin.com/X8rLUdJN
https://pastebin.com/V1Zmj7FJ
https://pastebin.com/UheVuGRp
https://pastebin.com/yKHmpBX3
https://pastebin.com/GQaa3tDW
https://pastebin.com/D00GfaUn
https://pastebin.com/2bTz1JVU
https://pastebin.com/hhdmx2bc
https://pastebin.com/KaXVaXRZ
https://pastebin.com/R0R3JCNA
https://pastebin.com/zQGT96rX
https://pastebin.com/cbqQbPyf
https://pastebin.com/6PJN3M7N
https://pastebin.com/k1yeEBr9
https://pastebin.com/GAVHCfGW
https://pastebin.com/V9BDRArR
https://pastebin.com/Ms7giaW9
https://pastebin.com/ddfs4xn7
https://pastebin.com/bhtqrAfp
https://pastebin.com/hCwrGzXp
https://pastebin.com/Cf6NEq1t
https://pastebin.com/i2YZAQTR
https://pastebin.com/PBVaCkjV
https://pastebin.com/CZh9JdFZ
https://pastebin.com/Cf4LLKEd
https://pastebin.com/EiNK5had
https://pastebin.com/j2XSp6pu
https://pastebin.com/1EM6BSxm
https://pastebin.com/P2Zf7gHn
https://pastebin.com/tjjJFy7s
https://pastebin.com/jZ2QMP9d
https://pastebin.com/vtrnXw8f
https://pastebin.com/rKNy2k7F
https://pastebin.com/t792XJE7
https://pastebin.com/BBb77Pfy
https://pastebin.com/euTtybUX
https://pastebin.com/DDiRxvXw
https://pastebin.com/SpLWzDPF
https://pastebin.com/jVk5exQP
https://pastebin.com/FXCHxBMy
https://pastebin.com/WWUqnR5j
https://pastebin.com/fPQs5mLa
https://pastebin.com/RH5zBVEh
https://pastebin.com/C5w1KpPC
https://pastebin.com/1u6PSz3M
https://pastebin.com/hEWn5WFA
https://pastebin.com/8yH2QWxL
https://pastebin.com/TVdRsjMp
https://pastebin.com/rDbhUfwu
https://pastebin.com/drQEKtGE
https://pastebin.com/99Rmnksr
https://pastebin.com/BXZmvJNc
https://pastebin.com/WG4vFfa5
https://pastebin.com/mMuTtyhm
https://pastebin.com/24dfdEw2
https://pastebin.com/3TkPm4xm
https://pastebin.com/X32JiquQ
https://pastebin.com/j5e9PiRm
https://pastebin.com/cuDaup6h
https://pastebin.com/cwxTRY00
https://pastebin.com/J71r912Y
https://pastebin.com/CzD4M9uD
https://pastebin.com/7KBxgcqK
https://pastebin.com/7NfWAa0J
https://pastebin.com/zwkkUavP
https://pastebin.com/shhZgj5L
https://pastebin.com/H68TdJqL
https://pastebin.com/A5DUFX8G
https://pastebin.com/pbPakzr2
https://pastebin.com/XyVssEmP
https://pastebin.com/wtuvMrT4
https://pastebin.com/w24BpmWr
https://pastebin.com/aiG2D9nf
https://pastebin.com/maNzV2CK
https://pastebin.com/cfE0zNBy

收起阅读 »