将 onPictureInPictureModeChanged 结果传递给 React Native 模块
Pass onPictureInPictureModeChanged result into a react native module
我正在尝试使用 react-native 做一些画中画模式。我写了一个反应模块
我需要生成与此类似的东西,但在 React Native 模块中
public class MainActivity extends AppCompatActivity {
private PlayerView playerView;
private Player player;
private boolean playerShouldPause = true;
...
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
// Hiding the ActionBar
if (isInPictureInPictureMode) {
getSupportActionBar().hide();
} else {
getSupportActionBar().show();
}
playerView.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}
...
}
在 ReactContextBaseJavaModule 中有一些方法可以用同样的方式做到这一点
public class ReactNativeBitmovinPlayerModule extends ReactContextBaseJavaModule {
...
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
// Hiding the ActionBar
if (isInPictureInPictureMode) {
getSupportActionBar().hide();
} else {
getSupportActionBar().show();
}
playerView.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}
...
}
是的,有可能实现。实际上,有不止一种方法可以从本机模块侦听画中画模式事件。
快捷方式
最简单的方法是将基础 java 模块设为 LifecycleStateObserver
并在 Activity.isInPictureInPictureMode()
上检查每个 activity 状态更新的变化。
public class ReactNativeCustomModule extends ReactContextBaseJavaModule implements LifecycleEventObserver {
private boolean isInPiPMode = false;
private final ReactApplicationContext reactContext;
public ReactNativeCustomModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
private void sendEvent(String eventName, @Nullable WritableMap args) {
reactContext
.getJSModule(RCTDeviceEventEmitter.class)
.emit(eventName, args);
}
@ReactMethod
public void registerLifecycleEventObserver() {
AppCompatActivity activity = (AppCompatActivity) reactContext.getCurrentActivity();
if (activity != null) {
activity.getLifecycle().addObserver(this);
} else {
Log.d(this.getName(), "App activity is null.");
}
}
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AppCompatActivity activity = (AppCompatActivity) source;
boolean isInPiPMode = activity.isInPictureInPictureMode();
// Check for changes on pip mode.
if (this.isInPiPMode != isInPiPMode) {
this.isInPiPMode = isInPiPMode;
Log.d(this.getName(), "Activity pip mode has changed to " + isInPiPMode);
// Dispatch onPictureInPicutreModeChangedEvent to js.
WritableMap args = Arguments.createMap();
args.putBoolean("isInPiPMode", isInPiPMode)
sendEvent("onPictureInPictureModeChanged", args);
}
}
}
// ...
}
请注意,不可能在模块的构造函数中注册生命周期观察器,因为 activity 仍然是 null
。需要在java脚本端注册。
因此,在您的组件初始化时调用 registerLifecycleEventObserver
,以便它可以开始接收 activity 状态更新。
import React, { useEffect } from 'react';
import { NativeEventEmitter, NativeModules } from 'react-native';
const ReactNativeCustomModule = NativeModules.ReactNativeCustomModule;
const eventEmitter = new NativeEventEmitter(ReactNativeCustomModule);
// JS wrapper.
export const CustomComponent = () => {
useEffect(() => {
// Register module to receive activity's state updates.
ReactNativeCustomModule.registerLifecycleEventObserver();
const listener = eventEmitter.addListener('onPictureInPictureModeChanged', (args) => {
console.log('isInPiPMode:', args.isInPiPMode);
});
return () => listener.remove();
}, []);
return (
// jsx
);
};
顺便说一下,我在 react-native-bitmovin-player
上打开了一个拉取请求来实现这个功能。请检查一下。
辛苦了
还有另一种监听 PiP 变化的方法,但它更复杂并且需要更深入地了解 android 和 RN 平台。但是,有了它,您可以获得在 onPictureInPictureModeChanged
方法上访问 newConfig
的优势(如果需要),而不是监听任何 activity 的生命周期事件。
首先将您的自定义本机视图(无论它是什么)嵌入到 Fragment
中,然后重写片段的 onPictureInPictureModeChanged
方法,最后在那里调度一个 RN 事件。以下是逐步完成的方法:
- 为您的自定义视图创建片段:
// Make sure to use android.app's version of Fragment if you need
// to access the `newConfig` argument.
import android.app.Fragment;
// If not, use androidx.fragment.app's version.
// This is the preferable way nowadays, but doesn't include `newConfig`.
// import androidx.fragment.app.Fragment;
// For the sake of example, lets use android.app's version here.
public class CustomViewFragment extends Fragment {
interface OnPictureInPictureModeChanged {
void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig);
}
private CustomView customView;
private OnPictureInPictureModeChanged listener;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
customView = new CustomView();
// Do all UI setups needed for customView here.
return customView;
}
// Android calls this method on the fragment everytime its activity counterpart is also called.
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
if (listener != null) {
this.listener.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}
}
public void setOnPictureInPictureModeChanged(OnPictureInPictureModeChanged listener) {
this.listener = listener;
}
// OnViewCreated, onPause, onResume, onDestroy...
}
- 创建一个RN
ViewGroupManager
来保存片段并导出
java脚本的功能和道具:
public class CustomViewManager extends ViewGroupManager<FrameLayout> implements CustomViewFragment.OnPictureInPictureModeChanged {
public static final String REACT_CLASS = "CustomViewManager";
public final int COMMAND_CREATE = 1;
ReactApplicationContext reactContext;
public CustomViewManager(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
// Expose `onPictureInPictureModeChanged` prop to javascript.
public Map getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.builder().put(
"onPictureInPictureModeChanged",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onPictureInPictureModeChanged")
)
).build();
}
@Override
public void onPictureInPictureModeChanged(boolean isInPiPMode, Configuration newConfig) {
Log.d(this.getName(), "PiP mode changed to " + isInPiPMode + " with config " + newConfig.toString());
// Dispatch onPictureInPictureModeChanged to js.
final WritableMap args = Arguments.createMap();
args.putBoolean("isInPiPMode", isInPiPMode);
args.putMap("newConfig", asMap(newConfig));
reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(), "onPictureInPictureModeChanged", args);
}
// Get the JS representation of a Configuration object.
private ReadableMap asMap(Configuration config) {
final WritableMap map = Arguments.createMap();
map.putBoolean("isNightModeActive", newConfig.isNightModeActive());
map.putBoolean("isScreenHdr", newConfig.isScreenHdr());
map.putBoolean("isScreenRound", newConfig.isScreenRound());
// ...
return map;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public FrameLayout createViewInstance(ThemedReactContext reactContext) {
return new FrameLayout(reactContext);
}
// Map the "create" command to an integer
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of("create", COMMAND_CREATE);
}
// Handle "create" command (called from JS) and fragment initialization
@Override
public void receiveCommand(@NonNull FrameLayout root, String commandId, @Nullable ReadableArray args) {
super.receiveCommand(root, commandId, args);
int reactNativeViewId = args.getInt(0);
int commandIdInt = Integer.parseInt(commandId);
switch (commandIdInt) {
case COMMAND_CREATE:
createFragment(root, reactNativeViewId);
break;
default: {}
}
}
// Replace RN's underlying native view with your own
public void createFragment(FrameLayout root, int reactNativeViewId) {
ViewGroup parentView = (ViewGroup) root.findViewById(reactNativeViewId).getParent();
// You'll very likely need to manually layout your parent view as well to make sure
// it stays updated with the props from RN.
//
// I recommend taking a look at android's `view.Choreographer` and RN's docs on how to do it.
// And, as I said, this requires some knowledge of native Android UI development.
setupLayout(parentView);
final CustomViewFragment fragment = new CustomViewFragment();
fragment.setOnPictureInPictureModeChanged(this);
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
// Make sure to use activity.getSupportFragmentManager() if you're using
// androidx's Fragment.
activity.getFragmentManager()
.beginTransaction()
.replace(reactNativeViewId, fragment, String.valueOf(reactNativeViewId))
.commit();
}
}
- 在一个包中注册
CustomViewManager
:
public class CustomPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new CustomViewManager(reactContext)
);
}
}
- 实施java脚本端:
import React, { useEffect, useRef } from 'react';
import { UIManager, findNodeHandle, requireNativeComponent } from 'react-native';
const CustomViewManager = requireNativeComponent('CustomViewManager');
const createFragment = (viewId) =>
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.CustomViewManager.Commands.create.toString(), // we are calling the 'create' command
[viewId]
);
export const CustomView = ({ style }) => {
const ref = useRef(null);
useEffect(() => {
const viewId = findNodeHandle(ref.current);
createFragment(viewId!);
}, []);
const onPictureInPictureModeChanged = (event) => {
console.log('isInPiPMode:', event.nativeEvent.isInPiPMode);
console.log('newConfig:', event.nativeEvent.newConfig);
}
return (
<CustomViewManager
style={{
...(style || {}),
height: style && style.height !== undefined ? style.height || '100%',
width: style && style.width !== undefined ? style.width || '100%'
}}
ref={ref}
onPictureInPictureModeChanged={onPictureInPictureModeChanged}
/>
);
};
最后一个示例很大程度上基于 RN's documentation。如果你努力的话,我怎么强调阅读它的重要性都不为过。
无论如何,我希望这个小指南能对您有所帮助。
此致。
我正在尝试使用 react-native 做一些画中画模式。我写了一个反应模块
我需要生成与此类似的东西,但在 React Native 模块中
public class MainActivity extends AppCompatActivity {
private PlayerView playerView;
private Player player;
private boolean playerShouldPause = true;
...
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
// Hiding the ActionBar
if (isInPictureInPictureMode) {
getSupportActionBar().hide();
} else {
getSupportActionBar().show();
}
playerView.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}
...
}
在 ReactContextBaseJavaModule 中有一些方法可以用同样的方式做到这一点
public class ReactNativeBitmovinPlayerModule extends ReactContextBaseJavaModule {
...
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
// Hiding the ActionBar
if (isInPictureInPictureMode) {
getSupportActionBar().hide();
} else {
getSupportActionBar().show();
}
playerView.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}
...
}
是的,有可能实现。实际上,有不止一种方法可以从本机模块侦听画中画模式事件。
快捷方式
最简单的方法是将基础 java 模块设为 LifecycleStateObserver
并在 Activity.isInPictureInPictureMode()
上检查每个 activity 状态更新的变化。
public class ReactNativeCustomModule extends ReactContextBaseJavaModule implements LifecycleEventObserver {
private boolean isInPiPMode = false;
private final ReactApplicationContext reactContext;
public ReactNativeCustomModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
private void sendEvent(String eventName, @Nullable WritableMap args) {
reactContext
.getJSModule(RCTDeviceEventEmitter.class)
.emit(eventName, args);
}
@ReactMethod
public void registerLifecycleEventObserver() {
AppCompatActivity activity = (AppCompatActivity) reactContext.getCurrentActivity();
if (activity != null) {
activity.getLifecycle().addObserver(this);
} else {
Log.d(this.getName(), "App activity is null.");
}
}
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AppCompatActivity activity = (AppCompatActivity) source;
boolean isInPiPMode = activity.isInPictureInPictureMode();
// Check for changes on pip mode.
if (this.isInPiPMode != isInPiPMode) {
this.isInPiPMode = isInPiPMode;
Log.d(this.getName(), "Activity pip mode has changed to " + isInPiPMode);
// Dispatch onPictureInPicutreModeChangedEvent to js.
WritableMap args = Arguments.createMap();
args.putBoolean("isInPiPMode", isInPiPMode)
sendEvent("onPictureInPictureModeChanged", args);
}
}
}
// ...
}
请注意,不可能在模块的构造函数中注册生命周期观察器,因为 activity 仍然是 null
。需要在java脚本端注册。
因此,在您的组件初始化时调用 registerLifecycleEventObserver
,以便它可以开始接收 activity 状态更新。
import React, { useEffect } from 'react';
import { NativeEventEmitter, NativeModules } from 'react-native';
const ReactNativeCustomModule = NativeModules.ReactNativeCustomModule;
const eventEmitter = new NativeEventEmitter(ReactNativeCustomModule);
// JS wrapper.
export const CustomComponent = () => {
useEffect(() => {
// Register module to receive activity's state updates.
ReactNativeCustomModule.registerLifecycleEventObserver();
const listener = eventEmitter.addListener('onPictureInPictureModeChanged', (args) => {
console.log('isInPiPMode:', args.isInPiPMode);
});
return () => listener.remove();
}, []);
return (
// jsx
);
};
顺便说一下,我在 react-native-bitmovin-player
上打开了一个拉取请求来实现这个功能。请检查一下。
辛苦了
还有另一种监听 PiP 变化的方法,但它更复杂并且需要更深入地了解 android 和 RN 平台。但是,有了它,您可以获得在 onPictureInPictureModeChanged
方法上访问 newConfig
的优势(如果需要),而不是监听任何 activity 的生命周期事件。
首先将您的自定义本机视图(无论它是什么)嵌入到 Fragment
中,然后重写片段的 onPictureInPictureModeChanged
方法,最后在那里调度一个 RN 事件。以下是逐步完成的方法:
- 为您的自定义视图创建片段:
// Make sure to use android.app's version of Fragment if you need
// to access the `newConfig` argument.
import android.app.Fragment;
// If not, use androidx.fragment.app's version.
// This is the preferable way nowadays, but doesn't include `newConfig`.
// import androidx.fragment.app.Fragment;
// For the sake of example, lets use android.app's version here.
public class CustomViewFragment extends Fragment {
interface OnPictureInPictureModeChanged {
void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig);
}
private CustomView customView;
private OnPictureInPictureModeChanged listener;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
customView = new CustomView();
// Do all UI setups needed for customView here.
return customView;
}
// Android calls this method on the fragment everytime its activity counterpart is also called.
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
if (listener != null) {
this.listener.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}
}
public void setOnPictureInPictureModeChanged(OnPictureInPictureModeChanged listener) {
this.listener = listener;
}
// OnViewCreated, onPause, onResume, onDestroy...
}
- 创建一个RN
ViewGroupManager
来保存片段并导出 java脚本的功能和道具:
public class CustomViewManager extends ViewGroupManager<FrameLayout> implements CustomViewFragment.OnPictureInPictureModeChanged {
public static final String REACT_CLASS = "CustomViewManager";
public final int COMMAND_CREATE = 1;
ReactApplicationContext reactContext;
public CustomViewManager(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
// Expose `onPictureInPictureModeChanged` prop to javascript.
public Map getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.builder().put(
"onPictureInPictureModeChanged",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onPictureInPictureModeChanged")
)
).build();
}
@Override
public void onPictureInPictureModeChanged(boolean isInPiPMode, Configuration newConfig) {
Log.d(this.getName(), "PiP mode changed to " + isInPiPMode + " with config " + newConfig.toString());
// Dispatch onPictureInPictureModeChanged to js.
final WritableMap args = Arguments.createMap();
args.putBoolean("isInPiPMode", isInPiPMode);
args.putMap("newConfig", asMap(newConfig));
reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(), "onPictureInPictureModeChanged", args);
}
// Get the JS representation of a Configuration object.
private ReadableMap asMap(Configuration config) {
final WritableMap map = Arguments.createMap();
map.putBoolean("isNightModeActive", newConfig.isNightModeActive());
map.putBoolean("isScreenHdr", newConfig.isScreenHdr());
map.putBoolean("isScreenRound", newConfig.isScreenRound());
// ...
return map;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public FrameLayout createViewInstance(ThemedReactContext reactContext) {
return new FrameLayout(reactContext);
}
// Map the "create" command to an integer
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of("create", COMMAND_CREATE);
}
// Handle "create" command (called from JS) and fragment initialization
@Override
public void receiveCommand(@NonNull FrameLayout root, String commandId, @Nullable ReadableArray args) {
super.receiveCommand(root, commandId, args);
int reactNativeViewId = args.getInt(0);
int commandIdInt = Integer.parseInt(commandId);
switch (commandIdInt) {
case COMMAND_CREATE:
createFragment(root, reactNativeViewId);
break;
default: {}
}
}
// Replace RN's underlying native view with your own
public void createFragment(FrameLayout root, int reactNativeViewId) {
ViewGroup parentView = (ViewGroup) root.findViewById(reactNativeViewId).getParent();
// You'll very likely need to manually layout your parent view as well to make sure
// it stays updated with the props from RN.
//
// I recommend taking a look at android's `view.Choreographer` and RN's docs on how to do it.
// And, as I said, this requires some knowledge of native Android UI development.
setupLayout(parentView);
final CustomViewFragment fragment = new CustomViewFragment();
fragment.setOnPictureInPictureModeChanged(this);
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
// Make sure to use activity.getSupportFragmentManager() if you're using
// androidx's Fragment.
activity.getFragmentManager()
.beginTransaction()
.replace(reactNativeViewId, fragment, String.valueOf(reactNativeViewId))
.commit();
}
}
- 在一个包中注册
CustomViewManager
:
public class CustomPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new CustomViewManager(reactContext)
);
}
}
- 实施java脚本端:
import React, { useEffect, useRef } from 'react';
import { UIManager, findNodeHandle, requireNativeComponent } from 'react-native';
const CustomViewManager = requireNativeComponent('CustomViewManager');
const createFragment = (viewId) =>
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.CustomViewManager.Commands.create.toString(), // we are calling the 'create' command
[viewId]
);
export const CustomView = ({ style }) => {
const ref = useRef(null);
useEffect(() => {
const viewId = findNodeHandle(ref.current);
createFragment(viewId!);
}, []);
const onPictureInPictureModeChanged = (event) => {
console.log('isInPiPMode:', event.nativeEvent.isInPiPMode);
console.log('newConfig:', event.nativeEvent.newConfig);
}
return (
<CustomViewManager
style={{
...(style || {}),
height: style && style.height !== undefined ? style.height || '100%',
width: style && style.width !== undefined ? style.width || '100%'
}}
ref={ref}
onPictureInPictureModeChanged={onPictureInPictureModeChanged}
/>
);
};
最后一个示例很大程度上基于 RN's documentation。如果你努力的话,我怎么强调阅读它的重要性都不为过。
无论如何,我希望这个小指南能对您有所帮助。
此致。