3.零基础学RN-开发一个App首页

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

1.简介

学习了Flexbox模型之后,对使用rn设计一个页面已经有了一些基础,这一章我们开始尝试使用rn做一个简单的App首页,首页布局从上之下依次为:

  • 状态栏:显示时间、电量等信息
  • 搜索栏:输入内容、点击搜索按钮
  • 广告banner:循环播放的广告等
  • 商品列表:商品信息列表

\

2.布局实现

整理布局如下:

// 样式
const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column'
  },
  search_bar: {
    marginTop: 20,
    height: 40,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 2,
    backgroundColor: 'red'
  },
  ad_banner: {
    height: 180,
    justifyContent: 'center',
    alignItems: 'flex-start',
    backgroundColor: 'green'
  },
  product_list: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: 'orange'
  }
});

// 布局
const TaobaoApp = () => {
  return (
    <View style={styles.container}>
      <View style={styles.search_bar}>
        <Text>搜索</Text>
      </View>
      <View style={styles.ad_banner}>
        <Text>banner</Text>
      </View>
      <View style={styles.product_list}>
        <Text>商品列表</Text>
      </View>
    </View>
    
  );
};

export default TaobaoApp;
复制代码
  • 搜索栏高度时40,即height: 40
  • banner高度为180,即height: 180
  • 商品列表高度为除去其他View之外填充满父View,可使用flex:1,类似Anroid中的match_parent。

效果如下:

问题:顶部会有一个多余的空白条。

由于是基于Android和iOS开发的差异,iOSUI从屏幕顶部开始,为了不与状态栏重叠,使用marginTop:20来解决,而Android默认开始的位置不包括状态栏,所以marginTop:20造成了空白区域的出现。

解决:React Native使用Platform开判断当前运行的系统,通过Platform.OS的返回值来决定marginTop的值即可,代码做如下修改:

//记得import Platform
import {View, Text, StyleSheet, Platform} from 'react-native';

// 将search_bar样式调整
marginTop: Platform.OS == 'android' ? 0 : 20,
复制代码

2.1 搜索栏

搜索栏包括一个可输入文字的文本框和一个搜索按钮,输入框可以使用TextInput,按钮使用Button,内容如下:

// 布局
const TaobaoApp = () => {
  return (
    <View style={styles.container}>
      <View style={styles.search_bar}>
        <TextInput style={styles.input} placeholder="搜索"></TextInput>
        <Button 
          style={styles.button} 
          title="搜索"/>
      </View>
      <View style={styles.ad_banner}>
        <Text>banner</Text>
      </View>
      <View style={styles.product_list}>
        <Text>商品列表</Text>
      </View>
    </View>
    
  );
};

export default TaobaoApp;
复制代码

效果如下:

2.2 banner

Android中使用ViewPager很容易就能实现,但是这是Android特有的组件,我们还是使用ScrollView来实现一个通用的banner。

2.2.1 ScrollView

ScrollView基本用法很简单,直接看一下吧:

const AdBanner = () => {
  return (
    <View style={styles.ad_banner}>
        <ScrollView 
        horizontal={true}
        pagingEnabled={true}
        showsHorizontalScrollIndicator={false}
        >
          <Text 
            style={{width: 100, height: 180, backgroundColor: 'red'}}>广告1</Text>
          <Text 
            style={{width: 100, height: 180, backgroundColor: 'yellow'}}>广告2</Text>
          <Text 
            style={{width: 100, height: 180, backgroundColor: 'blue'}}>广告3</Text>
        </ScrollView>
      </View>
  );
};
复制代码

banner为横向滚动,设置horizontal={true},并开启分页效果pagingEnabled={true},隐藏横向的滚动条showsHorizontalScrollIndicator={false}。

效果如下:

\

2.2.2 屏幕尺寸

首先,banner的宽度为屏幕的宽度,React Native已经提供了API,使用Dimensions来获取屏幕尺寸,宽度获取:Dimensions.get('window').width,代码修改如下:

const AdBanner = () => {
  return (
    <View style={styles.ad_banner}>
        <ScrollView 
        horizontal={true}
        pagingEnabled={true}
        showsHorizontalScrollIndicator={false}
        ref = {com => this._banner = com}
        >
          <Text 
            style={{width:Dimensions.get('window').width, height: 180, backgroundColor: 'red'}}>广告1</Text>
          <Text 
            style={{width:Dimensions.get('window').width, height: 180, backgroundColor: 'yellow'}}>广告2</Text>
          <Text 
            style={{width:Dimensions.get('window').width, height: 180, backgroundColor: 'blue'}}>广告3</Text>
        </ScrollView>
      </View>
  );
};
复制代码

效果如下:

2.2.3 自动滚动

抛开rn,如果让scrollView自动滚动,可以使用一个定时器,每隔指定时间,让scrollView进行横向的位移操作。

在rn中同样可以使用这个流程,这包含几个知识点:

  • 动态改变值:能动态的改变scrollView的值,使用state
  • 生命周期:在控件加载后开始动画,控件卸载后停止动画
  • 定时器:定时器的使用
  • 图片引用:引用本地图片

我们一个一个来解决:

state状态

一个基本的state定义为 const [pageNumber, setPageNumber] = useState(0);

即创建一个变量pageNumber,并赋初始值0,创建一个改变变量的值setPageNumber。

使用定时器动态改变pageNumber即可简介控制scrollView的偏移量。

生命周期

看到的资料一般都是使用继承自React.Component的Class控件来使用生命周期,而官方文档推荐使用函数式控件,后面稍微看了一下在函数式控件中可以使用useEffect来模拟声明周期,具体是

useEffect(() => {
   //模式didMount 在此编写逻辑
  return () => {
   	//模拟willUnMount 在此编写逻辑
  }
}, [])
复制代码

useEffect的第二个参数使用[],这样在函数中定义的逻辑只会在加载后执行一次,即模式didMount,在返回值中的逻辑会在卸载后执行一次,即模拟willUnMount。

定时器

使用setInterval函数即可,第一个参数是执行函数体,第二个参数是时间间隔。

图片引用

使用以下3个步骤即可:

1、创建存储图片文件的文件夹

2、创建图片文件引用

3、使用图片

import image_list from './image_list';

<Image 
  style={{width:Dimensions.get('window').width, height: 180}}
  source={image_list.img1}/>
复制代码

基于以上四点,实现如下:

const AdBanner = () => {
  const [pageNumber, setPageNumber] = useState(0);

  useEffect(() => {
    let interval = setInterval(() => {
      var nextPage = pageNumber + 1;
      if (nextPage >= 3) {
        nextPage = 0;
      }
      setPageNumber(nextPage);
      const offSetX = nextPage * ScreenWidth;
      this._banner.scrollTo({x: offSetX, y: 0, animated: true});
    }, 3000);
    return () => {
      clearInterval(interval);
      interval = null;
    }
  }, [])

  return (
    <View style={styles.ad_banner}>
        <ScrollView 
        horizontal={true}
        pagingEnabled={true}
        showsHorizontalScrollIndicator={false}
        ref = {com => this._banner = com}
        >
          <Image 
            style={{width:Dimensions.get('window').width, height: 180}}
            source={image_list.img1}/>
          <Image 
            style={{width:Dimensions.get('window').width, height: 180}}
            source={image_list.img2}/>
          <Image 
            style={{width:Dimensions.get('window').width, height: 180}}
            source={image_list.img3}/>
        </ScrollView>
      </View>
  );
};
复制代码

效果:

问题:图片只会切换一次就停止了?

查了一下log,useEffect中获取pageNumer的值一直是0,即使使用了setPageNumber方法也没有改变,因为我们使用的是模拟mount生命周期,useEffect只执行了一次,所以拿到的值始终是注册时的那个值,解决方法也很简单,使用另一个钩子函数获取正确的值,代码修改如下:

const AdBanner = () => {
  const [pageNumber, setPageNumber] = useState(0);
  const countRef = useRef(pageNumber);

  useEffect(() => {
    countRef.current = pageNumber;
  })

  useEffect(() => {
    let interval = setInterval(() => {
      var nextPage = countRef.current + 1;
      if (nextPage >= 3) {
        nextPage = 0;
      }
      setPageNumber(nextPage);
      const offSetX = nextPage * ScreenWidth;
      this._banner.scrollTo({x: offSetX, y: 0, animated: true});
    }, 3000);
    return () => {
      clearInterval(interval);
      interval = null;
    }
  }, [])

  return (
    <View style={styles.ad_banner}>
        <ScrollView 
        horizontal={true}
        pagingEnabled={true}
        showsHorizontalScrollIndicator={false}
        ref = {com => this._banner = com}
        >
          <Image 
            style={{width:Dimensions.get('window').width, height: 180}}
            source={image_list.img1}/>
          <Image 
            style={{width:Dimensions.get('window').width, height: 180}}
            source={image_list.img2}/>
          <Image 
            style={{width:Dimensions.get('window').width, height: 180}}
            source={image_list.img3}/>
        </ScrollView>
      </View>
  );
};
复制代码

这样,banner终于动起来了了,我们继续

2.3 商品列表

商品列表很容易想到使用ListView,无论iOS、Android中都有,但是尴尬的是RN中标记废弃了。。。。。

/**
 * @deprecated See Flatlist or SectionList
 * or use `deprecated-react-native-listview`
 * @see https://fb.me/nolistview
 */
复制代码

估计是优化的不好,那咱们就使用Flatlist来实现吧。

Flatlist很简单,必须实现两个属性data和renderItem,data为数据源,render时解析数据源并渲染组件的函数,代码如下:

const Products = () => {
  return (
    <View style={styles.product_list}>
      <FlatList 
      style={{flex: 1}}
        data={[
          {key: '商品1'},
          {key: '商品2'},
          {key: '商品3'},
          {key: '商品4'},
          {key: '商品5'},
          {key: '商品6'},
          {key: '商品7'},
          {key: '商品8'},
          {key: '商品9'},
          {key: '商品10'},
          {key: '商品11'},
          {key: '商品12'},
          {key: '商品13'},
          {key: '商品14'},
          {key: '商品15'},
          {key: '商品16'},
          {key: '商品17'},
          {key: '商品18'}
        ]}
        renderItem={({item}) => <Text style={styles.product_item}>{item.key}</Text>}/>
    </View>
  );
}
复制代码

效果如下:

这样,整个首页基本完成了,当然,需要细节优化的还有很多。

2.4 交互

按钮点击

为button增加点击事件,button中提供onPress方法,可以直接使用

<Button 
  style={styles.button} 
  title="搜索"
  onPress={() => {
    Alert.alert('点击搜索按钮', null, null);
  }}/>
复制代码

banner点击

可以通过为Image包一层TouchableHighlight来实现,具体如下:

<TouchableHighlight 
  onPress={() => {
    Alert.alert('点击banner', null, null);
  }}>
  <Image 
    style={{width:Dimensions.get('window').width, height: 180}}
    source={image_list.img1}/>
</TouchableHighlight>
复制代码

状态栏样式修改

class StatusBarComponent extends React.Component {
  render() {
    return (
      <View>
        <StatusBar 
        backgroundColor={'blue'}
        barStyle={'default'}
        networkActivityIndicatorVisible={true}
        ></StatusBar>
      </View>
    );
  }
}
复制代码

整体效果

3.总结

App首页基本完成,我们用到了flexbox用于布局,用到了声明周期方法、定时器、hook的简单使用,还用到了基本的点击交互,遇到了问题并解决了问题,后续我们会学习更多技巧,进一步完善这个应用。

猜你喜欢

转载自juejin.im/post/7014866780837380132