類似知乎或者微信,在文章詳情頁面,圖片并非是一次性加載完成,而是在滑動到快看到圖片時才開始加載,很明顯原生 Image 組件并不支持這種特性,所以需要封裝一層。
原理
首先圖片組件必須放在 ScrollView 組件內。
圖片組件在頁面完成布局后,根據onLayout
屬性可以獲取到這個組件在容器中的 x、y 值。
監聽 ScrollView 滾動事件,onScroll
會給出內容偏移量 contentOffset。當內容偏移量大于等于圖片組件的 y 值減去屏幕高度時,就可以加載圖片了。
懶加載原理
具體實現
首先搭建好頁面,為了填充內容復制了知乎上的一篇文章《如何去閱讀并學習一些優秀的開源框架的源碼?》
// imageLazyloadDemo.js
import React, {Component} from 'react'
import {
StyleSheet,
View,
Text,
// Image,
ScrollView,
} from 'react-native'
import Image from '../components/Image'
export default class ImageDemo extends Component {
constructor(props) {
super(props);
this.state = {
y: 0
}
this._onScroll = this._onScroll.bind(this)
}
// 滑動觸發
_onScroll(e) {
// 獲取滑動的距離
let {y} = e.nativeEvent.contentOffset;
this.setState({
y
})
}
render() {
return (
<ScrollView
style = {styles.container}
onScroll = {this._onScroll}
>
<Text style = {[styles.text, styles.title]}>假設這是文章詳情頁,存在很多文字和圖片</Text>
<Text style = {styles.text}>在我閱讀的前端庫、Python后臺庫的過程中,我們都是以造輪子為目的展開的。所以在最開始的時候,我需要一個可以工作,并且擁有我想要的功能的版本。</Text>
<Text style = {styles.text}>緊接著,我就可以開始去實踐這個版本中的一些功能,并理解他們是怎么工作的。再用git大法展開之前修改的內容,可以使用IDE自帶的Diff工具:</Text>
<Image
source = {{uri: 'https://pic4.zhimg.com/5bc23b15e827a033d2b4966b6038d987_b.jpg'}}
y = {this.state.y}
/>
<Text style = {styles.text}>或者類似于SourceTree這樣的工具,來查看修改的內容。</Text>
<Text style = {styles.text}>在我們理解了基本的核心功能后,我們就可以向后查看大、中版本的更新內容了。</Text>
<Text style = {styles.text}>開始之前,我們希望大家對版本號管理有一些基本的認識。</Text>
<Text style = {styles.text}>我最早閱讀的開始軟件是Linux,而下面則是Linux的Release過程:</Text>
<Image
source = {{uri: 'https://pic3.zhimg.com/f9d7c5343f3da040f891149a8993ed7e_b.png'}}
y = {this.state.y}
/>
<Text style = {styles.text}>表格源自一本書叫《Linux內核0.11(0.95)完全注釋》,簡單地再介紹一下:</Text>
<Text style = {styles.text}>版本0.00是一個hello,world程序</Text>
<Text style = {styles.text}>版本0.01包含了可以工作的代碼</Text>
<Text style = {styles.text}>版本0.11是基本可以正常的版本</Text>
<Text style = {styles.text}>1.項目初版本時,版本號可以為 0.1 或 0.1.0, 也可以為 1.0 或 1.0.0,如果你為人很低調,我想你會選擇那個主版本號為 0 的方式;</Text>
<Image
source = {{uri: 'https://pic4.zhimg.com/6a32830fab3b9105fecef3d4f830afe7_b.png'}}
y = {this.state.y}
/>
</ScrollView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
paddingHorizontal: 10
},
text: {
fontSize: 16,
marginVertical: 10
},
title: {
fontSize: 20,
marginTop: 10
}
})
重點在_onScroll
函數上,獲取到每次滑動的內容偏移量 contentOffset.y,并通過 state 傳遞給 Image 組件。
<Image
source = {{uri: 'https://pic4.zhimg.com/5bc23b15e827a033d2b4966b6038d987_b.jpg'}}
y = {this.state.y}
/>
這樣 Image 組件就能夠知道自己是否出現在可視范圍內了。
這里 Image 組件是一個封裝后的組件:
// components/Image.js
import React, {Component} from 'react'
import {
StyleSheet,
View,
Image,
Text,
Dimensions,
InteractionManager
} from 'react-native'
// 獲取屏幕寬高
const screenWidth = Dimensions.get('window').width
const screenHeight = Dimensions.get('window').height
export default class CustomImage extends Component {
constructor(props) {
super(props)
// 先給圖片一個默認寬高
this.state = {
width: screenWidth,
height: 300,
loaded: false
}
this._onLayout = this._onLayout.bind(this)
}
// 2、獲取組件初始化位置
_onLayout(e, node) {
let {y} = e.nativeEvent.layout
alert(y)
this.setState({
offsetY: y
})
}
// 獲取加載中、加載失敗視圖的函數
_renderLoad(text) {
return(
<View
style = {styles.loadContainer}
onLayout = {this._onLayout}
>
<Text
style = {styles.loadText}
>{text}</Text>
</View>
)
}
render() {
const {source} = this.props
if(this.state.loaded) {
// 如果真正的圖片加載好
return <Image
source = {source}
style = {{height: this.state.height}}
resizeMode = {'contain'}
/>
}
// 1、會先顯示加載中視圖
return this._renderLoad('正在加載中...')
}
}
const styles = StyleSheet.create({
// 加載容器樣式
loadContainer: {
backgroundColor: '#eee',
justifyContent: 'center',
alignItems: 'center',
height: 100,
marginVertical: 10
},
// 加載樣式
loadText: {
fontSize: 20,
color: '#ccc'
}
)
1、先加載 loading 視圖。
2、獲取到該 loading 視圖在 ScrollView 容器內的位置,有用的是 loading 距內容頂部的距離 y。
OK,然后可以開始計算了,如果傳過來的 y + 屏幕高度 >= loading 視圖距內容頂部的距離 y,就可以獲取遠程圖片了。
// components/Image.js
//...
// 從網絡請求圖片的方法
_fetchImg() {
InteractionManager.runAfterInteractions(() => {
const {source, style} = this.props
if(source.uri) {
// 如果是網絡圖片 在頁面還未加載前,獲取圖片寬高
Image.getSize(source.uri, (w, h) => {
let imgHeight = (h/w)*this.state.width
this.setState({
height: imgHeight,
loaded: true
})
}, (err) => {
// 獲取圖片寬高或者下載圖片失敗
this.setState({
loadFail: true
})
})
}
})
}
render() {
const {source, y} = this.props
// 3、判斷是否可以加載圖片了
if(y + screenHeight >= this.state.offsetY && !this.state.loaded) {
// 請求圖片
this._fetchImg()
}
// 4、如果加載了遠程圖片,就會渲染這里
if(this.state.loaded) {
// 如果真正的圖片加載好
return <Image
source = {source}
style = {{height: this.state.height}}
resizeMode = {'contain'}
/>
}
// 1、會先顯示加載中視圖
return this._renderLoad('正在加載中...')
}
lazyImg.gif
看起來還不錯。。。
總結
至此,完成了一個最最最簡單的圖片懶加載組件。
當然還可以繼續做一些優化,比如當網絡不好時,加載圖片顯示加載失敗,點擊可以重新加載等等。