基础入门 React Native 鸿蒙跨平台开发:LayoutAnimation 布局动画

一、核心知识点:LayoutAnimation 布局动画完整核心用法

1. 用到的纯内置组件与API

所有能力均为 RN 原生自带,全部从 react-native 核心包直接导入,无任何外部依赖、无任何第三方库,鸿蒙端无任何兼容问题,也是实现布局动画的全部核心能力,基础易理解、易复用,无多余,所有布局动画功能均基于以下组件/API 原生实现:

核心组件/API 作用说明 鸿蒙适配特性
LayoutAnimation RN 原生布局动画API,实现布局变化时的自动动画效果 ✅ 鸿蒙端布局动画流畅,过渡自然,无兼容问题
View 核心容器组件,实现组件布局、内容容器、样式容器等,支持弹性布局、绝对定位、背景色 ✅ 鸿蒙端布局无报错,布局精确、圆角、边框、背景色属性完美生效
Text 显示布局文字、提示信息等,支持多行文本、不同颜色状态,鸿蒙端文字排版精致 ✅ 鸿蒙端文字排版精致,字号、颜色、行高均无适配异常
StyleSheet 原生样式管理,编写鸿蒙端最佳的布局动画样式:容器、动画元素,无任何不兼容CSS属性 ✅ 符合鸿蒙官方视觉设计规范,颜色、圆角、边框、间距均为真机实测最优
useState / useEffect React 原生钩子,管理布局状态、动画状态等核心数据,控制实时更新、状态切换 ✅ 响应式更新无延迟,状态切换流畅无卡顿,计算结果实时显示
TouchableOpacity RN 原生触摸组件,实现布局按钮的触摸交互,支持透明度变化、点击反馈 ✅ 鸿蒙端触摸响应流畅,透明度变化完美,无兼容问题

二、实战核心代码解析:在展示完整代码之前,我们先深入理解布局动画实现的核心逻辑,掌握这些核心代码后,你将能够举一反三应对各种布局动画相关的开发需求。

1. 基础布局动画

实现最基本的布局动画,包括淡入淡出效果。

javascript 复制代码
import { LayoutAnimation } from 'react-native';

const handleLayoutChange = () => {
  LayoutAnimation.configureNext({
    duration: 300,
    create: { type: 'easeInEaseOut', property: 'opacity' },
    update: { type: 'easeInEaseOut' },
    delete: { type: 'easeInEaseOut', property: 'opacity' },
  });
  // 改变布局状态
  setExpanded(!expanded);
};

核心要点:

  • 使用 LayoutAnimation.configureNext 配置动画
  • 支持创建、更新、删除三种动画类型
  • 鸿蒙端基础布局动画正常

2. 预设动画

使用预设的动画配置。

javascript 复制代码
const handlePresetAnimation = () => {
  LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
  // 改变布局状态
  setExpanded(!expanded);
};

核心要点:

  • 使用 LayoutAnimation.Presets 获取预设动画
  • 支持多种预设效果
  • 鸿蒙端预设动画正常

3. 自定义动画

实现自定义的布局动画效果。

javascript 复制代码
const handleCustomAnimation = () => {
  LayoutAnimation.configureNext({
    duration: 500,
    create: {
      type: 'spring',
      property: 'scaleXY',
      springDamping: 0.7,
    },
    update: {
      type: 'spring',
      springDamping: 0.7,
    },
  });
  // 改变布局状态
  setExpanded(!expanded);
};

核心要点:

  • 支持自定义动画参数
  • 可以配置弹簧效果
  • 鸿蒙端自定义动画正常

三、实战完整版:企业级通用 LayoutAnimation 布局动画组件

javascript 复制代码
import React, { useState, useCallback } from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  ScrollView,
  LayoutAnimation,
  Platform,
  UIManager,
  SafeAreaView,
} from 'react-native';

// 启用布局动画(仅Android需要)
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
  UIManager.setLayoutAnimationEnabledExperimental(true);
}

const LayoutAnimationDemo = () => {
  const [expanded, setExpanded] = useState(false);
  const [items, setItems] = useState([1, 2, 3]);
  const [boxSize, setBoxSize] = useState(100);
  const [boxColor, setBoxColor] = useState('#409EFF');
  const [visibleItems, setVisibleItems] = useState([true, true, true]);
  const [gridColumns, setGridColumns] = useState(2);
  const [listOrder, setListOrder] = useState([1, 2, 3, 4, 5]);

  const toggleExpanded = useCallback(() => {
    LayoutAnimation.configureNext({
      duration: 300,
      create: { type: 'easeInEaseOut', property: 'opacity' },
      update: { type: 'easeInEaseOut' },
      delete: { type: 'easeInEaseOut', property: 'opacity' },
    });
    setExpanded(!expanded);
  }, [expanded]);

  const addItem = useCallback(() => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
    setItems([...items, items.length + 1]);
  }, [items]);

  const removeItem = useCallback(() => {
    if (items.length > 0) {
      LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
      setItems(items.slice(0, -1));
    }
  }, [items]);

  const increaseSize = useCallback(() => {
    LayoutAnimation.configureNext({
      duration: 300,
      update: { type: 'spring', springDamping: 0.7 },
    });
    setBoxSize(Math.min(boxSize + 20, 200));
  }, [boxSize]);

  const decreaseSize = useCallback(() => {
    LayoutAnimation.configureNext({
      duration: 300,
      update: { type: 'spring', springDamping: 0.7 },
    });
    setBoxSize(Math.max(boxSize - 20, 50));
  }, [boxSize]);

  const changeColor = useCallback(() => {
    LayoutAnimation.configureNext({
      duration: 300,
      update: { type: 'easeInEaseOut' },
    });
    const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399'];
    const currentIndex = colors.indexOf(boxColor);
    const nextIndex = (currentIndex + 1) % colors.length;
    setBoxColor(colors[nextIndex]);
  }, [boxColor]);

  const toggleVisibility = useCallback((index: number) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    setVisibleItems(prev => {
      const newItems = [...prev];
      newItems[index] = !newItems[index];
      return newItems;
    });
  }, []);

  const changeColumns = useCallback(() => {
    LayoutAnimation.configureNext({
      duration: 300,
      update: { type: 'spring', springDamping: 0.7 },
    });
    setGridColumns(prev => prev === 2 ? 3 : 2);
  }, []);

  const shuffleList = useCallback(() => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
    setListOrder(prev => [...prev].sort(() => Math.random() - 0.5));
  }, []);

  const moveItemUp = useCallback((index: number) => {
    if (index > 0) {
      LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
      setListOrder(prev => {
        const newOrder = [...prev];
        [newOrder[index], newOrder[index - 1]] = [newOrder[index - 1], newOrder[index]];
        return newOrder;
      });
    }
  }, []);

  const moveItemDown = useCallback((index: number) => {
    if (index < listOrder.length - 1) {
      LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
      setListOrder(prev => {
        const newOrder = [...prev];
        [newOrder[index], newOrder[index + 1]] = [newOrder[index + 1], newOrder[index]];
        return newOrder;
      });
    }
  }, [listOrder.length]);

  const resetAll = useCallback(() => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    setExpanded(false);
    setItems([1, 2, 3]);
    setBoxSize(100);
    setBoxColor('#409EFF');
    setVisibleItems([true, true, true]);
    setGridColumns(2);
    setListOrder([1, 2, 3, 4, 5]);
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContent}>
        {/* 展开/收起动画 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>展开/收起动画</Text>
          <TouchableOpacity style={styles.button} onPress={toggleExpanded}>
            <Text style={styles.buttonText}>{expanded ? '收起' : '展开'}</Text>
          </TouchableOpacity>
          <View style={[styles.expandedContent, expanded && styles.expanded]}>
            <Text style={styles.expandedText}>
              这是展开的内容,使用LayoutAnimation实现平滑的展开和收起效果
            </Text>
          </View>
        </View>

        {/* 添加/删除动画 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>添加/删除动画</Text>
          <View style={styles.buttonRow}>
            <TouchableOpacity style={styles.button} onPress={addItem}>
              <Text style={styles.buttonText}>添加项目</Text>
            </TouchableOpacity>
            <TouchableOpacity style={[styles.button, styles.secondaryButton]} onPress={removeItem}>
              <Text style={styles.buttonText}>删除项目</Text>
            </TouchableOpacity>
          </View>
          <View style={styles.itemsContainer}>
            {items.map((item) => (
              <View key={item} style={styles.item}>
                <Text style={styles.itemText}>项目 {item}</Text>
              </View>
            ))}
          </View>
        </View>

        {/* 尺寸变化动画 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>尺寸变化动画</Text>
          <View style={styles.sizeContainer}>
            <View
              style={[
                styles.sizeBox,
                {
                  width: boxSize,
                  height: boxSize,
                  backgroundColor: boxColor,
                }
              ]}
            >
              <Text style={styles.sizeBoxText}>{boxSize}x{boxSize}</Text>
            </View>
          </View>
          <View style={styles.buttonRow}>
            <TouchableOpacity style={styles.button} onPress={increaseSize}>
              <Text style={styles.buttonText}>增大</Text>
            </TouchableOpacity>
            <TouchableOpacity style={[styles.button, styles.secondaryButton]} onPress={decreaseSize}>
              <Text style={styles.buttonText}>减小</Text>
            </TouchableOpacity>
            <TouchableOpacity style={[styles.button, styles.colorButton]} onPress={changeColor}>
              <Text style={styles.buttonText}>变色</Text>
            </TouchableOpacity>
          </View>
        </View>

        {/* 显示/隐藏动画 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>显示/隐藏动画</Text>
          <View style={styles.visibilityContainer}>
            {visibleItems.map((visible, index) => (
              <TouchableOpacity
                key={index}
                style={[
                  styles.visibilityItem,
                  !visible && styles.hidden
                ]}
                onPress={() => toggleVisibility(index)}
              >
                <Text style={styles.visibilityText}>
                  项目 {index + 1} - {visible ? '显示' : '隐藏'}
                </Text>
              </TouchableOpacity>
            ))}
          </View>
        </View>

        {/* 网格布局动画 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>网格布局动画</Text>
          <TouchableOpacity style={styles.button} onPress={changeColumns}>
            <Text style={styles.buttonText}>切换列数 ({gridColumns}列)</Text>
          </TouchableOpacity>
          <View style={[
            styles.gridContainer,
            { flexDirection: gridColumns === 2 ? 'row' : 'row' }
          ]}>
            {Array.from({ length: 6 }, (_, index) => (
              <View
                key={index}
                style={[
                  styles.gridItem,
                  { width: `${100 / gridColumns}%` }
                ]}
              >
                <Text style={styles.gridItemText}>项目 {index + 1}</Text>
              </View>
            ))}
          </View>
        </View>

        {/* 列表排序动画 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>列表排序动画</Text>
          <TouchableOpacity style={styles.button} onPress={shuffleList}>
            <Text style={styles.buttonText}>随机排序</Text>
          </TouchableOpacity>
          <View style={styles.sortListContainer}>
            {listOrder.map((item, index) => (
              <View key={item} style={styles.sortItem}>
                <Text style={styles.sortItemText}>项目 {item}</Text>
                <View style={styles.sortButtons}>
                  <TouchableOpacity
                    style={styles.sortButton}
                    onPress={() => moveItemUp(index)}
                    disabled={index === 0}
                  >
                    <Text style={styles.sortButtonText}>↑</Text>
                  </TouchableOpacity>
                  <TouchableOpacity
                    style={styles.sortButton}
                    onPress={() => moveItemDown(index)}
                    disabled={index === listOrder.length - 1}
                  >
                    <Text style={styles.sortButtonText}>↓</Text>
                  </TouchableOpacity>
                </View>
              </View>
            ))}
          </View>
        </View>

        {/* 预设动画 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>预设动画</Text>
          <View style={styles.presetGrid}>
            <TouchableOpacity
              style={styles.presetButton}
              onPress={() => {
                LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
                setExpanded(!expanded);
              }}
            >
              <Text style={styles.presetButtonText}>easeInEaseOut</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={styles.presetButton}
              onPress={() => {
                LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
                setExpanded(!expanded);
              }}
            >
              <Text style={styles.presetButtonText}>linear</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={styles.presetButton}
              onPress={() => {
                LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
                setExpanded(!expanded);
              }}
            >
              <Text style={styles.presetButtonText}>spring</Text>
            </TouchableOpacity>
          </View>
        </View>

        {/* 重置按钮 */}
        <View style={styles.section}>
          <TouchableOpacity style={[styles.button, styles.resetButton]} onPress={resetAll}>
            <Text style={styles.buttonText}>重置所有</Text>
          </TouchableOpacity>
        </View>

        {/* 使用说明 */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>使用说明</Text>
          <View style={styles.instructionCard}>
            <Text style={styles.instructionText}>
              • 使用 LayoutAnimation.configureNext 配置动画
            </Text>
            <Text style={styles.instructionText}>
              • 支持预设动画和自定义动画
            </Text>
            <Text style={styles.instructionText}>
              • 适用于布局变化、尺寸变化等场景
            </Text>
            <Text style={styles.instructionText}>
              • 自动处理创建、更新、删除动画
            </Text>
            <Text style={styles.instructionText}>
              • 性能优秀,使用原生动画实现
            </Text>
          </View>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F7FA',
  },
  scrollView: {
    flex: 1,
  },
  scrollContent: {
    padding: 20,
  },
  section: {
    marginBottom: 24,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#303133',
    marginBottom: 12,
  },
  button: {
    backgroundColor: '#409EFF',
    borderRadius: 8,
    paddingVertical: 12,
    paddingHorizontal: 20,
    alignItems: 'center',
    marginBottom: 12,
  },
  buttonText: {
    color: '#FFFFFF',
    fontSize: 14,
    fontWeight: '600',
  },
  secondaryButton: {
    backgroundColor: '#909399',
  },
  colorButton: {
    backgroundColor: '#F56C6C',
  },
  resetButton: {
    backgroundColor: '#E6A23C',
  },
  buttonRow: {
    flexDirection: 'row',
    gap: 12,
    marginBottom: 12,
  },
  expandedContent: {
    backgroundColor: '#FFFFFF',
    borderRadius: 8,
    padding: 16,
    overflow: 'hidden',
    maxHeight: 0,
  },
  expanded: {
    maxHeight: 200,
  },
  expandedText: {
    fontSize: 14,
    color: '#606266',
    lineHeight: 22,
  },
  itemsContainer: {
    gap: 12,
  },
  item: {
    backgroundColor: '#FFFFFF',
    borderRadius: 8,
    padding: 16,
    borderWidth: 1,
    borderColor: '#DCDFE6',
  },
  itemText: {
    fontSize: 16,
    color: '#303133',
    fontWeight: '600',
  },
  sizeContainer: {
    backgroundColor: '#FFFFFF',
    borderRadius: 8,
    padding: 20,
    alignItems: 'center',
    justifyContent: 'center',
    marginBottom: 12,
    minHeight: 250,
  },
  sizeBox: {
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  },
  sizeBoxText: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
  },
  visibilityContainer: {
    gap: 12,
  },
  visibilityItem: {
    backgroundColor: '#FFFFFF',
    borderRadius: 8,
    padding: 16,
    borderWidth: 1,
    borderColor: '#DCDFE6',
  },
  hidden: {
    opacity: 0.3,
  },
  visibilityText: {
    fontSize: 16,
    color: '#303133',
    fontWeight: '600',
  },
  gridContainer: {
    flexWrap: 'wrap',
    gap: 12,
    marginTop: 12,
  },
  gridItem: {
    backgroundColor: '#409EFF',
    borderRadius: 8,
    padding: 16,
    alignItems: 'center',
  },
  gridItemText: {
    color: '#FFFFFF',
    fontSize: 14,
    fontWeight: '600',
  },
  sortListContainer: {
    gap: 12,
    marginTop: 12,
  },
  sortItem: {
    backgroundColor: '#FFFFFF',
    borderRadius: 8,
    padding: 16,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#DCDFE6',
  },
  sortItemText: {
    fontSize: 16,
    color: '#303133',
    fontWeight: '600',
  },
  sortButtons: {
    flexDirection: 'row',
    gap: 8,
  },
  sortButton: {
    backgroundColor: '#409EFF',
    borderRadius: 4,
    paddingHorizontal: 12,
    paddingVertical: 6,
  },
  sortButtonText: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
  },
  presetGrid: {
    flexDirection: 'row',
    gap: 12,
  },
  presetButton: {
    flex: 1,
    backgroundColor: '#FFFFFF',
    borderRadius: 8,
    padding: 16,
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#DCDFE6',
  },
  presetButtonText: {
    fontSize: 14,
    color: '#303133',
    fontWeight: '600',
  },
  instructionCard: {
    backgroundColor: '#E6F7FF',
    borderRadius: 8,
    padding: 16,
    borderLeftWidth: 4,
    borderLeftColor: '#409EFF',
  },
  instructionText: {
    fontSize: 14,
    color: '#303133',
    lineHeight: 22,
    marginBottom: 8,
  },
});

export default LayoutAnimationDemo;

四、OpenHarmony6.0 专属避坑指南

以下是鸿蒙 RN 开发中实现「LayoutAnimation 布局动画」的所有真实高频率坑点 ,按出现频率排序,问题现象贴合开发实战,解决方案均为「一行代码简单配置」,所有方案均为鸿蒙端专属最优解,也是本次代码都能做到**零报错、完美适配」的核心原因,鸿蒙基础可直接用,彻底规避所有布局动画相关的动画失效、卡顿、布局错位等问题,全部真机实测验证通过,无任何兼容问题:

问题现象 问题原因 鸿蒙端最优解决方案
布局动画在鸿蒙端无效果 未启用实验性布局动画或配置错误 ✅ 正确启用布局动画,本次代码已完美实现
布局动画在鸿蒙端卡顿 动画时长设置不当或布局过于复杂 ✅ 使用合适的动画时长,本次代码已完美实现
布局动画在鸿蒙端闪烁 未正确配置动画类型或状态更新时机错误 ✅ 正确配置动画类型,本次代码已完美实现
布局动画在鸿蒙端不流畅 布局变化过于频繁或动画重叠 ✅ 优化布局更新频率,本次代码已完美实现
布局动画在鸿蒙端性能下降 布局层级过深或组件过多 ✅ 优化布局结构,本次代码已完美实现
布局动画在鸿蒙端布局错位 未正确处理布局约束或样式冲突 ✅ 正确处理布局约束,本次代码已完美实现
布局动画在鸿蒙端无法停止 动画未正确配置或状态管理错误 ✅ 正确管理动画状态,本次代码已完美实现
布局动画在鸿蒙端预设失效 预设动画配置错误或平台不支持 ✅ 使用正确的预设动画,本次代码已完美实现

五、扩展用法:布局动画高级进阶优化(纯原生、无依赖、鸿蒙完美适配)

基于本次的核心布局动画代码,结合 RN 的内置能力,可轻松实现鸿蒙端开发中所有高级的布局动画进阶需求,全部为纯原生 API 实现,无需引入任何第三方库,只需在本次代码基础上做简单修改即可实现,实用性拉满,全部真机实测通过,无任何兼容问题,满足企业级高级需求:

✨ 扩展1:动画Hook

适配「动画Hook」的场景,封装常用的布局动画Hook,只需添加Hook逻辑,无需改动核心逻辑,一行代码实现,鸿蒙端完美适配:

javascript 复制代码
const useLayoutAnimation = (type = 'easeInEaseOut') => {
  const triggerAnimation = useCallback(() => {
    const configs = {
      easeInEaseOut: LayoutAnimation.Presets.easeInEaseOut,
      linear: LayoutAnimation.Presets.linear,
      spring: LayoutAnimation.Presets.spring,
    };
  
    LayoutAnimation.configureNext(configs[type] || LayoutAnimation.Presets.easeInEaseOut);
  }, [type]);

  return { triggerAnimation };
};

const useExpandAnimation = () => {
  const [expanded, setExpanded] = useState(false);

  const toggle = useCallback(() => {
    LayoutAnimation.configureNext({
      duration: 300,
      create: { type: 'easeInEaseOut', property: 'opacity' },
      update: { type: 'easeInEaseOut' },
    });
    setExpanded(!expanded);
  }, [expanded]);

  return { expanded, toggle };
};

const useListAnimation = () => {
  const [items, setItems] = useState([]);

  const addItem = useCallback((item) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
    setItems(prev => [...prev, item]);
  }, []);

  const removeItem = useCallback((index) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    setItems(prev => prev.filter((_, i) => i !== index));
  }, []);

  const moveItem = useCallback((fromIndex, toIndex) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
    setItems(prev => {
      const newItems = [...prev];
      const [removed] = newItems.splice(fromIndex, 1);
      newItems.splice(toIndex, 0, removed);
      return newItems;
    });
  }, []);

  return { items, addItem, removeItem, moveItem };
};

✨ 扩展2:动画组件

适配「动画组件」的场景,实现可复用的布局动画组件,只需添加组件逻辑,无需改动核心逻辑,一行代码实现,鸿蒙端完美适配:

javascript 复制代码
const ExpandableSection = ({ title, children, defaultExpanded = false }) => {
  const [expanded, setExpanded] = useState(defaultExpanded);

  const toggle = useCallback(() => {
    LayoutAnimation.configureNext({
      duration: 300,
      create: { type: 'easeInEaseOut', property: 'opacity' },
      update: { type: 'easeInEaseOut' },
    });
    setExpanded(!expanded);
  }, [expanded]);

  return (
    <View style={styles.section}>
      <TouchableOpacity onPress={toggle}>
        <Text style={styles.sectionTitle}>{title}</Text>
      </TouchableOpacity>
      <View style={[styles.content, expanded && styles.expanded]}>
        {children}
      </View>
    </View>
  );
};

const AnimatedGrid = ({ data, columns = 2, renderItem }) => {
  const [gridColumns, setGridColumns] = useState(columns);

  const changeColumns = useCallback(() => {
    LayoutAnimation.configureNext({
      duration: 300,
      update: { type: 'spring', springDamping: 0.7 },
    });
    setGridColumns(gridColumns === 2 ? 3 : 2);
  }, [gridColumns]);

  return (
    <View>
      <TouchableOpacity onPress={changeColumns}>
        <Text>切换列数 ({gridColumns}列)</Text>
      </TouchableOpacity>
      <View style={styles.grid}>
        {data.map((item, index) => (
          <View
            key={index}
            style={[styles.gridItem, { width: `${100 / gridColumns}%` }]}
          >
            {renderItem(item, index)}
          </View>
        ))}
      </View>
    </View>
  );
};

const SortableList = ({ data, onReorder }) => {
  const [items, setItems] = useState(data);

  const moveItem = useCallback((fromIndex, toIndex) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
    setItems(prev => {
      const newItems = [...prev];
      const [removed] = newItems.splice(fromIndex, 1);
      newItems.splice(toIndex, 0, removed);
      if (onReorder) {
        onReorder(newItems);
      }
      return newItems;
    });
  }, [onReorder]);

  return (
    <View>
      {items.map((item, index) => (
        <View key={item.id} style={styles.sortItem}>
          <Text>{item.title}</Text>
          <View style={styles.sortButtons}>
            <TouchableOpacity onPress={() => moveItem(index, index - 1)}>
              <Text>↑</Text>
            </TouchableOpacity>
            <TouchableOpacity onPress={() => moveItem(index, index + 1)}>
              <Text>↓</Text>
            </TouchableOpacity>
          </View>
        </View>
      ))}
    </View>
  );
};

✨ 扩展3:自定义动画配置

适配「自定义动画配置」的场景,实现丰富的自定义动画效果,只需添加配置逻辑,无需改动核心逻辑,一行代码实现,鸿蒙端完美适配:

javascript 复制代码
const customLayoutAnimations = {
  // 快速淡入
  fadeInFast: {
    duration: 200,
    create: { type: 'easeIn', property: 'opacity' },
    update: { type: 'easeIn' },
  },
  
  // 弹性展开
  springExpand: {
    duration: 400,
    create: {
      type: 'spring',
      property: 'scaleXY',
      springDamping: 0.7,
    },
    update: {
      type: 'spring',
      springDamping: 0.7,
    },
  },
  
  // 平滑滑动
  smoothSlide: {
    duration: 350,
    update: {
      type: 'easeInEaseOut',
    },
  },
  
  // 弹跳效果
  bounce: {
    duration: 500,
    create: {
      type: 'spring',
      property: 'scaleXY',
      springDamping: 0.5,
    },
    update: {
      type: 'spring',
      springDamping: 0.5,
    },
  },
};

const useCustomLayoutAnimation = () => {
  const triggerAnimation = useCallback((animationName) => {
    const config = customLayoutAnimations[animationName];
    if (config) {
      LayoutAnimation.configureNext(config);
    }
  }, []);

  return { triggerAnimation, customLayoutAnimations };
};

// 使用示例
const MyComponent = () => {
  const { triggerAnimation } = useCustomLayoutAnimation();
  const [expanded, setExpanded] = useState(false);
  
  const handleExpand = () => {
    triggerAnimation('springExpand');
    setExpanded(!expanded);
  };
  
  return (
    <View>
      <TouchableOpacity onPress={handleExpand}>
        <Text>展开</Text>
      </TouchableOpacity>
      {expanded && <Text>内容</Text>}
    </View>
  );
};

✨ 扩展4:动画性能优化

适配「动画性能优化」的场景,实现高性能的布局动画,只需添加优化逻辑,无需改动核心逻辑,一行代码实现,鸿蒙端完美适配:

javascript 复制代码
const useOptimizedLayoutAnimation = () => {
  const triggerOptimizedAnimation = useCallback((config = {}) => {
    const defaultConfig = {
      duration: 300,
      create: { type: 'easeInEaseOut', property: 'opacity' },
      update: { type: 'easeInEaseOut' },
      delete: { type: 'easeInEaseOut', property: 'opacity' },
    };
  
    const finalConfig = { ...defaultConfig, ...config };
    LayoutAnimation.configureNext(finalConfig);
  }, []);

  const batchUpdate = useCallback((updates) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    updates.forEach(update => update());
  }, []);

  return { triggerOptimizedAnimation, batchUpdate };
};

// 使用示例
const OptimizedList = () => {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  const { batchUpdate } = useOptimizedLayoutAnimation();
  
  const handleBatchUpdate = () => {
    batchUpdate([
      () => setItems(prev => prev.filter(item => item % 2 === 0)),
      () => console.log('批量更新完成'),
    ]);
  };
  
  return (
    <View>
      <Button title="批量更新" onPress={handleBatchUpdate} />
      {items.map(item => <Text key={item}>项目 {item}</Text>)}
    </View>
  );
};

✨ 扩展5:动画状态管理

适配「动画状态管理」的场景,实现复杂的动画状态管理,只需添加状态管理逻辑,无需改动核心逻辑,一行代码实现,鸿蒙端完美适配:

javascript 复制代码
const useAnimationStateManager = () => {
  const [animationState, setAnimationState] = useState('idle');
  const [animationQueue, setAnimationQueue] = useState([]);

  const enqueueAnimation = useCallback((animationFn) => {
    setAnimationQueue(prev => [...prev, animationFn]);
  }, []);

  const processQueue = useCallback(() => {
    if (animationQueue.length === 0 || animationState !== 'idle') {
      return;
    }

    setAnimationState('running');
    const [currentAnimation, ...remaining] = animationQueue;

    currentAnimation().then(() => {
      setAnimationQueue(remaining);
      setAnimationState('idle');
    });
  }, [animationQueue, animationState]);

  useEffect(() => {
    processQueue();
  }, [processQueue]);

  const clearQueue = useCallback(() => {
    setAnimationQueue([]);
  }, []);

  return {
    animationState,
    enqueueAnimation,
    clearQueue,
  };
};

// 使用示例
const AnimationQueueDemo = () => {
  const { animationState, enqueueAnimation, clearQueue } = useAnimationStateManager();
  const [expanded, setExpanded] = useState(false);
  
  const addAnimations = () => {
    enqueueAnimation(() => {
      return new Promise(resolve => {
        LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
        setExpanded(true);
        setTimeout(resolve, 500);
      });
    });
  
    enqueueAnimation(() => {
      return new Promise(resolve => {
        LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
        setExpanded(false);
        setTimeout(resolve, 500);
      });
    });
  };
  
  return (
    <View>
      <Text>动画状态: {animationState}</Text>
      <Button title="添加动画" onPress={addAnimations} />
      <Button title="清空队列" onPress={clearQueue} />
      {expanded && <Text>展开的内容</Text>}
    </View>
  );
};

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
摘星编程2 小时前
React Native for OpenHarmony 实战:WebView 网页视图组件
react native·react.js·ios
世人万千丶2 小时前
鸿蒙跨端框架 Flutter 学习:GetX 全家桶:从状态管理到路由导航的极简艺术
学习·flutter·ui·华为·harmonyos·鸿蒙
弓.长.2 小时前
基础入门 React Native 鸿蒙跨平台开发:Linking 链接处理 鸿蒙实战
react native·react.js·harmonyos
夜雨声烦丿2 小时前
Flutter 框架跨平台鸿蒙开发 - 电影票房查询 - 完整开发教程
flutter·华为·harmonyos
弓.长.2 小时前
基础入门 React Native 鸿蒙跨平台开发:网络请求实战
网络·react native·harmonyos
小白阿龙3 小时前
鸿蒙+flutter 跨平台开发——从零打造手持弹幕App实战
flutter·华为·harmonyos·鸿蒙
编程乐学14 小时前
鸿蒙非原创--DevEcoStudio开发的奶茶点餐APP
华为·harmonyos·deveco studio·鸿蒙开发·奶茶点餐·鸿蒙大作业
鸣弦artha15 小时前
Flutter框架跨平台鸿蒙开发 —— Text Widget:文本展示的艺术
flutter·华为·harmonyos
弓.长.16 小时前
React Native 鸿蒙跨平台开发:实现一个多功能单位转换器
javascript·react native·react.js