React-Native 之 ListView使用


前言

  • 學習本系列內容需要具備一定 HTML 開發基礎,沒有基礎的朋友可以先轉至 HTML快速入門(一) 學習

  • 本人接觸 React Native 時間並不是特別長,所以對其中的內容和性質了解可能會有所偏差,在學習中如果有錯會及時修改內容,也歡迎萬能的朋友們批評指出,謝謝

  • 文章第一版出自簡書,如果出現圖片或頁面顯示問題,煩請轉至 簡書 查看 也希望喜歡的朋友可以點贊,謝謝

更新公告:

  • 2017.05.16 —— 根據一些朋友私信我的代碼,發現有些錯誤是文中有一些拼寫錯誤導致,已進行更正,對此造成的不便,請見諒。

ListView組件介紹


  • ListView組件是React Native中一個比較核心的組件,用途非常廣,設計初衷就是用來高效的展示垂直滾動的列表數據

  • ListView 繼承了 ScrollView 的所有屬性

  • 使用步驟:

    • 創建一個ListView.DataSource數據源,然后給它傳遞一個普通的數組數據


    	getInitialState(){
    		// 初始化數據源(rowHasChanged是優化的一種手段,只有當r1 !== r2的時候才會重新渲染)
    		var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    		return{
    			// 給dataSource傳遞一組 數組
        		dataSource: ds.cloneWithRows(['內容0', '內容1', '內容2', '內容3', '內容4', '內容5'])
    		}
    	},
    
    
    • 使用數據源實例化一個ListView組件,定義一個renderRow回調函數,這個函數會接受數組中的每個數據作為參數,並返回一個可渲染的組件(也就是該列表的每一行Item)


    	render() {
    		return (
        		<View style={styles.container}>
        			// 根據數據源實例化一個ListView
          			<ListView style={{backgroundColor:'yellow'}}
          				// 獲取數據源
              			dataSource={this.state.dataSource}
              			// 根據數據源創建一個Item
              			// 注:這里的this.renderRow是隱式寫法,系統會根據函數的需要,將對應的參數傳遞過去(共有4個參數:rowData, sectionID, rowID, highlightRow)
              			renderRow={this.renderRow}
          			/>
        		</View>
    		);
    	},
    	
    	// 返回一個Item
    	renderRow(rowData,sectionID,rowID) {
    		return(
    			// 實例化Item
     			<View>
       				<Text style={{backgroundColor:'red', height:44}}>內容{rowData},在第{sectionID}組第{rowID}行</Text>
     			</View>
    		)
    	}
    
    

    效果:
    ListView初體驗.gif

  • ListView 同樣支持一些高級特性,包括設置每一組的粘性的頭部、支持設置列表 header 和 footter 視圖、當數據列表滑動到最底部的時候支持 onEndReached 方法回調、設備屏幕列表可見的視圖數據發生變化的時候回調 onChangeVisibleRows 以及一些性能方面的優化特性

ListView常用屬性


  • ScrollView 全部屬性

  • dataSource:設置ListView的數據源

  • initialListSize:指定在組件剛掛載的時候渲染多少行數據。用這個屬性來確保首屏顯示合適數量的數據,而不是花費太多幀逐步顯示出來

  • onChangeVisibleRows:((visibleRows, changedRows) => void)當可見的行的集合變化的時候調用此回調函數。visibleRows 以 { sectionID: { rowID: true }}的格式包含了所有可見行,而changedRows 以{ sectionID: { rowID: true | false }}的格式包含了所有剛剛改變了可見性的行,其中如果值為true表示一個行變得可見,而為false表示行剛剛離開可視區域而變得不可見

  • onEndReached:當所有的數據都已經渲染過,並且列表被滾動到距離最底部不足onEndReachedThreshold個像素的距離時調用。原生的滾動事件會被作為參數傳遞。譯注:當第一次渲染時,如果數據不足一屏(比如初始值是空的),這個事件也會被觸發

  • onEndReachedThreshold:調用onEndReached之前的臨界值,單位是像素

  • pageSize:每次事件循環(每幀)渲染的行數

  • removeClippedSubviews:用於提升大列表的滾動性能。需要給行容器添加樣式overflow:'hidden'。(Android已默認添加此樣式)此屬性默認開啟

  • renderFooter:(() => renderable)頁頭與頁腳會在每次渲染過程中都重新渲染(如果提供了這些屬性)。如果它們重繪的性能開銷很大,把他們包裝到一個StaticContainer或者其它恰當的結構中。頁腳會永遠在列表的最底部,而頁頭會在最頂部

  • renderHeader: 在每一次渲染過程中Footer(尾)該會一直在列表的底部,header(頭)該會一直在列表的頭部

  • renderRow:【(rowData, sectionID, rowID, highlightRow) => renderable

    • 從數據源(Data source)中接受一條數據,以及它和它所在section的ID。返回一個可渲染的組件來為這行數據進行渲染。默認情況下參數中的數據就是放進數據源中的數據本身,不過也可以提供一些轉換器
    • 如果某一行正在被高亮(通過調用highlightRow函數),ListView會得到相應的通知。當一行被高亮時,其兩側的分割線會被隱藏。行的高亮狀態可以通過調用highlightRow(null)來重置
  • renderScrollComponent:【(props) => renderable】指定一個函數,在其中返回一個可以滾動的組件。ListView將會在該組件內部進行渲染。默認情況下會返回一個包含指定屬性的ScrollView

  • renderSectionHeader:【(sectionData, sectionID) => renderable】

    • 如果提供了此函數,會為每個小節(section)渲染一個粘性的標題。
    • 粘性是指當它剛出現時,會處在對應小節的內容頂部;繼續下滑當它到達屏幕頂端的時候,它會停留在屏幕頂端,一直到對應的位置被下一個小節的標題占據為止
  • renderSeparator:【(sectionID, rowID, adjacentRowHighlighted) => renderable】

    • 如果提供了此屬性,一個可渲染的組件會被渲染在每一行下面,除了小節標題的前面的最后一行。在其上方的小節ID和行ID,以及鄰近的行是否被高亮會作為參數傳遞進來
  • scrollRenderAheadDistance:當一個行接近屏幕范圍多少像素之內的時候,就開始渲染這一行

  • stickyHeaderIndices(iOS):一個子視圖下標的數組,用於決定哪些成員會在滾動之后固定在屏幕頂端。舉個例子,傳遞stickyHeaderIndices={[0]}會讓第一個成員固定在滾動視圖頂端。這個屬性不能和horizontal={true}一起使用

方法


  • getMetrics():導出一些用於性能分析的數據

  • scrollTo(...args):滾動到指定的x, y偏移處,可以指定是否加上過渡動畫。

    • 參考 ScrollView#scrollTo.

ListView簡單優化建議


  • ListView 設計的時候,當需要動態加載非常大量或者渲染復雜的數據時,下面有一些方法可以提高 ListView 的性能
    • 只渲染更新數據變化的那個Item,rowHasChange方法會告訴ListView組件是否需要重新渲染當前Item
    • 選擇渲染的頻率,默認情況下,每一個event-loop(事件循環)只會渲染一行(可以同pageSize自定義屬性設置)這樣可以把大工作量進行分隔,提供整體渲染性能

ListView 基本布局


  • 這邊我們就按照下圖中的布局實現一個簡單的列表數據展示
    基本布局

  • 分析上圖整體布局,我這邊就將其划分為幾個模塊,首先需要有個 大的View 來包裝內部所有的內容,其次再給標題部分分配一個 小View 以方便維護,具體如下圖
    基本布局分析

  • 接下來就可以開始干活啦~

    • 首先,ListView 需要數據源,那么我們就先來自定義一下數據源的 Json 數據,


    	[
    		{"title" : "icon", "img" : "icon"},
    		{"title" : "lufei", "img" : "lufei"},
    		{"title" : "icon", "img" : "icon"},
    		{"title" : "lufei", "img" : "lufei"},
    		{"title" : "icon", "img" : "icon"},
    		{"title" : "lufei", "img" : "lufei"},
    		{"title" : "icon", "img" : "icon"},
    		{"title" : "lufei", "img" : "lufei"},
    		{"title" : "icon", "img" : "icon"},
    		{"title" : "lufei", "img" : "lufei"},
    		{"title" : "icon", "img" : "icon"},
    		{"title" : "lufei", "img" : "lufei"},
    		{"title" : "icon", "img" : "icon"},
    		{"title" : "lufei", "img" : "lufei"}
    	]
    	
    
    • 有了數據后,我們就可以根據數據來實例化 ListView

      • 獲取數據


      	var newData = require('./Data/localData.json');
      
      
      • 初始化數據源


      	getInitialState(){
      		var ds = new ListView.DataSource({rowHasChanged:(r1, r2) => r1 != r2});
      			return{
      				// 將獲得的數組傳遞給dataSource
      				dataSource : ds.cloneWithRows(newData)
      		}
      	},
      
      
      • 接着就是根據數據源實例化 ListView

        • 視圖部分


        	render(){
        		return(
        			<View style={styles.container}>
        				<ListView
        					dataSource={this.state.dataSource}
        					renderRow={this.renderRow}
        				/>
        			</View>
        		);
        	},
        	
        	// 返回一個Item
        	renderRow(rowData){
        		return(
        			<View style={styles.itemStyle}>
        				<Image source={{uri:rowData.img}} style={styles.imageStyle}/>
        				<View style={styles.subItemStyle}>
        					<Text style={{marginTop:5, fontSize:17}}>{rowData.title}</Text>
        					<Text style={{marginBottom:5, fontSize:13, color:'green'}}>簡介</Text>
        				</View>
        			</View>
        		);
        	}
        
        
        • 樣式部分


        	var styles = StyleSheet.create({
        		container: {
        			flex:1
        		},
        
        		itemStyle: {
        			// 主軸方向
        			flexDirection:'row',
        			// 下邊框
        			borderBottomWidth:1,
        			borderBottomColor:'gray'
        		},
        
        		imageStyle: {
        			// 尺寸
        			width:60,
        			height:60,
        			// 邊距
        			marginLeft:10,
        			margin:10
        		},
        
        		subItemStyle: {
        			// 對齊方式
        			justifyContent:'space-around'
        		}
        	});
        	
        

    效果:
    ListView案例一.gif

ListView 九宮格布局實現


  • 先來看下大概的布局

九宮格概念布局

  • 從上面可以看出,這個案例是為了實現類似 CollectionView 效果(比如常見的瀑布流),通常情況下,ListView 是縱向排列的,而此案例我們需要它橫向排列,那么就需要使用到上面提到的 contentContainerStyle 屬性,向里面添加 flexDirection:'row'和 flexWrap:'wrap'` 兩個屬性

    	contentViewStyle: {
    		// 主軸方向
    		flexDirection:'row',
    		// 換行
    		flexWrap:'wrap'
    	},
    
    
  • 當然了,我們還是需要自定義一組數據供 ListView 使用,這邊就使用上面案例的數據

  • 根據數據實例化 ListView ,參考上面案例,這里只粘貼 Item部分,其它的就不重復了

    • 視圖部分


    	var ListViewDemo = React.createClass({
    		getInitialState(){
    			// 初始化數據源
    			var ds = new ListView.DataSource({rowHasChanged:(r1, r2) => r1 != r2});
    			return{
      				dataSource : ds.cloneWithRows(newData)
    			}
    		},
    
    		render(){
    			return(
        			<ListView
            			dataSource={this.state.dataSource}
            			renderRow={this.renderRow}
            			// 設置contentContainerStyle
            			contentContainerStyle={styles.contentViewStyle}
        			/>
    			);
    		},
    
    		// 返回一個Item
    		renderRow(rowData){
    			return(
      			{/* 實例化Item */}
      			<View style={styles.itemStyle}>
        			<Image source={{uri:rowData.img}} style={styles.itemImageStyle}/>
        			<Text>{rowData.title}</Text>
      			</View>
    			);
    		}
    	});
    
    
    • 樣式部分


    	var styles = StyleSheet.create({
    
    		contentViewStyle: {
    			// 主軸方向
    			flexDirection:'row',
    			// 換行
    			flexWrap:'wrap'
    		},
    
    		itemStyle: {
    			// 對齊方式
    			alignItems:'center',
    			justifyContent:'center',
    			// 尺寸
    			width:itemWH,
    			height:itemWH,
    			// 左邊距
    			marginLeft:vMargin,
    			marginTop:hMargin
    		},
    
    		itemImageStyle: {
    			// 尺寸
    			width:60,
    			height:60,
    			// 間距
    			marginBottom:5
    		}
    	});
    
    

    效果:
    ListView九宮格布局

ListView 分組樣式的實現分析


  • 在移動設備里面,經常會看到 sticky效果,比如常見的通訊錄

  • 在React Native中,ScrollView組件要實現 sticky效果 很簡單,只需要使用
    stickyHeaderIndices 就可以了,但對於 ListView 來說,stickyHeaderIndices是無效的,下面我們就來分析怎樣才能使 ListView 實現吸頂效果

  • 首先,ListView要實現 sticky效果 需要使用到 cloneWithRowsAndSections 方法將 dataBlob(object), sectionIDs (array), rowIDs (array) 三個值傳遞出去

    • dataBlob:包含ListView所需的所有數據(section header 和 rows),在ListView渲染數據時,使用getSectionData 和 getRowData 來渲染每一行數據。 dataBlob 的 key 值包含 sectionID + rowId,參考下面模擬的數據結構


    	var dataBlob = {
      		'sectionID1' : {section1 data},
      		'sectionID1:rowID0' : {row0 data},
      		'sectionID1:rowID1' : {row1 data},
      		'sectionID2' : {section1 data},
      		'sectionID2:rowID0' : {row0 data},
      		'sectionID2:rowID1' : {row1 data},
      		'sectionID2:rowID2' : {row2 data},
      		...
    	};
    
    
    • sectionIDs:sectionIDs 用於標識每組section,參考下面模擬的數據結構


    	var sectionIDs = ['sectionID0','sectionID1','sectionID2', ...];
    
    
    • rowIDs:rowIDs 用於描述每個 section 里的每行數據的位置及是否需要渲染。在ListView渲染時,會先遍歷 rowIDs 獲取到對應的 dataBlob 數據,參考下面模擬的數據結構


    	var rowIDs = [['rowID0', 'rowID1', 'rowID2'...], ['rowID0', 'rowID1', ...], ['rowID0', 'rowID1'], ...];
    
    

ListView 分組樣式實現


  • 上面我們大概地分析了下 ListView 實現分組的原理,接下來就根據上面的分析加上實際的案例,來更直觀地體驗下 ListView分組功能的實現

  • 首先,因為要分組,所以數據肯定比之前的案例使用到的要復雜,但是不用擔心,這邊會盡量詳細地將數組的處理表述出來,先來看下我們需要使用到的數據

    	{
    		"data":[
    			{
      				"title":"A",
      				"icons":[
        				{
          					"icon" : "icon"
        					},
        				{
          					"icon" : "lufei"
        				},
        				{
          					"icon" : "icon"
        				}
      				]
    			},
    			{
      				"title":"B",
      				"icons":[
        				{
          					"icon" : "icon"
        				},
        				{
          					"icon" : "lufei"
        				},
        				{
          					"icon" : "icon"
        				},
        				{
          					"icon" : "lufei"
        				}
      				]
    			},
    			{
      				"title":"C",
      				"icons":[
        				{
          					"icon" : "icon"
        				},
        				{
          					"icon" : "lufei"
        				}
      				]
    			},
    			{
      				"title":"D",
      				"icons":[
        				{
          					"icon" : "icon"
        				},
        				{
          					"icon" : "lufei"
        				},
        				{
          					"icon" : "icon"
        				},
        				{
          					"icon" : "lufei"
        				}
      				]
    			},
    			{
      				"title":"E",
      				"icons":[
        				{
          					"icon" : "icon"
        				},
        				{
          					"icon" : "lufei"
        				},
        				{
          					"icon" : "lufei"
        				}
      				]
    			},
    			{
      				"title":"F",
      				"icons":[
        				{
          					"icon" : "icon"
        				},
        				{
          					"icon" : "lufei"
        				}
      				]
    			}
    	
    		]
    	}
    
    
  • 結合上面的分析,我們先來初始化數據源

    	getInitialState(){
    		// 初始化getSectionData
    		var getSectionData = (dataBlob, sectionID) => {
        		return dataBlob[sectionID];
    		};
    		
    		// 初始化getRowData
    		var getRowData = (dataBlob, sectionID, rowID) => {
        		return dataBlob[sectionID + ':' + rowID];
    		};
    
    		return {
        		// 初始化數據源
        		dataSource: new ListView.DataSource({
            		getSectionData : getSectionData,
            		getRowData : getRowData,
            		rowHasChanged : (r1, r2) => r1 !== r2,
            		sectionHeaderHasChanged : (s1, s2) => s1 !== s2
        		})
    		}
    	},
    	
    
  • 接着是數組的解析,然后將解析好的數據提供給 dataSource 進行更新,需要注意的是在實際開發中,數據的復雜程度遠遠要大於我們上面的數據,這是比較耗時的操作,所以我們會選擇在異步線程中執行,之前的文章中也提到過 —— 在React Native中,我們一般將耗時復雜的操作放到 componentDidMount 中執行

    	// 耗時、復雜操作放到這里處理
    	componentDidMount(){
    		// 加載數據
    		this.loadData();
    	},
    	
    	// 加載數據
    	loadData(){
    		// 拿到json數據中的數組
    		var jsonData = iconData.data;
    		// 定義變量
    		var dataBlob = {},
        		sectionIDs = [],
        		rowIDs = [],
        		icons = [];
    		// 遍歷數組中對應的數據並存入變量內
    		for (var i = 0; i<jsonData.length; i++){
        		// 將組號存入 sectionIDs 中
        		sectionIDs.push(i);
        		// 將每組頭部需要顯示的內容存入 dataBlob 中
        		dataBlob[i] = jsonData[i].title;
        		// 取出該組所有的 icon
        		icons = jsonData[i].icons;
        		rowIDs[i] = [];
        		// 遍歷所有 icon
        		for (var j = 0; j<icons.length; j++){
            		// 設置標識
            		rowIDs[i].push(j);
            		// 根據標識,將數據存入 dataBlob
            		dataBlob[i + ':' + j] = icons[j];
        		}
    		}
    		// 刷新dataSource狀態
    		this.setState({    			dataSource:this.state.dataSource.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs)
    		});
    	}
    
    
  • 最后,就是設置樣式,將布局調到我們想要的效果就可以了

    • 視圖部分


    	render(){
    		return(
        		<View style={styles.container}>
        			// 實例化頂部View
          			<View style={styles.topViewStyle}>
              			<Text style={{fontSize:21}}>分組樣式</Text>
          			</View>
          			// 實例化ListView
          			<ListView
              			dataSource={this.state.dataSource}
              			renderRow={this.renderRow}
              			renderSectionHeader={this.renderSectionHeader}
          			/>
        			</View>
    		);
    	},
    	
    	// 返回一個Item
    	renderRow(rowData, sectionID, rowID){
    		return(
        		<View style={styles.itemStyle}>
            		<Image source={{uri:rowData.icon}} style={{width: 60, height:60, marginTop:10, marginLeft:10}}></Image>
            		<Text style={{marginTop:15, marginLeft:10}}>示例</Text>
        		</View>
    		);
    	},
    	
    	// 返回一個SectionHeader
    	renderSectionHeader(sectionData, sectionID){
    		return(
        		<Text style={{backgroundColor:'yellow'}}>{sectionData}</Text>
    		);
    	},
    
    
    • 樣式部分


    	var styles = StyleSheet.create({
    		container:{
      			flex:1
    		},
    
    		topViewStyle: {
      			// 尺寸
      			height:44,
      			// 邊距
      			marginTop:20,
      			// 對齊方式
      			justifyContent:'center',
      			alignItems:'center'
    		},
    
    		itemStyle: {
      			// 尺寸
      			height:80,
      			// 主軸方向
      			flexDirection:'row',
      			// 下邊框
      			borderBottomWidth:1,
      			borderBottomColor:'gray'
    		},
    	});
    
    

    效果:
    ListView分組樣式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM