搭建开发环境

遇到问题

  // android/build.gradle 注释源,添加阿里云源
   repositories {
//        google()
//        mavenCentral()
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/public' }
        maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
    }
# ios/Podfile  文件第一行添加清华源
source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'
  • Could not find 'cocoapods' (>= 0) among N total gem(s) (Gem::LoadError) 错误
# 解决方案
sudo gem uninstall --all
sudo gem install -n /usr/local/bin cocoapods
  • 'value' is unavailable: introduced in iOS 12.0
# https://github.com/facebook/react-native/issues/34106
# 解决方案
In iOS Folder go to Pods/Pods.xcodeproj/xcuserdata/project.pbxproj

Change all the 'IPHONEOS_DEPLOYMENT_TARGET = 11.0' to 'IPHONEOS_DEPLOYMENT_TARGET = 12.4'. save and run.

创建项目

# 创建项目 指定版本
npx react-native init AwesomeProject --version 0.63.4

# 创建项目 指定模板
npx react-native init AwesomeTSProject --template react-native-template-typescript

# 查看react-native 环境情况
npx react-native doctor

React 基础

class 组件与函数组件

  • 类组件
import React from 'react'
import { View, Text } from 'react-native'

class ClassView extends React.Component {
  // 构造函数
  constructor(props) {
    super(props)
    this.state = {
      address: '江苏省南京市',
    }
  }

  // 组件挂载完成
  componentDidMount() {
    setTimeout(() => {
      this.setState({
        address: '浙江省杭州市',
      })
    }, 2000)
  }

  // 组件渲染
  render() {
    const { name, age, level, sex } = this.props
    const { address } = this.state

    return (
      <View style={{ width: '100%', height: 200, backgroundColor: '#00bcd4' }}>
        <Text style={{ fontSize: 20, color: 'white' }}>
          {`name=${name}, age=${age}, level=${level}, sex=${sex}`}
        </Text>
        <Text style={{ fontSize: 20, color: 'black' }}>{address}</Text>
      </View>
    )
  }
}

export default ClassView
  • 函数组件
import React, { useState, useEffect, useRef } from 'react'
import {
  View,
  Text,
  ScrollView,
  useWindowDimensions,
  useColorScheme,
} from 'react-native'
import InfoCard from './InfoCard'

export default () => {
  // 等级
  const [levelUp, setLevelUp] = useState(false)

  // 获取窗口宽高
  const { width, height } = useWindowDimensions()

  // 获取系统主题
  const colorScheme = useColorScheme()

  // ScrollView Ref
  const scrollViewRef = useRef(null)

  // 组件挂载完成
  useEffect(() => {
    setTimeout(() => {
      setLevelUp(true)
      // 滚动到底部
      scrollViewRef.current.scrollToEnd({ animated: true })
    }, 2000)
  }, [])

  const getLevelView = () =>
    levelUp && (
      <Text style={{ fontSize: 24, color: 'green', marginVertical: 10 }}>
        {`等级:高级`}
      </Text>
    )

  const getListView = () => {
    const viewList = []
    for (let i = 0; i < 5; i++) {
      viewList.push(
        <Text style={{ fontSize: 20 }}>{`List item ${i + 1}`}</Text>
      )
    }
    return viewList
  }

  const array = ['AAA', 'BBB', 'CCC', 'DDD']
  return (
    <View style={{ width: '100%', height: '100%', backgroundColor: '#F5F5F5' }}>
      <InfoCard name='张三' age={24} sex='男' levelView={getLevelView()}>
        <Text style={{ fontSize: 20, color: 'red', marginVertical: 10 }}>
          {`爱好:唱、跳、RAP、篮球`}
        </Text>
      </InfoCard>

      <ScrollView ref={scrollViewRef}>{getListView()}</ScrollView>
    </View>
  )
}
import React from 'react'
import { View, Text, StyleSheet } from 'react-native'

export default (props) => {
  const { name, age, sex, levelView } = props

  return (
    <View style={styles.root}>
      <Text style={[styles.txt, styles.txtBold]}>{`姓名:${name}`}</Text>

      <Text style={styles.txt}>{`年龄:${age}`}</Text>

      <Text style={[styles.txt, styles.txtBlue]}>{`性别:${sex}`}</Text>

      {levelView}
      {props.children}
    </View>
  )
}

const styles = StyleSheet.create({
  root: {
    width: '100%',
    flexDirection: 'column',
  },
  txt: {
    fontSize: 20,
    color: 'black',
    marginVertical: 10,
  },
  txtBold: {
    fontWeight: 'bold',
  },
  txtBlue: {
    color: 'blue',
  },
})

RN 计数器练习

import React, { useState, useEffect } from 'react'
import { View, StyleSheet, Text } from 'react-native'

function TimerView() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const interval = setInterval(() => {
      // 注意:这里的回调函数,不能使用箭头函数,否则会导致内存泄漏
      // setCount(count + 1) // 错误写法
      setCount((data) => {
        return data + 1
      })
    }, 1000)

    return () => {
      clearInterval(interval)
    }
  }, [])

  return (
    <View style={styles.root}>
      <Text style={styles.titleTxt}>RN计数器</Text>
      <Text style={styles.countTxt}>{count}</Text>
    </View>
  )
}

export default TimerView

const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    backgroundColor: '#353535',
    flexDirection: 'column',
    alignItems: 'center',
  },
  titleTxt: {
    marginTop: 96,
    fontSize: 36,
    fontWeight: 'bold',
    color: 'white',
  },
  countTxt: {
    marginTop: 64,
    fontSize: 96,
    fontWeight: 'bold',
    color: '#1876ff',
  },
})

RN 内置组件

View 组件

import React, { useEffect, useRef, useState } from 'react'
import { View, StyleSheet } from 'react-native'

export default () => {
  const viewRef = useRef(null)

  const [height, setHeight] = useState(100)

  useEffect(() => {
    setTimeout(() => {
      // 设置动态高度
      setHeight(200)
      // 设置背景颜色
      viewRef.current.setNativeProps({
        style: {
          backgroundColor: 'red',
        },
      })
    }, 2000)
  }, [])

  return (
    <View style={styles.root}>
      <View
        ref={viewRef}
        style={[styles.subView, { height }]}
        onLayout={(event) => {
          console.log(event.nativeEvent)
        }}
      />
    </View>
  )
}
// flexDirection 横向纵向布局
// flex 与 flexGrow 的区别
// 尺寸属性传值和百分比
// position: absoulte 绝对定位下,仍然受到父组件属性的影响
// onLayout: 布局信息的回调
// setNativeProps: 直接修改组件属性

const styles = StyleSheet.create({
  root: {
    flexDirection: 'column',
    width: '100%',
    height: '100%',
    backgroundColor: '#C0C0C0',
  },
  subView: {
    width: 200,
    // height: 200,
    backgroundColor: 'red',
  },
})

Text 组件

import React from 'react'
import { StyleSheet, View, Text } from 'react-native'

export default () => {
  return (
    <View style={styles.root}>
      <Text style={styles.txt}>本次期末考试</Text>
    </View>
  )
}

//字体属性: fontSize, fontWeight, color, fontFamily
// 行数以及修饰模式: numberOfLines, ellipsizeMode
// 是否可以选中及选中颜色设置: selectable, selectionColor
// 点击和长按: onPress, onLongPress
// 跟随系统字号: allowFontScaling
// 文字组件可以嵌套
// 文本对齐: textAlign, textAlignVertical
// 文本装饰: textDecorationLine, textDecorationStyle, textDecorationColor
// 文本阴影: textShadowColor, textShadowOffset, textShadowRadius
const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    backgroundColor: '#F0F0F0',
  },
  txt: {
    fontSize: 40,
    color: '#3025ff',
    textShadowColor: '#A0A0A0',
    textShadowOffset: { width: 2, height: 4 },
    textShadowRadius: 6,
  },
})

Image 组件

import React, { useEffect, useRef } from 'react'
import { StyleSheet, View, Image } from 'react-native'

// 本地图片
import avatar from '../assets/images/avatar.png'
// 网络图片
import { imageUri } from '../constants/Uri'

import icon_setting from '../assets/images/icon_setting.png'

export default () => {
  const imgRef = useRef(null)

  useEffect(() => {
    // 获取图片尺寸
    Image.getSize(
      'xxx.xx.jpg',
      (width, height) => {
        console.log(`width=${width}, height=${height}`)
      },
      (error) => {
        console.log(error)
      }
    )

    // 图片预加载
    Image.prefetch(imageUri)
      .then((data) => {
        console.log(data)
      })
      .catch((e) => {
        console.log(e)
      })
  }, [])

  return (
    <View style={styles.root}>
      <Image
        ref={imgRef}
        style={styles.img}
        source={icon_setting}
        defaultSource={avatar}
      />
    </View>
  )
}

//  resizeMode: cover, contain, stretch, repeat, center
// blurRadius: 模糊程度
// defaultSource: 默认图片
// fadeDuration: 淡入淡出时间
// onLoad: 加载成功回调
// onLoadStart: 开始加载回调
// onError: 加载失败回调
// onLoadEnd: 加载结束回调
// tintColor: 颜色渲染(针对图标)

// Image.getSize: 获取图片尺寸
// Image.prefetch: 图片预加载

const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    backgroundColor: '#F5F5F5',
  },
  img: {
    width: 240,
    height: 240,
    tintColor: 'blue',
  },
})

ImageBackground 组件

  • View 组件 与 Image 组件 组合
import React, { useEffect, useRef, useState } from 'react'
import { StyleSheet, View, Image, Text, ImageBackground } from 'react-native'

import bg_card from '../assets/images/bg_card.png'
import icon_bank from '../assets/images/icon_bank.png'

export default () => {
  return (
    <View style={styles.root}>
      <ImageBackground
        style={styles.viewStyle}
        imageStyle={styles.imgStyle}
        source={bg_card}
      >
        <Image style={styles.icon_logo} source={icon_bank} />
        <Text style={styles.txtBank}>
          {`招商银行\n`}
          <Text style={styles.cardTypeTxt}>{`储蓄卡\n\n`}</Text>
          <Text style={styles.cardNoTxt}>●●●● ●●●● ●●●● 3068</Text>
        </Text>
      </ImageBackground>
    </View>
  )
}

const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    flexDirection: 'column',
  },
  viewStyle: {
    width: '100%',
    height: 150,
    flexDirection: 'row',
    alignItems: 'flex-start',
  },
  imgStyle: {
    resizeMode: 'cover',
    borderRadius: 12,
  },
  icon_logo: {
    width: 48,
    height: 48,
    borderRadius: 24,
    marginLeft: 20,
    marginTop: 20,
  },
  txtBank: {
    fontSize: 24,
    color: 'white',
    marginLeft: 10,
    marginTop: 21,
    fontWeight: 'bold',
  },
  cardTypeTxt: {
    fontSize: 20,
    color: '#FFFFFFA0',
    fontWeight: 'normal',
  },
  cardNoTxt: {
    fontSize: 26,
    color: 'white',
  },
})

TextInput 组件

import React, { useRef, useEffect } from 'react'
import { SafeAreaView, StyleSheet, View, TextInput } from 'react-native'

export default () => {
  const inputRef = useRef(null)

  useEffect(() => {
    setTimeout(() => {
      // inputRef.current.blur();
    }, 2000)
  }, [])

  return (
    <View style={styles.root}>
      <TextInput
        ref={inputRef}
        style={styles.input}
        // autoFocus={true}
        // 点击 return 键是否收起键盘
        blurOnSubmit={true}
        // 光标隐藏
        caretHidden={false}
        // defaultValue="默认内容"
        editable={true}
        keyboardType='number-pad'
        returnKeyType='search'
        // maxLength={11}
        // multiline={true}
        // numberOfLines={2}
        onFocus={() => {}}
        onBlur={() => {}}
        onChange={(event) => {
          console.log(event.nativeEvent)
        }}
        onChangeText={(text) => {
          console.log(text)
        }}
        // selection={{start: 0, end: 3}}
        selectionColor='red'
        selectTextOnFocus={true}
        secureTextEntry={true}
      />
    </View>
  )
}

// keyboardType: default, number-pad, decimal-pad, numeric, email-address, phone-pad
// returnKeyType: done, go, next, search, send
//  注意:secureTextEntry 与 multiline 不能同时使用
const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    backgroundColor: '#F0F0F0',
  },
  input: {
    width: '100%',
    height: 56,
    backgroundColor: '#D0D0D0',
    fontSize: 24,
    color: '#333333',
    fontWeight: 'bold',
  },
})

TouchableOpacity 组件

import React from 'react'
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native'

export default () => {
  return (
    <View style={styles.root}>
      <TouchableOpacity
        style={styles.button}
        activeOpacity={0.5} // x ~ 1不透明度变化范围
        onPress={() => {
          console.log('onPress ...')
        }}
        onLongPress={() => {
          console.log('onLongPress ...')
        }}
        delayLongPress={1000}
        onPressIn={() => {
          console.log('onPressIn ...')
        }}
        onPressOut={() => {
          console.log('onPressOut ...')
        }}
      >
        <Text style={styles.txt}>按钮</Text>
      </TouchableOpacity>
    </View>
  )
}

const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    backgroundColor: '#F0F0F0',
  },
  button: {
    width: 300,
    height: 65,
    backgroundColor: '#2030FF',
    justifyContent: 'center',
    alignItems: 'center',
  },
  txt: {
    fontSize: 20,
    color: 'white',
    fontWeight: 'bold',
  },
})

TouchableHighlight 组件

import React from 'react'
import {
  StyleSheet,
  View,
  Text,
  TouchableHighlight,
  TouchableWithoutFeedback,
} from 'react-native'

export default () => {
  return (
    <View style={styles.root}>
      <TouchableHighlight
        style={styles.button}
        activeOpacity={0.8}
        onPress={() => {
          console.log('onPress ...')
        }}
        underlayColor='#00bcd4'
      >
        <Text style={styles.txt}>按钮</Text>
      </TouchableHighlight>

      <TouchableWithoutFeedback>
        <View style={styles.button2}>
          <Text style={styles.txt}>按钮</Text>
        </View>
      </TouchableWithoutFeedback>
    </View>
  )
}
//TouchableHighlight 与 onPress 配合使用 只能一个子节点
// TouchableWithoutFeedback 几乎不用 只能一个子节点 ,用在一些 暗文本上
const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    backgroundColor: '#F0F0F0',
  },
  button: {
    width: 300,
    height: 65,
    backgroundColor: '#2030FF',
    justifyContent: 'center',
    alignItems: 'center',
  },
  button2: {
    width: 300,
    height: 65,
    backgroundColor: 'red',
    justifyContent: 'center',
    alignItems: 'center',
  },
  txt: {
    fontSize: 20,
    color: 'white',
    fontWeight: 'bold',
  },
})

Button 组件

import React from 'react'
import { StyleSheet, View, Button } from 'react-native'

export default () => {
  const onPress = () => {}

  return (
    <View style={styles.root}>
      <Button title='按 钮' color={'green'} onPress={onPress} />
    </View>
  )
}

// disabled={true} 禁用

const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F0F0F0',
  },
})

Pressable 组件

import React from 'react'
import { StyleSheet, View, Text, Pressable } from 'react-native'

export default () => {
  return (
    <View style={styles.root}>
      <Pressable
        style={(state) => {
          return [styles.button, state.pressed && styles.buttonPressed]
        }}
      >
        {(state) => (
          <Text style={state.pressed ? styles.txtPressed : styles.txt}>
            按 钮
          </Text>
        )}
      </Pressable>
    </View>
  )
}

// 状态变化

const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    backgroundColor: '#A0A0A0',
    padding: 32,
  },
  button: {
    width: 300,
    height: 65,
    backgroundColor: '#2030FF',
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttonPressed: {
    backgroundColor: 'white',
  },
  txt: {
    fontSize: 20,
    color: 'white',
    fontWeight: 'bold',
  },
  txtPressed: {
    fontSize: 20,
    color: '#2030FF',
    fontWeight: 'bold',
  },
})

ScrollView 组件

import React, { useRef } from 'react'
import {
  StyleSheet,
  ScrollView,
  View,
  Text,
  TextInput,
  Button,
  Dimensions,
} from 'react-native'

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

export default () => {
  const scrollViewRef = useRef(null)

  const buildListView = () => {
    const array = []
    for (let i = 0; i < 20; i++) {
      array.push(
        <Text key={`item-${i}`} style={styles.txt}>{`List item ${i + 1}`}</Text>
      )
    }
    return array
  }

  return (
    <ScrollView
      ref={scrollViewRef}
      style={styles.root}
      // 内容容器样式
      contentContainerStyle={styles.containerStyle}
      // 滚动 键是否收起键盘
      keyboardDismissMode='on-drag'
      // 滚动键是否响应触摸事件
      keyboardShouldPersistTaps='handled'
      onMomentumScrollBegin={() => {
        // console.log('onMomentumScrollBegin ...')
      }}
      onMomentumScrollEnd={() => {
        // console.log('onMomentumScrollEnd ...')
      }}
      onScroll={(event) => {
        console.log(event.nativeEvent.contentOffset.y)
      }}
      // iOS 滚动 阈值 提高性能
      scrollEventThrottle={16}
      overScrollMode='never'
      scrollEnabled={true}
      contentOffset={{ y: 100 }}
      showsVerticalScrollIndicator={false}
      stickyHeaderIndices={[1]}
    >
      <TextInput style={styles.input} />
      <Button
        title='按钮'
        onPress={() => {
          // scrollViewRef.current.scrollTo({y: 500, animated: true});
          scrollViewRef.current.scrollToEnd({ animated: true })
        }}
      />
      {buildListView()}
    </ScrollView>
    //  固定数据
    // <ScrollView style={{ width: '100%', height: 200 }} horizontal={true} pagingEnabled={true}>
    //     <View style={{ width, height: 200, backgroundColor: 'red' }} />
    //     <View style={{ width, height: 200, backgroundColor: 'blue' }} />
    //     <View style={{ width, height: 200, backgroundColor: 'green' }} />
    // </ScrollView>
  )
}
// keyboardDismissMode: 拖拽滚动时是否要隐藏软键盘
// keyboardDismissMode: 'none' | 'on-drag' | 'interactive' | undefined;
// keyboardShouldPersistTaps: 滚动键是否响应触摸事件
// keyboardShouldPersistTaps: 'always' | 'never' | 'handled' | false | true | undefined;
// overScrollMode: 滚动到边界时是否显示滚动效果
// overScrollMode: 'auto' | 'always' | 'never' | undefined;
//  pagingEnabled: 是否分页滚动
// horizontal: 是否水平滚动
// contentOffset: 内容偏移量
// showsVerticalScrollIndicator: 是否显示垂直滚动条
//  stickyHeaderIndices: 粘性头部索引
const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    backgroundColor: 'white',
  },
  txt: {
    width: '100%',
    height: 56,
    textAlignVertical: 'center',
    fontSize: 24,
    color: 'black',
  },
  containerStyle: {
    paddingHorizontal: 16,
    backgroundColor: '#E0E0E0',
    paddingTop: 20,
  },
  input: {
    width: '100%',
    height: 60,
    backgroundColor: '#ff000050',
  },
})

FlatList 组件

import React, { useRef, useEffect } from 'react'
import { StyleSheet, View, FlatList, Text } from 'react-native'

const data = [
  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
]

export default () => {
  const flatListRef = useRef(null)

  useEffect(() => {
    setTimeout(() => {
      // flatListRef.current.scrollToIndex({
      //     index: 10,
      //     viewPosition: 0.5,
      //     animated: true,
      // });

      // flatListRef.current.scrollToItem({
      //     item: 5,
      //     viewPosition: 0,
      //     animated: true,
      // });

      // flatListRef.current.scrollToOffset({
      //     offset: 200,
      // });

      flatListRef.current.scrollToEnd({
        animated: true,
      })
    }, 2000)
  }, [])

  const renderItem = ({ item, index }) => {
    return <Text style={styles.txt}>{`List item ${item}`}</Text>
  }

  const renderItem2 = ({ item, index }) => {
    return <Text style={styles.txt2}>{`List item ${item}`}</Text>
  }

  // 列表头部
  const ListHeader = (
    <View style={styles.header}>
      <Text style={styles.extraTxt}>列表头部</Text>
    </View>
  )

  // 列表尾部
  const ListFooter = (
    <View style={[styles.header, styles.footer]}>
      <Text style={styles.extraTxt}>列表尾部</Text>
    </View>
  )

  // 列表空布局
  const ListEmpty = (
    <View style={styles.listEmpty}>
      <Text style={styles.extraTxt}>暂时无数据哦~</Text>
    </View>
  )

  return (
    <FlatList
      ref={flatListRef}
      style={styles.flatlist}
      data={data}
      renderItem={renderItem}
      keyExtractor={(_, index) => `item-${index}`}
      contentContainerStyle={styles.containerStyle}
      showsVerticalScrollIndicator={false}
      onScroll={(event) => {
        console.log(event.nativeEvent.contentOffset.y)
      }}
      keyboardDismissMode='on-drag'
      keyboardShouldPersistTaps='handled'
      ListHeaderComponent={ListHeader}
      ListFooterComponent={ListFooter}
      ListEmptyComponent={ListEmpty}
      ItemSeparatorComponent={<View style={styles.separator} />}
      initialNumToRender={15}
      inverted={false}
      // numColumns={1}
      // onViewableItemsChanged={(info) => {
      //     const { viewableItems } = info;
      //     console.log(viewableItems);
      // }}
    />

    // <FlatList
    //     style={styles.flatlist}
    //     data={data}
    //     renderItem={renderItem2}
    //     keyExtractor={(_, index) => `item-${index}`}
    //     horizontal={true}
    //     showsHorizontalScrollIndicator={false}
    // />
  )
}

// keyExtractor: key 值

const styles = StyleSheet.create({
  flatlist: {
    width: '100%',
    height: '100%',
  },
  txt: {
    width: '100%',
    height: 56,
    fontSize: 24,
    color: 'black',
    textAlignVertical: 'center',
  },
  txt2: {
    width: 200,
    height: 200,
    fontSize: 24,
    color: 'black',
    textAlign: 'center',
    textAlignVertical: 'center',
  },
  containerStyle: {
    paddingHorizontal: 16,
    paddingTop: 20,
    backgroundColor: '#F5F5F5',
  },
  header: {
    width: '100%',
    height: 48,
    backgroundColor: '#00ff0030',
    justifyContent: 'center',
    alignItems: 'center',
  },
  footer: {
    backgroundColor: '#ff000030',
  },
  listEmpty: {
    width: '100%',
    height: 400,
    justifyContent: 'center',
    alignItems: 'center',
  },
  extraTxt: {
    fontSize: 20,
    color: '#666666',
    textAlignVertical: 'center',
  },
  separator: {
    width: '100%',
    height: 2,
    backgroundColor: '#D0D0D0',
  },
})

SectionList, StatusBar 组件

import React, { useRef, useEffect, useSatte, useState } from 'react'
import {
  StyleSheet,
  View,
  Text,
  SectionList,
  RefreshControl,
  StatusBar,
} from 'react-native'

import { SectionData } from '../constants/Data'

export default () => {
  const sectionListRef = useRef(null)

  const [refreshing, setRefreshing] = useState(false)

  useEffect(() => {
    setTimeout(() => {
      // sectionListRef.current.scrollToLocation({
      //     sectionIndex: 1,
      //     itemIndex: 4,
      //     viewPosition: 0,
      //     animated: true,
      // });
    }, 2000)
  }, [])

  const renderItem = ({ item, index, section }) => {
    return <Text style={styles.txt}>{item}</Text>
  }

  const ListHeader = (
    <View style={styles.header}>
      <Text style={styles.extraTxt}>列表头部</Text>
    </View>
  )

  const ListFooter = (
    <View style={[styles.header, styles.footer]}>
      <Text style={styles.extraTxt}>列表尾部</Text>
    </View>
  )

  const renderSectionHeader = ({ section }) => {
    return <Text style={styles.sectionHeaderTxt}>{section.type}</Text>
  }

  return (
    <View style={styles.root}>
      <StatusBar
        barStyle='light-content'
        backgroundColor='transparent'
        animated={true}
        translucent={true}
        hidden={false}
      />
      <SectionList
        ref={sectionListRef}
        style={styles.sectionList}
        sections={SectionData}
        renderItem={renderItem}
        keyExtractor={(item, index) => `${item}-${index}`}
        contentContainerStyle={styles.containerStyle}
        showsVerticalScrollIndicator={false}
        onScroll={(event) => {
          // console.log(event.nativeEvent.contentOffset.y);
        }}
        keyboardDismissMode='on-drag'
        keyboardShouldPersistTaps='handled'
        ListHeaderComponent={ListHeader}
        ListFooterComponent={ListFooter}
        // renderSectionHeader: 渲染每个 section 的头部
        renderSectionHeader={renderSectionHeader}
        ItemSeparatorComponent={() => <View style={styles.separator} />}
        // 头部吸顶
        stickySectionHeadersEnabled={true}
        // 下拉刷新
        refreshControl={
          <RefreshControl
            refreshing={refreshing}
            onRefresh={() => {
              console.log('onRefresh ...')
              setRefreshing(true)
              // do request new data
              setTimeout(() => {
                setRefreshing(false)
              }, 1000)
            }}
          />
        }
        // 上拉加载更多
        onEndReached={() => {
          console.log('onEndReached ...')
          // do request next page data
        }}
        // onEndReachedThreshold: 距离底部多少距离触发 onEndReached
        onEndReachedThreshold={0.2}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
  },
  sectionList: {
    width: '100%',
    height: '100%',
  },
  txt: {
    width: '100%',
    height: 56,
    fontSize: 20,
    color: '#333333',
    textAlignVertical: 'center',
    paddingLeft: 16,
  },
  containerStyle: {
    // paddingHorizontal: 16,
    // paddingTop: 20,
    backgroundColor: '#F5F5F5',
  },
  header: {
    width: '100%',
    height: 48,
    backgroundColor: '#0000ff80',
    justifyContent: 'center',
    alignItems: 'center',
  },
  footer: {
    backgroundColor: '#ff000030',
  },
  extraTxt: {
    fontSize: 20,
    color: '#666666',
    textAlignVertical: 'center',
  },
  sectionHeaderTxt: {
    width: '100%',
    height: 36,
    backgroundColor: '#DDDDDD',
    textAlignVertical: 'center',
    paddingLeft: 16,
    fontSize: 20,
    color: '#333333',
    fontWeight: 'bold',
  },
  separator: {
    width: '100%',
    height: 2,
    backgroundColor: '#D0D0D0',
  },
})
import React, { useState } from 'react'
import {
  StyleSheet,
  View,
  Modal,
  Text,
  Button,
  SectionList,
  TouchableOpacity,
  Image,
} from 'react-native'
import icon_close_modal from '../assets/images/icon_close_modal.png'

import { SectionData } from '../constants/Data'

export default () => {
  const [visible, setVisible] = useState(false)

  const showModal = () => {
    setVisible(true)
  }

  const hideModal = () => {
    setVisible(false)
  }

  const renderItem = ({ item, index, section }) => {
    return <Text style={styles.txt}>{item}</Text>
  }

  const ListHeader = (
    <View style={styles.header}>
      <Text style={styles.extraTxt}>列表头部</Text>
      <TouchableOpacity style={styles.closeButton} onPress={() => hideModal()}>
        <Image style={styles.closeImg} source={icon_close_modal} />
      </TouchableOpacity>
    </View>
  )

  const ListFooter = (
    <View style={[styles.header, styles.footer]}>
      <Text style={styles.extraTxt}>列表尾部</Text>
    </View>
  )

  const renderSectionHeader = ({ section }) => {
    return <Text style={styles.sectionHeaderTxt}>{section.type}</Text>
  }

  return (
    <View style={styles.root}>
      <Button title='按钮' onPress={() => showModal()} />

      <Modal
        visible={visible}
        onRequestClose={() => hideModal()}
        transparent={true}
        statusBarTranslucent={true}
        animationType='slide'
        onShow={() => console.log('onShow ...')}
        onDismiss={() => console.log('onDismiss ...')}
      >
        <View style={styles.blank} />
        <View style={styles.content}>
          <SectionList
            style={styles.sectionList}
            sections={SectionData}
            renderItem={renderItem}
            keyExtractor={(item, index) => `${item}-${index}`}
            contentContainerStyle={styles.containerStyle}
            showsVerticalScrollIndicator={false}
            ListHeaderComponent={ListHeader}
            ListFooterComponent={ListFooter}
            renderSectionHeader={renderSectionHeader}
            ItemSeparatorComponent={() => <View style={styles.separator} />}
            stickySectionHeadersEnabled={true}
          />
        </View>
      </Modal>
    </View>
  )
}
// animationType: slide, fade, none
const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    paddingHorizontal: 16,
  },
  blank: {
    width: '100%',
    height: '10%',
    backgroundColor: '#00000050',
  },
  content: {
    width: '100%',
    height: '90%',
    backgroundColor: '#ff000030',
  },
  sectionList: {
    width: '100%',
    height: '100%',
  },
  txt: {
    width: '100%',
    height: 56,
    fontSize: 20,
    color: '#333333',
    textAlignVertical: 'center',
    paddingLeft: 16,
  },
  containerStyle: {
    // paddingHorizontal: 16,
    // paddingTop: 20,
    backgroundColor: '#F5F5F5',
  },
  header: {
    width: '100%',
    height: 48,
    backgroundColor: 'white',
    justifyContent: 'center',
    alignItems: 'center',
  },
  footer: {
    backgroundColor: '#ff000030',
  },
  extraTxt: {
    fontSize: 20,
    color: '#666666',
    textAlignVertical: 'center',
  },
  sectionHeaderTxt: {
    width: '100%',
    height: 36,
    backgroundColor: '#DDDDDD',
    textAlignVertical: 'center',
    paddingLeft: 16,
    fontSize: 20,
    color: '#333333',
    fontWeight: 'bold',
  },
  separator: {
    width: '100%',
    height: 2,
    backgroundColor: '#D0D0D0',
  },
  closeButton: {
    width: 24,
    height: 24,
    position: 'absolute',
    right: 16,
  },
  closeImg: {
    width: 24,
    height: 24,
  },
})

Switch 组件

import React, { useState } from 'react'
import { StyleSheet, View, Switch } from 'react-native'

export default () => {
  const [switchValue, setSwitchValue] = useState(true)

  return (
    <View style={styles.root}>
      <Switch
        value={switchValue}
        onValueChange={(value) => {
          setSwitchValue(value)
        }}
        disabled={false}
        trackColor={{ true: 'red', false: '#808080' }}
        thumbColor={switchValue ? '#2030ff' : '#303030'}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
  },
})

个人信息页面 练习

import React, { useState } from 'react'
import {
  StyleSheet,
  View,
  ImageBackground,
  Image,
  Text,
  TouchableOpacity,
  SectionList,
  Modal,
  StatusBar,
} from 'react-native'

import icon_bg from '../assets/images/icon_bg.png'
import icon_menu from '../assets/images/icon_menu.png'
import icon_share from '../assets/images/icon_share.png'
import avatar from '../assets/images/default_avatar.png'
import icon_add from '../assets/images/icon_add.png'
import icon_code from '../assets/images/icon_code.png'
import icon_male from '../assets/images/icon_male.png'
import icon_setting from '../assets/images/icon_setting.png'

import icon_1 from '../assets/images/icon_1.png'
import icon_2 from '../assets/images/icon_2.png'
import icon_3 from '../assets/images/icon_3.png'
import icon_close_modal from '../assets/images/icon_close_modal.png'

import { SectionData } from '../constants/Data'

export default () => {
  const [tabIndex, setTabIndex] = useState(0)
  const [visible, setVisible] = useState(false)

  const getContent = () => {
    const contentStyles = StyleSheet.create({
      icon: {
        width: 96,
        height: 96,
        resizeMode: 'contain',
      },
      desc: {
        fontSize: 16,
        marginTop: 16,
      },
      button: {
        width: 76,
        height: 28,
        borderRadius: 14,
        borderWidth: 1,
        borderColor: '#C0C0C0',
        textAlign: 'center',
        textAlignVertical: 'center',
        marginTop: 12,
        color: '#333333',
      },
    })
    const array = []
    array[0] = (
      <>
        <Image style={contentStyles.icon} source={icon_1} />
        <Text style={contentStyles.desc}>用一句话,分享今天的快乐吧~</Text>
        <Text style={contentStyles.button}>去分享</Text>
      </>
    )
    array[1] = (
      <>
        <Image style={contentStyles.icon} source={icon_2} />
        <Text style={contentStyles.desc}>快去收藏你喜欢的作品吧~</Text>
        <Text style={contentStyles.button}>去收藏</Text>
      </>
    )
    array[2] = (
      <>
        <Image style={contentStyles.icon} source={icon_3} />
        <Text style={contentStyles.desc}>你还没有给作品点赞哦~</Text>
        <Text style={contentStyles.button}>去点赞</Text>
      </>
    )
    return array
  }

  // 弹窗
  const renderModal = () => {
    const modalStyles = StyleSheet.create({
      root: {
        width: '100%',
        height: '100%',
        backgroundColor: 'transparent',
        flexDirection: 'column',
      },
      content: {
        width: '100%',
        height: '90%',
        backgroundColor: 'white',
      },
      nameTxt: {
        width: '100%',
        height: 46,
        textAlignVertical: 'center',
        paddingLeft: 16,
        fontSize: 16,
        color: '#333333',
      },
      typeTxt: {
        width: '100%',
        height: 36,
        backgroundColor: '#E0E0E0',
        textAlignVertical: 'center',
        paddingLeft: 16,
        fontSize: 16,
        color: '#666666',
      },
      listHeader: {
        width: '100%',
        flexDirection: 'column',
        paddingTop: 96,
      },
      titleLayout: {
        width: '100%',
        height: 46,
        backgroundColor: 'white',
        borderTopLeftRadius: 12,
        borderTopRightRadius: 12,
        justifyContent: 'center',
        alignItems: 'center',
      },
      titleTxt: {
        fontSize: 18,
        color: '#333333',
        fontWeight: 'bold',
      },
      closeButton: {
        position: 'absolute',
        right: 16,
      },
      closeImg: {
        width: 24,
        height: 24,
      },
    })
    return (
      <Modal
        visible={visible}
        onRequestClose={() => setVisible(false)}
        transparent={true}
        animationType='slide'
        statusBarTranslucent={true}
      >
        <View style={modalStyles.root}>
          <View style={modalStyles.listHeader}>
            <View style={modalStyles.titleLayout}>
              <Text style={modalStyles.titleTxt}>粉丝列表</Text>
              <TouchableOpacity
                style={modalStyles.closeButton}
                onPress={() => setVisible(false)}
              >
                <Image style={modalStyles.closeImg} source={icon_close_modal} />
              </TouchableOpacity>
            </View>
          </View>
          <View style={modalStyles.content}>
            <SectionList
              sections={SectionData}
              renderItem={({ item }) => (
                <Text style={modalStyles.nameTxt}>{item}</Text>
              )}
              keyExtractor={(item, index) => `${item}-${index}`}
              renderSectionHeader={({ section }) => (
                <Text style={modalStyles.typeTxt}>{section.type}</Text>
              )}
            />
          </View>
        </View>
      </Modal>
    )
  }

  // 顶部内容
  const renderDashboard = () => {
    return (
      <ImageBackground
        style={styles.imgBg}
        source={icon_bg}
        imageStyle={styles.bgImg}
      >
        <View style={styles.titleBar}>
          <Image style={styles.iconMenu} source={icon_menu} />
          <Image style={styles.iconShare} source={icon_share} />
        </View>
        <View style={styles.infoLayout}>
          <View style={styles.avatarLayout}>
            <Image style={styles.avatarImg} source={avatar} />
            <Image style={styles.iconAdd} source={icon_add} />
            <View style={styles.nameLayout}>
              <Text style={styles.nameTxt}>大公爵</Text>
              <View style={styles.idLayout}>
                <Text style={styles.idTxt}>小红书号:118302851</Text>
                <Image style={styles.iconCode} source={icon_code} />
              </View>
            </View>
          </View>
        </View>

        <Text style={styles.descTxt}>点击关注,填写简介</Text>

        <View style={styles.sexView}>
          <Image style={styles.sexImg} source={icon_male} />
        </View>

        <View style={styles.countLayout}>
          <TouchableOpacity
            style={styles.itemLayout}
            onPress={() => setVisible(true)}
          >
            <Text style={styles.itemCount}>142</Text>
            <Text style={styles.itemLabel}>关注</Text>
          </TouchableOpacity>
          <View style={styles.itemLayout}>
            <Text style={styles.itemCount}>2098</Text>
            <Text style={styles.itemLabel}>粉丝</Text>
          </View>
          <View style={styles.itemLayout}>
            <Text style={styles.itemCount}>1042</Text>
            <Text style={styles.itemLabel}>获赞与收藏</Text>
          </View>

          <View style={{ flex: 1 }} />

          <TouchableOpacity style={styles.editButton} activeOpacity={0.5}>
            <Text style={styles.editTxt}>编辑资料</Text>
          </TouchableOpacity>

          <TouchableOpacity style={styles.settingButton} activeOpacity={0.5}>
            <Image style={styles.iconSetting} source={icon_setting} />
          </TouchableOpacity>
        </View>
      </ImageBackground>
    )
  }

  // 顶部tab
  const renderTabs = () => {
    return (
      <>
        <View style={styles.tabsLayout}>
          <TouchableOpacity style={styles.tab} onPress={() => setTabIndex(0)}>
            <Text
              style={tabIndex === 0 ? styles.tabTxtSelected : styles.tabTxt}
            >
              笔记
            </Text>
            <View style={[styles.tabLine, tabIndex !== 0 && styles.hide]} />
          </TouchableOpacity>

          <TouchableOpacity style={styles.tab} onPress={() => setTabIndex(1)}>
            <Text
              style={tabIndex === 1 ? styles.tabTxtSelected : styles.tabTxt}
            >
              收藏
            </Text>
            <View style={[styles.tabLine, , tabIndex !== 1 && styles.hide]} />
          </TouchableOpacity>

          <TouchableOpacity style={styles.tab} onPress={() => setTabIndex(2)}>
            <Text
              style={tabIndex === 2 ? styles.tabTxtSelected : styles.tabTxt}
            >
              赞过
            </Text>
            <View style={[styles.tabLine, , tabIndex !== 2 && styles.hide]} />
          </TouchableOpacity>
        </View>

        <View style={styles.contentLayout}>{getContent()[tabIndex]}</View>
      </>
    )
  }

  return (
    <View style={styles.root}>
      <StatusBar
        barStyle='light-content'
        translucent={true}
        backgroundColor='transparent'
      />
      {renderDashboard()}
      {renderTabs()}
      {renderModal()}
    </View>
  )
}

const styles = StyleSheet.create({
  root: {
    width: '100%',
    height: '100%',
    backgroundColor: '#F5F5F5',
  },
  imgBg: {
    width: '100%',
    paddingTop: 20,
  },
  bgImg: {
    resizeMode: 'stretch',
  },
  titleBar: {
    width: '100%',
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 8,
    marginTop: 16,
  },
  iconMenu: {
    width: 25,
    height: 25,
    resizeMode: 'contain',
    marginHorizontal: 16,
  },
  iconShare: {
    width: 25,
    height: 25,
    resizeMode: 'contain',
    position: 'absolute',
    right: 16,
  },
  infoLayout: {
    flexDirection: 'column',
    padding: 16,
  },
  avatarLayout: {
    width: '100%',
    flexDirection: 'row',
    alignItems: 'flex-end',
  },
  avatarImg: {
    width: 86,
    height: 86,
    borderRadius: 48,
    backgroundColor: 'white',
  },
  iconAdd: {
    width: 24,
    height: 24,
    marginLeft: -20,
    marginBottom: 2,
  },
  nameLayout: {
    flexDirection: 'column',
    marginBottom: 16,
    marginLeft: 8,
  },
  nameTxt: {
    fontSize: 22,
    color: 'white',
    fontWeight: 'bold',
  },
  idLayout: {
    flexDirection: 'row',
    alignItems: 'center',
    marginTop: 12,
  },
  idTxt: {
    fontSize: 14,
    color: 'white',
  },
  iconCode: {
    width: 12,
    height: 12,
    marginLeft: 4,
    tintColor: 'white',
  },
  descTxt: {
    fontSize: 16,
    color: 'white',
    marginLeft: 16,
    marginBottom: 8,
  },
  sexView: {
    marginTop: 6,
    width: 24,
    height: 18,
    backgroundColor: '#ffffff60',
    borderRadius: 9,
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 16,
    marginLeft: 16,
  },
  sexImg: {
    width: 12,
    height: 12,
    resizeMode: 'contain',
    tintColor: '#1876ff',
  },
  countLayout: {
    width: '100%',
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 16,
    marginBottom: 28,
  },
  itemLayout: {
    flexDirection: 'column',
    alignItems: 'center',
    marginRight: 16,
  },
  itemCount: {
    fontSize: 16,
    color: 'white',
  },
  itemLabel: {
    fontSize: 14,
    color: '#ffffffc0',
    marginTop: 3,
  },
  editButton: {
    width: 80,
    height: 32,
    borderRadius: 16,
    borderColor: 'white',
    borderWidth: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  editTxt: {
    fontSize: 14,
    color: '#ffffffE0',
  },
  settingButton: {
    width: 46,
    height: 32,
    borderRadius: 16,
    marginLeft: 12,
    borderColor: 'white',
    borderWidth: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  iconSetting: {
    width: 20,
    height: 20,
    resizeMode: 'contain',
    tintColor: 'white',
  },
  tabsLayout: {
    width: '100%',
    height: 46,
    flexDirection: 'row',
    backgroundColor: 'white',
    borderTopLeftRadius: 12,
    borderTopRightRadius: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#E0E0E0',
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: -12,
    paddingTop: 4,
  },
  tab: {
    flexDirection: 'column',
    width: 64,
    height: '100%',
    justifyContent: 'center',
    alignItems: 'center',
  },
  tabTxt: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#909090',
  },
  tabTxtSelected: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#333333',
  },
  tabLine: {
    width: 28,
    height: 2,
    backgroundColor: '#f05856',
    marginTop: 4,
  },
  hide: {
    backgroundColor: 'transparent',
  },
  contentLayout: {
    width: '100%',
    flexGrow: 1,
    backgroundColor: '#FAFAFA',
    flexDirection: 'column',
    alignItems: 'center',
    paddingTop: 64,
  },
})

参考资料