问题现象
如何实现一种翻页动画效果,其中背景以圆弧形式逐步扩大,同时文字向左下方放大并移动?
背景知识
Stack:堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。
createAnimator:定义Animator类,可以设置动画播放时长、插值曲线等参数。
解决方案
可通过帧动画实现页面转场效果,具体步骤如下:
定义两个Page组件,分别用于展示不同的页面内容。
class PageContent {
content: string;
style: TextStyle;
constructor(content: string, style: TextStyle = {}) {
this.content = content;
this.style = style;
}
}
配置页面切换时的动画。
this.animator = this.getUIContext().createAnimator({
duration: 500,
easing: 'ease',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 0, //动画插值起点
end: 500 //动画插值终点
});
this.animator.onFrame = progress => {
this.pageController[0].onFrame?.(progress / 500, this.currentIndex === 1);
this.pageController[1].onFrame?.(progress / 500, this.currentIndex === 0);
};
this.animator.onFinish = () => {
let lastIndex = this.currentIndex;
this.currentIndex = 1 - this.currentIndex;
this.zIndexList[lastIndex] = 1;
this.zIndexList[this.currentIndex] = 0;
this.isAnimating = false;
};
使用Stack布局将两个页面组件堆叠显示。
Stack({ alignContent: Alignment.TopStart }) {
Page({
content: page1Content,
bgColor: '#000',
scaleValue: 1,
controller: this.pageController[0],
isShow: true
})
.zIndex(this.zIndexList[0])
.onClick(() => {
this.handleClick();
});
Page({
content: page2Content,
bgColor: '#fff',
scaleValue: 0.01,
controller: this.pageController[1]
})
.zIndex(this.zIndexList[1])
.onClick(() => {
this.handleClick();
});
}
.width('100%')
.height('100%')
.id('root')
.clip(true);
在用户点击右上角圆形按钮时,触发动画切换。
handleClick() {
if (this.isAnimating) {
return;
}
this.isAnimating = true;
this.animator?.play();
}
完整示例代码如下:
import { AnimatorResult } from '@kit.ArkUI';
class PageContent {
content: string;
style: TextStyle;
constructor(content: string, style: TextStyle = {}) {
this.content = content;
this.style = style;
}
}
const page1Content: PageContent[] = [
new PageContent('Hello everyone, I`am very happy to tell you hello world ',
{ fontSize: 30, fontWeight: 700, fontColor: Color.White }),
new PageContent('The text component is used to display a piece of textual information.Support universal attributes and universal text attributes.',
{ fontColor: Color.White }),
new PageContent('This is the setting of textOverflow to Clip text content This is the setting of textOverflow to None text content. This is the setting of textOverflow to Clip text content This is the setting of textOverflow to None text content.',
{ fontColor: Color.White }),
];
const page2Content: PageContent[] = [
new PageContent('Hello everyone, Please say hello! ', { fontSize: 30 }),
new PageContent('Nice to meet you.', { fontSize: 30 }),
new PageContent('I`m fine thank you and you.', { fontSize: 30 }),
new PageContent('No problem.', { fontSize: 30 }),
];
interface PageInfo {
width: number,
height: number,
centerX: number,
centerY: number
}
class PageController {
push?: (onFinish: () => void) => void;
pop?: (onFinish: () => void) => void;
onFrame?: (progress: number, isPush: boolean) => void;
}
@Entry
@Component
export struct TransitionPage {
@Provide info: PageInfo = {
width: 0,
height: 0,
centerX: 0,
centerY: 0
};
pageController: PageController[] = [];
@State zIndexList: number[] = [];
private isAnimating: boolean = false;
private animator?: AnimatorResult;
private currentIndex: number = 0;
aboutToAppear(): void {
let observer = this.getUIContext().getUIInspector().createComponentObserver('root');
observer.on('layout', () => {
let node = this.getUIContext().getAttachedFrameNodeById('root');
if (node) {
this.info = {
width: this.getUIContext().px2vp(node.getMeasuredSize().width),
height: this.getUIContext().px2vp(node.getMeasuredSize().height),
centerY: 50,
centerX: this.getUIContext().px2vp(node.getMeasuredSize().width) - 40
};
}
});
for (let i = 0; i < 2; i++) {
this.pageController.push(new PageController());
this.zIndexList.push(i);
}
// 页面切换时的动画
this.animator = this.getUIContext().createAnimator({
duration: 500,
easing: 'ease',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 0, //动画插值起点
end: 500 //动画插值终点
});
this.animator.onFrame = progress => {
this.pageController[0].onFrame?.(progress / 500, this.currentIndex === 1);
this.pageController[1].onFrame?.(progress / 500, this.currentIndex === 0);
};
this.animator.onFinish = () => {
let lastIndex = this.currentIndex;
this.currentIndex = 1 - this.currentIndex;
this.zIndexList[lastIndex] = 1;
this.zIndexList[this.currentIndex] = 0;
this.isAnimating = false;
};
}
aboutToDisappear(): void {
this.animator?.finish();
this.animator = undefined;
}
push(index: number) {
let promise: Promise<void>[] = [];
promise.push(new Promise(resolve => {
this.pageController[index].push?.(() => {
this.zIndexList[index] = 0;
resolve();
});
}));
let lastIndex = 1 - index;
promise.push(new Promise(resolve => {
this.pageController[lastIndex].pop?.(() => {
this.zIndexList[lastIndex] = 1;
resolve();
});
}));
Promise.all(promise).then(() => this.isAnimating = false);
}
// 动画切换事件
handleClick() {
if (this.isAnimating) {
return;
}
this.isAnimating = true;
this.animator?.play();
}
build() {
// 两个页面组件堆叠显示
Stack({ alignContent: Alignment.TopStart }) {
Page({
content: page1Content,
bgColor: '#000',
scaleValue: 1,
controller: this.pageController[0],
isShow: true
})
.zIndex(this.zIndexList[0])
.onClick(() => {
this.handleClick();
});
Page({
content: page2Content,
bgColor: '#fff',
scaleValue: 0.01,
controller: this.pageController[1]
})
.zIndex(this.zIndexList[1])
.onClick(() => {
this.handleClick();
});
}
.width('100%')
.height('100%')
.id('root')
.clip(true);
}
onDidBuild(): void {
if (!this.animator) {
return;
}
}
}
@Component
struct Page {
@Require content: PageContent[] = [];
@Consume @Watch('onInit') info: PageInfo;
@State scaleValue: number = 0.01;
@State r: number = 0;
@State isShow: boolean = false;
bgColor: string = '#fff';
controller?: PageController;
onInit() {
this.r = Math.sqrt(this.info.centerX this.info.centerX +
(this.info.height - this.info.centerY) (this.info.height - this.info.centerY));
}
aboutToAppear(): void {
if (this.controller) {
this.controller.push = (onFinish) => {
this.onPush(onFinish);
};
this.controller.pop = (onFinish) => {
this.onPop(onFinish);
};
this.controller.onFrame = (progress, isPush) => {
if (isPush) {
this.isShow = true;
this.scaleValue = 0.01 + (1 - 0.01) progress;
} else {
this.scaleValue = 1 + (4 - 1) progress;
if (progress >= 1) {
this.isShow = false;
this.scaleValue = 0.01;
}
}
};
}
}
onPush(onFinish: () => void) {
this.isShow = true;
this.getUIContext().animateTo({ duration: 500, onFinish: onFinish }, () => {
this.scaleValue = 1;
});
}
onPop(onFinish: () => void) {
this.getUIContext().animateTo({
duration: 500,
onFinish: () => {
this.scaleValue = 0.01;
this.isShow = false;
onFinish();
}
}, () => {
this.scaleValue = 4;
});
}
build() {
Column() {
if (this.isShow) {
Column({ space: 20 }) {
ForEach(this.content, (item: PageContent) => {
Text(item.content)
.fontSize(item.style.fontSize)
.fontColor(item.style.fontColor)
.fontWeight(item.style.fontWeight)
.fontStyle(item.style.fontStyle)
.fontFamily(item.style.fontFamily);
});
}
.position({ x: this.r - this.info.centerX, y: this.r - this.info.centerY })
.padding({ left: 16, right:16 })
.width(this.info.width)
.height(this.info.height)
.justifyContent(FlexAlign.SpaceEvenly);
}
}
.width(this.r 2)
.height(this.r 2)
.constraintSize({ minWidth: this.r * 2 })
.scale({ x: this.scaleValue, y: this.scaleValue })
.offset({ x: this.info.centerX - this.r, y: this.info.centerY - this.r })
.borderRadius(this.r)
.clip(true)
.backgroundColor(this.bgColor);
}
}
https://coub.com/view/4b7yjg
https://coub.com/view/4b7yjf
https://coub.com/view/4b7yjd
https://coub.com/view/4b7yiw
https://coub.com/view/4b7yis
https://coub.com/view/4b7yip
https://coub.com/view/4b7yik
https://coub.com/view/4b7yij
https://coub.com/view/4b7yig
https://coub.com/view/4b7yie
https://coub.com/view/4b7yic
https://coub.com/view/4b7yia
https://coub.com/view/4b7yi7
https://coub.com/view/4b7yi5
https://coub.com/view/4b7yi3
https://coub.com/view/4b7yhy
https://coub.com/view/4b7yht
https://coub.com/view/4b7yhr
https://coub.com/view/4b7yhp
https://coub.com/view/4b7yhh
https://coub.com/view/4b7yhg
https://coub.com/view/4b7yhf
https://coub.com/view/4b7yhb
https://coub.com/view/4b7yh9
https://coub.com/view/4b7yh6
https://coub.com/view/4b7yh4
https://coub.com/view/4b7ygz
https://coub.com/view/4b7ygx
https://coub.com/view/4b7ygu
https://coub.com/view/4b7ygr
https://coub.com/view/4b7ygn
https://coub.com/view/4b7ygk
https://coub.com/view/4b7ygh
https://coub.com/view/4b7ygf
https://coub.com/view/4b7ygd
https://coub.com/view/4b7ygb
https://coub.com/view/4b7yg8
https://coub.com/view/4b7yg5
https://coub.com/view/4b7yg2
https://coub.com/view/4b7yfr
https://coub.com/view/4b7yfo
https://coub.com/view/4b7yfk
https://coub.com/view/4b7yfh
https://coub.com/view/4b7yfe
https://coub.com/view/4b7yfc
https://coub.com/view/4b7yfa
https://coub.com/view/4b7yf7
https://coub.com/view/4b7yf5
https://coub.com/view/4b7yf1
https://coub.com/view/4b7yez
https://coub.com/view/4b7yet
https://coub.com/view/4b7yer
https://coub.com/view/4b7yep
https://coub.com/view/4b7yeo
https://coub.com/view/4b7ub4
https://coub.com/view/4b7ub3
https://coub.com/view/4b7ub2
https://coub.com/view/4b7ub0
https://coub.com/view/4b7uay
https://coub.com/view/4b7uax
https://coub.com/view/4b7uav
https://coub.com/view/4b7uap
https://coub.com/view/4b7uao
https://coub.com/view/4b7ual
https://coub.com/view/4b7uad
https://coub.com/view/4b7uaa
https://coub.com/view/4b7ua7
https://coub.com/view/4b7ua5
https://coub.com/view/4b7ua2
https://coub.com/view/4b7ua1
https://coub.com/view/4b7u9z
https://coub.com/view/4b7u9y
https://coub.com/view/4b7u9x
https://coub.com/view/4b7u9p
https://coub.com/view/4b7u9k
https://coub.com/view/4b7u9j
https://coub.com/view/4b7u9i
https://coub.com/view/4b7u9g
https://coub.com/view/4b7u9d
https://coub.com/view/4b7u9a
https://coub.com/view/4b7u98
https://coub.com/view/4b7u96
https://coub.com/view/4b7u94
https://coub.com/view/4b7u92
https://coub.com/view/4b7u91
https://coub.com/view/4b7u8z
https://coub.com/view/4b7u8t
https://coub.com/view/4b7u8q
https://coub.com/view/4b7u8p
https://coub.com/view/4b7u8n
https://coub.com/view/4b7u8j
https://coub.com/view/4b7u8i
https://coub.com/view/4b7u78
https://coub.com/view/4b7u76
https://coub.com/view/4b7u72
https://coub.com/view/4b7u6v
https://coub.com/view/4b7u6t
https://coub.com/view/4b7u6r
https://coub.com/view/4b7u6o
https://coub.com/view/4b7u6m
https://coub.com/view/4b7u6l
https://coub.com/view/4b7u6j
https://coub.com/view/4b7u6h
https://coub.com/view/4b7u6f
https://coub.com/view/4b7u6d
https://coub.com/view/4b7u6a
https://coub.com/view/4b7u67
https://coub.com/view/4b7u65
https://coub.com/view/4b7u60
https://coub.com/view/4b7u5t
https://coub.com/view/4b7u5p
https://coub.com/view/4b7u5i
https://coub.com/view/4b7u4t
https://coub.com/view/4b7u4p
https://coub.com/view/4b7u4l
https://coub.com/view/4b7u46
https://coub.com/view/4b7u3v
https://coub.com/view/4b7u3u
https://coub.com/view/4b7u3t
https://coub.com/view/4b7u3r
https://coub.com/view/4b7u3o
https://coub.com/view/4b7u3l
https://coub.com/view/4b7u3k
https://coub.com/view/4b7u3h
https://coub.com/view/4b7u3a
https://coub.com/view/4b7u38
https://coub.com/view/4b7u35
https://coub.com/view/4b7u34
https://coub.com/view/4b7u33
https://coub.com/view/4b7u30
https://coub.com/view/4b7u2z
https://coub.com/view/4b7u2w
0 个评论
要回复文章请先登录或注册