Skip to content

样式美化

在完成了待办清单应用的核心功能后,我们需要对应用进行样式美化,提升用户体验。在这一节中,我们将学习如何使用 React Native 的样式系统创建美观、现代的用户界面。

1. 主题与色彩方案

首先,让我们定义一个统一的主题和色彩方案,确保应用的视觉一致性:

js
// src/theme.js
export const theme = {
  colors: {
    primary: '#4F46E5', // 主色调 - 靛蓝色
    secondary: '#10B981', // 次要色调 - 绿色
    background: '#F3F4F6', // 背景色
    surface: '#FFFFFF', // 表面色
    text: '#1F2937', // 主要文本色
    textSecondary: '#6B7280', // 次要文本色
    border: '#E5E7EB', // 边框色
    error: '#EF4444', // 错误色
    success: '#10B981', // 成功色
    warning: '#F59E0B', // 警告色
  },
  spacing: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 32,
  },
  borderRadius: {
    sm: 4,
    md: 8,
    lg: 12,
    xl: 16,
  },
  shadows: {
    sm: {
      shadowColor: '#000',
      shadowOffset: {
        width: 0,
        height: 1,
      },
      shadowOpacity: 0.05,
      shadowRadius: 2,
      elevation: 2,
    },
    md: {
      shadowColor: '#000',
      shadowOffset: {
        width: 0,
        height: 2,
      },
      shadowOpacity: 0.1,
      shadowRadius: 4,
      elevation: 4,
    },
  },
};

2. 组件样式优化

2.1 容器样式

js
// src/components/TodoContainer.js
import React from 'react';
import { View, StyleSheet, SafeAreaView } from 'react-native';
import { theme } from '../theme';

const TodoContainer = ({ children }) => {
  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.content}>
        {children}
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: theme.colors.background,
  },
  content: {
    flex: 1,
    padding: theme.spacing.md,
  },
});

export default TodoContainer;

2.2 标题样式

js
// src/components/TodoHeader.js
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { theme } from '../theme';

const TodoHeader = ({ title, subtitle }) => {
  return (
    <View style={styles.header}>
      <Text style={styles.title}>{title}</Text>
      {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
    </View>
  );
};

const styles = StyleSheet.create({
  header: {
    marginBottom: theme.spacing.lg,
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: theme.colors.text,
    marginBottom: theme.spacing.xs,
  },
  subtitle: {
    fontSize: 16,
    color: theme.colors.textSecondary,
  },
});

export default TodoHeader;

2.3 待办项样式

js
// src/components/TodoItem.js
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { theme } from '../theme';

const TodoItem = ({ todo, onToggle, onDelete }) => {
  return (
    <TouchableOpacity 
      style={[styles.item, todo.completed && styles.completedItem]}
      onPress={() => onToggle(todo.id)}
    >
      <View style={[styles.checkbox, todo.completed && styles.checkboxCompleted]}>
        {todo.completed && <Text style={styles.checkmark}>✓</Text>}
      </View>
      <Text 
        style={[styles.text, todo.completed && styles.completedText]}
        numberOfLines={1}
      >
        {todo.text}
      </Text>
      <TouchableOpacity 
        style={styles.deleteButton}
        onPress={() => onDelete(todo.id)}
      >
        <Text style={styles.deleteText}>×</Text>
      </TouchableOpacity>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  item: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: theme.colors.surface,
    padding: theme.spacing.md,
    borderRadius: theme.borderRadius.md,
    marginBottom: theme.spacing.sm,
    ...theme.shadows.sm,
  },
  completedItem: {
    opacity: 0.7,
  },
  checkbox: {
    width: 24,
    height: 24,
    borderRadius: 12,
    borderWidth: 2,
    borderColor: theme.colors.primary,
    marginRight: theme.spacing.sm,
    justifyContent: 'center',
    alignItems: 'center',
  },
  checkboxCompleted: {
    backgroundColor: theme.colors.primary,
  },
  checkmark: {
    color: theme.colors.surface,
    fontWeight: 'bold',
  },
  text: {
    flex: 1,
    fontSize: 16,
    color: theme.colors.text,
  },
  completedText: {
    textDecorationLine: 'line-through',
    color: theme.colors.textSecondary,
  },
  deleteButton: {
    padding: theme.spacing.xs,
    borderRadius: theme.borderRadius.sm,
  },
  deleteText: {
    fontSize: 24,
    color: theme.colors.error,
    fontWeight: 'bold',
  },
});

export default TodoItem;

2.4 输入表单样式

js
// src/components/TodoForm.js
import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { theme } from '../theme';

const TodoForm = ({ onAddTodo }) => {
  const [text, setText] = useState('');

  const handleSubmit = () => {
    if (text.trim()) {
      onAddTodo(text);
      setText('');
    }
  };

  return (
    <View style={styles.form}>
      <TextInput
        style={styles.input}
        placeholder="添加新的待办事项..."
        placeholderTextColor={theme.colors.textSecondary}
        value={text}
        onChangeText={setText}
        returnKeyType="done"
        onSubmitEditing={handleSubmit}
      />
      <TouchableOpacity 
        style={styles.button}
        onPress={handleSubmit}
        activeOpacity={0.8}
      >
        <Text style={styles.buttonText}>+</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  form: {
    flexDirection: 'row',
    marginBottom: theme.spacing.lg,
  },
  input: {
    flex: 1,
    backgroundColor: theme.colors.surface,
    padding: theme.spacing.md,
    borderRadius: theme.borderRadius.md,
    marginRight: theme.spacing.sm,
    fontSize: 16,
    color: theme.colors.text,
    ...theme.shadows.sm,
  },
  button: {
    width: 50,
    height: 50,
    borderRadius: theme.borderRadius.md,
    backgroundColor: theme.colors.primary,
    justifyContent: 'center',
    alignItems: 'center',
    ...theme.shadows.md,
  },
  buttonText: {
    fontSize: 24,
    color: theme.colors.surface,
    fontWeight: 'bold',
  },
});

export default TodoForm;

3. 空状态和加载状态

3.1 空状态组件

js
// src/components/EmptyState.js
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { theme } from '../theme';

const EmptyState = ({ message }) => {
  return (
    <View style={styles.container}>
      <Text style={styles.icon}>📝</Text>
      <Text style={styles.message}>{message}</Text>
      <Text style={styles.subMessage}>点击上方添加你的第一个待办事项</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: theme.spacing.xl,
  },
  icon: {
    fontSize: 48,
    marginBottom: theme.spacing.md,
  },
  message: {
    fontSize: 18,
    fontWeight: '600',
    color: theme.colors.text,
    marginBottom: theme.spacing.sm,
    textAlign: 'center',
  },
  subMessage: {
    fontSize: 14,
    color: theme.colors.textSecondary,
    textAlign: 'center',
  },
});

export default EmptyState;

3.2 加载状态组件

js
// src/components/LoadingState.js
import React from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import { theme } from '../theme';

const LoadingState = () => {
  return (
    <View style={styles.container}>
      <ActivityIndicator size="large" color={theme.colors.primary} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default LoadingState;

4. 动画效果

为了提升用户体验,我们可以添加一些简单的动画效果:

js
// src/components/AnimatedTodoItem.js
import React, { useRef } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Animated } from 'react-native';
import { theme } from '../theme';

const AnimatedTodoItem = ({ todo, onToggle, onDelete }) => {
  const scaleAnim = useRef(new Animated.Value(1)).current;

  const handlePressIn = () => {
    Animated.spring(scaleAnim, {
      toValue: 0.95,
      useNativeDriver: true,
    }).start();
  };

  const handlePressOut = () => {
    Animated.spring(scaleAnim, {
      toValue: 1,
      friction: 3,
      tension: 40,
      useNativeDriver: true,
    }).start();
  };

  return (
    <Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
      <TouchableOpacity 
        style={[styles.item, todo.completed && styles.completedItem]}
        onPress={() => onToggle(todo.id)}
        onPressIn={handlePressIn}
        onPressOut={handlePressOut}
      >
        <View style={[styles.checkbox, todo.completed && styles.checkboxCompleted]}>
          {todo.completed && <Text style={styles.checkmark}>✓</Text>}
        </View>
        <Text 
          style={[styles.text, todo.completed && styles.completedText]}
          numberOfLines={1}
        >
          {todo.text}
        </Text>
        <TouchableOpacity 
          style={styles.deleteButton}
          onPress={() => onDelete(todo.id)}
        >
          <Text style={styles.deleteText}>×</Text>
        </TouchableOpacity>
      </TouchableOpacity>
    </Animated.View>
  );
};

const styles = StyleSheet.create({
  // 与 TodoItem 相同的样式
  item: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: theme.colors.surface,
    padding: theme.spacing.md,
    borderRadius: theme.borderRadius.md,
    marginBottom: theme.spacing.sm,
    ...theme.shadows.sm,
  },
  completedItem: {
    opacity: 0.7,
  },
  checkbox: {
    width: 24,
    height: 24,
    borderRadius: 12,
    borderWidth: 2,
    borderColor: theme.colors.primary,
    marginRight: theme.spacing.sm,
    justifyContent: 'center',
    alignItems: 'center',
  },
  checkboxCompleted: {
    backgroundColor: theme.colors.primary,
  },
  checkmark: {
    color: theme.colors.surface,
    fontWeight: 'bold',
  },
  text: {
    flex: 1,
    fontSize: 16,
    color: theme.colors.text,
  },
  completedText: {
    textDecorationLine: 'line-through',
    color: theme.colors.textSecondary,
  },
  deleteButton: {
    padding: theme.spacing.xs,
    borderRadius: theme.borderRadius.sm,
  },
  deleteText: {
    fontSize: 24,
    color: theme.colors.error,
    fontWeight: 'bold',
  },
});

export default AnimatedTodoItem;

5. 主应用组件整合

现在,让我们更新主应用组件,整合所有美化后的组件:

js
// App.js
import React, { useState, useEffect } from 'react';
import { View, FlatList, StyleSheet, Alert } from 'react-native';
import TodoContainer from './src/components/TodoContainer';
import TodoHeader from './src/components/TodoHeader';
import TodoForm from './src/components/TodoForm';
import AnimatedTodoItem from './src/components/AnimatedTodoItem';
import EmptyState from './src/components/EmptyState';
import LoadingState from './src/components/LoadingState';
import { todoStorage } from './src/utils/storage';
import { theme } from './src/theme';

export default function App() {
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(true);

  // 加载存储的待办事项
  useEffect(() => {
    const loadTodos = async () => {
      try {
        const storedTodos = await todoStorage.getTodos();
        setTodos(storedTodos);
      } catch (error) {
        console.error('加载待办事项失败:', error);
        Alert.alert('错误', '加载待办事项失败');
      } finally {
        setLoading(false);
      }
    };

    loadTodos();
  }, []);

  // 保存待办事项到存储
  useEffect(() => {
    if (!loading) {
      todoStorage.saveTodos(todos).catch(error => {
        console.error('保存待办事项失败:', error);
      });
    }
  }, [todos, loading]);

  // 添加待办事项
  const handleAddTodo = (text) => {
    const newTodo = {
      id: Date.now().toString(),
      text,
      completed: false,
      createdAt: new Date().toISOString(),
    };
    setTodos(prevTodos => [newTodo, ...prevTodos]);
  };

  // 切换待办事项状态
  const handleToggleTodo = (id) => {
    setTodos(prevTodos =>
      prevTodos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  // 删除待办事项
  const handleDeleteTodo = (id) => {
    Alert.alert(
      '确认删除',
      '确定要删除这个待办事项吗?',
      [
        { text: '取消', style: 'cancel' },
        {
          text: '删除',
          style: 'destructive',
          onPress: () => {
            setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
          },
        },
      ],
      { cancelable: true }
    );
  };

  // 渲染待办事项
  const renderTodo = ({ item }) => (
    <AnimatedTodoItem
      todo={item}
      onToggle={handleToggleTodo}
      onDelete={handleDeleteTodo}
    />
  );

  // 渲染列表头部
  const ListHeaderComponent = () => (
    <View>
      <TodoHeader 
        title="我的待办清单" 
        subtitle={`共 ${todos.length} 项,已完成 ${todos.filter(t => t.completed).length} 项`} 
      />
      <TodoForm onAddTodo={handleAddTodo} />
    </View>
  );

  if (loading) {
    return <LoadingState />;
  }

  return (
    <TodoContainer>
      <FlatList
        data={todos}
        renderItem={renderTodo}
        keyExtractor={item => item.id}
        ListHeaderComponent={ListHeaderComponent}
        ListEmptyComponent={
          <EmptyState message="还没有待办事项" />
        }
        contentContainerStyle={todos.length === 0 ? styles.emptyContainer : styles.listContainer}
        showsVerticalScrollIndicator={false}
      />
    </TodoContainer>
  );
}

const styles = StyleSheet.create({
  listContainer: {
    paddingBottom: theme.spacing.lg,
  },
  emptyContainer: {
    flexGrow: 1,
  },
});

6. 响应式设计

为了确保应用在不同尺寸的设备上都能良好显示,我们可以添加响应式设计:

js
// src/utils/responsive.js
import { Dimensions, Platform, StatusBar } from 'react-native';

const { width, height } = Dimensions.get('window');

export const isSmallDevice = width < 375;

export const spacing = {
  xs: isSmallDevice ? 4 : 4,
  sm: isSmallDevice ? 8 : 8,
  md: isSmallDevice ? 12 : 16,
  lg: isSmallDevice ? 16 : 24,
  xl: isSmallDevice ? 20 : 32,
};

export const fontSize = {
  xs: isSmallDevice ? 12 : 12,
  sm: isSmallDevice ? 14 : 14,
  md: isSmallDevice ? 16 : 16,
  lg: isSmallDevice ? 20 : 24,
  xl: isSmallDevice ? 24 : 28,
};

export const getStatusBarHeight = () => {
  return Platform.OS === 'ios' ? 44 : StatusBar.currentHeight || 0;
};

7. 主题切换功能

为了提供更好的用户体验,我们可以添加深色模式支持:

js
// src/contexts/ThemeContext.js
import React, { createContext, useState, useContext, useEffect } from 'react';
import { useColorScheme } from 'react-native';

const ThemeContext = createContext();

export const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
};

export const ThemeProvider = ({ children }) => {
  const systemColorScheme = useColorScheme();
  const [isDarkMode, setIsDarkMode] = useState(systemColorScheme === 'dark');

  const toggleTheme = () => {
    setIsDarkMode(!isDarkMode);
  };

  const theme = {
    colors: {
      primary: '#4F46E5',
      secondary: '#10B981',
      background: isDarkMode ? '#121212' : '#F3F4F6',
      surface: isDarkMode ? '#1E1E1E' : '#FFFFFF',
      text: isDarkMode ? '#FFFFFF' : '#1F2937',
      textSecondary: isDarkMode ? '#A0AEC0' : '#6B7280',
      border: isDarkMode ? '#2D3748' : '#E5E7EB',
      error: '#EF4444',
      success: '#10B981',
      warning: '#F59E0B',
    },
    spacing: {
      xs: 4,
      sm: 8,
      md: 16,
      lg: 24,
      xl: 32,
    },
    borderRadius: {
      sm: 4,
      md: 8,
      lg: 12,
      xl: 16,
    },
    shadows: {
      sm: {
        shadowColor: '#000',
        shadowOffset: {
          width: 0,
          height: 1,
        },
        shadowOpacity: 0.05,
        shadowRadius: 2,
        elevation: 2,
      },
      md: {
        shadowColor: '#000',
        shadowOffset: {
          width: 0,
          height: 2,
        },
        shadowOpacity: 0.1,
        shadowRadius: 4,
        elevation: 4,
      },
    },
  };

  return (
    <ThemeContext.Provider value={{ theme, isDarkMode, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

8. 最终效果

现在,我们的待办清单应用已经完成了样式美化,具有以下特点:

  1. 现代美观的界面:使用统一的色彩方案和布局
  2. 流畅的动画效果:添加了按压和过渡动画
  3. 完善的状态管理:包含空状态和加载状态
  4. 响应式设计:适配不同尺寸的设备
  5. 主题切换:支持浅色和深色模式

9. 总结

通过本章节的学习,我们掌握了如何使用 React Native 的样式系统创建美观、现代的用户界面。我们学习了:

  1. 如何定义和使用主题系统
  2. 如何优化组件样式
  3. 如何添加动画效果
  4. 如何实现响应式设计
  5. 如何添加主题切换功能

这些技能将帮助我们创建更加专业和用户友好的 React Native 应用。

10. 下一步

现在,我们已经完成了待办清单应用的开发,包括:

  1. 项目结构搭建
  2. 增删改查功能
  3. 本地数据存储
  4. 样式美化

接下来,我们可以继续学习其他实战项目,如新闻/资讯APP和个人主页/小工具APP,进一步提升我们的 React Native 开发技能。

© 2026 编程马·菜鸟教程 版权所有