Hướng dẫn này minh họa cách cho phép người dùng vẽ đường cong trên hình ảnh bằng ngón tay, sau đó cắt (crop) bitmap theo đường vẽ đó sử dụng Path và PorterDuffXfermode.
Bước 1: Tạo dự án mới
Mở Android Studio, chọn File → New Project.
Bước 2: Giao diện đơn giản (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</RelativeLayout>
Bước 3: Custom View vẽ và cắt (src/MainActivity.java)
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new CropView(this));
}
static class CropView extends View {
private Bitmap bitmap;
private Path path = new Path();
private Paint paintBorder;
private Paint paintCrop;
public CropView(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.demo);
paintBorder = new Paint();
paintBorder.setColor(Color.RED);
paintBorder.setStyle(Paint.Style.STROKE);
paintBorder.setStrokeWidth(5f);
paintCrop = new Paint();
paintCrop.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.drawPath(path, paintBorder);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
path.lineTo(x, y);
cropBitmap();
break;
}
invalidate();
return true;
}
private void cropBitmap() {
Bitmap cropped = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
Canvas cropCanvas = new Canvas(cropped);
cropCanvas.drawPath(path, paintCrop);
cropCanvas.drawBitmap(bitmap, 0, 0, paintCrop);
bitmap = cropped;
}
}
}
Bước 4: Manifest (androidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android" package="app.com.sample">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
⚠️ Lưu ý quan trọng
- Code này dùng Custom View chiếm toàn màn hình (không dùng layout XML)
- Cần đặt file ảnh
demo.pngvàores/drawable/ PorterDuff.Mode.SRC_INchỉ giữ phần bitmap giao với path- Mỗi lần vẽ xong, bitmap được thay thế → không thể undo
💡 Cải tiến thực tế
Để app thực tế, nên:
- Dùng
BitmapShaderhoặcCanvas.clipPath()cho hiệu năng tốt hơn - Tách Custom View vào class riêng, dùng trong layout XML
- Thêm nút "Undo", "Lưu", "Chọn từ gallery"
- Xử lý bitmap lớn: downsample trước khi xử lý tránh OOM
Chạy ứng dụng
Kết nối thiết bị, nhấn Run. Vẽ trên màn hình bằng ngón tay để cắt hình ảnh theo ý muốn.