[Học Android] Tìm hiểu về Canvas

[Học Android] Tìm hiểu  về Canvas

Tìm hiểu Canvas trong Android, sử dụng Canvas để custom View

1. Canvas là gì?

Canvas được xem như là một bền mặt 2D (hình dung như tờ giấy, bảng) mà chúng ta có thể vẽ bất cứ thứ gì lên đó. Ví dụ như vẽ một điểm, đường thằng, hình chữ nhật, đường tròn, elip, văn bản, hay thậm chí là một hình ảnh và các hình ảnh phức tạp khác nữa.

Canvas trong Android có cung cấp cho chúng ta các method để vẽ tất cả các đối tượng như sau:

  • Các đối tượng hình học cơ bản (point, line, oval, rect..)
  • Vẽ hình ảnh (bitmap, drawable)
  • Vẽ Path (Tập hợp các điểm)
  • Vẽ Text

Canvas được dùng trong rất nhiều trường hợp. Nhưng ở đây mình sẽ dùng Canvas để custom view mà mình tự định nghĩa nhằm giúp các bạn hiểu sơ qua về cách thức hoạt động của Canvas.

Trong Android nếu bạn muốn tạo một view mới và tự vẽ lại tất cả các thành phần của view đó thì một điều chắc chắn rằng bạn phải sử dụng canvas. Những thành phần mà chúng ta vẽ đều được xử lý trong phương thức onDraw(Canvas canvas) của class View.

2. Ví dụ

Khởi tạo 1 class TestCanvas và Override hàm onDraw

public class TestCanvas extends View {
    public TestCanvas(Context context) {
        super(context);
    }
    public TestCanvas(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public TestCanvas(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public TestCanvas(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

Thêm view mới tạo vào trong main_activity.xml

Lưu ý: Trong bài này mình chỉ làm đơn giản nhất nên mình không định nghĩa thêm các attributtes khác, chỉ đơn giản là hiển thị thôi, ở bài sau mình sẽ làm rõ hơn về phần này.

Mỗi view này cần được vẽ lại bằng cách bạn overide hàm onDraw(Canvas canvas) . Trước hiết bạn cần tìm hiểu về lifecycle của một View trong Android.

onMeasure

Trong hàm này các view sẽ biết được kích thước của nó, bạn cần xác định chính xác kích thước của view để có thể tính toán được các thành phần và vị trí trong view. Khi bạn thừa kế hàm này thì bạn phải gọi hàm setMeasuredDimension(int width, int height)  để set lại kích thước cho view.

Trước hết bạn cần tính toán được kích thước mà bạn mong muốn cho view (desired size) bao gồm width height. Sau đó trong onMeasure bạn sẽ có được kích thước của view đó và chế độ của từng kích thước (widthMode và heightMode).

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    }

Trong đo widthMode heightMode có các giá trị đó là:

MeasureSpec.UNSPECIFIED: bạn có thể hiểu kích thước của view sẽ không bị giới hạn, bạn muốn nó lớn bao nhiêu nó sẽ lớn bấy nhiêu. Ví dụ view chamatch_parent hoặc wrap_content thì view con sẽ không hề bị giới hạn, view con to thì view cha cũng to theo.

MeasureSpec.EXACTLY:  các layout bên ngoài đã giới hạn kích thước cho view đó, ví dụ view bên ngoài là RelativeLayout và có kích thước 150*150 thì Custom View cũng không thể có kích thước lớn hơn được mà vẫn phải bị bao bọc với kích thước đó (bao gồm cả padding).

MeasureSpec.AT_MOST: kích thước của view sẽ lớn đến một kích thước mà nó quy định.

int width;
if (widthMode == MeasureSpec.EXACTLY) {
  width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
  width = Math.min(desiredWidth, widthSize);
} else {
  width = desiredWidth;

Và cuối cùng đưa width height vào trong setMeasuredDimension để có thể xác định được kích thước cuối cùng của view, hãy kiểm tra các kích thước này xem có hợp lý và phù hợp với view của bạn hay không.

onDraw

Đây là hàm gần như quan trọng nhất, trong đây mình xin giới thiệu tiếp về Canvas và thêm một đối tượng mới không kém quan trọng để thiết kế view của bạn đó là PaintCanvas thể hiện cách bạn vẽ cái gì, còn Paint thể hiện bạn vẽ như thế nào. Một ví dụ mà mình đọc được ngay từ bài hướng dẫn trên trang của developer android: Canvas cung cấp cho bạn phương thức để vẽ một đường thẳng còn Paint cho bạn cách tô màu nó, bạn vẽ một hình vuông, Paint sẽ cho bạn cách để tô màu cho nó hay có thể để nó trống bên trong, tô viền, đổ bóng…

Trước khi vẽ bất cứ cái gì thì bạn cần xác định sẵn một đối tượng Paint tương ứng với mỗi phần tử mà bạn sẽ vẽ.

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(getResources().getColor(R.color.colorAccent));

Để vẽ một hình bất kỳ thì bên trong hàm onDraw bạn cần gọi các canvas.draw…. để vẽ hình tương ứng, cụ thể như sau:

drawText() để vẽ một text.

drawRect() để vẽ hình vuông, hình chữ nhật.

drawOval() để vẽ hình oval.

drawArc() để vẽ hình quạt.

drawBitmap() để vẽ ảnh bitmap.

… và rất nhiều hàm khác nữa, ở đây mình xin liệt kê những thứ cơ bản nhất thôi

Một lưu ý cực kỳ quan trọng. Hàm onDraw sẽ được gọi liên tục khi bạn thao tác bất kỳ gì khiến màn hình thay đổi, và để giữ nó luôn được mượt mà, cụ thể là 60 FPS  thì bằng cách đưa các hàm khởi tạo này vào trong một hàm riêng và gọi nó trong constructor, để nó không phải khởi tạo đi khởi tạo lại nhiều lần, tăng đốc độ UI lên.

Bây giờ mình muốn vẽ một hình pacman phiên bản thấp nhất, demo như sau:

Bạn có thể thấy đây là một hình cánh quạt với một góc quét tầm 300 độ (trừ cái miệng chiếm 60 độ) do vậy mình sẽ dùng hàm drawArc() để vẽ.

Hàm drawArc() được định nghĩa như sau: drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

Từ từ, các kích thước left, top, right, bottom kia là gì, chắc chắn nếu bạn mới học canvas thì bạn sẽ khá bỡ ngỡ với mấy cái này.

Giả sử kích thước custom view của bạn như hình trên, với view của bạn sẽ nằm trong một khối hình chữ nhật gồm trục Oxy và hình bên trong là vị trí bạn cần vẽ.

Do đó, giả sử bạn cần có hình pacman này thì nó sẽ nằm trong hình vuông có kích thước cạnh là 300, và cách lề trái là 100, top là 100 thì bạn sẽ có các kích thước như sau:

int square = 300;
float top = 100;
float left = 100;
float right = left + square;
float bottom = top + square;

Trong canvas, trục tọa độ như hình bên dưới, cố gắng nhớ nhé mọi người.

Ta cần thêm góc bắt đầu và góc quay, theo như hình bên trên thì mình sẽ cho quay ở góc 30 độ và tới góc 330 độ, do đó góc bắt đầu sẽ là 30 và góc quay là 300 độ, truyền đầy đủ tham số vào thì kết quả bạn sẽ được như hình của mình (màu có thể khác nhé).

Bây giờ mình muốn tiếp tục vẽ mắt cho con pacman này, với mắt màu vàng (mình nghĩ màu vàng sẽ hợp).

Mắt mình làm đơn giản đó là một hình tròn và được tô màu vàng. Định nghĩa của hàm drawCircle() như sau: drawCircle (float cx, float cy, float radius, Paint paint) trong đó cx, cy là tâm của hình tròn, radius là bán kinh của hình tròn.

float cx = left + 100;
float cy = top + 70;
float radius = 25;

Cuối cùng ta được thành quả như sau:

Mình mới chỉ lấy ví dụ là các view này đang có kích thước xác định nhưng thực thế nó được sử dụng đi sử dụng lại tại nhiều vị trí trong app nên không thể có kích thước chính xác như trên được, do đó bạn hãy dùng hàm getHeight()getWidth() để lấy ra kích thước view, sau đó dùng nó để tính toán ra kích thước các phần tử khác nhau.

Ví dụ bạn muốn cho hình con pacman này nằm trọn trong view, không hề có khoảng cách với viền thì bạn có thể đặt cho nó kích thước left = 0, top = 0, right = getWidth()bottom = getWidth()… tương tự với hình mắt.

Trong bài sau mình sẽ đi kỹ hơn vào Canvas, và các hàm khác để có thể tạo custom view linh động hơn, chúc các bạn trở thành một designer kiêm coder đa tài.

Full code:

public class TestCanvas extends View {
    Paint pacmanPaint;
    Paint eyeOfPacmanPaint;
    public TestCanvas(Context context) {
        super(context);
        init();
    }
    public TestCanvas(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public TestCanvas(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    public TestCanvas(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }
    private void init() {
        pacmanPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        pacmanPaint.setColor(getResources().getColor(R.color.colorPrimary));
        eyeOfPacmanPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        eyeOfPacmanPaint.setColor(getResources().getColor(R.color.colorAccent));
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int square = 300;
        float top = 100;
        float left = 100;
        float right = left + square;
        float bottom = top + square;
        canvas.drawArc(left, top, right, bottom, 30, 300, true, pacmanPaint);
        
        float cx = left + 180;
        float cy = top + 70;
        float radius = 25;
        canvas.drawCircle(cx, cy, radius, eyeOfPacmanPaint);
    }
}

Cảm ơn các bạn đã đọc bài viết. Chào thân ái và quyết thắng!

Bài viết gốc: https://tuanfadbg.com/cung-tim-hieu-canvas-trong-android-phan-1/

admin

Leave a Reply

Your email address will not be published. Required fields are marked *