最近的開發(fā)中要用到很多的各式各樣的組件产阱。但是發(fā)現(xiàn)ant design mobile(后面簡稱ANTDM)里很多的資源做葵。于是就分析一下,學習學習心墅。
ANTDM直接使用了typescript,沒有用ES2015榨乎,不過這不會是障礙怎燥,反而是學習typescript的一個好機會∶凼睿基本上可以學的開源項目里比這個好的也不多铐姚。
目錄結構
Popover
組件在:
|
|--components
|
|--popover
我們要分析的組件全部都在components這個目錄下。
在這個目錄里還包含tests, demo和style肛捍。里面分別存放測試代碼隐绵、實例和樣式。其他的文件包括[component name]_native.tsx和[component name].txs以及對應的index.native.tsx和index.tsx*拙毫,方便外部引入組件依许。
計算點擊組件的位置
這個是最核心的問題了!
實現(xiàn)React Native的彈出菜單缀蹄,需要達到在界面上的某個可點擊組件上點擊了之后峭跳,就可以在被點擊的組件緊挨著的下方出現(xiàn)一個菜單(其他的計算,比如彈出菜單在左下缺前、右下蛀醉,左上,右上的位置計算暫時不提)衅码。
用戶點擊了哪個組件(按鈕)拯刁,哪個按鈕的下面就出現(xiàn)一個菜單(View)。這就需要確定點擊組件的位置逝段。
我們看一下index.native.tsx這個文件垛玻。文件里基本上沒幾行代碼割捅,直接看render
方法里返回的是MenuContext
等。也就是夭谤,這個文件沒實現(xiàn)什么pop over需要的什么東西棺牧。都在import里了:
import Menu, { MenuContext, MenuOptions, MenuOption, MenuTrigger }from 'react-native-menu';
所以ANTDM的源碼分析到此為止。
我們要跳到react-native-menu
朗儒。我們分析代碼的方式就是無限遞歸颊乘,一直找到實現(xiàn)功能的代碼為止。那么我們就可以分析react-native-menu
了醉锄。
react-native-menu
這個項目的寫法也是很不同乏悄。用的是比較老的ES5的React版本。github地址在這里恳不。
這個項目里很多的文件檩小,各位可以后面慢慢看。我們來看makeMenuContext.js烟勋。
在這個項目里规求,除了index.js之外都是叫做makeXXX.js。里面都是HOC的實現(xiàn)方式卵惦。而且更加Trick的是HOC的前兩個參數(shù)是React
和ReactNative
阻肿。
回到makeMenuContext.js,在openMenu()
這個方法里就有實現(xiàn)的方式沮尿。這就是我們尋找代碼遞歸跳出的地方丛塌。我們來看一下實現(xiàn)方式:
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
用現(xiàn)代一點的寫法的話就是: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()
方法的回調(diào)里得到的就是該組件對于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()
方法是沒法用的V肀贰箱沦!
那么measure方法的第一個參數(shù),也就是measure的目標組件如何獲得呢雇庙?代碼在這里:const handle = ReactNative.findNodeHandle(this._menus[name].ref);
谓形。在findNodeHandle()
方法的參數(shù)是組件的ref
。那么疆前,通過組件的ref可以得到組件的handle寒跳。在通過這個handle
就可以來measure組件,得到這個組件的位置竹椒、寬高等數(shù)據(jù)童太。
到這里我們就知道如何來算出觸發(fā)組件的位置了。但是胸完,這個直接使用UIManager
的方法太復雜了书释。
基本上,組件可以直接調(diào)用measure方法赊窥。我們來簡單的實現(xiàn)一下這個彈出菜單的功能征冷。
Reimplement
不管單詞對錯了∈那恚總之是重寫一次。簡化版的肴捉!為了篇幅足夠長腹侣,我就把代碼都貼出來了。哈哈~
/**
* 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',
},
});
這個簡化版的實現(xiàn)思路就是:
- 點擊按鈕(
TouchableOpacity
)的時候measure按鈕組件 - 把measure出來的按鈕組件的位置作為參數(shù)發(fā)送給父組件
- 父組件在計算后的位置顯示menu
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上調(diào)用measure方法就可以:this.container.measure
跺株。獲得measure的結果之后,調(diào)用props傳過來的方法onPress
把需要用到的數(shù)據(jù)傳過去脖卖。
繪制Menu
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盏筐,超級簡化版的,就完成了砸讳。全部的代碼在這里琢融。
最后
我們在前文中說道過一個更好的獲得觸發(fā)組件的位置的方式,onLayout
绣夺。這個方法是空的吏奸。各位可以試著完成這個方法,或者全部完成這個popover組件作為練習陶耍。