Frame-by-Frame Layout and Shape Animation in ArkUI
Frame-by-Frame Layout with Text Component
This example demonstrates how to use the @AnimatableExtend
decorator and the number
data type to create a frame-by-frame layout effect by animating the width of a Text
component.
Step-by-Step Explanation
Custom Animatable Property: The
@AnimatableExtend
decorator is used to extend theText
component with a custom animatable propertyanimatableWidth
.Animation Setup: The
animation
method is called on theText
component to bind an animation to the custom property. This animation will control how the width changes over time.Triggering Animation: A
Button
component is used to toggle the width of theText
component. When clicked, it changes thetextWidth
state variable, triggering the animation.
Code Example
// Step 1: Use the @AnimatableExtend decorator to define a custom animatable property interface
@AnimatableExtend(Text)
function animatableWidth(width: number) {
return { width }; // Calls the system property interface. The frame-by-frame callback function modifies the animatable property value each frame to achieve a frame-by-frame layout effect.
}
@Entry
@Component
struct AnimatablePropertyExample {
@State textWidth: number = 80;
build() {
Column() {
Text("AnimatableProperty")
.animatableWidth(this.textWidth) // Step 2: Set the custom animatable property interface on the component
.animation({ duration: 2000, curve: Curve.Ease }) // Step 3: Bind an animation to the custom animatable property
Button("Play")
.onClick(() => {
this.textWidth = this.textWidth === 80 ? 160 : 80; // Step 4: Change the custom animatable property parameter to trigger the animation
})
}.width("100%")
.padding(10)
}
}
Shape Animation with Custom Data Types
This example illustrates how to use the @AnimatableExtend
decorator with custom data types to animate the shape of a Polyline
component.
Key Concepts
-
Custom Data Types: The
Point
andPointVector
classes are defined to represent points and a collection of points, respectively. -
Animation Arithmetic: The
PointClass
andPointVector
implement theAnimatableArithmetic
interface to define how points can be added, subtracted, multiplied, and compared. -
Custom Animatable Property: The
animatablePoints
function extends thePolyline
component to accept aPointVector
as an animatable property.
Code Example
declare type Point = number[];
// Define the parameter type for the animatable property interface and implement the AnimatableArithmetic<T> interface's addition, subtraction, multiplication, and equality check functions
class PointClass extends Array<number> {
constructor(value: Point) {
super(value[0], value[1]);
}
add(rhs: PointClass): PointClass {
const result: Point = new Array<number>();
for (let i = 0; i < 2; i++) {
result.push(rhs[i] + this[i]);
}
return new PointClass(result);
}
subtract(rhs: PointClass): PointClass {
const result: Point = new Array<number>();
for (let i = 0; i < 2; i++) {
result.push(this[i] - rhs[i]);
}
return new PointClass(result);
}
multiply(scale: number): PointClass {
const result: Point = new Array<number>();
for (let i = 0; i < 2; i++) {
result.push(this[i] * scale);
}
return new PointClass(result);
}
}
// Define the parameter type for the animatable property interface and implement the AnimatableArithmetic<T> interface's addition, subtraction, multiplication, and equality check functions
// Template T supports nested types that implement AnimatableArithmetic<T>
class PointVector extends Array<PointClass> implements AnimatableArithmetic<Array<Point>> {
constructor(initialValue: Array<Point>) {
super();
if (initialValue.length) {
initialValue.forEach((p: Point) => this.push(new PointClass(p)));
}
}
// Implement the IAnimatableArithmetic interface
plus(rhs: PointVector): PointVector {
const result = new PointVector([]);
const len = Math.min(this.length, rhs.length);
for (let i = 0; i < len; i++) {
result.push(this[i].add(rhs[i]));
}
return result;
}
subtract(rhs: PointVector): PointVector {
const result = new PointVector([]);
const len = Math.min(this.length, rhs.length);
for (let i = 0; i < len; i++) {
result.push(this[i].subtract(rhs[i]));
}
return result;
}
multiply(scale: number): PointVector {
const result = new PointVector([]);
for (let i = 0; i < this.length; i++) {
result.push(this[i].multiply(scale));
}
return result;
}
equals(rhs: PointVector): boolean {
if (this.length !== rhs.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this[i][0] !== rhs[i][0] || this[i][1] !== rhs[i][1]) {
return false;
}
}
return true;
}
}
// Custom animatable property interface
@AnimatableExtend(Polyline)
function animatablePoints(points: PointVector) {
return { points }; // Calls the system property interface. The frame-by-frame callback function modifies the animatable property value each frame to achieve a frame-by-frame layout effect.
}
@Entry
@Component
struct AnimatedShape {
squareStartPointX: number = 75;
squareStartPointY: number = 25;
squareWidth: number = 150;
squareEndTranslateX: number = 50;
squareEndTranslateY: number = 50;
@State pointVec1: PointVector = new PointVector([
[this.squareStartPointX, this.squareStartPointY],
[this.squareStartPointX + this.squareWidth, this.squareStartPointY],
[this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth],
[this.squareStartPointX, this.squareStartPointY + this.squareWidth],
]);
@State pointVec2: PointVector = new PointVector([
[this.squareStartPointX + this.squareEndTranslateX, this.squareStartPointY + this.squareStartPointY],
[this.squareStartPointX + this.squareWidth + this.squareEndTranslateX, this.squareStartPointY + this.squareStartPointY],
[this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth],
[this.squareStartPointX, this.squareStartPointY + this.squareWidth],
]);
@State polyline1Vec: PointVector = this.pointVec1;
build() {
Row() {
Polyline()
.width(300)
.height(200)
.backgroundColor("#0C000000")
.fill('#317AF7')
.animatablePoints(this.polyline1Vec)
.animation({ duration: 2000, delay: 0, curve: Curve.Ease })
.onClick(() => {
if (this.polyline1Vec.equals(this.pointVec1)) {
this.polyline1Vec = this.pointVec2;
} else {
this.polyline1Vec = this.pointVec1;
}
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
Top comments (0)