前言
- 本文有配套視頻,可以酌情觀看。
- 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯系我。
- 文中所有內容僅供學習交流之用,不可用於商業用途,如因此引起的相關法律法規責任,與我無關。
- 如文中內容對您造成不便,煩請聯系 277511806@qq.com 處理,謝謝。
- 轉載麻煩注明出處,謝謝。
資料:鏈接: https://pan.baidu.com/s/1b3abwy 密碼: k8p5
源碼托管到 github 上,需要源碼的 點我下載,喜歡的話記得 Star,謝謝!
ES5轉ES6
- 關於ES6語法,建議大家可以看下阮老師的 ECMAScriot 6
- 快速了解的話,大家可以參考一下 es6語法快速上手
項目簡介
- 先來看下我們仿照的這款APP的效果:
- 從上圖中,我們可以看出復雜度並不大,但是時間關系我們盡量將所有的模塊都做完,並完善細節。
譯注:
建議打開 視頻 配合 文字 學習,以免有某些細節文中沒有提到,但以文中內容為准(根據反饋進行相應更新)
之所以選擇這款APP,和我個人的愛好有關,當然關鍵還是因為這個APP整體並不復雜,包含了市面上常見APP的樣式,並且很順利地就獲取到所有請求參數和圖片資源,很適合我們體驗 React-native 大致的開發流程。
項目分析
-
在開發APP前,產品經理大致會進行需求的分析,然后開會討論開發過程中需要使用到的技術、會遇到的難點、分配相應任務、傾聽開發人員意見並進行相應的修改,最終確定整體原型圖、開發流程、技術、周期等等,當然其中還有UI的介入,我們沒有產品經理,UI也有現成的,所以大致給大家划分以下幾塊:
-
需求分析:這款APP主要是通過抓取各大電商平台的 商品優惠信息 進行篩選、分類並最終展現給用戶,使用戶可以方便、快捷、實時的獲取高質量的優惠信息。
-
開發模型:我們這邊類似基於 原型模型 開發。
-
使用的技術:React-Native
-
功能模塊:主要分為 首頁、海淘模塊、小時風雲榜 三大模塊等其它附屬模塊(酌情增加)。
-
整體架構:
- 主體:由 TabBar 作為主體框架,以 首頁、海淘模塊、小時風雲榜 為整體模塊,根據 原型圖 的效果選擇相應的跳轉方式
- 數據展示:根據 原型圖 選擇相應的數據展示方式
-
命名規則:參考 編碼規范文檔(不同公司之間都有差異,具體看公司提供的文檔,這邊先遵守下面提到的規則即可)
-
-
測試:MDZZ,誰測試→→!
工程環境配置
-
所有需要用到的資源點擊下載。
-
首先,來配置 iOS 端。
-
將壓縮包內的 Images.xcassets 文件夾直接替換掉我們iOS工程中的 Images.xcassets 文件夾。
-
這時候我們可以看到所有圖片資源已經成功導入到iOS工程中,接着我們點擊工程文件進行一些必要的配置。
-
General
——App Icons and Launch Images
—— 修改Launch Images Source
為Images.xcassets
文件夾內的 LaunchImage ,清除Launch Screen File
內容。 -
General
——Deployment Info
——Device Orientation
—— 只保留 Portrait 選項。 -
打開 info.plist 文件,找到 Bundle name 選項,將其內容修改為 逛丟學習
-
打開 info.plist 文件,找到 App Transport Security Settings 選項,給其添加 Allow Arbitrary Loads 選項並設置內容為 YES (如果使用
IPV6標准
可以忽略這一步) -
OK,至此 iOS 端配置完畢。
-
接着,來配置 Android 端。
-
將壓縮包內的 drawable-xxhdpi 文件夾復制粘貼到 GD/android/app/src/main/res/ 中。
-
設置 APP圖標 進入 GD/android/app/sec/ 打開 AndroidManifest 文件,修改 android:icon 項,如下:
<applicatio> android:icon="@drawable/icon" </application>
-
設置 APP名稱 進入 GD/android/app/src/main/res/values/ 中,打開 strings.xml 文件,做如下修改:
<resources> <string name="app_name">逛丟學習</string> </resources>
-
OK,至此 Android 配置完畢。
目錄結構與命名規則
- 為了方便理解,我們這邊先不按照常規的React-native開發結構進行開發,后續章節再慢慢轉變
- 這邊我們將文件分為 main(入口)、home(首頁)、ht(海淘)、hourList(小時風雲榜) 4大部分,將相關的文件放入對應的文件夾,避免開發中頻繁切換文檔給新手帶來煩躁感
- 命名規則:
- 文件夾命名方式我們就跟着 React-Native 默認的方式,采用 小寫 + 下划線 進行命名
- 文件命名方式我們采用 前綴(大寫) + 模塊名稱(帕斯卡) 的方式進行命名
- 函數、常量、變量等使用 駝峰命名規則
目錄結構:
譯注:
第三方框架
-
這邊來講下在 React-Native 中怎么導入第三方框架
-
首先,第三方框架肯定是要到 GitHub 找嘍。
-
在搜索框內搜索 react-native-tab-navigator 。
-
在下面的 說明 中告訴我們了,使用終端 —— 進到工程的主目錄下 —— 復制命令行()—— 回車 —— 等待下載完成就導入到工程中了。
-
到此,第三方框架導入完成,使用在下面會提到。
主體框架搭建
-
上面提到使用 TabBar 作為主體框架,但是官方只提供了iOS端的 TabBarIOS ,時間原因為了加快開發進度,並且順帶講解 第三方框架使用 所以我們使用 <react-native-tab-navigator>進行開發
-
既然要使用框架,肯定要先引入框架文件。
// 引用第三方框架
import TabNavigator from 'react-native-tab-navigator';
- 根據 使用說明 文檔可以看出,使用方法和官方的 TabBarIOS 類似(不清楚的麻煩參考React Native 之 TabBarIOS和TabBarIOS.Item使用),所以我們把 三大模塊 添加進TabBar,並且各個模塊都是以 Navigator 的形式存在。
export default class GD extends Component {
// ES6
// 構造
constructor(props) {
super(props);
// 初始狀態
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 })}>
// 添加導航功能
<Navigator
// 設置路由
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)}
{/* 小時風雲榜 */}
{this.renderTabBarItem("小時風雲榜", '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,
}
});
- 至此,主體框架搭建完畢。
自定義導航欄樣式
-
從效果圖中可以看出,導航欄的樣式都差不多,因為我們前面已經設置了 Navigator ,這邊的話我們還需要自定義 Navigator 的樣式,可以看到所有的 Navigator 樣式都是相近的,所以這邊我們就抽出來,讓所有的 Navigator 共用一個組件就可以了。
-
那么首先我們在 main 文件夾中創建 GDCommunalNavBar 文件並初始化一下里面基本的內容
-
接着,我們來看下首頁的導航欄,首頁導航欄分別有左中右三個按鈕,左邊為半小時熱門,中間為點擊下拉顯示支持篩選的平台的列表,右邊則是商品搜索,通常 Navigator 也只有這3個組件,為了使用者高度地自定義,這邊我們只在 currencyNavBar 中設置3個組件的布局,然后提供接口,獲取外部傳入的值,並在內部判斷是否需要創建相應的組件。
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,
},
});
- 這邊我們就已經完成了 Navigator 的樣式,我們到首頁來用一下,看好不好用,使用這邊就不說了(1.引用外部文件;2.<CommunalNavBar ...參數/>)
![Upload 自定義Navigator樣式.gif failed. Please try again.]
首頁半小時熱門
- 這邊我們就先從 半小時熱門 開始,像這樣的數據展示,我們肯定是優先選擇 ListView ,其中,cell 的樣式分解如下:
-
我們先將數據請求下來,確定正確獲取到數據后,再來定義 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 {
// 構造
constructor(props) {
super(props);
// 初始狀態
this.state = {
dataSource: new ListView.DataSource({rowHasChanged:(r1, r2) => r1 !== r2}),
};
// 綁定
this.fetchData = this.fetchData.bind(this);
}
// 網絡請求
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}>近半小時熱門</Text>
);
}
// 返回右邊按鈕
renderRightItem() {
return(
<TouchableOpacity
onPress={()=>{this.popToHome()}}
>
<Text style={styles.navbarRightItemStyle}>關閉</Text>
</TouchableOpacity>
);
}
// 返回每一行cell的樣式
renderRow(rowData) {
return(
<CommunalHotCell
image={rowData.image}
title={rowData.title}
/>
);
}
componentDidMount() {
this.fetchData();
}
render() {
return (
<View style={styles.container}>
{/* 導航欄樣式 */}
<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,
}
});
- 從效果圖中可以看出,我們還少了上面的提示標題,這邊很簡單,我們也來快速完成一些
{/* 頂部提示 */}
<View style={styles.headerPromptStyle}>
<Text>根據每條折扣的點擊進行統計,每5分鍾更新一次</Text>
</View>
樣式部分:
headerPromptStyle: {
height:44,
width:width,
backgroundColor:'rgba(239,239,239,0.5)',
justifyContent:'center',
alignItems:'center'
}
隱藏於顯示TabBar之通知的使用
- 配置TabBar隱藏與顯示條件
// ES6
// 構造
constructor(props) {
super(props);
// 初始狀態
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)}
{/* 小時風雲榜 */}
{this.renderTabBarItem("小時風雲榜", 'hourlist', 'tabbar_rank_30x30', 'tabbar_rank_selected_30x30', HourList)}
</TabNavigator>
-
這邊我們引入新的知識 —— 通知
-
使用通知很簡單,首先需要注冊通知並在適當的地方進行銷毀
componentDidMount() {
// 注冊通知
this.subscription = DeviceEventEmitter.addListener('isHiddenTabBar', (data)=>{this.tongZhi(data)});
}
componentWillUnmount() {
// 銷毀
this.subscription.remove();
}
- 接着在我們需要的地方發送通知
componentWillMount() {
// 發送通知
DeviceEventEmitter.emit('isHiddenTabBar', true);
}
componentWillUnmount() {
// 發送通知
DeviceEventEmitter.emit('isHiddenTabBar', false);
}