Android原生UI组件在React Native中的集成指南

Viewed 0

Android原生UI组件

Native Module和Native Components是React Native旧版架构中使用的稳定技术,但未来当新架构稳定后,它们将被弃用,由Turbo Native模块和Fabric原生组件替代。

React Native应用可以集成大量原生UI小部件,包括平台内置、第三方库或自定义组件。虽然React Native已封装了如ScrollViewTextInput等关键组件,但并非所有组件都被覆盖,因此需要自行封装现有组件以实现无缝集成。本指南假设你熟悉Android SDK编程,将展示如何构建原生UI组件,并以ImageView组件为例进行说明。注意:你还可以使用命令设置包含原生组件的本地库,详情请参考本地库设置指南。

ImageView示例

本示例将逐步介绍如何在JavaScript中使用ImageView。原生视图通过扩展ViewManagerSimpleViewManager来创建和操作,其中SimpleViewManager适用于常见属性如背景颜色、不透明度和Flexbox布局。这些子类是单例,桥只创建每个实例的一个实例,它们将原生视图发送到NativeViewHierarchyManager,后者委托它们设置和更新视图属性。ViewManagers通常也是视图的代表,通过桥将事件发送回JavaScript。

发送视图的步骤包括:创建ViewManager子类、实现createViewInstance方法、使用@ReactProp(或@ReactPropGroup)注释公开视图属性设置器、在应用包的createViewManagers中注册管理器,以及实现JavaScript模块。

1. 创建ViewManager子类

在此示例中,创建ReactImageManager类,扩展SimpleViewManager<ReactImageView>getName方法返回的名称用于在JavaScript中引用原生视图类型。

public class ReactImageManager extends SimpleViewManager<ReactImageView> {
  public static final String REACT_CLASS = "RCTImageView";
  ReactApplicationContext mCallerContext;

  public ReactImageManager(ReactApplicationContext reactContext) {
    mCallerContext = reactContext;
  }

  @Override
  public String getName() {
    return REACT_CLASS;
  }
}

2. 实现方法createViewInstance

视图在createViewInstance方法中创建,应初始化为默认状态,属性通过后续调用updateView设置。

@Override
public ReactImageView createViewInstance(ThemedReactContext context) {
  return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), null, mCallerContext);
}

3. 使用@ReactProp(或@ReactPropGroup)注释公开视图属性设置器

要在JavaScript中暴露的属性需用@ReactProp注释的setter方法。setter方法以视图为第一个参数,属性值为第二个参数,返回类型为void@ReactProp有强制参数name,以及可选参数如defaultBooleandefaultIntdefaultFloat,对于复杂类型默认值为null

@ReactProp(name = "src")
public void setSrc(ReactImageView view, @Nullable ReadableArray sources) {
  view.setSource(sources);
}

@ReactProp(name = "borderRadius", defaultFloat = 0f)
public void setBorderRadius(ReactImageView view, float borderRadius) {
  view.setBorderRadius(borderRadius);
}

@ReactProp(name = ViewProps.RESIZE_MODE)
public void setResizeMode(ReactImageView view, @Nullable String resizeMode) {
  view.setScaleType(ImageResizeMode.toScaleType(resizeMode));
}

4. 注册ViewManager

在应用包的createViewManagers方法中注册ViewManager。

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
  return Arrays.<ViewManager>asList(new ReactImageManager(reactContext));
}

5. 实现JavaScript模块

创建JavaScript模块定义接口,建议使用TypeScript、Flow或注释记录组件接口。requireNativeComponent函数接受原生视图的名称,对于更复杂的操作如自定义事件处理,应将原生组件包装在另一个React组件中。

import {requireNativeComponent} from 'react-native';

/**
 * Composes `View`.
 * - src: Array<{url: string}>
 * - borderRadius: number
 * - resizeMode: 'cover' | 'contain' | 'stretch'
 */
export default requireNativeComponent('RCTImageView');

事件

当原生事件发生时,原生代码应向JavaScript视图发出事件,两个视图通过getId()方法返回的值链接。例如,在自定义视图中触发事件:

class MyCustomView extends View {
  public void onReceiveNativeEvent() {
    WritableMap event = Arguments.createMap();
    event.putString("message", "MyMessage");
    ReactContext reactContext = (ReactContext)getContext();
    reactContext.getJSModule(RCTEventEmitter.class)
                .receiveEvent(getId(), "topChange", event);
  }
}

要将topChange事件映射到JavaScript的onChange回调,在ViewManager中覆盖getExportedCustomBubblingEventTypeConstants方法。

public Map getExportedCustomBubblingEventTypeConstants() {
  return MapBuilder.builder().put(
    "topChange",
    MapBuilder.of(
      "phasedRegistrationNames",
      MapBuilder.of("bubbled", "onChange")
    )
  ).build();
}

在JavaScript包装器组件中处理事件:

import {useCallback} from 'react';
import {requireNativeComponent} from 'react-native';

const RCTMyCustomView = requireNativeComponent('RCTMyCustomView');

export default function MyCustomView(props) {
  const onChange = useCallback(event => {
    props.onChangeMessage?.(event.nativeEvent.message);
  }, [props.onChangeMessage]);

  return <RCTMyCustomView {...props} onChange={onChange} />;
}

与Android Fragment示例集成

为了更精细地控制原生组件,可以使用Android Fragment代替从ViewManager返回View,以便利用生命周期方法如onViewCreatedonPauseonResume

1. 创建示例自定义视图

创建CustomView类扩展FrameLayout

package com.mypackage;

import android.content.Context;
import android.graphics.Color;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;

public class CustomView extends FrameLayout {
  public CustomView(@NonNull Context context) {
    super(context);
    this.setPadding(16,16,16,16);
    this.setBackgroundColor(Color.parseColor("#5FD3F3"));
    TextView text = new TextView(context);
    text.setText("Welcome to Android Fragments with React Native.");
    this.addView(text);
  }
}

2. 创建Fragment

创建MyFragment类并实现生命周期方法。

package com.mypackage;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import com.mypackage.CustomView;

public class MyFragment extends Fragment {
  CustomView customView;

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
    super.onCreateView(inflater, parent, savedInstanceState);
    customView = new CustomView(this.getContext());
    return customView;
  }

  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // 自定义逻辑
  }

  @Override
  public void onPause() {
    super.onPause();
    // 自定义逻辑
  }

  @Override
  public void onResume() {
    super.onResume();
    // 自定义逻辑
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    // 自定义逻辑
  }
}

3. 创建ViewManager子类

创建MyViewManager类处理Fragment集成。

package com.mypackage;

import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ThemedReactContext;
import java.util.Map;

public class MyViewManager extends ViewGroupManager<FrameLayout> {
  public static final String REACT_CLASS = "MyViewManager";
  public final int COMMAND_CREATE = 1;
  private int propWidth;
  private int propHeight;
  ReactApplicationContext reactContext;

  public MyViewManager(ReactApplicationContext reactContext) {
    this.reactContext = reactContext;
  }

  @Override
  public String getName() {
    return REACT_CLASS;
  }

  @Override
  public FrameLayout createViewInstance(ThemedReactContext reactContext) {
    return new FrameLayout(reactContext);
  }

  @Nullable
  @Override
  public Map<String, Integer> getCommandsMap() {
    return MapBuilder.of("create", COMMAND_CREATE);
  }

  @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: {}
    }
  }

  @ReactPropGroup(names = {"width", "height"}, customType = "Style")
  public void setStyle(FrameLayout view, int index, Integer value) {
    if (index == 0) propWidth = value;
    if (index == 1) propHeight = value;
  }

  public void createFragment(FrameLayout root, int reactNativeViewId) {
    ViewGroup parentView = (ViewGroup) root.findViewById(reactNativeViewId);
    setupLayout(parentView);
    final MyFragment myFragment = new MyFragment();
    FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
    activity.getSupportFragmentManager()
            .beginTransaction()
            .replace(reactNativeViewId, myFragment, String.valueOf(reactNativeViewId))
            .commit();
  }

  public void setupLayout(View view) {
    Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
      @Override
      public void doFrame(long frameTimeNanos) {
        manuallyLayoutChildren(view);
        view.getViewTreeObserver().dispatchOnGlobalLayout();
        Choreographer.getInstance().postFrameCallback(this);
      }
    });
  }

  public void manuallyLayoutChildren(View view) {
    int width = propWidth;
    int height = propHeight;
    view.measure(
      View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
      View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
    view.layout(0, 0, width, height);
  }
}

4. 注册ViewManager

在包中注册ViewManager。

package com.mypackage;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.List;

public class MyPackage implements ReactPackage {
  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Arrays.<ViewManager>asList(new MyViewManager(reactContext));
  }
}

5. 注册Package

在主应用中注册包。

@Override
protected List<ReactPackage> getPackages() {
  List<ReactPackage> packages = new PackageList(this).getPackages();
  packages.add(new MyPackage());
  return packages;
}

6. 实现JavaScript模块

首先,从自定义视图管理器开始,然后实现自定义视图调用create方法。

import {requireNativeComponent} from 'react-native';
export const MyViewManager = requireNativeComponent('MyViewManager');
import React, {useEffect, useRef} from 'react';
import {PixelRatio, UIManager, findNodeHandle} from 'react-native';
import {MyViewManager} from './my-view-manager';

const createFragment = viewId =>
  UIManager.dispatchViewManagerCommand(
    viewId,
    UIManager.MyViewManager.Commands.create.toString(),
    [viewId]
  );

export const MyView = () => {
  const ref = useRef(null);
  useEffect(() => {
    const viewId = findNodeHandle(ref.current);
    createFragment(viewId);
  }, []);
  return (
    <MyViewManager
      style={{
        height: PixelRatio.getPixelSizeForLayoutSize(200),
        width: PixelRatio.getPixelSizeForLayoutSize(200),
      }}
      ref={ref}
    />
  );
};

要使用@ReactProp暴露属性设置器,请参考上面的ImageView示例。

0 Answers