Hướng dẫn tạo Custom View trong Android
Bài đăng này đã không được cập nhật trong 6 năm
Giới thiệu
Chào các bạn, chắc hẳn khi làm việc với android bạn đều đã từng sử dụng các component widget như TextView
, EditText
, ImageView
, ... thì tất cả chúng đều được kế thừa từ một lớp cha đó là View
. Trong Android SDK đã cung cấp cho ta một số các component cơ bản và thông dụng để ta có thể tiện sử dụng. Tuy nhiên, trong một số trường hợp ta cần sử dụng một số các widget khác hoặc các view các mà không có sẵn trong gói SDK này, thì điều đó sẽ bắt buộc ta sẽ phải tự tạo cho mình một View
riêng, và bài này mình sẽ hướng dẫn một số bước cơ bản thông qua làm một ví dụ là custom một View để vẽ hình mặt cười, gọi là SmileyView nhé
View LifeCycle
Đầu tiên, cũng giống như Activity, Fragment thì View cũng có lifecycle, các bạn cùng xem lifecycle của View trong hình dưới đây nhé
Mình xin giới thiệu về các các hàm trong View LifeCycler nhé:
Constructor
Mỗi khi view bắt đầu một vòng đời thì nó sẽ bắt đầu từ Constructor, ở đây ta sẽ sử dụng các hàm để khởi tạo drawing, tính toán một số các kích thước và vị trí cho view, set các giá trị mặc định cho biến, ...
onAttachedToWindow
Sau khi View cha gọi hàm addView(View)
thì view sẽ được gắn vào trong View Hierachy, khi này thì view đã biết được các view khác xung quanh nó, nếu các view đó cùng nằm trong một layout như là layout.xml
thì đây là một nơi để có thể tìm kiếm các view khác theo id và lưu vào biến global
onMeasure
Đây là một hàm rất quan trọng, trong phần lớn các trường hợp thì đây là nơi để bạn tính toán kích cỡ của view sao cho phù hợp trong layout
Trong khi override
hàm này, bạn cần gọi hàm setMeasuredDimension(int width, int height)
để set giá trị.
onDraw
Ở hàm này bạn sẽ dùng các Canvas
và Paint
để vẽ ra view của mình.
Canvas để bạn có thể vẽ ra các đổi tượng như vẽ hình tròn, chữ nhật, ...
Paint là để bạn có thể xác định màu sắc, độ trọng suốt, ...
View Update
Trong view có 2 hàm để bạn có thể vẽ lại trong khi runtime đó là invalidate()
và requestLayout()
invalidate()
: hàm này được sử dụng để vẽ lại những trường hợp vẽ lại cơ bản, ví dụ như update lại text, màu sắc, hoặc vị trí onTouch, có nghĩa là view chỉ gọi lại hàmonDraw()
để cập nhật lại trạng tháirequestLayout()
: Hàm này khi bạn muốn cập nhật và tính toán lại cả kích cỡ, vị trí của view và sau đó vẽ lại view đó theo kích cỡ mới.
Draw
1. Tạo khung của view: Ta sẽ tạo một view vẽ hình mặt cười, gọi là SmileyView:
public class SmileyView extends View {
private Paint mCirclePaint;
private Paint mEyeAndMouthPaint;
private float mCenterX;
private float mCenterY;
private float mRadius;
private RectF mArcBounds = new RectF();
public SmileyView(Context context) {
this(context, null);
}
public SmileyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SmileyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaints();
}
private void initPaints() {/* ... */}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {/* ... */}
@Override
protected void onDraw(Canvas canvas) {/* ... */}
}
2. Tạo các Paint:
Paint
là một đối tượng để xác định các đối tượng sẽ được vẽ ra như thế nào (vd như màu sắc, style cho các nét vẽ, ...).
Tại đây, ta tạo ra 2 Paint
, một Paint để vẽ một hình tròn đặc màu vàng, và một Paint để vẽ các đường màu đen cho mắt và miệng.
private void initPaints() {
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setStyle(Paint.Style.FILL);
mCirclePaint.setColor(Color.YELLOW);
mEyeAndMouthPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mEyeAndMouthPaint.setStyle(Paint.Style.STROKE);
mEyeAndMouthPaint.setStrokeWidth(16 * getResources().getDisplayMetrics().density);
mEyeAndMouthPaint.setStrokeCap(Paint.Cap.ROUND);
mEyeAndMouthPaint.setColor(Color.BLACK);
}
3. Implement phương thức onMeasure(...)
:
cho phép layout cha (vd như FrameLayout) có thể căn chỉnh custom view. Hàm cung cấp một tập measureSpecs
để có thể xác định được chiều dài và chiều cao của view.
Dưới đây là một hình vuông bằng cách cho chiều dài và cao bằng nhau:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
int size = Math.min(w, h);
setMeasuredDimension(size, size);
}
Chú ý phương thức onMeasure(...)
phải gọi hàm setMeasuredDimension(..)
ít nhất một lần, nếu không view sẽ crash với lỗi IllegalStateException
4. Implement phương thức onSizeChange(...)
: cho phép lấy được chiều cao và chiều dài của view dùng để tính toán các thuộc tính trong quá trình rendering. Tại đây ta sẽ tính tâm và bán kính:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mCenterX = w / 2f;
mCenterY = h / 2f;
mRadius = Math.min(w, h) / 2f;
}
5. Implement phương thức onDraw(...)
:
Tại đây bạn có thể viết các hàm để vẽ view, hàm này cung cấp một đối tượng Canvas
để bạn có thể vẽ lên đó.
@Override
protected void onDraw(Canvas canvas) {
// draw face
canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint);
// draw eyes
float eyeRadius = mRadius / 5f;
float eyeOffsetX = mRadius / 3f;
float eyeOffsetY = mRadius / 3f;
canvas.drawCircle(mCenterX - eyeOffsetX, mCenterY - eyeOffsetY, eyeRadius, mEyeAndMouthPaint);
canvas.drawCircle(mCenterX + eyeOffsetX, mCenterY - eyeOffsetY, eyeRadius, mEyeAndMouthPaint);
// draw mouth
float mouthInset = mRadius /3f;
mArcBounds.set(mouthInset, mouthInset, mRadius * 2 - mouthInset, mRadius * 2 - mouthInset);
canvas.drawArc(mArcBounds, 45f, 90f, false, mEyeAndMouthPaint);
}
6. Thêm custom view vào layout:
Custom view có thể được thêm vào bất kì layout nào. Ví dụ ở đây ta sẽ cho vào FrameLayout
.
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.app.SmileyView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Sau khi hoàn thành, ta sẽ được màn hình:
Chúc các bạn thành công!
All rights reserved