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開發心得分享出來,大家一起來學習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章