ANTD mobile源碼分析 -- popover

最近的開發(fā)中要用到很多的各式各樣的組件产阱。但是發(fā)現(xiàn)ant design mobile(后面簡稱ANTDM)里很多的資源做葵。于是就分析一下,學習學習心墅。

ANTDM直接使用了typescript,沒有用ES2015榨乎,不過這不會是障礙怎燥,反而是學習typescript的一個好機會∶凼睿基本上可以學的開源項目里比這個好的也不多铐姚。

目錄結構

Popover組件在:

|
|--components
  |
  |--popover

我們要分析的組件全部都在components這個目錄下。

在這個目錄里還包含tests, demostyle肛捍。里面分別存放測試代碼隐绵、實例和樣式。其他的文件包括[component name]_native.tsx[component name].txs以及對應的index.native.tsxindex.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ù)是ReactReactNative阻肿。

回到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};
  });
}

所以,在彈出菜單的組件上使用onLayoutprops得到它的位置播揪。

注意

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)思路就是:

  1. 點擊按鈕(TouchableOpacity)的時候measure按鈕組件
  2. 把measure出來的按鈕組件的位置作為參數(shù)發(fā)送給父組件
  3. 父組件在計算后的位置顯示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組件作為練習陶耍。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奋蔚,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子烈钞,更是在濱河造成了極大的恐慌泊碑,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毯欣,死亡現(xiàn)場離奇詭異馒过,居然都是意外死亡,警方通過查閱死者的電腦和手機酗钞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門腹忽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砚作,你說我怎么就攤上這事窘奏。” “怎么了葫录?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵着裹,是天一觀的道長。 經(jīng)常有香客問我米同,道長骇扇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任面粮,我火速辦了婚禮少孝,結果婚禮上,老公的妹妹穿的比我還像新娘熬苍。我一直安慰自己韭山,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钱磅,像睡著了一般梦裂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盖淡,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天年柠,我揣著相機與錄音,去河邊找鬼褪迟。 笑死冗恨,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的味赃。 我是一名探鬼主播掀抹,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼心俗!你這毒婦竟也來了傲武?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤城榛,失蹤者是張志新(化名)和其女友劉穎揪利,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狠持,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡疟位,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了喘垂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甜刻。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖正勒,靈堂內(nèi)的尸體忽然破棺而出得院,到底是詐尸還是另有隱情,我是刑警寧澤昭齐,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站矾柜,受9級特大地震影響阱驾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怪蔑,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一里覆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缆瓣,春花似錦喧枷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽车荔。三九已至,卻和暖如春戚扳,著一層夾襖步出監(jiān)牢的瞬間忧便,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工帽借, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留珠增,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓砍艾,卻偏偏與公主長得像蒂教,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子脆荷,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容