前言
- 本文有配套視頻,可以酌情觀看拇惋。
- 文中內(nèi)容因各人理解不同,可能會(huì)有所偏差抹剩,歡迎朋友們聯(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/1b3abwy 密碼: k8p5
源碼托管到 github 上,需要源碼的 點(diǎn)我下載祭埂,喜歡的話記得 Star面氓,謝謝!
ES5轉(zhuǎn)ES6
- 關(guān)于ES6語法,建議大家可以看下阮老師的 ECMAScriot 6
- 快速了解的話侧但,大家可以參考一下 es6語法快速上手
項(xiàng)目簡介
- 先來看下我們仿照的這款A(yù)PP的效果:
- 從上圖中矢空,我們可以看出復(fù)雜度并不大,但是時(shí)間關(guān)系我們盡量將所有的模塊都做完禀横,并完善細(xì)節(jié)屁药。
譯注:
建議打開 視頻 配合 文字 學(xué)習(xí),以免有某些細(xì)節(jié)文中沒有提到柏锄,但以文中內(nèi)容為準(zhǔn)(根據(jù)反饋進(jìn)行相應(yīng)更新)
之所以選擇這款A(yù)PP酿箭,和我個(gè)人的愛好有關(guān),當(dāng)然關(guān)鍵還是因?yàn)檫@個(gè)APP整體并不復(fù)雜趾娃,包含了市面上常見APP的樣式缭嫡,并且很順利地就獲取到所有請求參數(shù)和圖片資源,很適合我們體驗(yàn) React-native 大致的開發(fā)流程抬闷。
項(xiàng)目分析
-
在開發(fā)APP前妇蛀,產(chǎn)品經(jīng)理大致會(huì)進(jìn)行需求的分析,然后開會(huì)討論開發(fā)過程中需要使用到的技術(shù)笤成、會(huì)遇到的難點(diǎn)评架、分配相應(yīng)任務(wù)、傾聽開發(fā)人員意見并進(jìn)行相應(yīng)的修改炕泳,最終確定整體原型圖纵诞、開發(fā)流程、技術(shù)培遵、周期等等浙芙,當(dāng)然其中還有UI的介入,我們沒有產(chǎn)品經(jīng)理籽腕,UI也有現(xiàn)成的嗡呼,所以大致給大家劃分以下幾塊:
需求分析:這款A(yù)PP主要是通過抓取各大電商平臺(tái)的 商品優(yōu)惠信息 進(jìn)行篩選、分類并最終展現(xiàn)給用戶节仿,使用戶可以方便晤锥、快捷、實(shí)時(shí)的獲取高質(zhì)量的優(yōu)惠信息廊宪。
開發(fā)模型:我們這邊類似基于 原型模型 開發(fā)矾瘾。
使用的技術(shù):React-Native
功能模塊:主要分為 首頁、海淘模塊箭启、小時(shí)風(fēng)云榜 三大模塊等其它附屬模塊(酌情增加)壕翩。
-
整體架構(gòu):
- 主體:由 TabBar 作為主體框架,以 首頁傅寡、海淘模塊放妈、小時(shí)風(fēng)云榜 為整體模塊北救,根據(jù) 原型圖 的效果選擇相應(yīng)的跳轉(zhuǎn)方式
- 數(shù)據(jù)展示:根據(jù) 原型圖 選擇相應(yīng)的數(shù)據(jù)展示方式
命名規(guī)則:參考 編碼規(guī)范文檔(不同公司之間都有差異,具體看公司提供的文檔芜抒,這邊先遵守下面提到的規(guī)則即可)
測試:MDZZ珍策,誰測試→→!
工程環(huán)境配置
所有需要用到的資源點(diǎn)擊下載宅倒。
首先攘宙,來配置 iOS 端。
將壓縮包內(nèi)的 Images.xcassets 文件夾直接替換掉我們iOS工程中的 Images.xcassets 文件夾拐迁。
這時(shí)候我們可以看到所有圖片資源已經(jīng)成功導(dǎo)入到iOS工程中蹭劈,接著我們點(diǎn)擊工程文件進(jìn)行一些必要的配置。
General
——App Icons and Launch Images
—— 修改Launch Images Source
為Images.xcassets
文件夾內(nèi)的 LaunchImage 线召,清除Launch Screen File
內(nèi)容铺韧。General
——Deployment Info
——Device Orientation
—— 只保留 Portrait 選項(xiàng)。打開 info.plist 文件缓淹,找到 Bundle name 選項(xiàng)哈打,將其內(nèi)容修改為 逛丟學(xué)習(xí)
打開 info.plist 文件,找到 App Transport Security Settings 選項(xiàng)割卖,給其添加 Allow Arbitrary Loads 選項(xiàng)并設(shè)置內(nèi)容為 YES (如果使用
IPV6標(biāo)準(zhǔn)
可以忽略這一步)OK,至此 iOS 端配置完畢前酿。
接著,來配置 Android 端鹏溯。
將壓縮包內(nèi)的 drawable-xxhdpi 文件夾復(fù)制粘貼到 GD/android/app/src/main/res/ 中。
-
設(shè)置 APP圖標(biāo) 進(jìn)入 GD/android/app/sec/ 打開 AndroidManifest 文件淹仑,修改 android:icon 項(xiàng)丙挽,如下:
<applicatio> android:icon="@drawable/icon" </application>
-
設(shè)置 APP名稱 進(jìn)入 GD/android/app/src/main/res/values/ 中,打開 strings.xml 文件匀借,做如下修改:
<resources> <string name="app_name">逛丟學(xué)習(xí)</string> </resources>
OK,至此 Android 配置完畢颜阐。
目錄結(jié)構(gòu)與命名規(guī)則
- 為了方便理解,我們這邊先不按照常規(guī)的React-native開發(fā)結(jié)構(gòu)進(jìn)行開發(fā)吓肋,后續(xù)章節(jié)再慢慢轉(zhuǎn)變
- 這邊我們將文件分為 main(入口)凳怨、home(首頁)、ht(海淘)是鬼、hourList(小時(shí)風(fēng)云榜) 4大部分肤舞,將相關(guān)的文件放入對應(yīng)的文件夾,避免開發(fā)中頻繁切換文檔給新手帶來煩躁感
- 命名規(guī)則:
- 文件夾命名方式我們就跟著 React-Native 默認(rèn)的方式均蜜,采用 小寫 + 下劃線 進(jìn)行命名
- 文件命名方式我們采用 前綴(大寫) + 模塊名稱(帕斯卡) 的方式進(jìn)行命名
- 函數(shù)李剖、常量、變量等使用 駝峰命名規(guī)則
目錄結(jié)構(gòu):
譯注:
第三方框架
這邊來講下在 React-Native 中怎么導(dǎo)入第三方框架
首先宰僧,第三方框架肯定是要到 GitHub 找嘍材彪。
在搜索框內(nèi)搜索 react-native-tab-navigator 。
在下面的 說明 中告訴我們了撒桨,使用終端 —— 進(jìn)到工程的主目錄下 —— 復(fù)制命令行()—— 回車 —— 等待下載完成就導(dǎo)入到工程中了查刻。
到此,第三方框架導(dǎo)入完成凤类,使用在下面會(huì)提到穗泵。
主體框架搭建
上面提到使用 TabBar 作為主體框架,但是官方只提供了iOS端的 TabBarIOS 谜疤,時(shí)間原因?yàn)榱思涌扉_發(fā)進(jìn)度佃延,并且順帶講解 第三方框架使用 所以我們使用 <react-native-tab-navigator>進(jìn)行開發(fā)
既然要使用框架,肯定要先引入框架文件夷磕。
// 引用第三方框架
import TabNavigator from 'react-native-tab-navigator';
- 根據(jù) 使用說明 文檔可以看出履肃,使用方法和官方的 TabBarIOS 類似(不清楚的麻煩參考React Native 之 TabBarIOS和TabBarIOS.Item使用),所以我們把 三大模塊 添加進(jìn)TabBar坐桩,并且各個(gè)模塊都是以 Navigator 的形式存在尺棋。
export default class GD extends Component {
// ES6
// 構(gòu)造
constructor(props) {
super(props);
// 初始狀態(tài)
this.state = {
selectedTab:'home',
};
}
// 返回TabBar的Item
renderTabBarItem(title, selectedTab, image, selectedImage, component) {
return(
<TabNavigator.Item
selected={this.state.selectedTab === selectedTab}
title={title}
selectedTitleStyle={{color:'black'}}
renderIcon={() => <Image source={{uri:image}} style={styles.tabbarIconStyle} />}
renderSelectedIcon={() => <Image source={{uri:selectedImage}} style={styles.tabbarIconStyle} />}
onPress={() => this.setState({ selectedTab: selectedTab })}>
// 添加導(dǎo)航功能
<Navigator
// 設(shè)置路由
initialRoute={{
name:selectedTab,
component:component
}}
renderScene={(route, navigator) => {
let Component = route.component;
return <Component {...route.params} navigator={navigator} />
}}
/>
</TabNavigator.Item>
);
}
render() {
return (
<TabNavigator>
{/* 首頁 */}
{this.renderTabBarItem("首頁", 'home', 'tabbar_home_30x30', 'tabbar_home_selected_30x30', Home)}
{/* 海淘 */}
{this.renderTabBarItem("海淘", 'ht', 'tabbar_abroad_30x30', 'tabbar_abroad_selected_30x30', HT)}
{/* 小時(shí)風(fēng)云榜 */}
{this.renderTabBarItem("小時(shí)風(fēng)云榜", 'hourlist', 'tabbar_rank_30x30', 'tabbar_rank_selected_30x30', HourList)}
</TabNavigator>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
tabbarIconStyle: {
width:Platform.OS === 'ios' ? 30 : 25,
height:Platform.OS === 'ios' ? 30 : 25,
}
});
- 至此,主體框架搭建完畢绵跷。
自定義導(dǎo)航欄樣式
從效果圖中可以看出膘螟,導(dǎo)航欄的樣式都差不多,因?yàn)槲覀兦懊嬉呀?jīng)設(shè)置了 Navigator 碾局,這邊的話我們還需要自定義 Navigator 的樣式荆残,可以看到所有的 Navigator 樣式都是相近的,所以這邊我們就抽出來净当,讓所有的 Navigator 共用一個(gè)組件就可以了内斯。
那么首先我們在 main 文件夾中創(chuàng)建 GDCommunalNavBar 文件并初始化一下里面基本的內(nèi)容
接著,我們來看下首頁的導(dǎo)航欄像啼,首頁導(dǎo)航欄分別有左中右三個(gè)按鈕俘闯,左邊為半小時(shí)熱門,中間為點(diǎn)擊下拉顯示支持篩選的平臺(tái)的列表埋合,右邊則是商品搜索备徐,通常 Navigator 也只有這3個(gè)組件,為了使用者高度地自定義甚颂,這邊我們只在 currencyNavBar 中設(shè)置3個(gè)組件的布局蜜猾,然后提供接口秀菱,獲取外部傳入的值,并在內(nèi)部判斷是否需要?jiǎng)?chuàng)建相應(yīng)的組件蹭睡。
export default class GDCommunalNavBar extends Component {
static propTypes = {
leftItem:PropTypes.func,
titleItem:PropTypes.func,
rightItem:PropTypes.func,
};
// 左邊
renderLeftItem() {
if (this.props.leftItem === undefined) return;
return this.props.leftItem();
}
// 中間
renderTitleItem() {
if (this.props.titleItem === undefined) return;
return this.props.titleItem();
}
// 右邊
renderRightItem() {
if (this.props.rightItem === undefined) return;
return this.props.rightItem();
}
render() {
return (
<View style={styles.container}>
{/* 左邊 */}
<View>
{this.renderLeftItem()}
</View>
{/* 中間 */}
<View>
{this.renderTitleItem()}
</View>
{/* 右邊 */}
<View>
{this.renderRightItem()}
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
width:width,
height:Platform.OS === 'ios' ? 64 : 44,
backgroundColor:'white',
flexDirection:'row',
justifyContent:'space-between',
alignItems:'center',
borderBottomWidth:0.5,
borderBottomColor:'gray',
paddingTop:Platform.OS === 'ios' ? 15 : 0,
},
});
- 這邊我們就已經(jīng)完成了 Navigator 的樣式衍菱,我們到首頁來用一下,看好不好用肩豁,使用這邊就不說了(1.引用外部文件脊串;2.<CommunalNavBar ...參數(shù)/>)
![Upload 自定義Navigator樣式.gif failed. Please try again.]
首頁半小時(shí)熱門
- 這邊我們就先從 半小時(shí)熱門 開始,像這樣的數(shù)據(jù)展示清钥,我們肯定是優(yōu)先選擇 ListView 琼锋,其中,cell 的樣式分解如下:
我們先將數(shù)據(jù)請求下來祟昭,確定正確獲取到數(shù)據(jù)后缕坎,再來定義 cell 的樣式。
接下來我們來自定義一下 cell 樣式
export default class GDCommunalNavBar extends Component {
static propTypes = {
image:PropTypes.string,
title:PropTypes.string,
};
render() {
return (
<View style={styles.container}>
{/* 左邊圖片 */}
<Image source={{uri:this.props.image}} style={styles.imageStyle} />
{/* 中間的文中 */}
<View>
<Text numberOfLines={3} style={styles.titleStyle}>{this.props.title}</Text>
</View>
{/* 右邊的箭頭 */}
<Image source={{uri:'icon_cell_rightArrow'}} style={styles.arrowStyle} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flexDirection:'row',
alignItems:'center',
justifyContent:'space-between',
backgroundColor:'white',
height:100,
width:width,
borderBottomWidth:0.5,
borderBottomColor:'gray',
marginLeft:15
},
imageStyle: {
width:70,
height:70,
},
titleStyle: {
width:width * 0.65,
},
arrowStyle: {
width:10,
height:10,
marginRight:30
}
});
- 好了篡悟,到這里 cell 樣式也定義完成并且效果是一樣的谜叹。
export default class GDHalfHourHot extends Component {
// 構(gòu)造
constructor(props) {
super(props);
// 初始狀態(tài)
this.state = {
dataSource: new ListView.DataSource({rowHasChanged:(r1, r2) => r1 !== r2}),
};
// 綁定
this.fetchData = this.fetchData.bind(this);
}
// 網(wǎng)絡(luò)請求
fetchData() {
fetch('http://guangdiu.com/api/gethots.php')
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.data)
});
})
.done()
}
popToHome() {
this.props.navigator.pop();
}
// 返回中間按鈕
renderTitleItem() {
return(
<Text style={styles.navbarTitleItemStyle}>近半小時(shí)熱門</Text>
);
}
// 返回右邊按鈕
renderRightItem() {
return(
<TouchableOpacity
onPress={()=>{this.popToHome()}}
>
<Text style={styles.navbarRightItemStyle}>關(guān)閉</Text>
</TouchableOpacity>
);
}
// 返回每一行cell的樣式
renderRow(rowData) {
return(
<CommunalHotCell
image={rowData.image}
title={rowData.title}
/>
);
}
componentDidMount() {
this.fetchData();
}
render() {
return (
<View style={styles.container}>
{/* 導(dǎo)航欄樣式 */}
<CommunalNavBar
titleItem = {() => this.renderTitleItem()}
rightItem = {() => this.renderRightItem()}
/>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
showsHorizontalScrollIndicator={false}
style={styles.listViewStyle}
/>
</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,
}
});
- 從效果圖中可以看出,我們還少了上面的提示標(biāo)題搬葬,這邊很簡單荷腊,我們也來快速完成一些
{/* 頂部提示 */}
<View style={styles.headerPromptStyle}>
<Text>根據(jù)每條折扣的點(diǎn)擊進(jìn)行統(tǒng)計(jì),每5分鐘更新一次</Text>
</View>
樣式部分:
headerPromptStyle: {
height:44,
width:width,
backgroundColor:'rgba(239,239,239,0.5)',
justifyContent:'center',
alignItems:'center'
}
隱藏于顯示TabBar之通知的使用
- 配置TabBar隱藏與顯示條件
// ES6
// 構(gòu)造
constructor(props) {
super(props);
// 初始狀態(tài)
this.state = {
selectedTab:'home',
isHiddenTabBar:false, // 是否隱藏tabbar
};
}
<TabNavigator
tabBarStyle={this.state.isHiddenTabBar !== true ? {} : {height:0, overflow:'hidden'}}
sceneStyle={this.state.isHiddenTabBar !== true ? {} : {paddingBottom:0}}
>
{/* 首頁 */}
{this.renderTabBarItem("首頁", 'home', 'tabbar_home_30x30', 'tabbar_home_selected_30x30', Home)}
{/* 海淘 */}
{this.renderTabBarItem("海淘", 'ht', 'tabbar_abroad_30x30', 'tabbar_abroad_selected_30x30', HT)}
{/* 小時(shí)風(fēng)云榜 */}
{this.renderTabBarItem("小時(shí)風(fēng)云榜", 'hourlist', 'tabbar_rank_30x30', 'tabbar_rank_selected_30x30', HourList)}
</TabNavigator>
這邊我們引入新的知識 —— 通知
使用通知很簡單,首先需要注冊通知并在適當(dāng)?shù)牡胤竭M(jìn)行銷毀
componentDidMount() {
// 注冊通知
this.subscription = DeviceEventEmitter.addListener('isHiddenTabBar', (data)=>{this.tongZhi(data)});
}
componentWillUnmount() {
// 銷毀
this.subscription.remove();
}
- 接著在我們需要的地方發(fā)送通知
componentWillMount() {
// 發(fā)送通知
DeviceEventEmitter.emit('isHiddenTabBar', true);
}
componentWillUnmount() {
// 發(fā)送通知
DeviceEventEmitter.emit('isHiddenTabBar', false);
}