最近的開發中要用到不少的各式各樣的組件。可是發現ant design mobile(後面簡稱ANTDM)裏不少的資源。因而就分析一下,學習學習。html
ANTDM直接使用了typescript,沒有用ES2015,不過這不會是障礙,反而是學習typescript的一個好機會。基本上能夠學的開源項目裏比這個好的也很少。react
Popover
組件在:git
| |--components | |--popover
咱們要分析的組件所有都在components這個目錄下。github
在這個目錄裏還包含tests, demo和style。裏面分別存放測試代碼、實例和樣式。其餘的文件包括*[component name]_native.tsx和[component name].txs以及對應的index.native.tsx和index.tsx,方便外部引入組件。typescript
這個是最核心的問題了!react-native
實現React Native的彈出菜單,須要達到在界面上的某個可點擊組件上點擊了以後,就能夠在被點擊的組件緊挨着的下方出現一個菜單(其餘的計算,好比彈出菜單在左下、右下,左上,右上的位置計算暫時不提)。app
用戶點擊了哪一個組件(按鈕),哪一個按鈕的下面就出現一個菜單(View)。這就須要肯定點擊組件的位置。源碼分析
咱們看一下index.native.tsx這個文件。文件裏基本上沒幾行代碼,直接看render
方法裏返回的是MenuContext
等。也就是,這個文件沒實現什麼pop over須要的什麼東西。都在import裏了:學習
import Menu, { MenuContext, MenuOptions, MenuOption, MenuTrigger }from 'react-native-menu';
因此ANTDM的源碼分析到此爲止。測試
咱們要跳到react-native-menu
。咱們分析代碼的方式就是無限遞歸,一直找到實現功能的代碼爲止。那麼咱們就能夠分析react-native-menu
了。
這個項目的寫法也是很不一樣。用的是比較老的ES5的React版本。github地址在這裏。
這個項目裏不少的文件,各位能夠後面慢慢看。咱們來看makeMenuContext.js。
在這個項目裏,除了index.js以外都是叫作makeXXX.js。裏面都是HOC的實現方式。並且更加Trick的是HOC的前兩個參數是React
和ReactNative
。
回到makeMenuContext.js,在openMenu()
這個方法裏就有實現的方式。這就是咱們尋找代碼遞歸跳出的地方。咱們來看一下實現方式:
openMenu(name) { const handle = ReactNative.findNodeHandle(this._menus[name].ref); UIManager.measure(handle, (x, y, w, h, px, py) => { this._menus[name].measurements = { x, y, w, h, px, py }; this.setState({ openedMenu: name, menuOptions: this._makeAndPositionOptions(name, this._menus[name].measurements), backdropWidth: this._ownMeasurements.w }); this._activeMenuHooks = this._menus[name]; this._activeMenuHooks && this._activeMenuHooks.didOpen(); }); },
這裏使用了UIManager
,來自:
const { UIManager, TouchableWithoutFeedback, ScrollView, View, BackHandler } = ReactNative
用現代一點的寫法的話就是:import { UIManager } from 'react-native';
。
使用的時候是這麼用的:
const handle = ReactNative.findNodeHandle(this._menus[name].ref); UIManager.measure(handle, (x, y, w, h, px, py) => { // x, y, width, height, pageX, pageY });
measure()
方法的回調裏獲得的就是該組件對於Screen的位置。還有其餘的measureXXX()
方法在這裏能夠看到。
measure獲得的x,y,w,h,px,py是這個組件的左上角座標(x,y)和寬、高。在這個measure方法裏獲得的px和py與這個組件的左上角座標值同樣。
注意:measure的時候,只有在原生視圖完成繪製以後纔會返回值。
因此,若是要快點獲得一個組件在screen上的座標值的話,那麼能夠這樣:
<View onLayout={this.onLayout}> </View> // onLayout onLayout() { const handle = ReactNative.findNodeHandle(this.refs.Container); UIManager.measure(handle, (x, y, w, h, px, py) => { this._ownMeasurements = {x, y, w, h, px, py}; }); }
因此,在彈出菜單的組件上使用onLayout
props獲得它的位置。
注意:
they(measureXXX方法) are not available on composite components that aren't directly backed by a native view.
大意是,若是組合組件的最外層不是一個原生view的話,measureXXX()
方法是無法用的!!
那麼measure方法的第一個參數,也就是measure的目標組件如何得到呢?代碼在這裏:const handle = ReactNative.findNodeHandle(this._menus[name].ref);
。在findNodeHandle()
方法的參數是組件的ref
。那麼,經過組件的ref能夠獲得組件的handle。在經過這個handle
就能夠來measure組件,獲得這個組件的位置、寬高等數據。
到這裏咱們就知道如何來算出觸發組件的位置了。可是,這個直接使用UIManager
的方法太複雜了。
基本上,組件能夠直接調用measure方法。咱們來簡單的實現一下這個彈出菜單的功能。
無論單詞對錯了。總之是重寫一次。簡化版的!爲了篇幅足夠長,我就把代碼都貼出來了。哈哈~
/** * Created by Uncle Charlie, 2018/03/01 * @flow */ import React from 'react'; import { TouchableOpacity, Text, View, StyleSheet } from 'react-native'; type Prop = { text: ?string, onPress: (e?: any) => void, styles?: { button: any, text: any }, }; export default class Button extends React.Component<Prop, {}> { static defaultProps = { text: 'Show Menu', }; handlePress = () => { const { onPress } = this.props; if (!this.container) { console.error('container view is empty'); return; } this.container.measure((x, y, w, h, px, py) => { console.log('===>measure', { x, y, w, h, px, py }); onPress && onPress({ left: x, top: y + h }); }); }; onLayout = () => {}; render() { const { text, styles } = this.props; const wrapper = styles && styles.wrapper ? styles.wrapper : innerStyles.wrapper; return ( <View style={wrapper} onLayout={this.onLayout} ref={container => (this.container = container)} > <TouchableOpacity onPress={this.handlePress}> <View> <Text>{text}</Text> </View> </TouchableOpacity> </View> ); } } const innerStyles = StyleSheet.create({ wrapper: { justifyContent: 'center', alignItems: 'center', backgroundColor: 'green', }, });
這個簡化版的實現思路就是:
TouchableOpacity
)的時候measure按鈕組件在measure組件以前,首先要得到這個組件的ref。
render() { // ... return ( <View ref={container => (this.container = container)} > // ... </View> ); }
獲得的ref就是this.container
。
handlePress = () => { const { onPress } = this.props; if (!this.container) { console.error('container view is empty'); return; } this.container.measure((x, y, w, h, px, py) => { console.log('===>measure', { x, y, w, h, px, py }); onPress && onPress({ left: x, top: y + h }); }); };
在點擊按鈕以後開始measure。直接在得到的ref上調用measure方法就能夠:this.container.measure
。得到measure的結果以後,調用props傳過來的方法onPress
把須要用到的數據傳過去。
renderMenu = () => { const { top, left, open } = this.state; if (!open) { return null; } return ( <View style={{ position: 'absolute', left, top, width: 100, height: 200, backgroundColor: 'rgba(52, 52, 52, 0.8)', }} > <Text>Menu</Text> </View> ); };
咱們要View顯示在一個特定的位置的時候,須要在style裏設置位置模式爲position: 'absolute'
,也就是啓用絕對定位。
上面的left
、和top
就是菜單的具體位置。寬、高暫時hard code了(簡化版。。。)。
這樣就一個popover,超級簡化版的,就完成了。所有的代碼在這裏。
咱們在前文中說道過一個更好的得到觸發組件的位置的方式,onLayout
。這個方法是空的。各位能夠試着完成這個方法,或者所有完成這個popover組件做爲練習。