是的,有可能实现它。实际上,有不止一种方法可以从本机模块监听 PiP 模式事件。
快捷方式
最简单的方法是使您的基本 java 模块 aLifecycleStateObserver
并检查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
实现这个特性的拉取请求。请检查一下 。
艰辛的道路
还有另一种监听画中画变化的方法,但它更复杂,需要更深入地了解 android 和 RN 平台。但是,有了它,您可以获得访问newConfig
方法onPictureInPictureModeChanged
(如果需要)而不监听任何活动的生命周期事件的优势。
首先将您的自定义本机视图(无论它是什么)嵌入到 aFragment
中,然后覆盖片段的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 的文档。如果你走得很辛苦,我怎么强调阅读它的重要性都不为过。
无论如何,我希望这个小指南可能会有所帮助。
此致。