【稀飯】react native 實戰系列教程之自定義原生模塊

影片詳情開發也是通過Cheerio抓取並分析網頁獲取到的詳情數據,本節就不作爲詳細內容來講解了,詳細的代碼可以看下我的github,效果如下:

詳情

在點擊播放時,會跳轉到播放界面,並且橫屏顯示,退出播放界面時,會恢復到豎屏狀態。但是,react native並沒有給我們提供設置橫豎屏的API,因此,我們需要自己使用原生的代碼來完成此功能。

使用原生代碼,我們可以爲react native作什麼呢?

  • 一個是功能性上的(模塊),比如提供橫豎屏設置、數據庫存儲等
  • 一個是UI層面上的(UI組件),比如可以自定義一個VideoView視頻播放器等

就目前項目而言,詳情頁開發涉及到的兩個正好是上面的兩種情況,橫豎屏設置(功能)和使用原生VideoView播放視頻(UI)。

原生模塊之橫豎屏設置功能開發

在我們項目目錄下XiFan/android 存放的是android項目的代碼,需要使用Android Studio來開發,如果你不是android開發者,那麼你可能需要先搭建android開發環境,這裏就不介紹了,可自行網上搜索查閱資料。這裏放一個工具下載地址 http://www.androiddevtools.cn/

打開android項目之後(打開之後會彈窗提示更改gradle版本,點擊忽略即可),目錄結構如下:

android項目結構

module是新建的一個包名,專門存放自定義的組件。開發一個功能組件,需要配對實現一個Module和Package。

實現JAVA端模塊

在module包下,新建OrientationModule類,並繼承ReactContextBaseJavaModule

public class OrientationModule extends ReactContextBaseJavaModule{

    public OrientationModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "Orientation";
    }

    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        return null;
    }
}

getName方法返回的是該組件的名稱(該名稱可以加RCT前綴,如RCTOrientation,但JS中調用的還是沒有加前綴的Orientation),在JS中供NativeModules使用;getConstants方法返回一組key/value常量屬性值,可供JS中調用。具體看實現吧。

首先我們需要提供一個方法,供JS中調用設置應用的橫豎屏。要讓JS中能調用,需要在方法上使用ReactMethod註解。

@ReactMethod
public void setOrientation(final int orientation){
    UiThreadUtil.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            Activity activity = getCurrentActivity();
            if(activity == null){
                return;
            }
            if(orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE){
                activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                //設置全屏
                Window window = activity.getWindow();
                WindowManager.LayoutParams params = window.getAttributes();
                params.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
                window.setAttributes(params);

            }else if(orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT){
                activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                //設置非全屏
                Window window = activity.getWindow();
                WindowManager.LayoutParams params = window.getAttributes();
                params.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
                window.setAttributes(params);
            }
        }
    });

}

這個方法接收一個orientation參數,只支持ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE和ActivityInfo.SCREEN_ORIENTATION_PORTRAIT常量參數值。由於react native是運行在JavaBridge線程,所以在使用android一些功能時,我們需要將在android UI線程裏進行操作,因此這裏調用UiThreadUtil.runOnUiThread。

那麼,我們如何讓使用者更方便的使用setOrientation這個方便,而不用知道orientation參數要傳什麼具體值呢?

這裏我們就需要使用上面提到的getConstants方法來定義常量值了。

private static final String ORIENTATION_LANDSCAPE_KEY = "LANDSCAPE";//橫屏
private static final String ORIENTATION_PORTRAIT_KEY = "PORTRAIT";//豎屏


@Nullable
@Override
public Map<String, Object> getConstants() {//定義返回值常量
    Map<String,Object> constants = MapBuilder.newHashMap();
    constants.put(ORIENTATION_LANDSCAPE_KEY, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    constants.put(ORIENTATION_PORTRAIT_KEY, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    return constants;
}

定義了LANDSCAPE 和 PORTRAIT兩個屬性常量,並分別賦予值ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 和 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

寫完了Module,我們需要再寫Package。新建OrientationPackage類,並實現ReactPackage接口。

public class OrientationPackage implements ReactPackage{
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Arrays.<NativeModule>asList(
                new OrientationModule(reactContext)
        );
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

OrientationModule是屬於NativeModule,所以,我們在createNativeModules方法中返回我們已經實現了的OrientationModule。

最後,我們需要將OrientationPackage註冊到react native中,讓它能識別到我們的Module。

打開MainApplication類,並將OrientationPackage添加到ReactPackage

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    protected boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          new OrientationPackage()
      );
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
      return mReactNativeHost;
  }
}

在getPackages方法中增加了我們的OrientationPackage。

接下來就是在JS中使用它了。

實現對應的JS模塊

新建VideoPlayScene.js,用於播放視頻的場景。我們需要在應用進入該頁面時,橫屏顯示,退出時恢復豎屏。

import React,{Component} from 'react';
import {
    View,
    WebView,
    NativeModules
} from 'react-native';
var Orientation = NativeModules.Orientation;

export default class VideoPlayScene extends Component{
    constructor(props){
        super(props);
    }

    componentWillMount(){
        Orientation.setOrientation(Orientation.LANDSCAPE);
    }

    componentWillUnmount(){
        Orientation.setOrientation(Orientation.PORTRAIT);
    }
}

我們需要import NativeModules這個模塊

var Orientation = NativeModules.Orientation;

NativeModules點後面的Orientation就是我們在原生代碼OrientationModule中getName返回的字符串值。

在componentWillMount 和 componentWillUnmount生命週期中調用setOrientation來實現需求。其中Orientation.LANDSCAPE 和 Orientation.PORTRAIT 就是我們在OrientationModule$getConstants 定義的兩個常量值。

那如果在js中需要接收一個回調方法,那麼原生代碼需要怎麼寫呢?

在OrientationModule在定義一個方法,並接收一個Callback參數

@ReactMethod
public void getRequestedOrientation(Callback callback){
    int orientation = getReactApplicationContext().getResources().getConfiguration().orientation;
    callback.invoke(orientation);
}

js中調用

componentWillMount(){
    Orientation.getRequestedOrientation((orientation)=>{
        console.log('current orientation :'+ orientation);
    });
    //Orientation.setOrientation(Orientation.LANDSCAPE);
}

這樣就完成了橫豎屏功能的開發了(不好的是,自定義的Module在IDE下並不能代碼提示,有知道如何讓它提示的話,請告知一下哈),貼下OrientationModule.java的完整代碼

public class OrientationModule extends ReactContextBaseJavaModule{
    private static final String ORIENTATION_LANDSCAPE_KEY = "LANDSCAPE";//橫屏
    private static final String ORIENTATION_PORTRAIT_KEY = "PORTRAIT";//豎屏

    public OrientationModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "Orientation";
    }

    @Nullable
    @Override
    public Map<String, Object> getConstants() {//定義返回值
        Map<String,Object> constants = MapBuilder.newHashMap();
        constants.put(ORIENTATION_LANDSCAPE_KEY, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        constants.put(ORIENTATION_PORTRAIT_KEY, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        constants.put("initOrientation",getReactApplicationContext().getResources().getConfiguration().orientation);
        return constants;
    }

    @ReactMethod
    public void setOrientation(final int orientation){
        UiThreadUtil.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Activity activity = getCurrentActivity();
                if(activity == null){
                    return;
                }
                if(orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE){
                    activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                    //設置全屏
                    Window window = activity.getWindow();
                    WindowManager.LayoutParams params = window.getAttributes();
                    params.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
                    window.setAttributes(params);

                }else if(orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT){
                    activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                    //設置非全屏
                    Window window = activity.getWindow();
                    WindowManager.LayoutParams params = window.getAttributes();
                    params.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
                    window.setAttributes(params);
                }
            }
        });

    }
    @ReactMethod
    public void getRequestedOrientation(Callback callback){
        int orientation = getReactApplicationContext().getResources().getConfiguration().orientation;
        callback.invoke(orientation);
    }
}

總結

本節我們完成了android的自定義模塊,使RN能夠使用原生提供的能力。而關於js如何向native發送命令以及native如何向js發送事件的問題,原生模塊和原生UI組件它們的通信都是一樣的,所以我們把這個問題留在下一節來講述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章