Search…

View trong Android - Những Điều Cần Biết

12/11/20207 min read
Tổng hợp những kiến thức tổng quát đầy đủ và hữu ích về View trong Android.

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 đặt layout_width hoặc layout_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ếu layout_width hoặc layout_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()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 ViewViewGroup 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()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.

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024