React Native填坑之旅--使用原生檢視Android

小紅星閃啊閃發表於2021-10-11

使用原生試圖,在RN裡是必不可少的一部分。如果有人在原生功能都做好了,直接拿來用或者微調一下試圖部分就可以用也就不需要再另外造一個,一套輪子了。

步驟

官方文件非常詳細了。直接引用如下:

1. 新建一個ViewManager子類
2. 實現createViewInstance方法
3. 通過註解@ReactProp或者@ReactPropGroup暴露檢視的屬性
4. 在createViewManagers方法裡註冊這個manager
5. 實現JS模組

和原生模組的開發基本一個流程。
但是,首先要有一個原生檢視。是這樣的:

public class FillingHoleView extends View {
    // ...

    public float getRadius()
    public void setRadius(float radius)

    public int getStrokeColor()
    public void setStrokeColor(int color)

    onDraw

    onMeasure

    // ...
}

就是一個Android的檢視,畫一個圈。可以通過setter控制圈的顏色半徑

然後就開始按照上面的順序開始新增程式碼。

ViewManager子類

public class FillingHoleViewManager extends SimpleViewManager<FillingHoleView> {
    public static final String REACT_CLASS = "FillingHoleView";
    ReactApplicationContext mCallerContext;

    public FillingHoleViewManager(ReactApplicationContext reactContext) {
        this.mCallerContext = reactContext;
    }

    @NonNull
    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @NonNull
    @Override
    protected FillingHoleView createViewInstance(@NonNull ThemedReactContext reactContext) {
        return new FillingHoleView(reactContext);
    }

    @ReactProp(name = "radius", defaultFloat = 50f)
    public void setRadius(FillingHoleView fillingHoleView, int radius) {
        fillingHoleView.setRadius(radius);
    }

    @ReactProp(name = "color", defaultInt = 1)
    public void setStrokeColor(FillingHoleView fillingHoleView, int color) {
        fillingHoleView.setStrokeColor(Color.RED);
    }
}

使用SimpleViewManager有一個好處,它預設提供了背景色、透明度和Flex佈局的功能,還有一些accessbility的功能。所以繼承了這個view manager就可以使用flex佈局了。

實現view manager的時候需要提供模組名稱,在JS也是通過模組名稱來獲取原生模組的。

createViewInstance方法返回出來原生檢視。

需要暴露給JS的屬性通過@ReactProp或者@ReactGroupProp註解修飾。第一個引數是你的原生檢視,第二個是要修改的屬性。name是必須的。這些屬性裡只有值型別的可以提供具體的預設值,引用型別的只能是預設為null。

註冊View Manager

如果還沒有package類的話需要新建一個:

public class MyAppPackage implements ReactPackage {
    @NonNull
    @Override
    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new FillingEventHole(reactContext));

        return modules;
    }

    @NonNull
    @Override
    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
            new FillingHoleViewManager(reactContext)
        );
    }

繼承ReactPackage實現其中的方法。主要有兩個,一個是註冊原生模組的,一個是註冊原生檢視的。之後這個package還需要新增到你的application類裡。參考這裡

在JS裡使用你的原生檢視

簡單版本的可以參考官網:

// FillingHoleView.js

import { requireNativeComponent } from 'react-native';

/**
 * Composes `View`.
 *
 * - radius: number
 * - color: number
 * - width: number
 */
module.exports = requireNativeComponent('FillingHoleView');

更加React的實現方法,可以參考這裡:

import React from 'react';
import { requireNativeComponent } from 'react-native';

const FillingNativeView = requireNativeComponent('FillingHoleView');

interface FillingHoleViewProps {
  radius: number;
  color: number; // 1, 2, 3
  width?: number;
}

const FillingHoleView: React.FC<FillingHoleViewProps> = props => {
  return <FillingNativeView {...props} />;
};

export { FillingHoleView };

requireNativeComponent返回的就是可以在其他React元件裡使用的元件了。但是缺少的是關於接收各種屬性和方法的強制說明。所以,在外滿包一層(其實並沒有增加實際的檢視層級)可以在使用中自動提示可以接受的屬性等。

還有事件可以接受,這裡可以參考官網。後續補充這部分內容。

佈局

本文的例子是畫一個背景色和半徑可調的圈。

這裡主要討論的是繪圖邏輯,通過setter制定半徑,然後得到檢視的寬高值(padding考慮在內了)。這樣,在檢視中執行flex佈局的時候會有一些奇怪的問題。

首先,React的單位和android的單位不是一回事。比如,本例中,在Android程式碼中設定寬度為50,在JS設定寬度為100在Android裡拿到的是275(在不同解析度下得到的值也是不一樣的)。

所以在開發中,最好統一寬、高入口。比如在本文最好都從JS設定,然後從寬度裡計算圓的半徑。半徑也可以在JS設定,但是在measure的時候寬度已經得到了,半徑還沒有,或者是例子中的預設值。只要是Android這裡拿到的值和JS得到的就是不同的(除非解析度合適)。

上面說的是單位的問題。現在還要說寬度的問題。

寬度必須要有,否則這個原生檢視在flex的佈局系統了是一個寬度、高度都是0的存在。如圖:
image.png

綠色圓形就被當做寬、高都為0的存在了。具體程式碼:

<View style={styles.fillingNative}>
  <FillingHoleView radius={50} color={2} />
  <Text>2</Text>
</View>

fillingNative的樣式為:

  fillingNative: {
    flex: 1,
    height: 160,
    flexDirection: 'row',
    justifyContent: 'flex-start',
    alignItems: 'center',
    backgroundColor: 'powderblue',
  },

在這個情況下綠色圓已經超出了邊界,而且文字也直接顯示在上面。反而看下面的黃色圓設定了寬度,只是出現了上面說的單位不同的問題,佈局正常。

紅色圓球的問題在於設定寬度和發高度的方式和寬高度都必須明確給出的問題。可以看到紅色圓和白色檢視在縱向佈局上有問題。當明確給出高度之後問題解決。如圖:
image.png

設定寬高,除了可以明確的把每一個作為一個prop傳進去:

<FillingHoleView width={60} height={60} radius={30} color={3} />

也可以使用style的方式,只是在本例需要在props裡寫出有style這個成員,否則會lint提示error。

<FillingHoleView style={{ width: 100, height: 100 }} radius={100} color={1} />

最後

解決React Native的效能問題,或者複用原生程式碼的問題都需要用到原生檢視的內容,當然也不會少了原生模組。

在開發的過程中除了步驟之外還要注意RN和原生檢視的度量單位和寬高對flex佈局的影響。

專案程式碼在這裡

相關文章