React Native 基於畫板簡析封裝安卓原生UI



1、創建 ViewManager 的子類


2、實現方法 createViewInstance


    @Override

    protected DrawCanvasView createViewInstance(ThemedReactContext reactContext) {

        this.mContext = reactContext;

        return new DrawCanvasView(reactContext.getApplicationContext(), reactContext);

    }


3、通過 @ReactProp 註解導出屬性的設置方法


@ReactProp 註解必須包含一個字符串類型的參數 name。這個參數指定了對應屬性在JavaScript 端的名字。

    @ReactProp(name = PROP_DRAW)

    public void setCanvasType(final DrawCanvasView drawCanvasView, boolean type) {

        System.out.println(TAG + " " + type);

        if (type) {

            //代表老師,可以繪製畫板

            drawCanvasView.setCanvasType(CanvasType.CAN_DRAW);

        } else {

            //代表學生,可以看畫板,不能繪製

            drawCanvasView.setCanvasType(CanvasType.CAN_SHOW);

        }

    }



4、註冊 ViewManager


    //DrawCanvasPackage.java

    @Override

public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {

    return Arrays.<ViewManager>asList(

            new DrawCanvasManager()

    );

}


5、實現對應的 JavaScript 模塊


    //DrawCanvasView.js

    import React, {ComponentPropTypesfrom 'react';

import {requireNativeComponentViewfrom 'react-native';

export default class DrawCanvasView extends Component {

    constructor(props) {

        super(props);

    };

 

    _onDraw=(event)=> {

        if (this.props.onDraw) {

            this.props.onDraw(event.nativeEvent);

        }

    }

 

    _onDrawUp=(event)=>{

        if(this.props.onDrawUp){

            this.props.onDrawUp(event.nativeEvent);

        }

    }

 

    _onMenuClick=(event)=>{

        if(this.props.onMenuClick){

            this.props.onMenuClick(event.nativeEvent);

        }

    }

 

    setNativeProps(nativeProps) {

        this._root.setNativeProps(nativeProps);

    }

 

    loadMaterial = (material) => {

        this.setNativeProps({material: material});

    }

    sendCommand = (holder) => {

        this.setNativeProps({send_command: holder});

    }

    _assignRoot = (component) => {

        this._root = component;

    }

 

    render() {

        const nativeProps = Object.assign({}, this.props);

        Object.assign(nativeProps, {

            stylenativeProps.style,

            loadMaterialthis.loadMaterial,

            sendCommandthis.sendCommand,

            onDrawthis._onDraw,

            onDrawUp:this._onDrawUp,

            onMenuClick:this._onMenuClick,

        })

        return (

            <RCTDrawCanvasView

                ref={this._assignRoot}

                {...nativeProps}

            />

        )

    }

}

DrawCanvasView.propTypes={

    ...View.propTypes,

    material:PropTypes.func,

    send_command:PropTypes.func,

    onDraw:PropTypes.func,

    onDrawUp:PropTypes.func,

    can_draw:PropTypes.bool,

    onMenuClick:PropTypes.func,

}

const RCTDrawCanvasView requireNativeComponent("DrawCanvasView",DrawCanvasView, null)


Notes_1514181742807.jpeg



6、自定義事件註冊


對於用戶的操作,例如繪製畫板,縮放,拖拽,JS端需要響應用戶的操作,所以需要原生視圖向JS端發送事件,傳遞數據。


·        列舉註冊事件


   //PaintView.java

   public enum Events {

    EVENT_ON_DRAW("onDraw"),

    EVENT_ON_DRAW_UP("onDrawUp"),

    EVENT_MENU_CLICK("onMenuClick");

 

    private final String mName;

 

    Events(final String name) {

        mName = name;

    }

 

    @Override

    public String toString() {

        return mName;

    }

}


·        導出自定義事件


//DrawCanvasManager.java

@Override

@Nullable

public Map getExportedCustomDirectEventTypeConstants() {

    MapBuilder.Builder builder = MapBuilder.builder();

    for (Events event : Events.values()) {

        builder.put(event.toString()MapBuilder.of("registrationName"event.toString()));

    }

    return builder.build();

}


·        發送事件


//DrawCanvasView.java

@Override

    public void onClick(View v) {

        WritableMap event= Arguments.createMap();

        switch (v.getId()) {

            case R.id.ll_undo:

                mPaintView.undo();

                event.putInt("command"Command.UNDO);

                break;

            case R.id.ll_redo:

                mPaintView.redo();

                event.putInt("command"Command.REDO);

                break;

            case R.id.ll_reset:

                mPaintView.clear();

                event.putInt("command"Command.CLEAR);

                break;

            case R.id.ll_save:

                mPaintView.clear();

                event.putInt("command"Command.SAVE);

                break;

            default:

                Log.i("ID---view"Integer.toString(v.getId()));

                break;

        }

//        System.out.println(TAG+"1     "+getId());

        reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(

                getId()PaintView.Events.EVENT_MENU_CLICK.toString(),event);

    }


7、原生端接收 JS 端傳遞的數據解析


使用 ReadableMap 接收數據,如果接收的是數組則使用 ReadableArray

/**

  * 畫板接收到命令

  *

  * @param drawCanvasView

  @param holder

  */

 @ReactProp(name PROP_COMMAND)

 public void sendCommand(final DrawCanvasView drawCanvasViewReadableMap holder) {

     System.out.println(TAG+" "+holder);

     int command = holder.getInt("command");

     switch (command) {

         case Command.DRAW: {

             ReadableArray path=holder.getArray("path");

             float size=(float) holder.getDouble("paintSize");

             int color=holder.getInt("paintColor");

             drawCanvasView.sendDrawCommand(command,path,size,color);

         }

         break;

/*         case Command.DRAG:{

             float x=(float)holder.getDouble("currentDistanceX");

             float y=(float)holder.getDouble("currentDistanceY");

             drawCanvasView.sendCommand(x,y);

         }

         break;*/

         case Command.GESTURE:{

             ReadableArray data=holder.getArray("data");

             drawCanvasView.sendCommand(data);

         }

         default:

             //數據只有command

             drawCanvasView.sendCommand(command);

             break;

         }

 }





8、原生組件使用


onDraw , onDrawUp , onMenuClick 皆是用於響應用戶操作,原生向 JS端 發送事件

<DrawCanvasView

    ref={(ref)=>{this._drawCanvas=ref}}

    onDraw={(e)=>this.onDraw(e)}

    onDrawUp={(e)=>this.onDrawUp(e)}

    can_draw={true}

    onMenuClick={(e)=>this._onMenuClick(e)}

style={{width:width,height:536*size}}/>

 

主要代碼展示


     //DrawCanvasManager.java

     public class DrawCanvasManager extends SimpleViewManager<DrawCanvasView> {

 

    private static String TAG = DrawCanvasManager.class.getSimpleName();

    public static final String PROP_MATERIAL = "material";

    public static final String PROP_DRAW = "can_draw";

    public static final String PROP_COMMAND = "send_command";

    private Context mContext;

 

    @Override

    public String getName() {

        return "DrawCanvasView";

    }

 

    @Override

    protected DrawCanvasView createViewInstance(ThemedReactContext reactContext) {

        this.mContext = reactContext;

        return new DrawCanvasView(reactContext.getApplicationContext()reactContext);

    }

 

    @Override

    @Nullable

    public Map getExportedCustomDirectEventTypeConstants() {

        MapBuilder.Builder builder = MapBuilder.builder();

        for (Events event : Events.values()) {

            builder.put(event.toString()MapBuilder.of("registrationName"event.toString()));

        }

        return builder.build();

    }

 

    /**

     * name 名稱不能包含大寫

     *

     * @param drawCanvasView

     * @param material

     * @throws Exception

     */

    @ReactProp(name = PROP_MATERIAL)

    public void loadMaterial(final DrawCanvasView drawCanvasViewReadableMap material) throws Exception {

        //content:// 路徑

       /* Uri url=Uri.parse(material.getString("uri"));

        String _uri=getRealFilePath(mContext,url);

        FileInputStream fis=new FileInputStream(_uri);*/

 

        //真實路徑

        // Bitmap bitmap=BitmapFactory.decodeFile(uri);

 

        //圖片資源

        int name = 0;

        String _name = material.getString("uri");

        if (_name.equals("image1")) {

            // name=R.drawable.image1;

            name = R.drawable.ic_iamge1;

        else if (_name.equals("image2")) {

            name = R.drawable.image2;

            //name=R.drawable.ic_image2;

        else if (_name.equals("image3")) {

            name = R.drawable.image3;

        }

    /*    InputStream fis=mContext.getResources().openRawResource(name);

        Bitmap bitmap = BitmapFactory.decodeStream(fis);

        if(bitmap==null){

            return;

        }

        System.out.println("DrawCanvasManager==" + material.getString("uri")+" "+bitmap);

 

        //最好是矢量圖,位圖縮放後會變得模糊

     //   Bitmap newBitMpa=big(bitmap);

        drawCanvasView.drawImage(bitmap,(float) material.getDouble("left"),(float) material.getDouble("top"));*/

        Drawable drawable = ContextCompat.getDrawable(mContextname);

        drawCanvasView.addSticker(new DrawableSticker(drawable));

    }

 

    /**

     * 設置Canvas畫布的類型

     *

     * @param drawCanvasView

     * @param type

     */

    @ReactProp(name = PROP_DRAW)

    public void setCanvasType(final DrawCanvasView drawCanvasView, boolean type) {

        System.out.println(TAG + " " + type);

        if (type) {

            //代表老師,可以繪製畫板

            drawCanvasView.setCanvasType(CanvasType.CAN_DRAW);

        else {

            //代表學生,可以看畫板,不能繪製

            drawCanvasView.setCanvasType(CanvasType.CAN_SHOW);

        }

    }

 

    /**

     * 畫板接收到命令

     *

     * @param drawCanvasView

     * @param holder

     */

    @ReactProp(name = PROP_COMMAND)

    public void sendCommand(final DrawCanvasView drawCanvasViewReadableMap holder) {

        System.out.println(TAG+" "+holder);

        int command = holder.getInt("command");

        switch (command) {

            case Command.DRAW: {

                ReadableArray path=holder.getArray("path");

                float size=(float) holder.getDouble("paintSize");

                int color=holder.getInt("paintColor");

                drawCanvasView.sendDrawCommand(command,path,size,color);

            }

            break;

   /*         case Command.DRAG:{

                float x=(float)holder.getDouble("currentDistanceX");

                float y=(float)holder.getDouble("currentDistanceY");

                drawCanvasView.sendCommand(x,y);

            }

            break;*/

            case Command.GESTURE:{

                ReadableArray data=holder.getArray("data");

                drawCanvasView.sendCommand(data);

            }

            default:

                //數據只有command

                drawCanvasView.sendCommand(command);

                break;

            }

    }

 

    /**

     * Try to return the absolute file path from the given Uri

     *

     * @param context

     * @param uri

     * @return the file path or null

     */

    public static String getRealFilePath(final Context context, final Uri uri) {

        if (null == uri) return null;

        final String scheme = uri.getScheme();

        String data = null;

        if (scheme == null) {

            data = uri.getPath();

        else if (ContentResolver.SCHEME_FILE.equals(scheme)) {

            data = uri.getPath();

        else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {

            Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);

            if (null != cursor) {

                if (cursor.moveToFirst()) {

                    int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);

                    if (index > -1) {

                        data = cursor.getString(index);

                    }

                }

                cursor.close();

            }

        }

        return data;

    }

 

    private static Bitmap big(Bitmap bitmap) {

        Matrix matrix = new Matrix();

        matrix.postScale(2.5f2.5f)//長和寬放大縮小的比例

        Bitmap resizeBmp = Bitmap.createBitmap(bitmap00bitmap.getWidth()bitmap.getHeight()matrix, true);

        return resizeBmp;

    }

}

//DrawCanvasView.java

public class DrawCanvasView extends RelativeLayout implements View.OnClickListener,OnSeekBarChangeListener,PaintView.OnDrawListener,PaintView.OnStickerOperationListener{

 

    private PaintView mPaintView;

    private VerticalSeekBar mVerticalSeekBar;

    private static final String TAG = DrawCanvasView.class.getSimpleName();

    private Context mContext;

    private ReactContext reactContext;

    View btnUndo,btnRedo;

    public DrawCanvasView(Context contextReactContext reactContext) {

        super(context);

        this.mContext = context;

        this.reactContext=reactContext;

        LayoutInflater.from(context).inflate(R.layout.activity_draw, this);

        initView(context);

    }

 

    public void initView(Context context) {

        //初始化顏色板

        //  initColorPickerDialog();

        //初始化自定義的ToolBar

        initToolbar();

        mPaintView = (PaintView) findViewById(R.id.draw_view);

        mVerticalSeekBar = (VerticalSeekBar) findViewById(R.id.seekBar);

        mVerticalSeekBar.setOnSeekBarChangeListener(this);

        mPaintView.setStrokeWidth(mVerticalSeekBar.getProgress());

        mPaintView.setBgColor(Color.WHITE);

        mPaintView.setOnDrawListener(this);

        mPaintView.setOnStickerOperationListener(this);

        mPaintView.setConstrained(true);

        mPaintView.myContext=reactContext;

    }

 

    /**

     * 初始化自定義toolbar

     */

    private void initToolbar() {

        btnUndo=findViewById(R.id.ll_undo);

        btnUndo.setOnClickListener(this);

        btnUndo.setEnabled(false);

        btnRedo=findViewById(R.id.ll_redo);

        btnRedo.setOnClickListener(this);

        btnRedo.setEnabled(false);

        findViewById(R.id.ll_reset).setOnClickListener(this);

        findViewById(R.id.ll_save).setOnClickListener(this);

    }

 

    @Override

    public void onClick(View v) {

        WritableMap event= Arguments.createMap();

        switch (v.getId()) {

            case R.id.ll_undo:

                mPaintView.undo();

                event.putInt("command"Command.UNDO);

                break;

            case R.id.ll_redo:

                mPaintView.redo();

                event.putInt("command"Command.REDO);

                break;

            case R.id.ll_reset:

                mPaintView.clear();

                event.putInt("command"Command.CLEAR);

                break;

            case R.id.ll_save:

                mPaintView.clear();

                event.putInt("command"Command.SAVE);

                break;

            default:

                Log.i("ID---view"Integer.toString(v.getId()));

                break;

        }

//        System.out.println(TAG+"1     "+getId());

        reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(

                getId()PaintView.Events.EVENT_MENU_CLICK.toString(),event);

    }

 

    @Override

    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

        mPaintView.setStrokeWidth(progress);

    }

 

    @Override

    public void onStartTrackingTouch(SeekBar seekBar) {

 

    }

 

    @Override

    public void onStopTrackingTouch(SeekBar seekBar) {

 

    }

 

    public void drawImage(Bitmap bitmap, float left, float top) {

        mPaintView.drawImage(bitmaplefttop);

    }

 

    @Override

    public void afterPaintInit(int viewWidth, int viewHeight) {

 

    }

 

    @Override

    public void afterEachPaint(ArrayList<DrawShape> drawShapes) {

        setUndoEnable(drawShapes);

    }

 

    @Override

    public void afterRedoEachPaint(ArrayList<DrawShape> drawShapes) {

        setRedoEnable(drawShapes);

    }

 

    private void setUndoEnable(ArrayList<DrawShape> drawShapes) {

        if (drawShapes.size() == 0) {

            btnUndo.setEnabled(false);

        }else {

            btnUndo.setEnabled(true);

        }

    }

    private void setRedoEnable(ArrayList<DrawShape> drawShapes) {

        if (drawShapes.size() == 0) {

            btnRedo.setEnabled(false);

        }else {

            btnRedo.setEnabled(true);

        }

    }

 

    public void addSticker(@NonNull Sticker sticker){

        addSticker(sticker,Sticker.Position.CENTER);

    }

 

    public void addSticker(@NonNull final Sticker sticker,final @Sticker.Position int position){

        if(ViewCompat.isLaidOut(this)){

            mPaintView.addStickerImmediately(stickerposition);

        }else{

            post(new Runnable() {

                @Override public void run() {

                    mPaintView.addStickerImmediately(stickerposition);

                }

            });

        }

 

    }

 

    public void setCanvasType(@NonNull CanvasType type){

        mPaintView.viewId=getId();

        System.out.println(TAG+"2     "+getId());

        mPaintView.setCanvasType(type);

    }

 

    public void sendDrawCommand(int commandReadableArray path, float paintSize, int paintColor){

        mPaintView.sendDrawCommand(command,path,paintSize,paintColor);

    }

 

    public void sendCommand(int command){

        switch(command){

            case Command.UNDO:{

                mPaintView.undo();

            }

            break;

            case Command.CLEAR:{

                mPaintView.clear();

            }

            break;

            case Command.REDO:{

                mPaintView.redo();

            }

            break;

            case Command.SAVE:{

 

            }

        }

    }

 

 /*   public void sendCommand(float x,float y){

        mPaintView.sendCommand(x,y);

    }*/

 

    public void sendCommand(ReadableArray data){

        mPaintView.sendCommand(data);

    }

 

    @Override

    public void onStickerAdded(@NonNull Sticker sticker) {

        Log.i(TAG"onStickerAdded");

    }

 

    @Override

    public void onStickerClicked(@NonNull Sticker sticker) {

        Log.i(TAG"onStickerClicked");

    }

 

    @Override

    public void onStickerDeleted(@NonNull Sticker sticker) {

 

    }

 

    @Override

    public void onStickerDragFinished(@NonNull Sticker sticker) {

 

    }

 

    @Override

    public void onStickerZoomFinished(@NonNull Sticker sticker) {

 

    }

 

    @Override

    public void onStickerFlipped(@NonNull Sticker sticker) {

 

    }

 

    @Override

    public void onStickerDoubleTapped(@NonNull Sticker sticker) {

 

    }

 

    @Override public boolean onInterceptTouchEvent(MotionEvent ev) {

        mPaintView.onInterceptTouchEvent(ev);

        return super.onInterceptTouchEvent(ev);

    }

 

}



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