08-首页

作者: Right01 | 来源:发表于2021-08-25 15:31 被阅读0次

    学习内容

    • 类目Icon功能
    • 底部猜你喜欢
    • 上拉加载更多
    • 下拉刷新
    • 尾部视图处理
    • 自定义顶部标签导航栏

    根据网络数据结构,定义数据模型

    //header icon 模型
    export interface HeaderItemIconModel 
    {
        title: string,
        coverPath: string,
        darkCoverPath: string,
        properties: HeaderItemIconUrlModel,
    }
    //header Icon 模型 的 properties 模型
    export interface HeaderItemIconUrlModel
    {
        uri: string,
    }
    

    类目Icon组件封装

    • src/pages/Home/ 创建 icon.tsx
    interface IProps {
        iconList: HeaderItemIconModel[],
    }
    class Icon extends React.Component<IProps> {
        renderItem = ({ item }: { item: HeaderItemIconModel }, parallaxProps?: AdditionalParallaxProps) => {
            return (
                <View style={styles.item}>
                    <Image source={{uri: item.coverPath}} style={styles.itemImage}/>
                    <Text style={styles.itemTitle} numberOfLines={1}>
                        {item.title}
                    </Text>
                </View>
            );
        }
        render() {
            const { iconList } = this.props;
            if (iconList != null && iconList.length > 0) {
                return (
                    <View style={styles.container}>
                        {/* <Text>{JSON.stringify(iconList)}</Text> */}
                        <FlatList
                            data={iconList}
                            renderItem={this.renderItem}
                            numColumns={5}
                        />
                    </View>
                );
            }
            else {
                return null;
            }
        }
    }
    
    const styles = StyleSheet.create({
        container: {
            // backgroundColor: '#fff',
            borderRadius: 8,
            marginTop: 5,
            margin: 16,
        },
        item: {
            flex : 1,
            marginHorizontal: 5,
            marginVertical: 6,
        },
        itemImage: {
            width: '100%',
            height: 70,
            borderRadius: 50,
            marginBottom: 5,
        },
        itemTitle: {
            textAlign: 'center',
            fontSize: 14,
            color: '#333333',
            marginBottom: 5,
        }
    });
    export default Icon;
    
    • src/pages/Home/Home.tsx 调用
    render() {
            ...
            var icons: HeaderItemIconModel[] = [];
            header.forEach(element => {
                ...
                else if (element.item.moduleType == 'square' && element.item.list.length > 0) {
                    //广告Icon
                    icons = element.item.list;
                }
            });
            return (
                <View>
                    ...
                    <Icon iconList={icons}/>
                </View>
            );
    }
    

    ps: 当然样式上实现了,但是点击事件都没有做

    • 封装点击事件组件,在src/components/Touchable.tsx
    import React from "react";
    import { TouchableOpacity, TouchableOpacityProps } from "react-native";
    const Touchable: React.FC<TouchableOpacityProps>= (props) => (
        <TouchableOpacity activeOpacity={0.8} {...props} />
    )
    export default Touchable;
    
    • 重新封装类目Icon的 item
    return (
        <Touchable
            style={styles.item}
            onPress={() => {
                  Alert.alert(item.properties.uri);
            }}
        >
             ...
        </Touchable>
    );
    

    底部猜你喜欢模块

    • 封装猜你喜欢模块
      src/pages/Home/Guess.tsx
    interface IProps {
        data: HomeBodyItemModel;
    }
    
    class GuessItem extends React.Component<IProps> {
        render() {
            const { data } = this.props;
            // console.log("_________:", data.intro);
            return (
                <Touchable
                    style={styles.container}
                    onPress={() => {
                        Alert.alert(data.title);
                    }}
                >
                    <Image source={{ uri: data.coverPath }} style={styles.image} />
                    <View>
                        <Text style={styles.title}>{data.title}</Text>
                        <Text style={styles.subTitle} numberOfLines={1}>{data.intro}</Text>
                        <View style={styles.playContainer}>
                            <IconBofang2 color='#999999' />
                            <Text style={styles.playNum}>{numberChange(data.playsCounts)}</Text>
                        </View>
                    </View>
                    <IconClose color='#999999' style={styles.close} />
                </Touchable>
            );
        }
    }
    
    const styles = StyleSheet.create({
        container: {
            flexDirection: 'row',
            marginLeft: 15,
            marginRight: 15,
            marginBottom: 10,
            // backgroundColor: '#ffffff',
        },
        image: {
            width: 80,
            height: 80,
            borderRadius: 12,
            backgroundColor: '#dedede',
        },
        title: {
            marginTop: 10,
            marginLeft: 10,
            fontSize: 16,
            fontWeight: 'bold',
        },
        subTitle: {
            marginLeft: 10,
            marginTop: 10,
            marginRight: 5,
            width: viewPortWidth - 165,
        },
        playContainer: {
            flexDirection: 'row',
            marginLeft: 6,
            // backgroundColor: 'red',
            marginTop: 10,
        },
        playNum: {
            marginLeft: 2,
            color: '#999999',
        },
        close: {
            marginTop: 10,
        }
    });
    export default GuessItem;
    
    • 调用封装的猜你喜欢模块
      src/pages/Home/Home.tsx
    renderItem = ({ item }: { item: HomeBodyModel }, parallaxProps?: AdditionalParallaxProps) => {
            return (
                <GuessItem data={item.item} />
            );
    }
    

    全屏滚动

    • 组件抽取
      get header() {
            const { header, body } = this.props;
            var banner: HeaerItemBannerModel[] = [];
            var icons: HeaderItemIconModel[] = [];
    
            header.forEach(element => {
    
                if (element.item.moduleType == 'focus' && element.item.list.length > 0) {
                    //banner广告
                    element.item.list.forEach((ele: HeaerItemBanner) => {
                        banner = ele.data;
                    });
                }
                else if (element.item.moduleType == 'square' && element.item.list.length > 0) {
                    //广告Icon
                    icons = element.item.list;
                }
    
            });
    
            return (
                <View>
                    <Banner banner={banner} />
                    <Icon iconList={icons} />
                    {/* <GuessTitle guessData={body} /> */}
                </View>
            );
        }
    
        get footer() {
            return (
                <View>
                    <Text>加载中...</Text>
                </View>
            );
        }
    
        get empty() {
            return (
                <View>
                    <Text style={styles.textStyle}>正在加载中...</Text>
                </View>
            );
        }
    
    
    • 将 猜你喜欢前面的模块整合到FlatList组件的头部
            return (
    
                <FlatList
                    ListHeaderComponent={this.header}
                    ListFooterComponent={this.footer}
                    numColumns={1}
                    ListEmptyComponent={this.empty}
    
                    data={body} renderItem={this.renderItem}
                >
    
                </FlatList>
            );
    
    image.png

    上拉加载更多

    修改src/pages/Home/Home.tsx

    • FlatList设置属性onEndReached
    onEndReached={this.onEndReached}
    onEndReachedThreshold={0.2}  //比例:距离内容底部多远开始掉接口
    
    • 加载更多方法实现
    //加载更多
        onEndReached = () => {
            console.log("加载更多");
            const { dispatch, loading, hasMore } = this.props
            if (loading || !hasMore) return;
            dispatch({
                type: "home/fetchGuess",
                payload: {
                    loadMore: true,
                },
            });
        }
    

    修改src/models/http.ts,添加接口调用

          *fetchGuess({ callback, payload }, { call, put, select }) {
                const home = yield select((state: RootState) => state.home);
    
                let page = 1;
                if (payload && payload.loadMore) {
                    page = home.pagenation.current + 1;
                }
    
                const { data, pagenation } = yield call(axios.get, GUESS_PAGE_URL, {
                    params: {
                        page,
                    }
                });
    
                let newData = data;
                if (payload && payload.loadMore) {
                    newData = home.body.concat(newData);
                }
                console.log("___________数据个数:", newData.length);
                yield put({
                    type: 'setState',
                    payload: {
                        body: newData,
                        pagenation: {
                            current: pagenation.current,
                            total: pagenation.total,
                            hasMore: newData.length < pagenation.total,
                        }
                    }
                });
                if (typeof callback == 'function') {
                    callback();
                }
            },
        },
    

    下拉刷新

    修改src/pages/Home/Home.tsx

    • FlatList设置属性onRefresh 和 refreshing
    
    interface IState {
        refreshing: boolean;
    }
    
    /**
     * 首页类
     */
    class Home extends React.Component<IProps, IState> {
    
        state = {
            refreshing: false,
        };
        ...
        //下拉加载更多
        onRefresh = () => {
            console.log("下拉刷新");
            //1.修改刷新状态为 true
            this.setState({
                refreshing: true,
            });
    
            //2.获取数据
            const { dispatch } = this.props;
            dispatch({
                type: "home/fetchHome",
                //3.修改刷新状态为false
                callback: () => {
                    this.setState({
                        refreshing: false,
                    })
                }
            });
        }
        ...
       
        render() {
            const { body } = this.props;
            const { refreshing } = this.state;
            return (
                <FlatList
                   ...
                    onRefresh={this.onRefresh}
                    refreshing={refreshing}
                    ...
                >
                </FlatList>
            );
        }
    }
    

    底部视图处理

    • 处理加载更多 和 到底了
        get footer() {
            const { body, hasMore, loading } = this.props;
            if (!hasMore) {
                return (
                    <View style={styles.footerView}>
                        <Text style={styles.footerStyle}>--- 我是有底线的 ---</Text>
                    </View>
                );
            }
            if (loading && hasMore && body.length > 0) {
                return (
                    <View style={styles.footerView}>
                        <Text style={styles.footerLoadingStyle}>正在加载中...</Text>
                    </View>
                );
            }
        }
    

    赋值到 FlatList 的 ListFooterComponent

    导航栏顶部自定义标签

    首页沉浸式头部

    • 将头部导航隐藏,修改BottomTabs.tsx

          componentDidMount() {
              this.setOptions();
          }
      
          componentDidUpdate() {
              this.setOptions();
          }
      
          setOptions = () => {
              const { navigation, route } = this.props;
      
              //使用route.state的时候:官方提示用getFocusedRouteNameFromRoute
              const routeName = getFocusedRouteNameFromRoute(route) ?? 'HomeTabs';
      
              if (routeName === 'HomeTabs') {
                  navigation.setOptions({
                      //头部透明
                      headerTransparent: true,
                      headerTitle: '',
                  });
              }
              else {
                  navigation.setOptions({
                      //展示头部样式
                      headerTransparent: false,
                      headerTitle: getHeaderTitle(routeName),
                  });
              }
          }
      

    封装顶部top导航栏

    • 新增pages/views/TopTabBarWrapper.tsx

      import { MaterialTopTabBar, MaterialTopTabBarProps } from "@react-navigation/material-top-tabs";
      import React from "react";
      import { View } from "react-native";
      
      interface IProps extends MaterialTopTabBarProps {
      }
      
      class TopTabBarWrapper extends React.Component<IProps> {
          render() {
              const { props } = this;
              return (
                  <View>
                      <MaterialTopTabBar {...props} />
                  </View>
              );
          }
      }
      
      export default TopTabBarWrapper;
      
    • iPhone X 之后的 全面屏 状态栏高度 计算插件 react-native-iphone-x-helper堆栈式导航器依赖了这个插件。查看node_modules里面是否已经依赖。

      • 如果未依赖,就安装一下

        yarn add react-native-iphone-x-helper
        
      • 如果依赖,直接使用

        //...
        import { getStatusBarHeight } from 'react-native-iphone-x-helper';
        
        class TopTabBarWrapper extends React.Component<IProps> {
            render() {
                //...
                return (
                    <View style={styles.container}>
                       //...
                    </View>
                );
            }
        }
        
        const styles = StyleSheet.create({
            container: {
                back
                paddingTop: getStatusBarHeight(),
            }
        });
        
        export default TopTabBarWrapper;
        

    导航栏布局

    • 背景色修改 6.x 版本导航栏背景色修改放到了screenOptionsMaterialTopTabBar没有更多的属性设置

      //修改导航栏上的背景色
      tabBarStyle: {
        backgroundColor: 'transparent',
      },
      
    • 分类,搜索框(语音,搜索按钮),历史记录

      class TopTabBarWrapper extends React.Component<IProps> {
          render() {
              const { props } = this;
              return (
                  <View style={styles.container}>
                      <View style={styles.topTabBarView}>
                          <View style={styles.tabbar}>
                              <MaterialTopTabBar {...props} />
                          </View>
                          <Touchable style={styles.categoryBtn}>
                              <IconFenlei color='#ff6600' style={styles.categoryImage} />
                          </Touchable>
                      </View>
                      <View style={styles.bottom}>
                          <Touchable style={styles.searchBtn}>
                              <Text style={styles.searchText}>搜索按钮</Text>
                              <Touchable style={styles.yuyinBtn}>
                                  <IconYuyin color='#555555'/>
                              </Touchable>
                              <View style={styles.line}></View>
                              <Touchable style={styles.searchIconBtn}>
                                  <IconSousuo color='#ff5500'/>
                              </Touchable>
                          </Touchable>
                          <Touchable style={styles.historyBtn}>
                              <IconLishi color='#999999' />
                          </Touchable>
                      </View>
                  </View>
              );
          }
      }
      

    导航栏背景渐变

    • 安装插件

      yarn add react-native-linear-gradient
      
      # 需要原生支持
      ./pod.sh
      
    • 渐变色动画组件

      yarn add react-native-linear-animated-gradient-transition
      
    • 添加全局状态属性

      activeBannerIndex: number,  //当前轮播图的下标
      gradientVisible: boolean, // 渐变色组件是否显示的状态
      
    • 改造src/navigator/HomeTabs.tsx拿到 渐变组件的显示状态,来修改导航栏的样式

      import React from "react";
      import { createMaterialTopTabNavigator, MaterialTopTabBarProps } from "@react-navigation/material-top-tabs";
      import Home from "@/pages/Home/Home";
      import TopTabBarWrapper from "@/pages/views/TopTabBarWrapper";
      import { StyleSheet } from "react-native";
      import { RootState } from "../models";
      import { connect, ConnectedProps } from "react-redux";
      
      //声明变量,接收函数返回值
      const Tab = createMaterialTopTabNavigator();
      
      const mapStateToProps = ({home}: RootState) => {
          return {
              gradientVisible: home.gradientVisible,
          };
      };
        
      const connector = connect(mapStateToProps);
        
      type ModelState = ConnectedProps<typeof connector>;
        
      interface IProps extends ModelState {}
      
      class HomeTabs extends React.Component<IProps> {
      
          renderTabBar = (props: MaterialTopTabBarProps) => {
              return (
                  <TopTabBarWrapper {...props} />
              );
          }
      
          render() {
              const { gradientVisible } = this.props;
              return (
                  // {/* 'tabBarOptions' is deprecated. Migrate the options to 'screenOptions' instead. */}
                  <Tab.Navigator
                      tabBar={this.renderTabBar}
                      screenOptions={{
                          //...
                          //tab底部横条样式
                          tabBarIndicatorStyle: {
                                //...
                              backgroundColor: gradientVisible ? '#fff' : '#f86442',
                          },
                          //...
                          tabBarActiveTintColor: gradientVisible ? '#fff' : '#f86442',
                      }}
                  >
                  //...
                  </Tab.Navigator>
              );
          }
      }
      
      const styles = StyleSheet.create({
          sceneContainer: {
              backgroundColor: 'transparent',
          }
      });
      export default connector(HomeTabs);
      
    • 改造 banner广告组件src/pages/Home/Banner.tsx,更新banner轮播的当前索引值

      import { RootState } from "@/models/index";
      import { HeaerItemBannerModel } from "@/models/home";
      import { hp, viewPortWidth, wp } from "@/utils/index";
      import React from "react";
      import { Platform, StyleSheet, View } from "react-native";
      import SnapCarousel, { AdditionalParallaxProps, Pagination, ParallaxImage } from "react-native-snap-carousel";
      import { connect, ConnectedProps } from "react-redux";
      
      //...
      
      const mapStateToProps = ({ home }: RootState) => {
          return {
              activeBannerIndex: home.activeBannerIndex,
          };
      };
      
      const connector = connect(mapStateToProps);
      
      type ModelState = ConnectedProps<typeof connector>;
      
      interface IProps extends ModelState {
          banner: HeaerItemBannerModel[],
      }
      
      
      class Banner extends React.Component<IProps> {
            //更新 banner 轮播的索引值
          onSnapToItem = (index: number) => {
              const { dispatch } = this.props;
              dispatch({
                  type: 'home/setState',
                  payload: {
                      activeBannerIndex: index,
                  },
              });
          }
            //...
          render() {
              //...
          }
      }
      
      // 定义样式
      const styles = StyleSheet.create({
          //...
      })
      
      export default connector(Banner);
      
    • 改造首页/src/pages/Home/Home.tsx, 监听FlatList的 onScroll,来计算出 FlatList滚动的偏移量,来计算是否需要展示渐变图层

      //...
      
      //拿到 models 中 home的 num 值
      const mapStateToProps = ({ home, loading }: RootState) => ({
          header: home.header,
          body: home.body,
          hasMore: home.pagenation.hasMore,
          loading: loading.effects['home/fetchGuess'],
          gradientVisible: home.gradientVisible, //渐变色图层显示状态
      });
      
      //获取到函数
      const connector = connect(mapStateToProps);
      
      type MadelState = ConnectedProps<typeof connector>;
      
      interface IProps extends MadelState {
          navigation: RootStackNavigation;
      }
      
      interface IState {
          refreshing: boolean;
      }
      
      /**
       * 首页类
       */
      class Home extends React.Component<IProps, IState> {
            //...
          onScroll = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
              const offSetY = nativeEvent.contentOffset.y;
              // console.log("___________offSetY:", offSetY);
              // console.log("___________sideWidth:", sideHeight);
              let newGradientVisible = offSetY < sideHeight;
      
              const {dispatch, gradientVisible} = this.props;
              
              if (gradientVisible !== newGradientVisible) {
                  dispatch({
                      type: "home/setState",
                      payload: {
                          gradientVisible: newGradientVisible,
                      },
                  });
              }
          }
            //...
          render() {
              return (
      
                  <FlatList
                      //...
                      onScroll={this.onScroll}
                  >
                  </FlatList>
              );
          }
      }
      
      const styles = StyleSheet.create({
          //...
      });
      
      // 导出首页类
      export default connector(Home);
      
    • 顶部自定义标签组件的优化/src/pages/views/TopTabBarWrapper.tsx,

      • 根据banner轮播的索引值获取渐变色数组,返回LinearAnimatedGradientTransition图层
      • 根据是否展示渐变色图层,修改顶部自定义标签子控件的眼神
      //...
      const mapStateToProps = ({ home }: RootState) => {
          return ({
                //索引值
              activeBannerIndex: home.activeBannerIndex,
                //渐变层是否展示的状态
              gradientVisible: home.gradientVisible,
              header: home.header,
          });
      }
      
      const connector = connect(mapStateToProps);
      
      type ModelState = ConnectedProps<typeof connector>;
      
      interface IProps extends MaterialTopTabBarProps, ModelState {
      }
      
      class TopTabBarWrapper extends React.Component<IProps> {
      
          get linearGradient() {
      
              const { activeBannerIndex, gradientVisible, header } = this.props;
      
              var banner: HeaerItemBannerModel[] = [];
      
              header.forEach(element => {
                  if (element.item.moduleType == 'focus' && element.item.list.length > 0) {
                      //banner广告
                      element.item.list.forEach((ele: HeaerItemBanner) => {
                          banner = ele.data;
                      });
                  }
              });
                    //根据banner轮播的索引值获取渐变色数组,返回LinearAnimatedGradientTransition图层
              var linearColors = banner[activeBannerIndex] ? banner[activeBannerIndex].colors : ['#fff', '#ff5500'];
      
              if (gradientVisible) {
                  // console.log("_______:",activeBannerIndex);
                  // console.log("________:来了",linearColors); 
                  return (
                      <LinearAnimatedGradientTransition
                          colors={linearColors}
                          style={styles.grandient}
                      />
                  );
              }
              return null;
          }
      
          render() {
              const { gradientVisible, ...restProps } = this.props;
              let textStyle = styles.searchText;
              let lineStyle = styles.line;
              let yuyinColor = '#555';
              let historyColor = '#999';
              let searchColor = '#ff5500';
              let categoryColor = searchColor;
                //根据是否展示渐变色图层,修改顶部自定义标签子控件的眼神
              if (gradientVisible) {
                  textStyle = styles.whiteText;
                  yuyinColor = historyColor = searchColor = categoryColor = '#fff';
                  lineStyle = styles.whiteLine;
              }
              return (
                  <View style={styles.container}>
                      {this.linearGradient}
                      <View style={styles.topTabBarView}>
                          <View style={styles.tabbar}>
                              <MaterialTopTabBar {...restProps} />
                          </View>
                          <Touchable style={styles.categoryBtn}>
                              <IconFenlei color={categoryColor} style={styles.categoryImage} />
                          </Touchable>
                      </View>
                      <View style={styles.bottom}>
                          <Touchable style={styles.searchBtn}>
                              <Text style={textStyle}>搜索按钮</Text>
                              <Touchable style={styles.yuyinBtn}>
                                  <IconYuyin color={yuyinColor} />
                              </Touchable>
                              <View style={lineStyle}></View>
                              <Touchable style={styles.searchIconBtn}>
                                  <IconSousuo color={searchColor} />
                              </Touchable>
                          </Touchable>
                          <Touchable style={styles.historyBtn}>
                              <IconLishi color={historyColor} />
                          </Touchable>
                      </View>
                  </View>
              );
          }
      }
      
      const styles = StyleSheet.create({
          //...
      });
      
      export default connector(TopTabBarWrapper);
      

    ps:一步一个脚印👣,up~

    相关文章

      网友评论

        本文标题:08-首页

        本文链接:https://www.haomeiwen.com/subject/moasiltx.html