[Android] Tìm hiểu sâu hơn về LayoutInflater trong Android

Đăng bởi: Admin | Lượt xem: 4690 | Chuyên mục: Android

LayoutInflater là 1 component giúp bạn chuyển layout file (xml) thành View (Java code) trong Android. Nó thường được trong phương thức onCreateView của Fragment hoặc phương thức getView khi custom adapter.


1. Cách tạo đối tượng LayoutInflater

Cách 1: Gọi LayoutInflater như 1 System Service

LayoutInflater là 1 System Service của Android và cách sử dụng của nó giống như các System Service khác như khi bạn sử dụng WINDOW_SERVICE, ALARM_SERVICE hay LOCATION_SERVICE.

LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

Đây là cách được khuyên dùng nhưng nó hơi dài dòng và mình rất ít khi sử dụng cách này.

Cách 2: Sử dụng static method của LayoutInflater

LayoutInflater layoutInflater = LayoutInflater.from(context);

Đây là cách mình hay sử dụng nhất vì nó ngắn gọn hơn nhiều.

2. Phương thức Inflate

Công việc của LayoutInflater là đọc xml layout file và chuyển đổi các thuộc tính của nó thành 1 View trong Java code bằng phương thức inflate. Ta có 2 phương thức inflate với số lượng tham số khác nhau:

1. View view = layoutInflater.inflate(int resource, ViewGroup parent)
2. View view = layoutInflater.inflate(int resource, ViewGroup parent, boolean attachToRoot)

Các bạn sẽ thắc mắc các tham số của inflate có ý nghĩa gì? 2 Phương thức inflate trên chỉ khác nhau tham số attachToRoot vậy attachToRoot là gì? Cùng tìm hiểu thông qua 1 số ví dụ nhé.

Trước tiên chúng ta tìm hiểu 3 tham số của nó là gì đã nhé: Như định nghĩa thì nhiệm vụ của LayoutInflater là chuyển đổi xml layout file thành đối tượng View trong java code, vậy thì:

  • Tham số thứ nhất là: int resource, nó chính là xml layout file mà chúng ta muốn chuyển đổi thành View.
  • Tham số thứ hai là: ViewGroup parent, nó là ViewGroup nơi mà xml layout file(tham số thứ nhất) có thể được nhúng vào, LayoutInflater sẽ chuyển đổi xml layout file thành View và sử dụng các thuộc tính phù hợp với ViewGroup parrent.
  • Tham số thứ ba là: attachToRoot, khi mà attachToRoot=true thì ngay sau khi quá trình chuyển đổi xml file(resource) thành View hoàn thành thì nó sẽ nhúng View đó vào ViewGroup parent (RIGHT NOW) , khi attachToRoot = false thì nó chỉ chuyển đổi xml file(resource) thành View trong java mà không thêm ngay vào ViewGroup(NOT NOW)

Rồi cùng đi vào ví dụ cho dễ hiểu nào. Mình có xml layout file có tên là activity_main.xml với root là LinearLayout hướng vertical:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:id="@+id/ll_main"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent">
</LinearLayout>

Và 1 xml layout file khác tên là item_button.xml như sau:

<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Custom Button"
    android:id="@+id/custom_button">
</Button>

và bây giờ mình sẽ sử dụng lần lượt các phương thức inflate và chỉ ra kết quả sau khi sử dụng mỗi phương thức

Trường hợp 1: 2 tham số

Chúng ta chỉ sử dụng 2 tham số tuy nhiên attachToRoot sẽ được đặt mặc định bằng true và kết quả là item_button sẽ được chuyển đổi thành View và được add vào llMain ngay khi chuyển đổi xong.

public class DemoLayoutInflater extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        LinearLayout llMain = findViewById(R.id.ll_main);
        View view = LayoutInflater.from(this).inflate(R.layout.item_button, llMain);
	}
}

Trường hợp 2: 3 tham số với attachToRoot = true

item_button sẽ được chuyển thành View và cũng được add vào llMain ngay khi chuyển đổi hoàn tất giống trường hợp 1.

public class DemoLayoutInflater extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        LinearLayout llMain = findViewById(R.id.ll_main);
        View view = LayoutInflater.from(this).inflate(R.layout.item_button, llMain, true);
	}
}

Trường hợp 1 và 2 có cùng kết quả như hình dưới đây:

Trường hợp 3: 3 tham số với attachToRoot = false

LayoutInflater chỉ chuyển đổi item_button thành View trong java mà không làm bất cứ thứ gì khác.

public class DemoLayoutInflater extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        LinearLayout llMain = findViewById(R.id.ll_main);
        View view = LayoutInflater.from(this).inflate(R.layout.item_button, llMain, false);
	}
}

Kết quả trường hợp 3: 

View này sẽ không được add vào llMain (ViewGroup) và chỉ khi chúng ta gọi phương thức addView thì button mới được add vào, sau khi gọi phương thức addView thì kết quả sẽ giống TH1 và TH2:

public class DemoLayoutInflater extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        LinearLayout llMain = findViewById(R.id.ll_main);
        View view = LayoutInflater.from(this).inflate(R.layout.item_button, llMain, false);
        llMain.addView(view); //Different
	}
}

3. Lưu ý khi sử dụng LayoutInflater trong custom adapter

Qua 3 ví dụ trên chắc bạn cũng đã hiểu tham số attachToRoot dùng để làm gì rồi phải không? Tóm lại thì attachToRoot quyết định View được tạo ra bởi qua trình inflate của LayoutInflater có được add vào ViewGroup parent hay không.

Nhưng chúng ta thử phân tích trường hợp dùng LayoutInflater trong phương thức getView khi custom adapter xem nhé. Ở trong phương thức getView chúng ta hay sử dụng như sau:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View view = LayoutInflater.from(mContext).inflate(R.layout.item_message, parent, false);
    /*
    *chỗ nàyánh xạ view và cập nhật dữ liệu của view
    */
    return view;
}

Ở phương thức trên thì LayoutInflater sẽ đọc file item_message.xml và chuyển đổi nó thành 1 view và sẽ không attach ngay vào ViewGroup parent (ListView, GridView...) Nếu bạn sử dụng đoạn code trên và bên Activity bạn set adapter thì mọi chuyện đều ổn..ứng dụng bạn chạy bình thường và listview sẽ hiển thị các tin nhắn.

Nhưng bạn nhận thấy là listview muốn hiển thị các item message, vậy tại sao khi tạo ra view từ item_message.xml chúng ta không gán luôn nó vào listview bằng cách cho tham số attachToRoot=true ở trong phương thức getView luôn nhỉ...Thử code bên dưới nhé

 @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_message, parent, true);
        /*
        *chỗ nàyánh xạ view và cập nhật dữ liệu của view
        */
        return view;
    }

Nếu bạn sử dụng code này thì ứng dụng của bạn sẽ bị dừng đột ngột với nguyên nhân:

01-17 01:57:21.961 14112-14112/com.dvt.abc E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.dvt.abc, PID: 14112
    android.view.InflateException: Binary XML file line #20: addView(View, LayoutParams) is not supported in AdapterView
    Caused by: java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView
        at android.widget.AdapterView.addView(AdapterView.java:880)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:534)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:427)
        at com.dvt.abc.MessageAdapter.getView(MessageAdapter.java:38)

Tại sao lại như vậy... Khi bạn sử dụng đoạn code:

public class DemoLayoutInflater extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        LinearLayout llMain = findViewById(R.id.ll_main);
        View view = LayoutInflater.from(this).inflate(R.layout.item_button, llMain, true);
	}
}

Ở đây LayoutInflater sẽ đọc file item_button.xml thành View (Java code) và add nó làm con của ViewGroup llMain. Kết quả sẽ tương tự với xml layout file này:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:id="@+id/ll_main"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent">
    <Button 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Custom Button"
        android:id="@+id/custom_button">
    </Button>
     <!--We can add more child item to a Viewgroup-->
</LinearLayout>

Hành động add view đối với các ViewGroup như LinearLayout hay RelativeLayout thì hoàn toàn bình thường, vì các viewgroup có thể chứa nhiều View con hoặc ViewGroup khác ở trong nó. nhưng một lớp con của AdapterView như ListView, GridView thì các item con của nó không thể thủ công bằng việc add trong xml hoặc trong code như dưới đây:

//xml layout file
<ListView
        android:id="@+id/lv_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    <!--Don't do this-->
</ListView>
//Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ListView lvData = findViewById(R.id.lv_data);
    TextView tvContent = new TextView(this);
    lvData.addView(tvContent); //Don't do this
}
//Adapter
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    //Don't set attachToRoot=true
    View view = LayoutInflater.from(mContext).inflate(R.layout.item_message, parent, true);
    /*
    *chỗ nàyánh xạ view và cập nhật dữ liệu của view
    */
    return view;
}

Nên nếu bạn gặp lỗi: addView(View, LayoutParams) is not supported in AdapterView thì hãy kiểm tra lại xem bạn có gọi add view thủ công vào AdapterView không nhé..

Đó là toàn bộ những gì mình muốn nói với các bạn về LayoutInflater.

Cảm ơn các bạn đã đọc bài viết.

Chào thân ái và quyết thắng!

vncoder logo

Theo dõi VnCoder trên Facebook, để cập nhật những bài viết, tin tức và khoá học mới nhất!



Khóa học liên quan

Khóa học: Android

Học Kotlin cơ bản
Số bài học:
Lượt xem: 17161
Đăng bởi: Admin
Chuyên mục: Android

Học lập trình Flutter cơ bản
Số bài học:
Lượt xem: 55604
Đăng bởi: Admin
Chuyên mục: Android

Lập trình Android cơ bản
Số bài học:
Lượt xem: 22310
Đăng bởi: Admin
Chuyên mục: Android