* Copyright (c) 2024 Huawei Technologies Co., Ltd.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {useRef, useState} from 'react';
import {
Animated,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
export function AnimatedPropsExample() {
const animValue = useRef(new Animated.Value(0)).current;
const [isActive, setIsActive] = useState(false);
const toggle = () => {
Animated.timing(animValue, {
toValue: isActive ? 0 : 1,
duration: 800,
useNativeDriver: false,
}).start();
setIsActive(!isActive);
};
const interp = (output: any[]) =>
animValue.interpolate({inputRange: [0, 1], outputRange: output});
const TestCard = ({label, style, extra, canvasStyle}: any) => (
<View style={styles.testItem}>
<Text style={styles.itemLabel}>{label}</Text>
<View style={[styles.canvas, canvasStyle]}>
{extra && extra()}
<Animated.View style={[styles.box, style]}>
<Text style={styles.boxText}>Demo</Text>
</Animated.View>
</View>
</View>
);
const NewTestCard = ({label, style, extra, canvasStyle, children}: any) => (
<View style={styles.testItem}>
<Text style={styles.itemLabel}>{label}</Text>
<View style={[styles.canvas, canvasStyle]}>
{extra && extra()}
<Animated.View style={[styles.box, style]}>{children}</Animated.View>
</View>
</View>
);
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerTitle}>Animated属性增强</Text>
</View>
<ScrollView contentContainerStyle={styles.scrollContent}>
<Text style={styles.catTitle}>布局定位 (Layout & Position)</Text>
<View style={styles.grid}>
<TestCard
label="Position & L/T/R/B"
style={{
position: isActive ? 'absolute' : 'relative',
left: interp([0, 20]),
top: interp([0, 10]),
right: interp([0, 5]),
bottom: interp([0, 5]),
backgroundColor: '#9b59b6',
}}
extra={() => <View style={styles.placeholderBox} />}
/>
<TestCard
label="zIndex (重叠换层)"
style={{
zIndex: interp([1, 10]),
position: 'absolute',
top: 15,
left: 15,
}}
extra={() => (
<View
style={{
position: 'absolute',
width: 45,
height: 45,
backgroundColor: '#e67e22',
zIndex: 5,
top: 25,
left: 25,
}}
/>
)}
/>
<TestCard
label="direction (镜像验证)"
style={{
direction: isActive ? 'rtl' : 'ltr',
flexDirection: 'row',
width: '100%',
justifyContent: 'flex-start',
}}
extra={() => (
<View
style={{
flexDirection: 'row',
position: 'absolute',
top: 5,
left: 5,
}}>
<View
style={{
width: 10,
height: 10,
backgroundColor: '#e67e22',
marginRight: 4,
}}
/>
<View
style={{width: 10, height: 10, backgroundColor: '#2ecc71'}}
/>
</View>
)}
/>
</View>
<Text style={styles.catTitle}>Flex / 尺寸 / 间距 / 边框</Text>
<View style={styles.grid}>
<TestCard
label="flex和flexGrow"
style={{
flex: interp([0.3, 1]),
flexGrow: interp([0, 1]),
}}
/>
{/* 直接创建 flex 容器,不使用 TestCard */}
<View style={styles.testItem}>
<Text style={styles.itemLabel}>flexGrow 验证</Text>
<View style={[styles.canvas, {flexDirection: 'row'}]}>
{/* 固定宽度参照物 */}
<View
style={{width: 30, height: 40, backgroundColor: '#e74c3c'}}
/>
{/* 实验对象:flexGrow 动态变化 */}
<Animated.View
style={{
flexGrow: interp([0, 1]),
height: 40,
backgroundColor: '#3498db',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text style={styles.boxText}>Grow</Text>
</Animated.View>
</View>
</View>
<View style={styles.testItem}>
<Text style={styles.itemLabel}>flexShrink 验证</Text>
<View
style={{
flexDirection: 'row',
width: '100%',
height: 50,
backgroundColor: '#ecf0f1',
alignItems: 'center',
}}>
{/* 绿色 - 固定不收缩 */}
<View
style={{
width: 30,
height: 40,
flexShrink: 0,
backgroundColor: '#2ecc71',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text style={{fontSize: 8, color: '#fff'}}>固定</Text>
</View>
{/* 黄色 - 动态收缩 */}
<Animated.View
style={{
width: 100,
flexShrink: interp([0, 5]), // 0 → 5, 收缩权重更高
height: 40,
backgroundColor: '#f1c40f',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text style={{fontSize: 8, color: '#333'}}>Shrink</Text>
</Animated.View>
{/* 红色 - 参与收缩对照 */}
<View
style={{
width: 80,
height: 40,
flexShrink: 0,
backgroundColor: '#e74c3c',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text style={{fontSize: 8, color: '#fff'}}>固定</Text>
</View>
</View>
</View>
<TestCard
label="flexBasis"
style={{flexBasis: interp(['40%', '100%'])}}
/>
<TestCard
label="min/maxWidth"
style={{minWidth: interp([40, 80]), maxWidth: interp([80, 120])}}
/>
<TestCard
label="margin 系列"
style={{marginTop: interp([0, 20]), marginLeft: interp([0, 20])}}
/>
<TestCard
label="padding 系列"
style={{paddingTop: interp([0, 20]), paddingLeft: interp([0, 20])}}
/>
<TestCard
label="borderWidth 系列"
style={{
borderTopWidth: interp([1, 10]),
borderRightWidth: interp([1, 5]),
borderColor: '#333',
}}
/>
<TestCard
label="borderRadius 系列"
style={{
borderTopLeftRadius: interp([0, 25]),
borderBottomRightRadius: interp([0, 25]),
}}
/>
<TestCard
label="borderStyle/Curve(不支持borderCurve)"
style={{
borderStyle: isActive ? 'dashed' : 'solid',
borderCurve: isActive ? 'continuous' : 'circular',
borderWidth: 2,
borderRadius: 10,
backgroundColor: '#f00',
}}
/>
</View>
<Text style={styles.catTitle}>Outline 细分验证</Text>
<View style={styles.grid}>
<TestCard
label="outlineWidth"
style={{
outlineWidth: interp([0, 8]),
outlineStyle: 'solid',
outlineColor: '#e74c3c',
}}
/>
<TestCard
label="outlineOffset(不支持)"
style={{
outlineWidth: 4,
outlineOffset: interp([0, 10]),
outlineStyle: 'solid',
outlineColor: '#e74c3c',
}}
/>
<TestCard
label="outlineColor"
style={{
outlineWidth: 4,
outlineStyle: 'solid',
outlineColor: interp(['#f1c606ff', '#e74c3c']),
}}
/>
<TestCard
label="outlineStyle(不支持)"
style={{
outlineWidth: 4,
outlineColor: '#e74c3c',
outlineStyle: isActive ? 'dashed' : 'solid',
}}
/>
</View>
<Text style={styles.catTitle}>Filter 细分验证</Text>
<View style={styles.grid}>
<TestCard
label="filter: blur()"
style={{filter: interp(['blur(0px)', 'blur(8px)']) as any}}
/>
<TestCard
label="filter: grayscale()"
style={{filter: interp(['grayscale(0)', 'grayscale(1)']) as any}}
/>
<TestCard
label="filter: brightness(0->2)"
style={{filter: [{brightness: interp([0, 2])}]}}
/>
<TestCard
label="filter: Invert(0->1)"
style={{filter: [{invert: interp([0, 1])}]}}
/>
<TestCard
label="Saturate (0 -> 5)"
style={{filter: [{saturate: interp([0, 5])}]}}
/>
<TestCard
label="Sepia (0 -> 1)"
style={{filter: [{sepia: interp([0, 1])}]}}
/>
<TestCard
label="Filter: Opacity (0 -> 1)(不支持)"
style={{
filter: [{opacity: interp([0, 1])}],
}}
/>
</View>
<Text style={styles.catTitle}>Transform 细分验证</Text>
<View style={styles.grid}>
<TestCard
label="transform: scale"
style={{transform: [{scale: interp([1, 1.5])}]}}
/>
<TestCard
label="transform: rotate"
style={{transform: [{rotate: interp(['0deg', '180deg'])}]}}
/>
<TestCard
label="transform: translate"
style={{
transform: [
{translateX: interp([0, 30])},
{translateY: interp([0, 20])},
],
}}
/>
<TestCard
label="transformOrigin"
style={{
transformOrigin: isActive ? 'top left' : 'center',
transform: [{rotate: interp(['0deg', '45deg'])}],
}}
/>
<TestCard
label="Transform: SkewX (0deg -> 45deg)"
style={{
transform: [
{
skewX: interp([0, 45]).interpolate({
inputRange: [0, 45],
outputRange: ['0deg', '45deg'],
}),
},
],
}}
/>
<TestCard
label="Transform: SkewY (0deg -> 45deg)"
style={{
transform: [
{
skewY: interp([0, 1]).interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '45deg'], // 从水平状态垂直倾斜 45 度
}),
},
],
}}
/>
<TestCard
label="Transform: Perspective (100 -> 1000)"
style={{
transform: [
{
// 动态改变视距:从强烈的近距离透视变为远距离平缓透视
perspective: interp([0, 1]).interpolate({
inputRange: [0, 1],
outputRange: [100, 800],
}),
// perspective: 400,
},
{
// 配合旋转才能看出透视变化
// rotateY: '30deg',
rotateY: interp([0, 1]).interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '45deg'],
}),
},
],
}}
/>
</View>
<Text style={styles.catTitle}>Shadow & Opacity</Text>
<View style={styles.grid}>
<TestCard
label="boxShadow (验证静态字符串)"
style={{
boxShadow: '10px 5px 5px red',
}}
/>
<NewTestCard
label="boxShadow (对象数组)"
style={{
boxShadow: [
{
offsetX: interp([0, 10]),
offsetY: interp([0, 10]),
blurRadius: interp([0, 20]),
spreadDistance: 0,
color: animValue.interpolate({
inputRange: [0, 1],
outputRange: ['rgba(0,0,255,0)', 'rgba(0,0,255,0.7)'],
}),
},
],
}}>
<Text style={styles.boxText}>Demo</Text>
</NewTestCard>
<TestCard
label="shadowColor/Opacity"
style={{
shadowColor: interp(['#000', '#ff0000']),
// shadowOpacity: 0.5, // 用于单项属性测试
// shadowColor: '#ff0000',
shadowOpacity: interp([0, 0.8]),
shadowRadius: 5,
shadowOffset: {width: 5, height: 10},
}}
/>
<TestCard
label="shadowOffset/Radius"
style={{
shadowColor: '#000',
shadowOpacity: 0.5,
shadowOffset: {width: interp([0, 10]), height: interp([0, 10])},
shadowRadius: interp([0, 15]),
}}
/>
<TestCard label="opacity" style={{opacity: interp([1, 0.1])}} />
</View>
<Text style={styles.catTitle}>背景与其他</Text>
<View style={styles.grid}>
<TestCard
label="backgroundColor"
style={{backgroundColor: interp(['#3498db', '#2ecc71'])}}
/>
<TestCard
label="overflow (裁剪验证)"
// 1. 固定父容器尺寸,方便观察边界
canvasStyle={{
overflow: isActive ? 'hidden' : 'visible',
width: 70,
height: 70,
borderWidth: 1,
borderColor: '#e74c3c', // 红色边框标示父容器范围
justifyContent: 'center',
alignItems: 'center',
}}
// 2. 子元素尺寸设为 100x100,显著大于父容器的 70x70
style={{
width: 100,
height: 100,
backgroundColor: 'rgba(52, 152, 219, 0.6)',
borderWidth: 1,
borderColor: '#3498db',
}}
// 3. 辅助文字,放在子元素边缘
extra={() => (
<Text
style={{
position: 'absolute',
top: -5,
fontSize: 8,
color: 'red',
}}>
OUTSIDE
</Text>
)}
/>
<TestCard
label="mixBlendMode(不支持)"
style={{mixBlendMode: isActive ? 'difference' : 'normal'}}
extra={() => <View style={styles.blendCircle} />}
/>
<NewTestCard
label="isolation (透明度组验证)(不支持)"
style={{
isolation: isActive ? 'isolate' : 'auto',
opacity: 0.5, // 开启组透明
backgroundColor: '#3498db',
width: 60,
height: 60,
}}>
<View
style={{
width: 30,
height: 30,
backgroundColor: '#3498db',
marginTop: 20,
marginLeft: 20,
}}
/>
</NewTestCard>
<TestCard
label="backfaceVisibility"
style={{
backfaceVisibility: isActive ? 'hidden' : 'visible',
transform: [{rotateY: interp(['0deg', '180deg'])}],
}}
/>
<TestCard
label="pointerEvents"
style={{
pointerEvents: isActive ? 'none' : 'auto',
opacity: isActive ? 0.4 : 1,
}}
/>
<TestCard
label="cursor"
style={{cursor: isActive ? 'pointer' : 'not-allowed'}}
/>
</View>
</ScrollView>
<TouchableOpacity
style={[styles.btn, isActive && styles.btnActive]}
onPress={toggle}>
<Text style={styles.btnText}>
{isActive ? 'RESET' : 'RUN ANIMATION'}
</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {flex: 1, backgroundColor: '#f0f3f5'},
header: {
padding: 16,
backgroundColor: '#fff',
borderBottomWidth: 1,
borderColor: '#eee',
alignItems: 'center',
},
headerTitle: {fontSize: 16, fontWeight: 'bold'},
scrollContent: {padding: 12, paddingBottom: 120},
catTitle: {
fontSize: 11,
fontWeight: 'bold',
color: '#444',
marginTop: 15,
marginBottom: 8,
backgroundColor: '#e1e8ed',
padding: 4,
paddingLeft: 10,
borderRadius: 4,
},
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
},
testItem: {
width: '48%',
backgroundColor: '#fff',
borderRadius: 8,
padding: 10,
marginBottom: 12,
elevation: 2,
shadowColor: '#000',
shadowOpacity: 0.05,
shadowRadius: 3,
},
itemLabel: {
fontSize: 9,
color: '#777',
marginBottom: 6,
textAlign: 'center',
fontWeight: '600',
},
canvas: {
height: 85,
backgroundColor: '#f8f8f8',
borderRadius: 6,
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
position: 'relative',
},
box: {
width: 40,
height: 40,
backgroundColor: '#3498db',
justifyContent: 'center',
alignItems: 'center',
},
boxText: {color: '#fff', fontSize: 8, fontWeight: 'bold'},
placeholderBox: {
width: 40,
height: 40,
backgroundColor: '#eee',
borderStyle: 'dashed',
borderWidth: 1,
borderColor: '#ccc',
},
blendCircle: {
position: 'absolute',
width: 60,
height: 60,
borderRadius: 20,
backgroundColor: '#f1c40f',
top: 15,
left: 15,
},
btn: {
position: 'absolute',
bottom: 30,
left: 20,
right: 20,
backgroundColor: '#007AFF',
padding: 18,
borderRadius: 12,
alignItems: 'center',
},
btnActive: {backgroundColor: '#FF3B30'},
btnText: {color: '#fff', fontWeight: 'bold', fontSize: 16},
});