深入解析Android中的自定義繪圖

Home PDF

本博客文章在ChatGPT-4o的幫助下編寫。


介紹

在這篇博客中,我們將探討 DrawActivity 類,這是一個在Android應用中實現自定義繪圖視圖的全面示例。我們將分解每個組件和使用的算法,詳細解釋它們如何協同工作以實現所需的功能。


目錄

DrawActivity概述
初始化Activity
處理圖像操作
Fragment管理
事件處理
撤銷和重做功能
自定義DrawView
歷史管理
結論


DrawActivity概述

DrawActivity 是處理繪圖操作、圖像裁剪以及與其他組件(如fragment和圖像上傳)交互的主要活動。它提供了一個用戶界面,用戶可以在其中繪圖、撤銷、重做和操作圖像。

public class DrawActivity extends Activity implements View.OnClickListener {
  // 請求代碼和fragment ID的常量
  public static final int CAMERA_RESULT = 1;
  public static final int CROP_RESULT = 2;
  public static final int DRAW_FRAGMENT = 0;
  public static final int RECOG_FRAGMENT = 1;
  public static final int RESULT_FRAGMENT = 2;
  public static final int WAIT_FRAGMENT = 3;
  public static final int MATERIAL_RESULT = 4;
  public static final String RESULT_JSON = "resultJson";
  public static final int INIT_FLOWER_ID = R.drawable.flower_b;
  public static final int LOGOUT = 0;
  public static final int IMAGE_RESULT = 0;
  
  // 處理圖像和繪圖操作的變量
  String baseUrl;
  DrawView drawView;
  Bitmap originImg;
  public static DrawActivity instance;
  View dir, clear, cameraView, materialView, scale;
  ImageView undoView, redoView;
  View upload;
  String cropPath;
  Tooltip toolTip;
  int curFragmentId = -1;
  int serverId = -1;
  private Bitmap resultBitmap;
  private RadioGroup radioGroup;
  Fragment curFragment;
  int curDrawMode;
  RadioButton drawBackBtn;
  private Activity cxt;
  Uri curPicUri;
}

初始化Activity

在Activity創建時,執行各種初始化操作,如設置視圖、加載初始圖像和配置事件監聽器。

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  instance = this;
  cxt = this;
  cropPath = PathUtils.getCropPath();
  setContentView(R.layout.draw_layout);
  findView();
  setSize();
  initOriginImage();
  toolTip = new Tooltip(this);
  initUndoRedoEnable();
  setIp();
  initDrawmode();
}

findView()
此方法初始化Activity中使用的視圖。

private void findView() {
  drawView = findViewById(R.id.drawView);
  undoView = findViewById(R.id.undo);
  redoView = findViewById(R.id.redo);
  scale = findViewById(R.id.scale);
  upload = findViewById(R.id.upload);
  clear = findViewById(R.id.clear);
  dir = findViewById(R.id.dir);
  materialView = findViewById(R.id.material);
  cameraView = findViewById(R.id.camera);

  dir.setOnClickListener(this);
  materialView.setOnClickListener(this);
  undoView.setOnClickListener(this);
  scale.setOnClickListener(this);
  redoView.setOnClickListener(this);
  clear.setOnClickListener(this);
  cameraView.setOnClickListener(this);
  upload.setOnClickListener(this);
  initRadio();
}

setSize()
設置繪圖視圖的大小。

private void setSize() {
  setSizeByResourceSize();
  setViewSize(drawView);
}

private void setSizeByResourceSize() {
  int width = getResources().getDimensionPixelSize(R.dimen.draw_width);
  int height = getResources().getDimensionPixelSize(R.dimen.draw_height);
  App.drawWidth = width;
  App.drawHeight = height;
}

private void setViewSize(View v) {
  ViewGroup.LayoutParams lp = v.getLayoutParams();
  lp.width = App.drawWidth;
  lp.height = App.drawHeight;
  v.setLayoutParams(lp);
}

initOriginImage()
加載將用於繪圖的初始圖像。

private void initOriginImage() {
  Bitmap bitmap = BitmapFactory.decodeResource(getResources(), INIT_FLOWER_ID);
  String imgPath = PathUtils.getCameraPath();
  BitmapUtils.saveBitmapToPath(bitmap, imgPath);
  Uri uri1 = Uri.fromFile(new File(imgPath));
  setImageByUri(uri1);
}

處理圖像操作

Activity處理各種圖像操作,如通過URI設置圖像、裁剪和保存繪製的位圖。

setImageByUri(Uri uri)
從給定的URI加載圖像並準備繪圖。

private void setImageByUri(final Uri uri) {
  new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
      curPicUri = uri;
      Bitmap bitmap = null;
      try {
        if (uri != null) {
          bitmap = BitmapUtils.getBitmapByUri(DrawActivity.this, uri);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }

      int originW = bitmap.getWidth();
      int originH = bitmap.getHeight();
      if (originW != App.drawWidth || originH != App.drawHeight) {
        float originRadio = originW * 1.0f / originH;
        float radio = App.drawWidth * 1.0f / App.drawHeight;
        if (Math.abs(originRadio - radio) < 0.01) {
          Bitmap originBm = bitmap;
          bitmap = Bitmap.createScaledBitmap(originBm, App.drawWidth, App.drawHeight, false);
          originBm.recycle();
        } else {
          cropIt(uri);
          return;
        }
      }
      ImageLoader imageLoader = ImageLoader.getInstance();
      imageLoader.addOrReplaceToMemoryCache("origin", bitmap);
      originImg = bitmap;
      serverId = -1;

      drawView.setSrcBitmap(originImg);
      showDrawFragment(App.ALL_INFO);
      curDrawMode = App.DRAW_FORE;
    }
  }, 500);
}

cropIt(Uri uri)
啟動圖像裁剪活動。

public void cropIt(Uri uri) {
  Crop.startPhotoCrop(this, uri, cropPath, CROP_RESULT);
}

saveBitmap()
將繪製的位圖保存到文件並上傳到服務器。

public void saveBitmap() {
  Bitmap handBitmap = drawView.getHandBitmap();
  Bitmap originBitmap = drawView.getSrcBitmap();
  saveBitmapToFileAndUpload(handBitmap, originBitmap);
}

saveBitmapToFileAndUpload(Bitmap handBitmap, Bitmap originBitmap)
將位圖保存到文件並異步上傳。

private void saveBitmapToFileAndUpload(Bitmap handBitmap, Bitmap originBitmap) {
  final String originPath = PathUtils.getOriginPath();
  BitmapUtils.saveBitmapToPath(originBitmap, originPath);
  final String handPath = PathUtils.getHandPath();
  BitmapUtils.saveBitmapToPath(handBitmap, handPath);
  new AsyncTask<Void, Void, Void>() {
    boolean res;
    Bitmap foreBitmap;
    Bitmap backBitmap;

    @Override
    protected void onPreExecute() {
      super.onPreExecute();
      showWaitFragment();
    }

    @Override
    protected Void doInBackground(Void... params) {
      try {
        if (baseUrl == null) {
          throw new Exception("baseUrl is null");
        }
        String jsonRes = UploadImage.upload(baseUrl, serverId, Web.STATUS_CONTINUE, originPath, handPath, null, false);
        getJsonData(jsonRes);
        res = true;
      } catch (Exception e) {
        res = false;
        e.printStackTrace();
      }
      return null;
    }

    private void getJsonData(String jsonRes) throws Exception {
      JSONObject json = new JSONObject(jsonRes);
      if (serverId == -1) {
        serverId = json.getInt(Web.ID);
      }
      String foreUrl = json.getString(Web.FORE);
      String backUrl = json.getString(Web.BACK);
      String resultUrl = json.getString(Web.RESULT);
      foreBitmap = Web.getBitmapFromUrlByStream1(foreUrl, 0);
      backBitmap = Web.getBitmapFromUrlByStream1(backUrl, 0);
      resultBitmap = Web.getBitmapFromUrlByStream1(resultUrl, 0);
    }

    @Override
    protected void onPostExecute(Void aVoid) {
      super.onPostExecute(aVoid);
      if (res) {
        showRecogFragment(foreBitmap, backBitmap);
      } else {
        Utils.toast(DrawActivity.this, R.string.server_error);
        recogNo();
      }
    }


  }.execute();
}

Fragment管理

Activity管理不同的fragment以處理應用的各種狀態,如繪圖、識別和等待。

showDrawFragment(int infoId)
顯示繪圖fragment。

private void showDrawFragment(int infoId) {
  curFragmentId = DRAW_FRAGMENT;
  curFragment = new DrawFragment(infoId);
  showFragment(curFragment);
}

showWaitFragment()
顯示等待fragment。

private void showWaitFragment() {
  curFragmentId = WAIT_FRAGMENT;
  showFragment(new WaitFragment());
}

showFragment(Fragment fragment)
用指定的fragment替換當前fragment。

private void showFragment(Fragment fragment) {
  FragmentTransaction trans = getFragmentManager().beginTransaction();
  trans.replace(R.id.rightLayout, fragment);
  trans.commit();
}

事件處理

Activity處理各種用戶交互,如按鈕點擊和菜單選擇。

onClick(View v)
處理不同視圖的點擊事件。

@Override
public void onClick(View v) {
  int id = v.getId();
  if (id == R.id.drawOk) {
    if (drawView.isDrawFinish()) {
      saveBitmap();
    } else {
      Utils.alertDialog(this, R.string.please_draw_finish);
    }
  } else if (id == R.id.recogOk) {
    recogOk();
  } else if (id == R.id.recogNo) {
    recogNo();
  } else if (id == R.id.dir) {
    Utils.getGalleryPhoto(this, IMAGE_RESULT);
  } else if (id == R.id.clear) {
    clearEverything();
  } else if (id == R.id.undo) {
    drawView.undo();
  } else if (id == R.id.redo) {
    drawView.redo();
  } else if (id == R.id.camera) {
    Utils.takePhoto(cxt, CAMERA_RESULT);
  } else if (id == R.id.material) {
    goMaterial();
  } else if (id == R.id.upload) {
    com.lzw.commons.Utils.goActivity(cxt, PhotoActivity.class);
  } else if (id == R.id.scale) {
    cropIt(curPicUri);
  }
}

onActivityResult(int requestCode, int resultCode, Intent data)
處理其他活動的結果,如圖像選擇或裁剪。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (resultCode != RESULT_CANCELED) {
    Uri uri;
    switch (requestCode) {
      case IMAGE_RESULT:
        if (data != null) {
          setImageByUri(data.getData());
        }
        break;
      case CAMERA_RESULT:
        setImageByUri(Utils.getCameraUri());
        break;
      case CROP_RESULT:
        uri = Uri.fromFile(new File(cropPath));
        setImageByUri(uri);
        break;
      case MATERIAL_RESULT:
        setImageByUri(data.getData());
    }
  }
}

撤銷和重做功能

Activity提供繪圖操作的撤銷和重做功能。

initUndoRedoEnable()
通過設置回調函數初始化撤銷和重做功能。

void initUndoRedoEnable() {
  drawView.history.setCallBack(new History.CallBack() {
    @Override
    public void onHistoryChanged() {
      setUndoRedoEnable();
      if (curFragmentId != DRAW_FRAGMENT) {
        showDrawFragment(curDrawMode);
      }
    }
  });
}

void setUndoRedoEnable() {
  redoView.setEnabled(drawView.history.canRedo());
  undoView.setEnabled(drawView.history.canUndo());
}

自定義DrawView

DrawView 是一個自定義視圖,用於處理繪圖操作、觸摸事件和縮放。

onTouchEvent(MotionEvent event)
處理繪圖和縮放的觸摸事件。

@Override
public boolean onTouchEvent(MotionEvent event) {
  if (!scaleMode) {
    handleDrawTouchEvent(event);
  } else {
    handleScaleTouchEvent(event);
  }
  return true;
}

private void handleDrawTouchEvent(MotionEvent event) {
  int action = event.getAction();
  float x = event.getX();
  float y = event.getY();
  if (action == MotionEvent.ACTION_DOWN) {
    path.moveTo(x, y);
  } else if (action == MotionEvent.ACTION_MOVE) {
    path.quadTo(preX, preY, x, y);
  } else if (action == MotionEvent.ACTION_UP) {
    Matrix matrix1 = new Matrix();
    matrix.invert(matrix1);
    path.transform(matrix1);
    paint.setStrokeWidth(strokeWidth * 1.0f / totalRatio);
    history.saveToStack(path, paint);
    cacheCanvas.drawPath(path, paint);
    paint.setStrokeWidth(strokeWidth);
    path.reset();
  }
  setPrev(event);
  invalidate();
}

private void handleScaleTouchEvent(MotionEvent event) {
  switch (event.getActionMasked()) {
    case MotionEvent.ACTION_POINTER_DOWN:
      lastFingerDist = calFingerDistance(event);
      break;
    case MotionEvent.ACTION_MOVE:
      if (event.getPointerCount() == 1) {
        handleMove(event);
      } else if (event.getPointerCount() == 2) {
        handleZoom(event);
      }
      break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_POINTER_UP:
      lastMoveX = -1;
      lastMoveY = -1;
      break;
    default:
      break;
  }
}

private void handleMove(MotionEvent event) {
  float moveX = event.getX();
  float moveY = event.getY();
  if (lastMoveX == -1 && lastMoveY == -1) {
    lastMoveX = moveX;
    lastMoveY = moveY;
  }
  moveDistX = (int) (moveX - lastMoveX);
  moveDistY = (int) (moveY - lastMoveY);
  if (moveDistX + totalTranslateX > 0 || moveDistX + totalTranslateX + curBitmapWidth < width) {
    moveDistX = 0;
  }
  if (moveDistY + totalTranslateY > 0 || moveDistY + totalTranslateY + curBitmapHeight < height) {
    moveDistY = 0;
  }
  status = STATUS_MOVE;
  invalidate();
  lastMoveX = moveX;
  lastMoveY = moveY;
}

private void handleZoom(MotionEvent event) {
  float fingerDist = calFingerDistance(event);
  calFingerCenter(event);
  if (fingerDist > lastFingerDist) {
    status = STATUS_ZOOM_OUT;
  } else {
    status = STATUS_ZOOM_IN;
  }
  scaledRatio = fingerDist * 1.0f / lastFingerDist;
  totalRatio = totalRatio * scaledRatio;
  if (totalRatio < initRatio) {
    totalRatio = initRatio;
  } else if (totalRatio > initRatio * 4) {
    totalRatio = initRatio * 4;
  }
  lastFingerDist = fingerDist;
  invalidate();
}

onDraw(Canvas canvas)
繪製視圖的當前狀態。

@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  if (scaleMode) {
    switch (status) {
      case STATUS_MOVE:
        move(canvas);
        break;
      case STATUS_ZOOM_IN:
      case STATUS_ZOOM_OUT:
        zoom(canvas);
        break;
      default:
        if (cacheBm != null) {
          canvas.drawBitmap(cacheBm, matrix, null);
          canvas.drawPath(path, paint);
        }
    }
  } else {
    if (cacheBm != null) {
      canvas.drawBitmap(cacheBm, matrix, null);
      canvas.drawPath(path, paint);
    }
  }
}

move(Canvas canvas)
處理縮放期間的移動操作。

private void move(Canvas canvas) {
  matrix.reset();
  matrix.postScale(totalRatio, totalRatio);
  totalTranslateX = moveDistX + totalTranslateX;
  totalTranslateY = moveDistY + totalTranslateY;
  matrix.postTranslate(totalTranslateX, totalTranslateY);
  canvas.drawBitmap(cacheBm, matrix, null);
}

zoom(Canvas canvas)
處理縮放操作。

```java private void zoom(Canvas canvas) { matrix.reset(); matrix.postScale(totalRatio, totalRatio); int scaledWidth = (int) (cacheBm.getWidth() * totalRatio); int scaledHeight = (int) (cacheBm.getHeight() * totalRatio); int translateX; int translateY; if (curBitmapWidth < width) { translateX = (width - scaledWidth) / 2; } else { translateX = (int) (centerPointX + (totalTranslateX - centerPointX) * scaled


Back 2025.01.18 Donate