مكونات واجهة المستخدم الأصيلة لنظام Android في React Native

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث

هناك الكثير من أدوات واجهة المستخدم (UI widgets) الأصيلة الجاهزة للاستخدام في أحدث التطبيقات، بعضها جزء من المنصّة، والبعض الآخر متاح كمكتبات تابعة لجهات طرف ثالث (third-party libraries)، إضافة إلى ما تستخدمه أنت كذلك. يحتوي React Native على العديد من مكوّنات المنصّة الأكثر أهمية، وهي جاهزة للاستخدام، مثل ScrollView و TextInput، ولكن ليس جميعها، وبالطبع، فهذا لا يشمل تلك التي قد تكون كتبتها بنفسك. لحسن الحظ، من السهل جدًا تغليف هذه المكونات الحالية لإدماجها بسلاسة مع تطبيق React Native الخاص بك.

على غرار صفحة الوحدات الأصيلة، فهذا الدليل كذلك أكثر تقدمًا، ويَفترِض أنك معتاد إلى حد ما على برمجة Android SDK. سيوضح لك هذا الدليل كيفية إنشاء مكون واجهة مستخدم أصيل، وسيرشدك إلى كتابة مجموعة فرعيّة من مكون ImageView الموجود في مكتبة React Native الأساسية (the core React Native library).

الواجهة ImageView كمثال

في هذا المثال، سنتعرف على متطلبات الإجراء (implementation requirements) للسماح باستخدام ImageView في JavaScript.

تُنشأ الواجهات الأصيلة Native views وتُعالَج عن طريق توسيع ViewManager أو SimpleViewManager بشكل أكثر شيوعًا. يعتبر SimpleViewManager مناسبًا في هذه الحالة لأنه يطبق خاصيات شائعة مثل لون الخلفية والتعتيم وتخطيط Flexbox.

هذه الأصناف الفرعية هي مفردات (singletons) أساسا، أي تُنشأ نسخة واحدة فقط لكل منها بواسطة الجسر. وهي تُزوِّد الواجهات الأصيلة إلى NativeViewHierarchyManager، والذي يُفوِّض لها مرة أخرى لتعيين وتحديث خاصيات الواجهات حسب الضرورة. عادةً ما يكون مدراء الواجهات (أصناف ViewManager) مفوَّضين للواجهات، مُرسلةً الأحداث مرة أخرى إلى JavaScript عبر الجسر.

تزويدُ واجهة (Vending a view) أمرٌ بسيط:

  1. إنشاء الصنف الفرعي لمدير الواجهة ViewManager
  2. كتابة التابع createViewInstance
  3. توفير الوصول إلى توابع setter لضبط خاصيّات الواجهة باستخدام التوصيف ‎‎@ReactProp‎‎ (‎‎أو ‎‎@ReactPropGroup‎‎)
  4. تسجيل المدير في createViewManagers الخاص بحزمة التطبيقات.
  5. كتابة وحدة JavaScript

1. إنشاء الصنف الفرعي لمدير الواجهة ViewManager

في هذا المثال، سننشئ صنف مدير الواجهة ReactImageManager الذي يوسِّع SimpleViewManager من النوع ReactImageView. النوع ReactImageView هو نوع الكائن الذي يديره المدير، وسيكون هذا هو الواجهة الأصيلة المخصصة. يُستخدَم الاسم المعاد من طرف getName لإحالة (to reference) نوع الواجهة الأصيلة من 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، يجب أن يُهيِّئَ الواجهة نفسها في حالته الافتراضية (default state)، وستُعيَّن الخاصيات عبر استدعاء updateView لاحقًا.

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

3. توفير الوصول إلى توابع ضابطة لضبط خاصيات الواجهة باستخدام التوصيف ‎‎@ReactProp‎‎ (‎‎أو ‎@ReactPropGroup‎‎)

يجب توفير الوصول للخاصيّات التي يجب أن تنعكس في JavaScript كتابع setter موصوف (annotated) باستخدام التوصيف ‎‎@ReactProp‎‎ (‎‎أو ‎@ReactPropGroup‎‎). يجب أن يأخذ تابع ضابط Setter الواجهة المراد تحديثها (من نوع الواجهة الحالية) كأوّل معامل، وقيمة الخاصية كمعامل ثانٍ. يجب إعلان الضابط Setter كتابع void ويجب أن يكون عامًّا (public). يُحدّد نوع الخاصية المرسلة إلى JavaScript تلقائيًا استنادًا إلى نوع المعامل القيمة الخاص بتابع setter. أنواع القيم المدعومة حاليًا هي:

‎‎

boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap‎‎

يحتاج التوصيف ‎‎@ReactProp‎‎ معاملًا إلزاميًّا واحدًا يُسمّى name من النوع String يمثّل اسم التوصيف. يُستخدَم الاسم المعيَّن للتوصيف ‎‎@ReactProp‎‎ المرتبط بالتابع الضابط setter للإشارة إلى الخاصية في جانب JavaScript.

باستثناء name، يقبل التوصيف ‎‎@ReactProp‎‎ المعاملات الاختيارية التالية: defaultBoolean، وdefaultInt، وdefaultFloat. يجب أن تكون هذه المعاملات من النوع البدائي المقابل (بالتالي boolean، وint، وfloat) وستمرَّر القيمة المعطاة إلى تابع ضابط setter في حالة إزالة الخاصية التي يشير إليها تابع ضابط setter من المكون. لاحظ أن القيم "الافتراضية ("default") موفَّرة فقط للأنواع البدائية، إذا كان التابع الضابط setter نوعًا معقدًا، ستوفَّر القيمة null كقيمة افتراضية في حالة إزالة الخاصية المطابقة.

متطلبات إعلان (declaration) تابع ضابط Setter للتوابع الموصوفة بالتوصيف ‎‎@ReactPropGroup‎‎ مختلفة عن متطلبات ‎‎@ReactProp‎‎، يرجى الرجوع إلى توثيقات صنف التوصيف ‎‎@ReactPropGroup‎‎ للمزيد من المعلومات حول هذا الموضوع.

ملاحظة مهمّة: في ReactJS تحديث قيمة الخاصية سيؤدي إلى استدعاء تابع ضباط setter. لاحظ أن إحدى الطرق لتحديث المكون هي بإزالة الخاصيات التي عُيِّنت من قبل. في هذه الحالة، سيستدعى تابع ضابط setter كذلك لإشعار مدير الواجهة بأن الخاصية قد تغيرت. في هذه الحالة، ستوفَّر القيمة "الافتراضية" (للأنواع البدائية، يُمكن تحديد القيم "الافتراضيّة" باستخدام defaultBoolean، وdefaultFloat، إلخ. والتي هي معاملات التوصيف ‎‎@ReactProp‎‎، للأنواع المعقَّدة، سيُستدعى الضابط setter مع القيمة 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

الخطوة الأخيرة في Java هي تسجيل مدير الواجهة للتطبيق، وهذا يحدث بطريقة مشابهة للوحدات الأصيلة عبر دالة عضو حزمة (package member function) التطبيق createViewManagers.

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

5. إنشاء وحدة JavaScript

الخطوة الأخيرة هي إنشاء وحدة JavaScript تحدد طبقة الواجهة (interface layer) بين Java و JavaScript لمستخدمي الواجهة الجديدة. يوصى بتوثيق واجهة المكون في هذه الوحدة (باستخدام Flow أو TypeScript أو التعليقات مثلًا).

// ImageView.js

import { requireNativeComponent } from 'react-native';

/**
 * Composes `View`.
 *
 * - src: string
 * - borderRadius: number
 * - resizeMode: 'cover' | 'contain' | 'stretch'
 */
module.exports = requireNativeComponent('RCTImageView');

تأخذ الدالة requireNativeComponent اسم الواجهة الأصيلة. لاحظ أنه إذا احتاج مكونك إلى القيام بأي شيء أعقد (مثل معالجة الأحداث المخصصة)، فعليك تغليف المكون الأصيل في مكون React آخر. هذا موضح في مثال MyCustomView أدناه.

الأحداث (Events)

نعرف الآن كيف نوفر الوصول لمكونات الواجهة الأصيلة التي يمكننا التحكم فيها بسهولة من JavaScript، لكن كيف نتعامل مع الأحداث من المستخدم، مثل القرص للتكبير أو للتصغير أو المسح (panning). عند حدوثِ حدثٍ أصيل، يجب أن تصدر الشيفرة الأصيلة حدثًا للواجهة في 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 مع خاصية دالة رد النداء onChange في JavaScript، سجِّله عن طريق تجاوز تابع ‎‎getExportedCustomBubblingEventTypeConstants‎‎ في مدير الواجهة ViewManager الخاص بك:

public class ReactImageManager extends SimpleViewManager<MyCustomView> {
    ...
    public Map getExportedCustomBubblingEventTypeConstants() {
        return MapBuilder.builder()
            .put(
                "topChange",
                MapBuilder.of(
                    "phasedRegistrationNames",
                    MapBuilder.of("bubbled", "onChange")))
                    .build();
    }
}

تُستدعَى دالة رد النداء هذه مع الحدث الخام (raw event)، والذي نقوم بمعالجته عادةً في المكون المُغلِّف (wrapper component) لإنشاء واجهة برمجة تطبيقات أبسط:

// MyCustomView.js

class MyCustomView extends React.Component {
  constructor(props) {
    super(props);
    this._onChange = this._onChange.bind(this);
  }
  _onChange(event: Event) {
    if (!this.props.onChangeMessage) {
      return;
    }
    this.props.onChangeMessage(event.nativeEvent.message);
  }
  render() {
    return <RCTMyCustomView {...this.props} onChange={this._onChange} />;
  }
}
MyCustomView.propTypes = {
  /**
   * دالة رد نداء تُستدعى باستمرار عندما يتفاعل المستخدم مع المكوّن
   */
  onChangeMessage: PropTypes.func,
  ...
};

var RCTMyCustomView = requireNativeComponent(`RCTMyCustomView`);

مصادر