React Native :加载新闻列表

骑猪看日落 2022-05-12 06:40 266阅读 0赞

代码地址如下:
http://www.demodashi.com/demo/13212.html

标签与内容页联动

上一节(React Native : 自定义视图)做到了点击标签自动移动,还差跟下面的视图进行联动。

首先创建 NewsList.js :

  1. import React from 'react'
  2. import {
  3. View,
  4. Text,
  5. ListView,
  6. Image,
  7. StyleSheet,
  8. Dimensions
  9. } from 'react-native'
  10. const {width, height} = Dimensions.get('window')
  11. export default class NewsList extends React.Component {
  12. render() {
  13. const {style} = this.props
  14. return (
  15. <View style={[styles.view,style]}> </View> ) } } const styles = StyleSheet.create({ view: { flex: 1, backgroundColor:'red' } })

然后在 Home.js 引入,再加入 ScrollView ,现在 Home.jsredner() 是这样子的,这里加入的 ScrollView 我们在后文中称为 NewsScrollView

  1. render() {
  2. return (
  3. <View style={styles.view}> <NavigationBar title="首页" unLeftImage={true} /> <SegmentedView ref="SegmentedView" list={this.state.list} style={ {height: 30}} /> <ScrollView style={styles.view} ref="ScrollView" horizontal={true} showsHorizontalScrollIndicator={false} pagingEnabled={true} > { this._getNewsLists()} </ScrollView> </View> ) }

_getNewsLists() 方法:

  1. _getNewsLists() {
  2. let lists = []
  3. if (this.state.list) {
  4. for (let index in this.state.list) {
  5. let dic = this.state.list[index]
  6. lists.push(
  7. <NewsList key={index} style={ {backgroundColor:'#' + this._getColor('',0), width: width, height: height - 49 - 64 - 30}} dic={dic} /> ) } } return lists } _getColor(color, index) { index ++ if (index == 7) { return color } color = color + '0123456789abcdef'[Math.floor(Math.random()*16)] return this._getColor(color, index) }

根据返回的数据创建对应数量的视图,给随机颜色方便看效果。

先设置滑动 NewsScrollView 让标签跟着移动。

我们把 SegmentedViewitems.push 中的 onPress 方法的实现单独写到一个方法里,然后在这里调用:

  1. _moveTo(index) {
  2. const { list } = this.props //获取到 传入的数组
  3. this.state.selectItem && this.state.selectItem._unSelect()
  4. this.state.selectItem = this.refs[index]
  5. if (list.length > maxItem) {
  6. let meiosis = parseInt(maxItem / 2)
  7. this.refs.ScrollView.scrollTo({x: (index - meiosis < 0 ? 0 : index - meiosis > list.length - maxItem ? list.length - maxItem : index - meiosis ) * this.state.itemWidth, y: 0, animated: true})
  8. }
  9. }

这里会发现我们给 this.state 加了一个 itemWidth ,原来我们获取 itemWidth 是在 _getItems() 中计算的,但是在渲染的过程中无法调用 setState() ,我们把计算 itemWidth 的方法移动到 :

  1. componentWillReceiveProps(props) {
  2. const { list } = props //获取到 传入的数组
  3. if (!list || list.length == 0) return
  4. // 计算每个标签的宽度
  5. let itemWidth = width / list.length
  6. if (list.length > maxItem) {
  7. itemWidth = width / maxItem
  8. }
  9. this.setState({
  10. itemWidth
  11. })
  12. }

componentWillReceiveProps(props) 方法会在属性更新后调用,参数 props 是新的属性。

现在运行会发现点击标签可以正常改变标签的状态,然而拖动 NewsScrollView 只会让上一个选中的变为未选中,新的标签并没有变为选中,这是因为选中状态只在标签被点击的时候进行了设置,我们需要给 Item 添加一个选中的方法 :

  1. _select() {
  2. this.setState({
  3. isSelect: true
  4. })
  5. }

然后在 _moveTo(index) 进行调用:

  1. this.state.selectItem && this.state.selectItem._unSelect()
  2. this.state.selectItem = this.refs[index]
  3. this.state.selectItem._select()

现在运行滑动 NewsScrollView 上面的 SegmentedView 可以正常运行了。

最后设置点击标签可以让 NewsScrollView 滑动到对应的位置,我们需要给 SegmentedView 加入一个回调函数,在标签被点击的时候调用返回点击的 index

  1. <SegmentedView
  2. ref="SegmentedView"
  3. list={
  4. this.state.list}
  5. style={
  6. {height: 30}}
  7. selectItem={(index) => {
  8. this.refs.ScrollView.scrollTo({x: width * index, y: 0, animated: true})
  9. }}
  10. />

SegmentedView 进行调用:

  1. _getItems() {
  2. const { list, selectItem } = this.props //获取到 传入的数组
  3. if (!list || list.length == 0) return []
  4. let items = []
  5. for (let index in list) {
  6. let dic = list[index]
  7. items.push(
  8. <Item ref={index} key={index} isSelect={index == 0} itemHeight={this.state.itemHeight} itemWidth={this.state.itemWidth} dic={dic} onPress={() => { this._moveTo(index) selectItem && selectItem(index) }} /> ) } return items }

加载新闻列表第一页数据 加载新闻列表第一页数据

Home.js 中已经给 NewsList 传入了数据,我们再给传入一个参数识别是否是第一页,初始只加载第一页的数据,也方便调试:

  1. _getNewsLists() {
  2. let lists = []
  3. if (this.state.list) {
  4. for (let index in this.state.list) {
  5. let dic = this.state.list[index]
  6. lists.push(
  7. <NewsList key={index} style={ {backgroundColor:'white'}} dic={dic} isRequest={index == 0} /> ) } } return lists }

然后去 NewsList.js 进行请求数据:

  1. // 构造
  2. constructor(props) {
  3. super(props);
  4. // 初始状态
  5. this.state = {
  6. page: 1,
  7. rn: 1,
  8. };
  9. }
  10. componentDidMount() {
  11. if (!this.props.isRequest) return
  12. this._onRefresh()
  13. }
  14. _onRefresh(page) {
  15. if (this.props.dic) {
  16. let url = 'http://api.iapple123.com/newspush/list/index.html?clientid=1114283782&v=1.1&type='
  17. + this.props.dic.NameEN
  18. + '&startkey=&newkey=&index='
  19. + (page ? page : this.state.page)
  20. + '&size=20&ime=6271F554-7B2F-45DE-887E-4A336F64DEE6&apptypeid=ZJZYIOS1114283782&rn='
  21. + this.state.rn
  22. LOG('url=》', url)
  23. fetch(url, {
  24. method: 'GET',
  25. headers: {
  26. 'Accept': 'application/json',
  27. 'Content-Type': 'application/json',
  28. },
  29. })
  30. .then((res) => {
  31. res.json()
  32. .then((json) => {
  33. LOG('GET SUCCESSED then =>', url, json)
  34. })
  35. })
  36. .catch((e) => {
  37. LOG('GET ERROR then =>', url, e)
  38. })
  39. })
  40. .catch((error) => {
  41. LOG('GET ERROR=>', url, '==>', error)
  42. })
  43. }
  44. }

请求到数据后我们需要用 ListView (官方文档) 来显示, 所以导入 ListView ,然后去 render() 加入:

  1. render() {
  2. const {style} = this.props
  3. return (
  4. <View style={[styles.view,style]}> <ListView style={ {flex:1}} dataSource={this.state.dataSource} //设置数据源 renderRow={this.renderRow} //设置cell /> </View> ) }

然后加入 dataSourcerenderRow:

  1. // 构造
  2. constructor(props) {
  3. super(props);
  4. var getRowData = (dataBlob, sectionID, rowID) => {
  5. return dataBlob[sectionID][rowID]
  6. };
  7. // 初始状态
  8. this.state = {
  9. page: 1,
  10. rn: 1,
  11. dataSource: new ListView.DataSource({
  12. getRowData: getRowData,
  13. rowHasChanged: (r1, r2) => r1 !== r2,
  14. }),
  15. };
  16. this.renderRow = this.renderRow.bind(this)
  17. }
  18. renderRow(rowData, rowID, highlightRow) {
  19. return (
  20. <View /> ) }

运行效果

我们要做的界面是这个样子
xNl37a9lue1xPYCXziv.jpg

从上图可以看出来新闻分为 3 种样式,轮播图、有一张图片的和二、三张图片的。

接下来开始解析数据,解析完 json 数据发现只有一个数组,轮播图是取了前四个,剩下的根据 ImagesList 里图片的个数来判断,

.then((json) => { 加入

  1. let list = json.NewsList
  2. let swipers = []
  3. let news = []
  4. for (let index in list) {
  5. let dic = list[index]
  6. index < 4 ? swipers.push(dic) : news.push(dic)
  7. }
  8. news.splice(0, 0, swipers)
  9. this.setState({
  10. dataSource: this.state.dataSource.cloneWithRows(news)
  11. })

现在 news 的数据结构为:

  1. [
  2. [
  3. {},
  4. {}
  5. ],
  6. {},
  7. {}
  8. }

然后去 renderRow 处理数据

如果是数组,那么返回轮播图:

  1. if (Object.prototype.toString.call(rowData) === '[object Array]') {
  2. return (
  3. <CarousePicture index={2} ref="ScrollView" rowData={rowData} style={ {width, height: 200}} touchIn={this.props.touchIn} > </CarousePicture> ) }

这里的轮播图本来用的 Swiper,但是在 Android 上有很多 BUG,我只好自己写了一个,但是在 Android 上的体验差强人意,源码在这里,把文件导入项目即可。

具体的可以看这里

touchIn 是由于在 Andoird 上两个 ScrollView 重叠时,处于顶部的 ScrollView 滑动事件不会响应,因为底部的 ScrollView 进行了响应并拦截了事件,我们需要在手指接触到轮播图的时候禁用底部 ScrollView 的滑动属性,再手指离开的时候再进行恢复,所以还需要去 Home.js 加入:

  1. _getNewsLists() {
  2. let lists = []
  3. if (this.state.list) {
  4. for (let index in this.state.list) {
  5. let dic = this.state.list[index]
  6. lists.push(
  7. <NewsList key={index} style={ {backgroundColor:'white', width: width, height: height - 64 - 49 - 30}} dic={dic} isRequest={index == 0} touchIn={(scrollEnabled) => { this.refs.ScrollView.setNativeProps({scrollEnabled: !scrollEnabled}) }} /> ) } } return lists }

然后根据 ImagesList 的个数来区分:

  1. let imagesList = rowData.ImagesList
  2. if (imagesList && imagesList.length == 1) {
  3. return (
  4. <TouchableOpacity style={ {width, backgroundColor:'white'}}> <View style={ {width, backgroundColor:'white', flexDirection:'row', justifyContent:'space-between', flex:1}}> <Image resizeMode="cover" style={ {marginTop: 10, marginBottom:10, marginLeft: 10, width: 80, height: 80, backgroundColor:'#EEEEEE'}} source={ {uri:imagesList[0].ImgPath}} /> <View style={ { marginRight: 10,backgroundColor:'white', marginTop: 10, height: 80, width: width - 110}} > <Text>{rowData.Title}</Text> <View style={ {flex:1, flexDirection: 'row', justifyContent: 'space-between'}}> <Text style={ {marginTop:10, fontSize: 13, color: '#999999'}}>{rowData.Source}</Text> <Text style={ {marginRight:0,marginTop:10,fontSize: 13, color: '#999999'}}>{rowData.PublishTime}</Text> </View> </View> </View> <View style={ {width, height:1, backgroundColor: '#EEEEEE'}}></View> </TouchableOpacity> ) } let images = [] for (let index in imagesList) { let dic = imagesList[index] images.push( <Image resizeMode="cover" key={index} style={ {marginRight: 10, marginLeft: index == 0 ? 10 : 0, marginTop:10, marginBottom: 10,flex:1, height: 90}} source={ {uri:dic.ImgPath}} /> ) } return ( <TouchableOpacity style={ {width, backgroundColor:'white'}}> <View style={ {width,backgroundColor:'white'}}> <Text style={ {marginLeft: 10, marginTop: 10}}>{rowData.Title}</Text> </View> <View style={ {flexDirection:'row'}}> {images} </View> <View style={ {flex:1, flexDirection: 'row', justifyContent: 'space-between'}}> <Text style={ {marginLeft: 10, marginBottom: 10,fontSize: 13, color: '#999999'}}>{rowData.Source}</Text> <Text style={ {marginRight:10,fontSize: 13, marginBottom: 10,color: '#999999'}}>{rowData.PublishTime}</Text> </View> <View style={ {width, height:1, backgroundColor: '#EEEEEE'}}></View> </TouchableOpacity> )

我这里的 style 没有进行整理,所以看着比较乱,正式开发中应该整理到 styles 里,看起来就简洁多了。

现在运行就可以显示第一页的数据了。

项目结构

项目结构如下:

SXxB3F91796lMUCuBtF.jpg

React Native :加载新闻列表

代码地址如下:
http://www.demodashi.com/demo/13212.html

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

发表评论

表情:
评论列表 (有 0 条评论,266人围观)

还没有评论,来说两句吧...

相关阅读