我正在尝试使用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);
}
...
}
是的,实现它是可能的。实际上,从本机模块监听PiP模式事件的方法不止一种。
快捷方式
最简单的方法是使您的基本java模块成为LifecycleStateObserver
,并在每次活动状态更新时检查Activity.isInPictureInPictureMode()
上的更改。
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);
}
}
}
// ...
}
请注意,不可能在模块的构造函数中注册生命周期观察器,因为活动仍然是null
。它需要在javascript端注册。
因此,在组件初始化时调用registerLifecycleEventObserver
,以便它可以开始接收活动状态更新。
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
上打开了一个pull请求。请检查一下。
艰难的道路
还有另一种方法可以倾听PiP的变化,但它更复杂,需要对android和RN平台有更深入的了解。但是,使用它,您可以在onPictureInPictureModeChanged
方法上访问newConfig
(如果需要),而不必侦听任何活动的生命周期事件。
首先将自定义的本机视图(无论是什么)嵌入到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
以保存碎片并导出javascript的函数和道具:
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)
);
}
}
- 实现javascript端:
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的文档。我再怎么强调也不为过,如果你走的很艰难,阅读它是多么重要。
无论如何,我希望这个小指南能有所帮助。
致以最良好的问候。