前言
- 本文有配套視頻,可以酌情觀看。
- 文中內(nèi)容因各人理解不同,可能會有所偏差,歡迎朋友們聯(lián)系我。
- 文中所有內(nèi)容僅供學(xué)習(xí)交流之用,不可用于商業(yè)用途,如因此引起的相關(guān)法律法規(guī)責(zé)任,與我無關(guān)。
- 如文中內(nèi)容對您造成不便,煩請聯(lián)系 277511806@qq.com 處理,謝謝。
- 轉(zhuǎn)載麻煩注明出處,謝謝。
本篇資源:鏈接: https://pan.baidu.com/s/1jIbW2n8 密碼: wqe4
從這篇開始我們就將源碼托管到 github 上,需要源碼的 點我下載,喜歡的話記得 Star,謝謝!
Android啟動頁面
-
從上面的效果可以看出,安卓端還沒有啟動頁面,這邊我們就通過
React-Native
的方式解決。- 思路:新建一個組件作為 Android 的啟動頁,index.android.js 的初始化窗口改為 Android啟動頁,設(shè)置定時器,使其在
1.5秒
后自動跳轉(zhuǎn)到 Main 組件。
export default class GDLaunchPage extends Component { componentDidMount() { setTimeout(() => { this.props.navigator.replace({ component:Main }) }, 1500) } render() { return( <Image source={{uri:'launchimage'}} style={styles.imageStyle} /> ); } }
- 思路:新建一個組件作為 Android 的啟動頁,index.android.js 的初始化窗口改為 Android啟動頁,設(shè)置定時器,使其在
git使用
項目的版本管理也是程序猿必須具備的一項技能,它能夠讓我們避免許多開發(fā)中遇到的尷尬問題。
-
公司里面一般使用 SVN 和 Git 兩種,而現(xiàn)在 Git 的份額逐漸在蠶食著 SVN,這邊我給大家提供了 SVN 和 Git 的詳情版,大家可以前往閱讀。
這小結(jié)建議觀看視頻,視頻內(nèi)有具體操作!
錯誤修正 —— 模態(tài)
以前看官方文檔竟然沒有發(fā)現(xiàn) React-Native 提供了 model 組件,在這里給大家道個歉,以后跪著寫教程,不用讓我起來,反正我感覺膝蓋軟軟的!
前幾天在看官方文檔的時候,無意中看見 model 組件,我嘞個天,有這東西就可以減少開發(fā)中很多功能開發(fā)難度。當(dāng)初怎么沒發(fā)現(xiàn),還傻傻地一步一步去封裝這個東西 T^T,在這告誡各位,不能太粗心!
這邊我們就將原本 近半小時熱門 這個模塊的跳轉(zhuǎn)模式改成 正宗的 模態(tài),代碼如下:
render() {
return (
<View style={styles.container}>
{/* 初始化模態(tài) */}
<Modal
animationType='slide'
transparent={false}
visible={this.state.isModal}
onRequestClose={() => this.onRequestClose()}
>
<Navigator
initialRoute={{
name:'halfHourHot',
component:HalfHourHot
}}
renderScene={(route, navigator) => {
let Component = route.component;
return <Component
removeModal={(data) => this.closeModal(data)}
{...route.params}
navigator={navigator} />
}} />
</Modal>
{/* 導(dǎo)航欄樣式 */}
<CommunalNavBar
leftItem = {() => this.renderLeftItem()}
titleItem = {() => this.renderTitleItem()}
rightItem = {() => this.renderRightItem()}
/>
{/* 根據(jù)網(wǎng)絡(luò)狀態(tài)決定是否渲染 listview */}
{this.renderListView()}
</View>
);
}
注:這邊需要注意一下 逆向傳值 的方式,這里用到最基本的逐層傳值,類似于
block
的功能,具體的代碼參考 Demo , Demo 下載地址在上面。
關(guān)于更詳細(xì)地 model 使用,可以參照官方文檔 model ,當(dāng)然我也給各位上了這道菜 —— React-Native 之 model介紹與使用 。
通過查看 modal 的源碼,我們不難發(fā)現(xiàn) —— 其實 modal 實現(xiàn)原理也只是使用了 絕對定位,所以如果 modal 無法滿足我們的功能,我們可以使用 絕對定位 來自己實現(xiàn)一下類似功能。
加載更多功能完善
這邊我們來完善一下 加載更多功能數(shù)據(jù) 的加載,需要注意的一點就是,拼接數(shù)組需要使用
concat
方法來拼接,它會返回一個 新的數(shù)組 給我們使用,而不修改傳入的數(shù)組。這邊我們加載數(shù)據(jù)的方法分為 2 個,代碼看起來重復(fù)性很高,但是其實這就取決于我們的需求了,我們分為 2 個的好處是看起來更清晰,減少溝通成本,想象一下,如果我們把所有邏輯都放到同一個方法內(nèi),那么是不是這個方法內(nèi)的邏輯是不是特別復(fù)雜,不方便后期維護(hù)?!所以這就是為什么分為 2 個方法進(jìn)行加載的原因。
-
那來看一下加載最新數(shù)據(jù)這邊邏輯:
// 加載最新數(shù)據(jù)網(wǎng)絡(luò)請求 loadData(resolve) { let params = {"count" : 10 }; HTTPBase.get('https://guangdiu.com/api/getlist.php', params) .then((responseData) => { // 清空數(shù)組 this.data = []; // 拼接數(shù)據(jù) this.data = this.data.concat(responseData.data); // 重新渲染 this.setState({ dataSource: this.state.dataSource.cloneWithRows(this.data), loaded:true, }); // 關(guān)閉刷新動畫 if (resolve !== undefined){ setTimeout(() => { resolve(); }, 1000); } // 存儲數(shù)組中最后一個元素的id let cnlastID = responseData.data[responseData.data.length - 1].id; AsyncStorage.setItem('cnlastID', cnlastID.toString()); }) .catch((error) => { }) }
-
再來看下加載更多這邊的邏輯:
加載更多需要在獲取 最新 數(shù)據(jù)的時候?qū)?shù)組中
最后一個元素
內(nèi)的ID保存起來,因為不是大批量數(shù)據(jù)存儲,這邊我們就使用 AsyncStorage 進(jìn)行id
的存儲。接著,我們拼接請求參數(shù)。
// 加載更多數(shù)據(jù)的網(wǎng)絡(luò)請求 loadMoreData(value) { let params = { "count" : 10, "sinceid" : value }; HTTPBase.get('https://guangdiu.com/api/getlist.php', params) .then((responseData) => { // 拼接數(shù)據(jù) this.data = this.data.concat(responseData.data); this.setState({ dataSource: this.state.dataSource.cloneWithRows(this.data), loaded:true, }); // 存儲數(shù)組中最后一個元素的id let cnlastID = responseData.data[responseData.data.length - 1].id; AsyncStorage.setItem('cnlastID', cnlastID.toString()); }) .catch((error) => { }) }
Cell 點擊實現(xiàn)
-
我們回到主頁這邊來實現(xiàn)以下
cell
的點擊,需要注意的是對row
進(jìn)行綁定操作,不然會找不到當(dāng)前的this
。// 綁定 renderRow={this.renderRow.bind(this)}
-
接著來看下
renderRow
方法實現(xiàn):// 返回每一行cell的樣式 renderRow(rowData) { return( <TouchableOpacity onPress={() => this.pushToDetail(rowData.id)} > <CommunalHotCell image={rowData.image} title={rowData.title} /> </TouchableOpacity> ); }
-
再來看下
pushToDetail
方法實現(xiàn),params意思就是將uri
參數(shù)傳遞到CommunalDetail
組件:// 跳轉(zhuǎn)到詳情頁 pushToDetail(value) { this.props.navigator.push({ component:CommunalDetail, params: { uri: 'https://guangdiu.com/api/showdetail.php' + '?' + 'id=' + value } }) }
詳情頁
既然我們已經(jīng)保存了
id
那么就可以來做詳情頁了,當(dāng)我們點擊 cell 的時候,需要跳轉(zhuǎn)到對應(yīng)的 詳情頁 。這邊服務(wù)器返回給我們的是個 網(wǎng)頁數(shù)據(jù) ,我們這邊就直接使用
webView組件
展示,具體使用我們就不多做介紹了,很簡單,詳情就參考官方文檔 WebView。-
先來看詳情頁的實現(xiàn):
export default class GDCommunalDetail extends Component { static propTypes = { uri:PropTypes.string, }; // 返回 pop() { this.props.navigator.pop(); } // 返回左邊按鈕 renderLeftItem() { return( <TouchableOpacity onPress={() => {this.pop()}} > <Text>返回</Text> </TouchableOpacity> ); } componentWillMount() { // 發(fā)送通知 DeviceEventEmitter.emit('isHiddenTabBar', true); } componentWillUnmount() { // 發(fā)送通知 DeviceEventEmitter.emit('isHiddenTabBar', false); } render() { return( <View style={styles.container}> {/* 導(dǎo)航欄 */} <CommunalNavBar leftItem = {() => this.renderLeftItem()} /> {/* 初始化WebView */} <WebView style={styles.webViewStyle} source={{url:this.props.url, method: 'GET' }} javaScriptEnabled={true} domStorageEnabled={true} scalesPageToFit={false} /> </View> ); } } const styles = StyleSheet.create({ container: { flex:1 }, webViewStyle: { flex:1 } });
按照上面的方法,我們完成一下 近半小時熱門模塊 的跳轉(zhuǎn)詳情功能。
海淘半小時熱門
-
和 近半小時熱門 效果是一樣的,只是請求參數(shù)變了,所以 Copy 然后修改下相應(yīng)參數(shù)啊:
export default class GDUSHalfHourHot extends Component { // 構(gòu)造 constructor(props) { super(props); // 初始狀態(tài) this.state = { dataSource: new ListView.DataSource({rowHasChanged:(r1, r2) => r1 !== r2}), loaded:true, }; this.fetchData = this.fetchData.bind(this); } static defaultProps = { removeModal:{} } // 網(wǎng)絡(luò)請求 fetchData(resolve) { let params = { "c" : "us" }; HTTPBase.get('http://guangdiu.com/api/gethots.php', params) .then((responseData) => { this.setState({ dataSource: this.state.dataSource.cloneWithRows(responseData.data), loaded:true, }); if (resolve !== undefined){ setTimeout(() => { resolve(); // 關(guān)閉動畫 }, 1000); } }) .catch((error) => { }) } popToHome(data) { this.props.removeModal(data); } // 返回中間按鈕 renderTitleItem() { return( <Text style={styles.navbarTitleItemStyle}>近半小時熱門</Text> ); } // 返回右邊按鈕 renderRightItem() { return( <TouchableOpacity onPress={()=>{this.popToHome(false)}} > <Text style={styles.navbarRightItemStyle}>關(guān)閉</Text> </TouchableOpacity> ); } // 根據(jù)網(wǎng)絡(luò)狀態(tài)決定是否渲染 listview renderListView() { if (this.state.loaded === false) { return( <NoDataView /> ); }else { return( <PullList onPullRelease={(resolve) => this.fetchData(resolve)} dataSource={this.state.dataSource} renderRow={this.renderRow.bind(this)} showsHorizontalScrollIndicator={false} style={styles.listViewStyle} initialListSize={5} renderHeader={this.renderHeader} /> ); } } // 返回 listview 頭部 renderHeader() { return ( <View style={styles.headerPromptStyle}> <Text>根據(jù)每條折扣的點擊進(jìn)行統(tǒng)計,每5分鐘更新一次</Text> </View> ); } // 跳轉(zhuǎn)到詳情頁 pushToDetail(value) { this.props.navigator.push({ component:CommunalDetail, params: { url: 'https://guangdiu.com/api/showdetail.php' + '?' + 'id=' + value } }) } // 返回每一行cell的樣式 renderRow(rowData) { return( <TouchableOpacity onPress={() => this.pushToDetail(rowData.id)} > <CommunalHotCell image={rowData.image} title={rowData.title} /> </TouchableOpacity> ); } componentWillMount() { // 發(fā)送通知 DeviceEventEmitter.emit('isHiddenTabBar', true); } componentWillUnmount() { // 發(fā)送通知 DeviceEventEmitter.emit('isHiddenTabBar', false); } componentDidMount() { this.fetchData(); } render() { return ( <View style={styles.container}> {/* 導(dǎo)航欄樣式 */} <CommunalNavBar titleItem = {() => this.renderTitleItem()} rightItem = {() => this.renderRightItem()} /> {/* 根據(jù)網(wǎng)絡(luò)狀態(tài)決定是否渲染 listview */} {this.renderListView()} </View> ); } } const styles = StyleSheet.create({ container: { flex:1, alignItems: 'center', }, navbarTitleItemStyle: { fontSize:17, color:'black', marginLeft:50 }, navbarRightItemStyle: { fontSize:17, color:'rgba(123,178,114,1.0)', marginRight:15 }, listViewStyle: { width:width, }, headerPromptStyle: { height:44, width:width, backgroundColor:'rgba(239,239,239,0.5)', justifyContent:'center', alignItems:'center' } });
海淘模塊
-
我們可以發(fā)現(xiàn) 海淘 這一塊和 首頁 是類似的,只是數(shù)據(jù)請求參數(shù)不同,所以我們還是 Copy 一下代碼,然后將請求參數(shù)改為如下:
export default class GDHome extends Component { // 構(gòu)造 constructor(props) { super(props); // 初始狀態(tài) this.state = { dataSource: new ListView.DataSource({rowHasChanged:(r1, r2) => r1 !== r2}), loaded:false, isModal:false }; this.data = []; this.loadData = this.loadData.bind(this); this.loadMore = this.loadMore.bind(this); } // 加載最新數(shù)據(jù)網(wǎng)絡(luò)請求 loadData(resolve) { let params = { "count" : 10, "country" : "us" }; HTTPBase.get('https://guangdiu.com/api/getlist.php', params) .then((responseData) => { // 拼接數(shù)據(jù) this.data = this.data.concat(responseData.data); // 重新渲染 this.setState({ dataSource: this.state.dataSource.cloneWithRows(this.data), loaded:true, }); // 關(guān)閉刷新動畫 if (resolve !== undefined){ setTimeout(() => { resolve(); }, 1000); } // 存儲數(shù)組中最后一個元素的id let uslastID = responseData.data[responseData.data.length - 1].id; AsyncStorage.setItem('uslastID', uslastID.toString()); }) .catch((error) => { }) } // 加載更多數(shù)據(jù)的網(wǎng)絡(luò)請求 loadMoreData(value) { let params = { "count" : 10, "sinceid" : value, "country" : "us" }; HTTPBase.get('https://guangdiu.com/api/getlist.php', params) .then((responseData) => { // 拼接數(shù)據(jù) this.data = this.data.concat(responseData.data); this.setState({ dataSource: this.state.dataSource.cloneWithRows(this.data), loaded:true, }); // 存儲數(shù)組中最后一個元素的id let uslastID = responseData.data[responseData.data.length - 1].id; AsyncStorage.setItem('uslastID', uslastID.toString()); }) .catch((error) => { }) } // 加載更多數(shù)據(jù)操作 loadMore() { // 讀取id AsyncStorage.getItem('uslastID') .then((value) => { // 數(shù)據(jù)加載操作 this.loadMoreData(value); }) } // 模態(tài)到近半小時熱門 pushToHalfHourHot() { this.setState({ isModal:true }) } // 跳轉(zhuǎn)到搜索 pushToSearch() { this.props.navigator.push({ component:Search, }) } // 安卓模態(tài)銷毀處理 onRequestClose() { this.setState({ isModal:false }) } // 關(guān)閉模態(tài) closeModal(data) { this.setState({ isModal:data }) } // 返回左邊按鈕 renderLeftItem() { return( <TouchableOpacity onPress={() => {this.pushToHalfHourHot()}} > <Image source={{uri:'hot_icon_20x20'}} style={styles.navbarLeftItemStyle} /> </TouchableOpacity> ); } // 返回中間按鈕 renderTitleItem() { return( <TouchableOpacity> <Image source={{uri:'navtitle_home_down_66x20'}} style={styles.navbarTitleItemStyle} /> </TouchableOpacity> ); } // 返回右邊按鈕 renderRightItem() { return( <TouchableOpacity onPress={()=>{this.pushToSearch()}} > <Image source={{uri:'search_icon_20x20'}} style={styles.navbarRightItemStyle} /> </TouchableOpacity> ); } // ListView尾部 renderFooter() { return ( <View style={{height: 100}}> <ActivityIndicator /> </View> ); } // 根據(jù)網(wǎng)絡(luò)狀態(tài)決定是否渲染 listview renderListView() { if (this.state.loaded === false) { return( <NoDataView /> ); }else { return( <PullList onPullRelease={(resolve) => this.loadData(resolve)} dataSource={this.state.dataSource} renderRow={this.renderRow.bind(this)} showsHorizontalScrollIndicator={false} style={styles.listViewStyle} initialListSize={5} renderHeader={this.renderHeader} onEndReached={this.loadMore} onEndReachedThreshold={60} renderFooter={this.renderFooter} /> ); } } // 跳轉(zhuǎn)到詳情頁 pushToDetail(value) { this.props.navigator.push({ component:CommunalDetail, params: { url: 'https://guangdiu.com/api/showdetail.php' + '?' + 'id=' + value } }) } // 返回每一行cell的樣式 renderRow(rowData) { return( <TouchableOpacity onPress={() => this.pushToDetail(rowData.id)} > <CommunalHotCell image={rowData.image} title={rowData.title} /> </TouchableOpacity> ); } componentDidMount() { this.loadData(); } render() { return ( <View style={styles.container}> {/* 初始化模態(tài) */} <Modal animationType='slide' transparent={false} visible={this.state.isModal} onRequestClose={() => this.onRequestClose()} > <Navigator initialRoute={{ name:'halfHourHot', component:USHalfHourHot }} renderScene={(route, navigator) => { let Component = route.component; return <Component removeModal={(data) => this.closeModal(data)} {...route.params} navigator={navigator} /> }} /> </Modal> {/* 導(dǎo)航欄樣式 */} <CommunalNavBar leftItem = {() => this.renderLeftItem()} titleItem = {() => this.renderTitleItem()} rightItem = {() => this.renderRightItem()} /> {/* 根據(jù)網(wǎng)絡(luò)狀態(tài)決定是否渲染 listview */} {this.renderListView()} </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', backgroundColor: 'white', }, navbarLeftItemStyle: { width:20, height:20, marginLeft:15, }, navbarTitleItemStyle: { width:66, height:20, }, navbarRightItemStyle: { width:20, height:20, marginRight:15, }, listViewStyle: { width:width, }, });
獲取最新數(shù)據(jù)個數(shù)功能
-
這里需要
cnmaxid
和usmaxid
參數(shù),他們分別是最新數(shù)據(jù)中第一個元素的id
,也就是我們每次 刷新 的時候都保存一下數(shù)組中的第一個元素的id
。// 首頁存儲數(shù)組中第一個元素的id let cnfirstID = responseData.data[0].id; AsyncStorage.setItem('cnfirstID', cnfirstID.toString());
-
這個功能是從程序啟動的時候就開始 定時循環(huán)執(zhí)行 ,也就是我們需要放到 入口文件中(Main文件)。
componentDidMount() { // 注冊通知 this.subscription = DeviceEventEmitter.addListener('isHiddenTabBar', (data)=>{this.tongZhi(data)}); // 聲明變量 let cnfirstID = 0; let usfirstID = 0; // 最新數(shù)據(jù)的個數(shù) setInterval(() => { // 取出id AsyncStorage.getItem('cnfirstID') .then((value) => { cnfirstID = parseInt(value); }); AsyncStorage.getItem('usfirstID') .then((value) => { usfirstID = parseInt(value); }); if (cnfirstID !== 0 && usfirstID !== 0) { // 參數(shù)不為0 // 拼接參數(shù) let params = { "cnmaxid" : cnfirstID, "usmaxid" : usfirstID }; // 請求數(shù)據(jù) HTTPBase.get('http://guangdiu.com/api/getnewitemcount.php', params) .then((responseData) => { console.log(responseData); this.setState({ cnbadgeText:responseData.cn, usbadgeText:responseData.us }) }) } }, 30000); }
注:上面使用到的
setInterval
也是個定時器,和我們之前使用的setTimeout
不同的是,setInterval
是周期定時器,比如上面時間為30000毫秒
,意思就是每過30000毫秒
就會執(zhí)行一次里面的代碼。而setTimeout
則是會在規(guī)定的時間后盡快
執(zhí)行任務(wù)。