Giới thiệu
Tiếp nối bài viết Canvas trong Android (Phần 1) thì bài viết hôm nay tôi sẽ giới thiệu vẽ những thành phần còn lại còn lại lên canvas đó là vẽ Bitmap và vẽ Text chuyên sâu. Và sau đó là giới thiệu những các phép biến đổi cơ bản trên canvas (translate, rotate, scale) và cách sử dụng save, restore canvas trong Android.
Vẽ các đối tượng hình ảnh, text lên canvas
Vẽ Bitmap
Các phương thức dùng để vẽ bitmap lên Canvas
drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint)
Vẽ bitmap lên canvas có apply matrix.
drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst, @Nullable Paint paint)
Vẽ bitmap lên canvas với các đối số là
bitmap: source bitmap dùng để vẽ lên canvas
src: là hình chữ nhật cắt bitmap để vẽ. Ví dụ bạn muốn gắt một phần nào đó của bitmap để vẽ chứ không muốn vẽ toàn bộ hình ảnh. Trường hợp vẽ toàn hình ảnh sẽ truyền vào null.
dst: là hình chữ nhật mô tả toạ độ để vẽ lên canvas.
Lưu ý: Kiểu dữ liệu của các thuộc tính của dst đều là số thực float (left, top, right, bottom).
drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst, @Nullable Paint paint)
Giống như phương thức trên nhưng kiểu dữ liệu của các thuộc tính dst đều kiểu nguyên (left, top, right, bottom).
drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)
Vẽ bitmap bắt đầu ở vị trí left và top xác định trên màn hình. Và điểm vẽ tính từ góc trái trên của bitmap.
Ví dụ mình sẽ vẽ một bitmap ở dưới giữa màn hình như sau:
Trước tiên chúng ta sẽ chuẩn bị 1 bitmap để vẽ:
1 |
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.android); |
Trong method onDraw ta hiện thực như sau:
1 2 3 4 |
//Draw Bitmap float left = (getWidth() - mBitmap.getWidth()) / 2.0f; float top = getHeight() - mBitmap.getHeight(); canvas.drawBitmap(mBitmap, left, top, mPaint); |
Kết quả sau khi run chương trình
Bây giờ tôi muốn cắt 1/4 góc trái bên của bitmap và vẽ nó ở right-bottom của màn hình:
1 2 3 4 5 6 7 |
//Draw Bitmap Rect src = new Rect(0, 0, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2); int left = getWidth() - src.width(); int top = getHeight() - src.height(); Rect des = new Rect(left, top, left + src.width(), top + src.height()); canvas.drawBitmap(mBitmap, src, des, mPaint); |
Ví dụ cuối cùng về vẽ bitmap là tôi sẽ vẽ một bitmap full (không có cắt xén) và vẽ nó ở chính giữa màn hình.
1 2 3 4 5 |
//Draw Bitmap float left = (getWidth() - mBitmap.getWidth()) / 2.0f; float top = (getHeight() - mBitmap.getHeight()) / 2.0f; RectF dst = new RectF(left, top, left + mBitmap.getWidth(), top + mBitmap.getHeight()); canvas.drawBitmap(mBitmap, null, dst, mPaint); |
Vậy là chúng ta đã đi qua xong phần vẽ bitmap lên canvas. Và dưới đây là một phần khá hứng thú đã là vẽ text lên canvas.
Vẽ text
Việc vẽ text được sử dụng khá nhiều trong Android. Vì với bất kì ứng dụng nào, bạn cũng thấy text có mặt bất cứ ở đâu. Và việc vẽ những đoạn text phức tạp cũng sẽ được đề cập ở ngay phần dưới đây.
Với việc vẽ text Android có cung cấp cho chúng ta class có tên là TextPaint kế thừa từ Paint giúp chúng ta vẽ text lên canvas mạnh mẽ hơn. Và khi và text tôi khuyến khích các bạn nên sử dụng TextPaint thay vì sử dụng Paint.
Trong method initPaint() đã viết ở phần 1 tôi thêm vào phần khởi tạo TextPaint sử dụng để vẽ text.
1 2 3 4 5 6 7 8 9 10 11 |
private void initPaint() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.BLUE); mPaint.setStrokeWidth(30); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeCap(Paint.Cap.ROUND); mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setColor(Color.parseColor("#16a085")); mTextPaint.setTextSize(50); } |
Vẽ text thông thường
Các phương thức để vẽ text
drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
Vẽ string với vị trí bắt đầu vẽ x, y.
drawText(@NonNull String text, int start, int end, float x, float y, @NonNull Paint paint)
Vẽ String với các đối số start, end xác định vẽ từ vị trí nào tới vị trí nào của string với vị trí bắt đầu x, y.
drawText(@NonNull char[] text, int index, int count, float x, float y, @NonNull Paint paint)
Vẽ một mảng char, bắt đầu với index và số lượng phần tử cần vẽ và vẽ ở vị trí x, y.
drawText(@NonNull CharSequence text, int start, int end, float x, float y, @NonNull Paint paint)
Vẽ CharSequence với vị trí bắt đầu và kết thúc ở vị trí x, y.
Ví dụ tôi muốn vẽ chuỗi “Eitguide Android” ở vị trí x = 100, y = 400:
1 2 3 4 |
//Draw text float x = 100; float y = 400; canvas.drawText("Eitguide Android", x, y, mTextPaint); |
Tuy nhiên tôi ví dụ tiếp theo tôi muốn sử dụng đoạn text “Eitguide Android” để vẽ nhưng chỉ muốn vẽ đoạn “guide Androi” thì tôi làm như sau:
1 2 3 4 5 |
//Draw text float x = 100; float y = 400; String str = "Eitguide Android"; canvas.drawText(str, 3, str.length() - 1, x, y, mTextPaint); |
Trường hợp đoạn text quá dài so với view. Ví dụ chuỗi “We can actually use any custom font that we’d like within our applications.” Và tôi muốn hiển thị chuỗi “…” khi chuỗi chạm mép của view thì tôi làm như sau:
1 2 3 4 5 6 |
//Draw text float x = 0; float y = 400; CharSequence str = TextUtils.ellipsize("We can actually use any custom font that we'd like within our applications.", mTextPaint, getWidth(), TextUtils.TruncateAt.END); canvas.drawText(str, 0, str.length(), x, y, mTextPaint); |
Tiếp theo chúng ta sẽ tìm hiểu các vẽ text phức tạp hơn đó là trong 1 text chúng ta có thể vẽ như sau
Vẽ text với Spanable.
Tôi muốn vẽ text đủ các style như hình dưới đây:
- Đoạn text đầu tiên tôi cho in đậm text
- Đoạn tiếp theo sẽ có background màu đỏ
- Đoạn tiết có foreground màu xanh
- Đoạn tiếp nữa có style chữ in nghiêng
- Và đoạn cuối sẽ là chữ bình thường và gạch chân
Và làm sao chúng ta làm được chuyện đó. Giải pháp là chúng ta sử dụng StaticLayout.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//Draw text float x = 0; float y = 400; CharSequence str = TextUtils.ellipsize("We can actually use any custom font that we'd like within our applications.", mTextPaint, getWidth(), TextUtils.TruncateAt.END); SpannableString wordToSpan = new SpannableString(str); wordToSpan.setSpan(new StyleSpan(Typeface.BOLD), 0, 10, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); wordToSpan.setSpan(new BackgroundColorSpan(Color.RED), 10, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); wordToSpan.setSpan(new ForegroundColorSpan(Color.CYAN), 15, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); wordToSpan.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 15, 25, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); wordToSpan.setSpan(new UnderlineSpan(), 25, wordToSpan.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); StaticLayout staticLayout = new StaticLayout(wordToSpan, mTextPaint, getWidth(), Layout.Alignment.ALIGN_NORMAL, 1, 0, false); staticLayout.draw(canvas); |
Với việc sử dụng Spanable để setSpan cho CharSequence chúng ta có thể set bất cứ style nào cho text. Và sử dụng StaticLayout để vẽ lên cavas.
Các tính năng nâng cao của Canvas
Canvas cho chúng ta thực hiện các phép biến đổi hình học như translate, scale, rotate để giúp chúng ta vẽ các đối tượng một cách linh hoạt hơn.
Translate
Thực hiện dịch chuyển canvas một đoạn dx, và dy:
1 |
translate(float dx, float dy) |
Việc translate canvas được hiểu như là thay đổi gốc toạ độ để vẻ. Mặt định góc toạ độ sẽ nằm ở gốc trái trên của View. Sau khi tịnh tiến một khoảng dx, dy thì gốc toạ độ sẽ nằm ở dx, và dy.
Ví dụ khi bạn chưa translate canvas và bạn vẽ một điểm ở vị trí (x, y) = (200, 400) sẽ có kết quả giống với
kết quả bạn translate canvas một đoạn dx = 200, dy = 400 và ngay sau đó vẽ một điểm tại vị trí (x, y) = (0, 0).
Scale
Scale là phép biến đổi thay đổi kích thước của vật thể. Trương trường hợp này là thay đổi kích thước của các đối tượng vẻ trên canvas.
1 |
scale(float sx, float sy) |
Mặc định scale sx, sy có giá trị 1. Nếu chúng ta set sx = 2, sy = 4 thì đối tượng sẽ có kích thước theo chiều x lớn hơn 2 lần và kích thước lớn hơn chiều y là 4 lần.
Rotate
Rotate là phép biển đổi xoay đối tượng theo một góc xác định
1 |
void rotate(float degrees) |
Ví dụ khi muốn xoay các đối tượng vẽ 45 độ. Ta set dễ dàng như sau:
1 |
canvas.rotate(45); |
Save và Restore Canvas
Thử set trường hợp tôi muốn vẽ hai đối tượng. Tôi muốn vẽ đối tượng thứ nhất lớn hơn đối tượng thứ đối tượng gốc của nó hơn 4 lần cả chiều x và chiều y. Và đối tượng thứ 2 tôi vẽ y nguyên giống hình dạng của nó không có scale. Các bước là như sau:
Bước 1:
Set Scale cho canvas sx, sy = 4;
1 |
canvas.setScale(4, 4); |
Bước 2:
Vẽ đối tượng thứ nhất
Bước 3:
Scale canvas về lại trạng thái ban đầu
1 |
canvas.setScale(-4, -4) |
Bước 4:
Vẽ đối tượng thứ 2
Bước 5:
Kết thúc
Các bạn thấy rằng mỗi khi thay đổi trạng thái của canvas(translate, scale, rotate), muốn đưa về trạng thái củ chúng ta phải nhớ những thông số mà chúng ta làm thay đổi trạng thái và set ngược lại giá trị đó để đưa canvas về trạng thái cũ.
Save Canvas
Trước khi bạn thay đổi trạng thái của canvas gọi canvas.Save(); để lưu lại trạng thái hiện tại của canvas.
Khi đã save trạng thái thì bạn cứ thoải mái sử dụng các phép biến đổi canvas.
Restore Canvas
Gọi canvas.Restore(); để đưa canvas về trạng thái nó đã save trước đó.
Lưu ý. canvas.Restore() chỉ có thể gọi được nếu bạn đã call canvas.Save();. Nếu bạn cố tình gọi canvas.Restore() mà chưa gọi canvas.Save() ở trước thì Android sẽ ném ra ngoại lệ.
Kết luận
Qua hai bài viết tôi đã cùng các bạn tìm hiểu từ cơ bản cho đến nâng cao về canvas trong Android. Tôi tin rằng qua hai bài viết này các bạn có thể vẽ bất cứ thứ gì lên cavas giống như những View được Google xây dựng sẵn trong Android. Và việc bạn tạo View mới sử dụng cho dự án của bạn hoàn toàn dễ dàng. Nếu có bất cứ thắc mắc nào các bạn có thể để lại commnet để tối có thể giải đáp cho các bạn.
alert(‘Test XSS’)
hay, cảm ơn bạn Nguyễn Nghĩa
Thường xuyến ghé blog lập trình của mình nha bạn.
Bạn ơi, trong android có thể sử dụng repaint() ko vậy. Nếu ko thì có thể sử dụng phương thức nào thay thế?
Ý của bạn là vẽ lại phải không ạ. Bạn có thể sử dụng phương thức invalidate(). Phương thức này sẽ call lại method onDraw() để thực hiện vẽ lại View nha bạn.
Bạn muốn tìm hiểu rõ hơn phần này có thể liên hệ qua facebook của mình https://www.facebook.com/nguyennghia.uit
Ngoài ra bạn cũng có thể sử dụng phương thức postInvalidateDelayed(long); để thực hiện vẽ lại View sau một khoảng thời gian nào đó tính bằng milisecond.
Chào bạn,
Mình đang muốn làm một chức năng như thế này: Mình sẽ tạo một con đường đi từ A đến B (đường đi này ngoằn ngoèo), user sẽ chạm tay vào từ điểm A và di chuyển đến điểm B. Vậy làm thế nào mình check được user đó có đi ra khỏi con đường đó không nhỉ?
Nếu làm như vậy bạn đã lưu lại các điểm trên đoạn từ A tới B. Với việc check user có đi ra ngoài đoạn đường không thì trong View#onTouchEvent bạn lấy ra vị trí bạn đang chạm rồi kiểm tra với data các điểm của bạn.