React Native 的 Image 以及觸控

閱讀資訊:
以下文章常會見到 "RN" 字樣,我用來代表 React Native。
此文撰寫時,React Native 的版本為 0.26。
其實我也主要是在讀官方文件而已 XD 
這篇文章寫的部分你可以對應到 <歐萊禮 React Native 學習手冊> 的第四章。

樣式
React Native 偏好重複使用有樣式的元件,而非重複使用樣式。p.48 <歐萊禮 React Native  學習手冊 - 使用 JavaScript 打造原生 App.>
  • RN 的程式不能跟 React 用在 web 的共用。記得 RN 是 “Learning once, write anywhere”。
  • RN 的 componet “樣式 (StyleSheet)” 並沒有繼承的概念,除了樣式要寫在樣式物件 (StyleSheet) 之外,你可以把有樣式的元件包起來。

Image
  • 在 RN 引入圖片並不像 web 使用 img 標籤,而是使用 `<Image>`,另外屬性也不是 web img 的 src,而是 source。
  • ex: 引入 local 圖片: <Image source={require('./my-icon.png')}></Image>
  • ex: 引入外部圖片:
<Image
      source={{ uri: 'http://xxx.xx.xx/xx.png'}}
      style={{width: xx, height xx}}></Image>
  • 引入外面的圖片需要指定寬高。
  • 引入外部的圖片需指定 uri。
  • require 指定的路徑,會跟看 Javascript module 的方式一樣 (就先當作是...相對路徑)
  • 可針對平台載入同名的檔案,只要在圖檔命名上加入 platform。
  my-icon.ios.png
  my-icon.android.png

針對不同的螢幕 densities,你可能會有 @2x, @3x, 等其他的後綴檔名圖片,可以這樣擺放:
// 假設這是一個 todo 的 component 資料夾
todo
  ├── todo.js
  └── img
      ├── check@2x.png
      └── check@3x.png
然而在 todo.js 裡面的 Image 可以這樣引入 check 這個圖片
<Image source={require('./img/check.png')} />
  • 假若是 iphone5s 則會用到 check@2x (React Native 會知道要用 @2x,你無須擔心這個...,只要把 source 檔名寫好),但在 Nexus 5 會用到 @3x,若沒有適合的圖片,RN 會選最接近的來用。
  • 使用 windows 作業系統的人,假如你加入圖片到 project 下,可能需要重啟。


Image 的優點:
  1. 可同時使用在 ios, android 上。
  2. Component self-contained. 圖片跟著 component. 跟 js code 放在同一層。
  3. 沒有 global namespace 的問題。
  4. 加圖片或是換圖片無須重新編譯,只要重新整理模擬器。

官方建議,在 require 裡面的值,應該要是 statically 的,請看官方的範例:
// GOOD
<Image source={require('./my-icon.png')} />

// BAD
var icon = this.props.active ? 'my-icon-active' : 'my-icon-inactive';
<Image source={require('./' + icon + '.png')} />

// GOOD,把整個 require 的部分抽出來,放到 icon 變數下存取。
var icon = this.props.active ? require('./my-icon-active.png') : require('./my-icon-inactive.png');
<Image source={icon} />

Local Filesystem Image 本地端圖片檔案

這個本機我想指的並不是你的電腦啦,應該是實際在 run 的那隻手機上裡面的圖片。這個部分可以看 CameraRoll 就有範例可以知道如何取得 local resource.


觸控介面
  • RN 提供 TouchableHighlight 元件, 以及 PanResponder, Gesture Responder System 兩個低階 API.
  • 任何可以被處碰的 component 應該都要有 TouchableHighlight 包裝者(wrapper)。舉例來說,button 是可以被點按的地方,所以用一個 TouchableHighlight 把這個 Button 包起來。
  • TouchableHighlight 監聽 onPress 事件 (也有其他事件啦...,要看文件才知),然後你把要 callback 的 function 寫好,等著發生時觸發相對應的動作。
  • PanResponder 比 Gesture Responder System 還要高階。
  • PanResponder 提供抽象。


TouchableHighlight 範例
如希望監聽某 Image 的 touch 事件 (範例改自官網): 要注意 TouchableHighlight 只能包覆一個 子component 摟!

<TouchableHighlight onPress={this._onPressButton}>
      <Image
        style={styles.button}
        source={require('image!myButton')}
      />
</TouchableHighlight>


Gesture Responder System
  • 手機上觸控的行為非常複雜,當然不只有觸碰而已,也有可能多點觸碰或其他 (scrolling, sliding on a widget, or tapping)。而手勢回應就跟 Gesture Responder System 有關。(在官網可看到 Gesture Responder 的詳細)
  • Gesture Responder 是處理特定觸碰事件的 View。要成為一個  Gesture Responder 的 View 有幾個一定要填寫的屬性:
         (1) View.props.onStartShouldSetResponder: (evt) => true
         (2) View.props.onMoveShouldSetResponder: (evt) => true 
  • 假如其中一個回傳 true (view 會嘗試 (attempts) 成為一個 responder),以下兩個事件也會發生其中一個 (要嘛接受或是拒絕,會發生其中一個):
         (3) View.props.onResponderGrant: (evt) => {}
         (4) View.props.onResponderReject: (evt) => {} 

其實也不只這些,後頭還有很多種 Handlers 可以用。但簡單來說整個 Gesture 的生命週期是: 開始 -> 移動 -> 釋放。所以你會很常看到 handlers 的名稱含有 start, release, move...等字樣。

Gesture Responder 具有冒泡事件的特性,如果要覆寫這些行為,可在父元件將
View.props.onStartShouldSetResponder, View.props.onMoveShouldSetResponder 回傳 true。


觸控事件發生之後,相關的 handlers 也會被呼叫。被呼叫的時候會接收 evt (nativeEvent) 參數,有很多屬性值可以取得,如觸碰的 ID, 觸碰元素的 x,y 軸座標,時間... 等等。

參考自官方文件:

identifier - The ID of the touch
locationX - The X position of the touch, relative to the element
locationY - The Y position of the touch, relative to the element
pageX - The X position of the touch, relative to the root element
pageY - The Y position of the touch, relative to the root element
target - The node id of the element receiving the touch event
timestamp - A time identifier for the touch, useful for velocity calculation
touches - Array of all current touches on the screen
(source from: https://facebook.github.io/react-native/docs/gesture-responder-
system.html#content)

PanResponder
PanResponder 提供比較高階的 api 處理單點與多點觸控,也能存取原始的事件 (nativeEvent)。他的用法主要是透過 gestureState 可以得到一些你可能有需要的資訊:

  • stateID (只要螢幕上有至少一個碰觸)
  • moveX, moveY (最近一次觸碰移動的螢幕座標)
  • x0, y0
  • dx (從碰觸開始累計手勢的距離)
  • dy
  • vx (目前手勢的向量)
  • vy
  • numberActiveTouches (螢幕上碰觸的數量)

用法: 建立一個 PanResponder 的物件,你要準備一些 callback function。然後在 render 的方法中使用他。

componentWillMount: function() {
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => this._handlePanResponder,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
      onPanResponderGrant: (evt, gestureState) => {},
      onPanResponderMove: (evt, gestureState) => {},
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {},
      onPanResponderTerminate: (evt, gestureState) => {},
      onShouldBlockNativeResponder: (evt, gestureState) => {},
    });
  },

  render: function() {
    return (
      <View {...this._panResponder.panHandlers} />
    );
  },


結語

  1. 提供一般元件的觸碰回應,使用 TouchableHighlight。
  2. 自訂觸碰介面或是較複雜的應用如遊戲類,使用 PanResponder, Gesture Responder System。

參考

留言

這個網誌中的熱門文章

[Android] 筆記 手機上測試自己的 APP

解決fatal: Not a git repository (or any of the parent directories): .git錯誤

[Android 筆記] 設定 ImageView 的圖檔來源