HelloRN代码分析笔记

HelloRN代码分析笔记

HelloRN是一个react-native入门demo,适配android ios,主要展示网页、图片、音乐、地图,项目使用版本"react": “~15.4.0-rc.4”, “react-native”: “0.40.0”。
github地址


网页展示

在文章内容页面,作者使用WebView控件展示网页内容,灵活运用fetch分别获取网页内容及css文件,将其合并后赋值给WebView的source属性。

export default class Image extends Component {
	constructor() {
		super()
		this.state = {
			html: ''
		}
	}

	componentDidMount() {
		this.timer = setTimeout(() => this._fetch(),400)
	}

	componentWillUnmount() {
        this.timer && clearTimeout(this.timer)
    }

	_fetch() {
		fetch('http://news-at.zhihu.com/api/4/news/' + this.props.articleID)
			.then((response) => response.json())
			.then((responseJson) => {
				fetch('http://daily.zhihu.com/css/share.css?v=5956a')
					.then((responseCSS) => responseCSS.text())
					.then((css) => {
						let cssLink = '<style>'+css+'</style>',
							imgLink = '<div class="img-wrap"><h1 class="headline-title">'+responseJson.title+'</h1><span class="img-source"></span><img src="'+responseJson.image+'" alt=""><div class="img-mask"></div></div>'
						this.setState({
							html: cssLink + responseJson.body.replace(/<div class=\"img-place-holder\"><\/div>/, imgLink),
						})
					})
		})
	}

	render() {
		return (
			<View style={styles.container}>
				<WebView
					style={{flex:1}}
					source={{html: this.state.html}}
					javaScriptEnabled={false}
				/>
            </View>
		)
	}
}

图片展示

在页面加载时,单独编写控件LoadingSpinner增加用户体验。

export default class LoadingSpinner extends Component {
	render() {
		return (
            <View style={styles.container}>
                <ActivityIndicator
                    animating={this.props.animating}
					color='#FFDB42'
					size='large'
				/>
            </View>
		)
	}
}

作者编写方法_fetchImages(page = 1, callback)用于分页加载数据,编写控件ImageModal用于用户点击列表后展示图片。通常列表页的state包含“是否正在加载”、“数据源”、“数据分页”等状态。

let deviceWidth = Dimensions.get('window').width

const ImageItem = ({ url, images, rowID, t }) => {
	let gif = url.endsWith('.gif'), newUrl = url
	if (gif) newUrl = url.replace('mw690','small')
					  .replace('mw1024','small')
					  .replace('mw1200','small')
	return (
		<TouchableWithoutFeedback onPress={() => {t.setState({modalUri: url, modalHide: false})}}>
			<View style={{padding: 10, margin: 10, borderRadius: 5, backgroundColor: '#FFF'}}>
				<Image 
					style={{
						flex: 1,
						height: 200,
						justifyContent: 'center',
						alignItems: 'center'
					}}
					resizeMethod='scale'
					source={{uri: newUrl}}
				>
				<Text
					style={{
						backgroundColor: 'transparent',
						color: '#FFF',
						fontSize: 18
					}}
				>
						{gif && 'PLAY'}
					</Text>
				</Image>
			</View>
		</TouchableWithoutFeedback>
	)
}

export default class Images extends Component {
	constructor() {
		super()
		const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
		this.state = {
			imageDS: ds,
			images: [],
			isRefreshing: false,
			currentPage: 1,
			modalUri: '',
			modalHide: true
		}
	}

	componentDidMount() {
		this._fetchImages()
	}
	
	_fetchImages(page = 1, callback) {
		let url = 'http://i.jandan.net/?oxwlxojflwblxbsapi=jandan.get_ooxx_comments&page=' + page
		fetch(url)
			.then((res) => res.json())
			.then((res) => {
				let tmp = page == 1 ? [] : this.state.images
				res.comments.forEach((ele, index, arr) => {
					tmp = tmp.concat(ele.pics)
				})
				this.setState({
					imageDS: this.state.imageDS.cloneWithRows(tmp),
					images: tmp,
					currentPage: page
				}, callback && callback())
			})
	}

	_onRefresh() {
		this.setState({isRefreshing: true})
		this._fetchImages(1,this.setState({isRefreshing: false}))
	}

	_onLoadMore() {
		let page = ++this.state.currentPage
		this._fetchImages(page)
	}

	render() {
		if (this.state.images.length == 0) return <LoadingSpinner animating={true} />
		return (
            <View style={styles.container}>
				<ListView
					dataSource={this.state.imageDS}
					renderRow={(rowData, sectionID, rowID) => 
						<ImageItem url={rowData} images={this.state.images} key={rowID} rowID={rowID} t={this} />
					}
					refreshControl={
						<RefreshControl
							refreshing={this.state.isRefreshing}
							onRefresh={this._onRefresh.bind(this)}
							tintColor='#FFDB42'
							title='拼命加载中'
							titleColor="black"
							colors={['black']}
							progressBackgroundColor="#FFDB42"
						/>
					}
					onEndReachedThreshold={250}
					onEndReached={this._onLoadMore.bind(this)}
				/>
				<ImageModal
					uri={this.state.modalUri}
					hide={this.state.modalHide}
					onPress={()=>this.setState({modalHide: true})}
				/>
            </View>
		)
	}
}

音乐展示

作者使用react-native-login插件播放音乐,使用react-natve-modalbox插件作为界面弹出层,使用react-native-root-toast作为信息提示。

let deviceWidth = Dimensions.get('window').width

const SongItem = ({ data, rowID, t }) => {
	let current = t.state.currentSong == data
	return (
		<TouchableOpacity
			onPress={() => {
				t.setState({
					sliderValue: 0,
					current: '00:00',
					videoPause: false,
					playButton: 'pause-circle',
					currentSong: t.state.songs[rowID]
					},t.refs.modal.close())
				}
			}
		>
			<View style={{
				flexDirection: 'row',
				justifyContent: 'space-between',
				backgroundColor: '#fff',
				alignItems: 'center',
				padding: 15
				}}
			>
				<View style={{flexDirection: 'row', alignItems: 'flex-end',}}>
				<Text style={{color: current ? 'red' : 'black'}}>{data.name}</Text>
				<Text style={{fontSize: 11, color: current ? 'red' : '#AAA'}}> - {data.artists[0].name}</Text>
			</View>
				{ current && <Icon name='play' size={12} color='red' /> }		
			</View>
		</TouchableOpacity>
	)
}

export default class Music extends Component {

	constructor() {
		super()
		const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
		this.state = {
			songDS: ds,
			songs: [],
			currentSong: {},
			sliderValue: 0,
			videoPause: false,
			playButton: 'pause-circle',
			current: '00:00',
		}
  	}

	componentDidMount() {

		// 网易云音乐 云音乐热歌榜 api
		let url = 'http://music.163.com/api/playlist/detail?id=3778678&updateTime=-1'
		fetch(url)
			.then((data) => {
				return data.json()
			})
			.then((res) => {
				let songs = res.result.tracks
				// 取前20首
				songs.length = 20
				this.setState({
					songDS: this.state.songDS.cloneWithRows(songs),
					songs: songs,
					currentSong: songs[0],
				})
			})
	}

	_playButton() {
		this.setState({
			playButton: this.state.videoPause ? 'pause-circle' : 'play-circle',
			videoPause: !this.state.videoPause
		})
	}

	_onProgress(data) {
		// currentTime 23.313s
		let val = parseInt(data.currentTime * 1000)
		this.setState({
			sliderValue: val,
			current: this._formatTime(Math.floor(data.currentTime))
		})
	}

	_formatTime(time) {
		// 71s -> 01:11
		let min = Math.floor(time / 60)
		let second = time - min * 60
		min = min >= 10 ? min : '0' + min
		second = second >= 10 ? second : '0' + second
		return min + ':' + second
	}

	render() {
		if (!this.state.currentSong.name) return <LoadingSpinner animating={true} /> 
		return (
			<View style={styles.container}>
				{ this.state.songs.length != 0 ?
					<Video 
						source={{uri: this.state.currentSong.mp3Url}}   // Can be a URL or a local file.
						ref='video'                           // Store reference
						rate={1.0}                     // 0 is paused, 1 is normal.
						volume={1.0}                   // 0 is muted, 1 is normal.
						muted={false}                  // Mutes the audio entirely.
						paused={this.state.videoPause}                 // Pauses playback entirely.
						repeat={false}                  // Repeat forever.
						playInBackground={false}       // Audio continues to play when app entering background.
						playWhenInactive={false}       // [iOS] Video continues to play when control or notification center are shown.
						progressUpdateInterval={250.0} // [iOS] Interval to fire onProgress (default to ~250ms)
						onProgress={this._onProgress.bind(this)}
						onEnd={() => {
							let index = this.state.songs.indexOf(this.state.currentSong)
							index = index == this.state.songs.length-1 ? 0 : index+1
							this.setState({
								currentSong: this.state.songs[index],
								sliderValue: 0,
								current: '00:00',
							})
						}}
						onError={(e) => {
							console.log(e)
							Toast.show('mp3资源出错',{
								position: Toast.positions.CENTER,
								onHidden: () => {
									let index = this.state.songs.indexOf(this.state.currentSong)
									index = index == this.state.songs.length-1 ? 0 : index+1
									this.setState({
										currentSong: this.state.songs[index],
										sliderValue: 0,
										current: '00:00',
									})
								}
							})
						}}
					/>
					:
					null
				}
				<Image
					style={styles.image}
					source={{uri: this.state.currentSong.album.picUrl}}
					resizeMode='cover'
				/>
				<View style={styles.playingInfo}>
					<Text>{this.state.currentSong.name} - {this.state.currentSong.artists[0].name}</Text>
					<Text>{this.state.current} - {this._formatTime(Math.floor(this.state.currentSong.duration / 1000))}</Text>
				</View>
				<View style={styles.playingControl}>
					<TouchableOpacity onPress={this._playButton.bind(this)}>
						<Icon name={this.state.playButton} size={40} color='#FFDB42' />
					</TouchableOpacity>
					<Slider
						ref='slider'
						style={{flex: 1, marginLeft: 10, marginRight: 10}}
						value={this.state.sliderValue}
						onValueChange={(value) => {
							this.setState({
								videoPause: true,
								current: this._formatTime(Math.floor(value / 1000))
								})
							}
						}
						onSlidingComplete={(value) => {
							this.refs.video.seek(value / 1000)
							// 判断是否处于播放状态			
							if (this.state.playButton === 'pause-circle') this.setState({videoPause: false})
							}
						}
						maximumValue={this.state.currentSong.duration}
						step={1}
						minimumTrackTintColor='#FFDB42'
					/>
					<TouchableOpacity onPress={() => this.refs.modal.open()}>
						<Icon name='list-ul' size={30} color='#FFDB42' />
					</TouchableOpacity>
				</View>
				
				<Modal style={styles.modal} position={"bottom"} ref='modal'>
					<ListView
						initialListSize={20}
 						dataSource={this.state.songDS}
 						renderRow={(rowData, sectionID, rowID) => <SongItem data={rowData} key={rowID} rowID={rowID} t={this} />}
 						renderSeparator={(sectionID, rowID, adjacentRowHighlighted) => {
							return <View style={{borderWidth: .3, borderColor: '#CCC'}} key={rowID}></View>
 						}}
 					/>
				</Modal>
            </View>
		)
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值