1. 程式人生 > >React-Native系列Android自定義原生UI元件

React-Native系列Android自定義原生UI元件

由於官方的Android原生UI元件解釋的並不是很完整,根據個人的不斷摸索,終於成功完成原生元件的製作,所以寫下這篇文章作為記錄,也給讓小白們少走些彎路。

我這裡通過講解制作一個繪圓元件的流程,來學習製作android原生UI元件。這個繪圓元件並沒有實際的使用價值,只是為了更容易的瞭解android原生UI元件的製作過程。好了廢話不多說,現在開始吧。

  • react-native版本:0.33.0

首先初始化react-native專案,最好能弄個VPN

react-native init AndroidNativeModule

初次build android專案,先開啟虛擬機器或連線手機。

react-native run-android

專案的建立就講到這,詳細的專案建立和排錯請自行百度。現在我們用Android Stuido進入android專案。

  • 建立基本的UI元件框架,這裡元件名稱嚴格更具功能進行命名,為了看的更清楚進行了一些打包
    專案目錄

    • CircleManager.java CIrcle原生元件管理器,實現JS和JAVA資訊傳遞
    • CircleView.java Circle原生元件
    • MainPackage.java 自定義元件註冊包

接下來給建立好的java新增基本結構,然後在後面更具需要新增細節功能

  • CircleView.java 基礎結構
package
com.androidnativemodule.module.circle; import android.content.Context; import android.view.View; /** * 圓形元件元件基礎類 */ public class CircleView extends View { public CircleView(Context context) { super(context); } }

UI元件繼承於View類,這裡沒有加什麼功能,就建立一個基本的類

  • CircleManager.java
package com.androidnativemodule.module.circle;

import
com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; /** * 圓形元件基礎類管理器 */ public class CircleManager extends SimpleViewManager<CircleView> { /** * 設定js引用名 * @return String */ @Override public String getName() { return "MCircle"; } /** * 建立UI元件例項 * @param reactContext * @return CircleView */ @Override protected CircleView createViewInstance(ThemedReactContext reactContext) { return new CircleView(reactContext); } }

所有的元件管理器需要繼承SimpleViewManager類,後面加上自己定義的元件基礎類,下面2個方法是SimpleViewManager類的必須實現的方法,記住getName()返回的名字要和JS裡的應用名進行統一。

  • MainPackge.java
package com.androidnativemodule;

import com.androidnativemodule.module.circle.CircleManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 自定義元件模組註冊類
 */
public class MainPackage implements ReactPackage {

    /**
     * 建立原生模組
     * @param reactContext
     * @return
     */
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    /**
     * 建立原生UI元件控制器
     * @param reactContext
     * @return
     */
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
            new CircleManager()
        );
    }
}

這個是註冊類,實現ReactPackage介面,可以想自己設計的元件統一寫在這個類中,然後一起在Application中註冊,注意:返回空值需要返回Collections.emptyList()。它還可以註冊原生模組,這個官方有詳細的講解,地址如下:http://facebook.github.io/react-native/docs/native-modules-android.html

註冊類寫好後,還需要將這個類載入進Application中,我們在通過MainApplication中將其加入進去。

  • MainApplication.java
package com.androidnativemodule;

import android.app.Application;
import android.util.Log;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;

import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    protected boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          new MainPackage() // 在這裡載入我們自己的註冊類
      );
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
      return mReactNativeHost;
  }
}

到這裡我們的自定義UI元件框架基本完成,我們可以run一下看看,如果沒有效果,可以嘗試重新react-native run-android 下。

在Android Monitor中可以看到已經載入了我們的CircleModule,只有有沒我們JS並沒有例項話他,所以Could not find generated setter for class com.androidnativemodule.module.circle.CircleManager
Android Monitor截圖

接下來我們給我們的原生UI元件加上功能。

  • CircleView.java
package com.androidnativemodule.module.circle;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;

/**
 * 圓形元件元件基礎類
 */
public class CircleView extends View {

    private final String TAG = "CircleView";
    private Paint mPaint; // 畫筆

    public CircleView(Context context) {
        super(context);
        mPaint = new Paint();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(100, 100, 100, mPaint); // 畫一個半徑為100px的圓
        Log.d(TAG, "繪圖");
    }
}

這裡我先在CircleView內寫死程式碼,看看這個原生UI到底能不能在JS中使用

現在我們來到react-native專案,我們先定製一個Circle元件的JS介面,方便其他元件呼叫。

  • 建立Circle.js
import { PropTypes } from 'react';
import { requireNativeComponent, View } from 'react-native';

const MCircle = requireNativeComponent('MCircle', {
  propTypes: {
    ...View.propTypes // 包含預設的View的屬性
  },
});

export default MCircle;

使用requireNativeComponent根據先前在管理器中定義好的元件名引用原生元件,由於我們還沒建立介面,所以這個元件暫時只有父類View的介面。

現在我們在index.android.js中例項化元件看看效果

  • index.android.js
'use strict';

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';
import Circle from './Circle';

class AndroidNativeModule extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Circle style={{width: 100, height: 100}} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  }
});

AppRegistry.registerComponent('AndroidNativeModule', () => AndroidNativeModule);

效果圖:
模擬器截圖

可以看到成功使用Android canvas 畫了一個圓

現在我們給他加一些介面,從而實現JS和JAVA的通訊,直接在JS中更改元件樣式。

  • CircleView.java
package com.androidnativemodule.module.circle;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;

import com.facebook.react.uimanager.PixelUtil;

/**
 * 圓形元件元件基礎類
 */
public class CircleView extends View {

    private final String TAG = "CircleView";
    private Paint mPaint; // 畫筆
    private float mRadius;  // 圓的半徑

    public CircleView(Context context) {
        super(context);
        mPaint = new Paint();
    }

    /**
     * 設定圓的背景色
     * @param color
     */
    public void setColor(Integer color) {
        mPaint.setColor(color); // 設定畫筆顏色
        invalidate();   // 更新畫板
    }

    /**
     * 設定圓的半徑
     * @param radius
     */
    public void setRadius(Integer radius) {
        /**
         * 由於JS傳過的數字是dip單位,需要轉換為實際畫素
         * 使用com.facebook.react.uimanager包中的PixelUtil,進行轉換
         */
        mRadius = PixelUtil.toPixelFromDIP(radius);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mRadius, mRadius, mRadius, mPaint); // 畫一個半徑為100px的圓
        Log.d(TAG, "繪圖");
    }
}

這裡添加了2個介面,實現對圓的大小和顏色更改,還使用了PixelUtil實現畫素的轉換

  • CircleManager.java
package com.androidnativemodule.module.circle;

import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;

/**
 * 圓形元件基礎類管理器
 */
public class CircleManager extends SimpleViewManager<CircleView> {

    /**
     * 設定js引用名
     * @return String
     */
    @Override
    public String getName() {
        return "MCircle";
    }

    /**
     * 建立UI元件例項
     * @param reactContext
     * @return CircleView
     */
    @Override
    protected CircleView createViewInstance(ThemedReactContext reactContext) {
        return new CircleView(reactContext);
    }

    /**
     * 傳輸背景色引數
     * @param view
     * @param color
     */
    @ReactProp(name = "color")
    public void setColor(CircleView view, Integer color) {
        view.setColor(color);
    }

    /**
     * 傳輸半徑引數
     * @param view
     * @param radius
     */
    @ReactProp(name = "radius")
    public void setRadius(CircleView view, Integer radius) {
        view.setRadius(radius);
    }
}

管理器中引用介面,將引數傳輸過去

現在我們的CircleModule已經完成了,然後我們修改下Circle元件JS介面,實現介面使用。

  • Circle.js
'use strict';

import React, { Component, PropTypes } from 'react';
import {
  View,
  requireNativeComponent,
  processColor  // 字元Color轉換為數字
} from 'react-native';

const MCircle = requireNativeComponent('MCircle', {
  propTypes: {
    color: PropTypes.number,
    radius: PropTypes.number,
    ...View.propTypes // 包含預設的View的屬性
  },
});

class Circle extends Component {

  static propTypes = {
    radius: PropTypes.number,
    color: PropTypes.string, // 這裡傳過來的是string
    ...View.propTypes // 包含預設的View的屬性
  }

  render() {
    const { style, radius, color } = this.props;

    return (
      <MCircle
        style={style}
        radius={radius}
        color={processColor(color)}
      />
    );
  }

}

module.exports = Circle;

由於color使用的是String,我們可以用react-native的processColor將其轉換為數字,從而可以讓java識別出顏色,為了方便使用,所以我這邊重寫建立了Component,作為中間元件,對color進行轉換。

然後我的就可以通過Circle.js使用CircleModule了,例如:

<Circle
  style={{width: 100, height: 100}}
  color="#25c5f7"
  radius={50}
/>

效果圖:
模擬器截圖

到此Android自定義原生UI元件CircleModule的設計就結束了,如果有錯誤的地方,大家可以指出來反饋給我,也希望大家可以將自己的react-native開發心得分享出來,大家一起來學習。