Nhắc lại khái niệm View trong Android
Những gì có thể nhìn thấy trên màn hình thiết bị Android được gọi là View
(trong Windows thường được gọi là Control), View
được vẽ trên thiết bị Android với 1 hình chữ nhật.
View
có 1 sự ràng buộc chặt chẽ với dữ liệu, ví dụ với ImageView
thì dữ liệu chính là Bitmap
, hay TextView
thì có dữ liệu chính là text
. View
sẽ dựa vào dữ liệu để vẽ lên màn hình Android.
View
có thể tương tác với người dùng thông qua các action touch, drag, drop, ... với các action xảy ra trên View thì từ đó View sẽ được vẽ lại, thay đổi kích thước hay là thay đổi dữ liệu.
Trong Android, các View
thường xuyên sử dụng như EditText
, Button
, TextView
, ImageView
, RadioButton
, CheckBox
, ImageButton
hay những View
thuộc ViewGroup
như FrameLayout
, LinearLayout
, RelativeLayout
, TableLayout
hoặc ListView
, Toolbar
, RecyclerView
. Ngoài ra còn có những View
như ConstrainLayout
.
Điều gì xảy ra khi bạn thêm 1 View vào ViewGroup
Khi thêm 1 View
và 1 ViewGroup
(FrameLayout
, LinearLayout
, RelativeLayout
) thì các phương thức sau sẽ được gọi, các phương thức sẽ được chạy từ trên xuống dưới.

Giải thích 1 số phương thức
Constructor
Hàm khởi tạo.
onAttachedToWindow()
onAttachedToWindow()
được gọi khi View
được gắn vào 1 cửa sổ.
onMeasure()
onMeasure()
là phương thức tính toán, ước lượng kích thước cho View
, sau khi tính toán xong kích thước phải gọi phương thức setMeasuredDimension()
để đặt chiều rộng và chiều cao cho View
.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e(TAG, "onMeasure: ");
}
widthMeasureSpect
, heightMeasureSpec
là 2 giá trị mà ViewGroup
truyền xuống.
Mỗi tham số này sẽ có 2 giá trị chứa trong nó đó là:
- Giá trị kích thước (chiều rộng, chiều cao).
- Giá trị
MeasureSpec
gồm có 3 giá trị làEXACTLY
,AT_MOST
,UNSPECIFIED
.
int width = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
Với từng giá trị có ý nghĩa như sau:
MeasureSpec.EXACTLY
:View
sẽ có kích thước xác định theo giá trị của người dùng đặt, trường hợp này xảy ra nếu đặtlayout_width
hoặclayout_height
là 1 con số xác định hoặc làmatch_parent
.MeasureSpec.AT_MOST
:View
sẽ có kích thước nhỏ hơn hoặc bằng đúng giá trị của kích thước của cha.MeasureSpec.UNSPECIFIED
:View
sẽ có kích thước như mong muốn hoặc có thể lớn hơn kích thước của cha, trường hợp xảy ra nếulayout_width
hoặclayout_height
có giá trị làwrap_content
.
Lưu ý: Phương thức này được gọi nhiều lần.
Nếu đã từng gặp lỗi getWidth()
và getHeight()
của View
mà trả về giá trị 0 thì nguyên nhân do phương thức onMeasure()
chưa được gọi và chưa đặt setMeasureDimension()
.
onLayout()
onLayout()
dùng để xác định vị trí định vị của View
, thông thường phương thức này được sử dụng khi muốn tạo 1 ViewGroup
mới.
Lưu ý: Phương thức này được gọi nhiều lần.
onDraw()
onDraw()
là phương thức vẽ của View
. Trong Android mọi thứ đều được vẽ lên Canvas, sau khi chạy xong phương thức này thì View
chính thức được vẽ lên màn hình Android.
Nếu View
có thuộc tính Visibility = GONE
thì Android sẽ không thực hiện vẽ View
đó.
Lưu ý: Phương thức này không được gọi nếu View
đó là ViewGroup
.
requestLayout(), invalidate(), postInvalidate()
requestLayout()
Khi gọi requestLayout()
thì Android thực hiện tính toán lại kích thước của View
, nghĩa là phải chạy phương thức onMeasure()
và đi xuống những phương thức khác để vẽ View
.
Phương thức này được gọi khi muốn thay đổi kích thước của View
.
invalidate() và postInvalidate()
Khi gọi phương thức này thì Android sẽ gọi phương thức onDraw()
của View
để tiến hành vẽ lại View
.
Trường hợp gọi phương thức này khi muốn vẽ lại View
nhưng không thay đổi kích thước của View
. Ví dụ khi đặt màu sắc cho TextView
thì trong phương thức setTextColor()
sẽ gọi invalidate()
.
Vậy 2 phương thức này khác gì nhau?
2 phương thức này có cùng chức năng và cùng trường hợp sử dụng, nhưng chỉ khác nhau ở điểm nếu muốn vẽ trong UI thread hay Main thread thì gọi invalidate()
còn nếu muốn vẽ trong 1 thread khác không phải là Main thread thì phải gọi postInvalidate()
.
Các phương thức Touch trên View
Trên View
cơ bản không phải ViewGroup
, có 1 phương thức touch()
trên View
là phương thức onTouchEvent()
:
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent: " + event);
return super.onTouchEvent(event);
}
Đối tượng MotionEvent
giữ các thông tin khi chạm vào màn hình như vị trí, hành động, ...
Ngoài ra, nếu View
là ViewGroup
thì có nhiều phương thức xử lý chạm hơn nữa, có thể tìm hiểm các phương thức sau:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
Ví dụ minh họa
Tạo 1 lớp CustomView
có nội dung như sau:
package net.eitguide.nguyennghia.customview;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by nguyennghia on 12/11/20.
*/
public class CustomView extends View {
private static final String TAG = "CustomView";
public CustomView(Context context) {
super(context);
Log.e(TAG, "CustomView: ");
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.e(TAG, "CustomView: ");
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.e(TAG, "CustomView: ");
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Log.e(TAG, "onAttachedToWindow: ");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e(TAG, "onMeasure: ");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.e(TAG, "onLayout: ");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e(TAG, "onDraw: ");
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.e(TAG, "onDetachedFromWindow: ");
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent: " + event);
return super.onTouchEvent(event);
}
}
Sau đó add View
này vào RelativeLayout
trong XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="net.eitguide.nguyennghia.customview.MainActivity">
<net.eitguide.nguyennghia.customview.CustomView
android:layout_width="400dp"
android:layout_height="400dp" />
</RelativeLayout>
Tiến hành chạy ứng dụng và xem log:
10-19 22:28:54.812 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: CustomView:
10-19 22:28:54.900 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onAttachedToWindow:
10-19 22:28:54.904 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onMeasure:
10-19 22:28:54.904 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onMeasure:
10-19 22:28:55.003 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onMeasure:
10-19 22:28:55.003 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onMeasure:
10-19 22:28:55.003 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onLayout:
10-19 22:28:55.471 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onMeasure:
10-19 22:28:55.471 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onMeasure:
10-19 22:28:55.471 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onLayout:
10-19 22:28:55.472 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onDraw:
Nhận thấy rằng onMeasure()
và onLayout()
được chạy nhiều lần.
Tiếp tục xem tiếp log khi chạm vào View
:
10-19 22:32:26.455 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onTouchEvent: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=512.4463, y[0]=490.3125, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1100939, downTime=1100939, deviceId=0, source=0x1002 }
10-19 22:32:27.221 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onTouchEvent: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=600.5127, y[0]=610.3711, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1101706, downTime=1101706, deviceId=0, source=0x1002 }
10-19 22:32:28.734 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onTouchEvent: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=184.14185, y[0]=827.51953, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1103219, downTime=1103219, deviceId=0, source=0x1002 }
10-19 22:32:29.146 11337-11337/net.eitguide.nguyennghia.customview E/CustomView: onTouchEvent: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=255.20142, y[0]=929.53125, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1103631, downTime=1103631, deviceId=0, source=0x1002 }
Khi xóa View
ra khỏi ViewGroup
hay trở lại Activity trước thì phương thức onDetachedFromWindows()
được gọi.