影片詳情開發也是通過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版本,點擊忽略即可),目錄結構如下:
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組件它們的通信都是一樣的,所以我們把這個問題留在下一節來講述。