Được chỉnh sửa ngày 29/10/2019.
Chào mừng các bạn đã đến với bài học Android thứ 30, bài học về Fragment. Đây là bài học trong chuỗi bài viết về lập trình ứng dụng Android bằng Java của Yellow Code Books.
Như vậy, đến bài học hôm nay, bạn đã biết rõ khái niệm về Activity trong Android rồi. Bạn đều biết, Activity chính là thành phần được dùng để tổ chức một màn hình trong ứng dụng. Tất nhiên rằng, các thiết bị phần cứng ở những giai đoạn đầu của Android đều có màn hình khá nhỏ và chật chội, nên một Activity khi này đủ mạnh để quản lý một màn hình. Nhưng đời không như mơ, khi mà nhu cầu sử dụng các thiết bị với màn hình lớn ngày càng tăng, không những các tablet mới có màn hình lớn, mà ngay cả các thiết bị được gọi là phone ngày xưa cũng tăng kích thước màn hình lên để trở thành các phaplet (lai tạp giữa phone và tablet). Việc tăng kích thước màn hình trên các thiết bị Android như thế ít nhiều làm tăng thêm “gánh nặng” cho Activity, khiến việc thiết kế UI sao cho vừa có thể chạy tốt trên phone và tablet lẫn phaplet bỗng trở nên khó khăn hơn bao giờ hết cho các lập trình viên chúng ta. Chính vì vậy mà Fragment đã được Google giới thiệu ra nhằm “giảm tải” cái gánh nặng đó.
Qua lời “dẫn truyện” trên đây, bạn đã phần nào hiểu được vai trò của Fragment rồi đúng không nào. Hãy cùng mình xem tiếp các mục bên dưới để có cái nhìn đầy đủ hơn về Fragment này nhé.
Fragment Là Gì?
Với những ý trên đây, bạn có thể thấy Fragment đóng vai trò quản lý một giao diện của màn hình y như Activity vậy. Nhưng Fragment lại không phải là một thành phần quản lý giao diện độc lập như Activity, Fragment thuộc về quản lý của Activity.
Như vậy bạn có thể hình dung Fragment chịu trách nhiệm quản lý một không gian màn hình, nhiều khi không gian này cũng chính là toàn màn hình. Và cái không gian màn hình đó của Fragment phải nằm trong một Activity nào đó. Một Activity có thể có nhiều Fragment, có khi nhiều Fragment của Activity đó cùng nhau hiển thị lên một màn hình, cũng có khi chúng luân phiên hiển thị nếu như mỗi chúng đều chiếm cả không gian màn hình. Và một ý nữa, một Fragment nào đó cũng có thể được khai báo và sử dụng bởi nhiều Activity khác nhau.
Bạn có thể thấy rằng, cũng bởi Fragment chịu trách nhiệm quản lý một phần giao diện, nên nó cũng có vòng đời y như với Activity vậy. Tuy nhiên, vì Fragment nằm trong quản lý của Activity, nên vòng đời của Fragment sẽ phụ thuộc rất nhiều vào vòng đời của Activity. Chúng ta sẽ cùng nhau nói rõ hơn về vòng đời của Fragment ở bài học kế tiếp nhé, bây giờ hãy tìm hiểu vì sao chúng ta phải dùng Fragment.
Tại Sao Lại Dùng Fragment?
Câu hỏi này rất hay, và rất quan trọng. Theo như mình có nói, bất kỳ một ứng dụng nào sau khi được thiết kế ra, bạn sẽ dễ biết được ngay ứng dụng đó có bao nhiêu Activity. Nhưng trớ trêu thay, bạn lại khó mà biết được sẽ có bao nhiêu Fragment trong đám Activity đó. Bạn chỉ có thể biết được bao nhiêu và khi nào nên dùng Fragment khi tiếp cận vào nhu cầu thực tế của từng Activity đó mà thôi.
Qua ý trên, bạn có thể thấy sẽ có ứng dụng hoàn toàn không dùng đến Fragment, nếu UI của nó đủ đơn giản. Cũng sẽ có ứng dụng có nhiều Fragment, các Fragment này sẽ được bạn thêm vào trong chương trình trong quá trình xây dựng chúng, nó phụ thuộc rất nhiều vào kinh nghiệm của bạn. Dù sao đi nữa, nếu bạn biết triển khai một Activity thành nhiều Fragment con, có thể sẽ giúp tiết kiệm khá nhiều thời gian thiết kế, xây dựng và sửa lỗi nữa đấy.
Bỏ qua cái sự kinh nghiệm về việc dùng bao nhiêu Fragment đi, chúng ta tiếp tục trả lời câu hỏi tại sao của mục này. Như ở đầu bài viết, mình cũng nói đến vai trò cơ bản của Fragment đó là giúp giảm tải cho lập trình viên khi phải thiết kế giao diện linh động trên nhiều màn hình lớn nhỏ khác nhau. Bạn hãy nhìn vào minh họa cho một tình huống sau.
Với tình huống trên chúng ta có nhu cầu muốn thiết kế một giao diện linh động. Sao cho khi hiển thị trên màn hình lớn, như hình bên trái ở trên đây, thì giao diện hiển thị kiểu danh sách các item ở phía trái, click vào từng item sẽ hiển thị nội dung chi tiết của item đó ở phía phải. Nhưng ở hình bên phải, với thiết bị có màn hình nhỏ, danh sách các item được hiển thị hết toàn màn hình, và click vào từng item sẽ dẫn sang màn hình khác hiển thị nội dung của item đó.
Yêu cầu là vậy đó, nếu bạn chỉ sử dụng một Activity để tự động hiển thị danh sách và nội dung cùng nhau ở màn hình lớn, và hiển thị danh sách tách biệt với nội dung của nó ở màn hình nhỏ trên đây, thì bạn có thiết kế được không? Câu trả lời là được, nhưng phức tạp lắm.
Quay lại hình minh họa. Nếu bạn thiết kế danh sách các item là một Fragment, hình minh họa gọi nó là Fragment A, và nội dung của từng item sẽ là Fragment B. Thì với màn hình lớn bên trái, bạn hiển thị cả hai Fragment A và Fragment B lên cùng một Activity. Còn với màn hình nhỏ bên phải, bạn có thể hiển thị Fragment A và Fragment B thay phiên nhau, trên cùng một Activity hoặc khác Activity là do bạn tổ chức. Cách tổ chức các thành phần UI như vậy lên các Fragment rồi biến tấu chúng trên từng vùng của Activity rõ ràng vừa linh động, dễ dàng, hiệu quả như mình đã nói, lại còn thú vị nữa.
Ngoài ví dụ trên đây cho thấy bạn dễ dàng tạo được sự linh động của UI khi sử dụng Fragment với màn hình nhỏ (phone) và lớn (tablet), thì bạn còn có thể tổ chức sự linh động đối với các chế độ xoay màn hình của cùng một thiết bị. Ở bài học tiếp theo chúng ta sẽ vận dụng kiến thức tạo Fragment hôm nay để từng bước xây dựng một màn hình đạt chuẩn linh động khi xoay ngang/dọc này.
Fragment Và Sự Tương Thích Ngược
Như bạn có thể thấy rằng, Fragment ra đời cũng bởi một lý do chính, đó là hỗ trợ giao diện trên tablet. Nên Fragment rất gắn liền với sự ra đời của tablet. Và hệ điều hành đánh dấu cho việc hỗ trợ chính thức tablet chính là hệ điều hành Android 3.0 (APL level 11), nếu bạn còn nhớ, ở bài về ActionBar chúng ta cũng đã nói về sự hỗ trợ tablet này rồi.
Vậy thì, nếu project của bạn có khai báo minSdkVersion từ 11 trở lên, thì không có gì để nói. Nhưng nếu giá trị thiết lập này nhỏ hơn 11? Tin vui cho bạn rằng hệ thống vẫn hỗ trợ tương thích ngược đến với các ứng dụng như thế này, nhưng sẽ có một chút khác biệt đối với việc quyết định sử dụng lớp Fragment. Khác biệt đó như sau.
Khi xây dựng Fragment, bạn sẽ luôn thấy có hai lựa chọn Fragment ở hai package khác nhau như hai dòng đầu tiên ở hình bên dưới. Một Fragment thuộc về android.app và một Fragment thuộc về android.support.v4.app. Tất nhiên, bạn chỉ nên sử dụng một trong hai thôi, cách chọn lựa Fragment nào để sử dụng thì mình sẽ nói rõ hơn ở dưới đây.
Sau đây là cách chọn lựa Fragment nào dành cho bạn.
- Nếu minSdkVersion của ứng dụng từ 11 trở lên. Bạn cứ thoải mái sử dụng Fragment “chính thức”, đó chính là Fragment trong gói android.app như dòng đầu tiên ở hình trên. Kèm theo đó bạn phải sử dụng phương thức getFragmentManager() khi cần hiển thị động Fragment lên Activity (bạn sẽ được làm quen với cách hiển thị này ở bài tiếp theo).
- Còn nếu minSdkVersion của ứng dụng nhỏ hơn 11. Bạn hãy dùng đến Fragment ở gói tương thích ngược android.support.v4.app ở dòng thứ hai của hình. Nhưng khi này bạn phải dùng phương thức getSupportFragmentManager() cho mục đích hiển thị động Fragment. Và lại có ràng buộc nữa rằng Activity chứa đựng Fragment khi này không phải Activity thường mà phải là FragmentActivity. Tuy nhiên nếu bạn thấy Activity bạn dùng đang kế thừa từ AppCompatActivity rồi thì cũng yên tâm nhé, vì AppCompatActivity vừa hỗ trợ ActionBar đến các hệ điều hành Android cũ hơn, cũng vừa hỗ trợ cả Fragment.
Cách Xây Dựng Một Fragment
Ý tưởng của việc tổ chức và quản lý UI của Fragment rất giống với Activity. Nếu như với Activity, bạn phải xây dựng bộ đôi “thần thánh” XyzActivity.java và activity_xyz.xml, bạn có thể xem lại ý này ở link này. Thì với Fragment, bạn cũng sẽ phải xây dựng bộ đôi XyzFragment.java và fragment_xyz.xml. Khi đó file xml sẽ chịu trách nhiệm trong việc hiển thị UI, còn file java sẽ chịu trách nhiệm xử lý các logic. Và việc tạo bộ file này cho Fragment cũng được hỗ trợ hoàn toàn bởi wizard của Android Studio.
Nhưng khác với Activity. Fragment sẽ không cần phải khai báo với manifest, vì đây không phải là một thành phần cơ bản của ứng dụng. Và Fragment sẽ không cần một Intent để kích hoạt, việc sử dụng Fragment như thế nào sẽ nằm ở bài tiếp theo.
Các Bước Tạo Mới Một Fragment
Phần này chúng ta chỉ làm quen qua các bước để tạo một Fragment. Bạn nên cùng thực hành để quen. Tuy nhiên code của bài thực hành này sẽ không có trên GitHub nhé, vì đến khi chính thức thực hành với TourNote chúng ta cũng sẽ tạo lại một Fragment.
Như đã nói, chúng ta vẫn sẽ dùng wizard để tạo mới Fragment. Cách tạo này sẽ giúp bạn có sẵn bộ java và xml.
Để bắt đầu, ở cửa sổ Project, bạn hãy để vệt sáng ở package sẽ chứa file Fragment java mà bạn muốn tạo. Rồi chọn theo menu File > New > Fragment > Fragment_Nào_Đó. Hoặc nhấn chuột phải và chọn New > Fragment > Fragment_Nào_Đó như hình dưới đây.
Cũng như khi bạn tạo mới Activity, menu trên cho bạn nhiều tùy chọn template Fragment khác nhau. Bạn có thể cân nhắc sử dụng bất kỳ loại Fragment nào có sẵn trong menu này cũng được, mỗi loại như vậy sẽ tự động thêm các source code tương ứng cho bạn. Để có thể dễ dàng để làm quen ban đầu thì mình khuyên bạn nên chọn loại Fragment (Blank).
Sau khi chọn template Fragment ở bước trên, một dialog xuất hiện yêu cầu bạn đặt tên cho Fragment mới này, ở cả file Java và file xml. Chúng ta đặt tên cho nó là FirstFragment nhé. Lưu ý là từ Android Studio 3.0, khi tạo mới một Activity hay Fragment, bạn đã có thể lựa chọn ngôn ngữ là Java hay Kotlin ở mục Source Language ở bước này được rồi đấy nhé.

Một ý bổ sung nữa là, ở dialog trên, với Fragment này bạn đừng check vào include fragment factory method?. Mình sẽ giải thích hai check chọn này là gì cho bạn hiểu rõ ràng hơn.
- Include fragment factory methods? – Check chọn này cho phép wizard sau đó sẽ tạo mới Fragment có kèm thêm các phương thức truyền dữ liệu từ bên ngoài vào. Dĩ nhiên là chúng ta vẫn sẽ dùng Bundle để truyền dữ liệu từ Activity vào Fragment (khi khởi tạo Fragment), cách dùng Bundle này cũng sẽ giống với cách mà bạn đã làm quen ở Activity vậy. Bài hôm nay các bạn chưa làm quen đến các đoạn code này vì FirstFragment này chưa cần dùng đến. Chúng ta sẽ được tiếp cận việc tạo và truyền dữ liệu qua lại này ở bài sau.
- Include interface callbacks? – Check chọn này cho phép wizard sau đó sẽ tạo mới Fragment có kèm thêm các phương thức lắng nghe và hiện thực các sự kiện gọi về (Interface Callback). Các sự kiện này sẽ cho phép Activity chứa Fragment đó biết được các hành động mà người dùng tương tác bên trong Fragment, để có các hành xử logic thích hợp. Dĩ nhiên FirstFragment này nên được check vào check chọn này vì MainActivity cần phải biết khi nào và Button nào bên trong FirstFragment được click. Chúng ta cũng sẽ có dịp làm quen với các sự kiện này ở bài sau luôn.
Làm Quen Với Fragment
Bạn đã tạo mới một FirstFragment ở bước trên đây. Việc tạo một Fragment cực dễ như vậy đó. Nhưng code mà bạn nhận được sau đó… đọc vào rối rắm quá đúng không. Mục này chúng ta sẽ làm quen sơ bộ các dòng code này, để có thể hiểu rõ hơn cách mà một Fragment hiển thị nội dung và thực hiện các logic của nó. Bạn sẽ hiểu toàn bộ các dòng code của Fragment thông qua bài viết về vòng đời của Fragment.
Giao Diện Của Fragment (File XML)
Đầu tiên, mời bạn làm quen với giao diện của FirstFragment được chứa trong file xml mà bạn mới tạo trên kia, chính là file fragment_first.xml. Giao diện thì không có gì để nói thêm.
Tuy nhiên, dù cho giao diện hiện tại của FirstFragment thế nào, mình muốn bạn chỉnh sửa cho nó giống như sau. Mình muốn FirstFragment này chính là list các item, như Fragment A ở mục trên kia. Nhưng vì chúng ta chưa có học về list, thì hãy tạo tạm cho mình hai Button nhé, chúng ta giả lập list hiện tại có hai item như vậy thôi.
<LinearLayout 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"
android:orientation="vertical"
tools:context="com.yellowcode.tournote.FirstFragment">
<Button
android:id="@+id/btnItem1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Item 1"/>
<Button
android:id="@+id/btnItem2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Item 2"/>
</LinearLayout>
Logic Của Fragment (File Java)
Fragment cũng như Activity, chúng đều không có constructor. Vậy phương thức nào sẽ dùng để khởi tạo các giao diện và các giá trị cho chúng? Vâng, bạn đã biết Activity dùng đến phương thức khởi tạo là onCreate(). Thì với Fragment bạn phải dùng onCreateView(). Tại sao lại là phương thức này thì đến mục về vòng đời của Fragment bạn sẽ hiểu rõ hơn nhé. Còn bây giờ mời bạn làm quen với onCreateView(). Đây là các dòng code mặc định của phương thức này khi bạn tạo mới một Fragment.
Qua dòng code trên, bạn có thể phát hiện thêm một điều khác biệt nữa giữa Activity và Fragment. Đó là Fragment không dùng phương thức setContentView() để đọc file xml UI như với Activity. Thay vào đó nó phải gọi phương thức inflate() của LayoutInflater, như hình trên. Phương thức này cho phép bạn định nghĩa một xml UI, trong trường hợp này là R.layout.fragment_first, rồi gắn nó vào trong container chính là một ViewGroup.
Nếu kiến thức về inflate gì đó quá lu bu thì bạn có thể tạm chưa cần phải nhớ kỹ lúc này. Cái bạn quan tâm là nếu bây giờ chúng ta muốn tạo sự kiện click cho hai Button đã khai báo ở giao diện xml trên kia thì làm sao? Bạn đã biết, nếu ở Activity chúng ta chỉ việc “lấy” Button từ xml qua Java bằng phương thức findViewById(), như mình có nói đến ở đây. Thì ở Fragment, dòng code findViewById() cũng sẽ được dùng, nhưng thông qua một cấp view mà bạn vừa inflate, bạn hãy xem code sửa đổi sau.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_first, container, false);
Button btnItem1 = (Button) view.findViewById(R.id.btnItem1);
Button btnItem2 = (Button) view.findViewById(R.id.btnItem2);
btnItem1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getActivity(), "Button 1 clicked", Toast.LENGTH_SHORT).show();
}
});
btnItem2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getActivity(), "Button 2 clicked", Toast.LENGTH_SHORT).show();
}
});
return view;
}
Với code trên, mình đã cố tình tách dòng inflate ra để tạo thành một view, để mà dùng đến view đó để gọi các Button thông qua ID của chúng. Cuối cùng là return view đó sau khi đã “đạt được mục đích”.
Mọi thứ không quá khó ha. Tuy nhiên, qua các dòng code show message dạng Toast trên đây, nếu chú ý kỹ, bạn sẽ lại bắt gặp một chi tiết nữa khác nhau giữa Fragment và Activity mà bạn đã biết. Đó là ở tham số thứ nhất của Toast.makeText(), tham số này đòi hỏi một Context, tuy cho đến bài học hôm nay bạn vẫn chưa nắm rõ khái niệm Context lắm, nhưng như những gì đã làm quen với Activity, bạn có biết rằng Activity chính là một Context. Và do đó, bên Activity, bạn truyền vào từ khóa this. Qua Fragment, bạn cần phải có Activity để truyền vào, thế là bạn chỉ cần gọi getActivity(), phương thức này sẽ tự động gọi lấy Activity đang chứa Fragment này. Phương thức getActivity() này cũng sẽ rất hữu dụng cho các tình huống sau này nữa. Bạn nhớ ghi chú lại nhé.
Cơ bản tạo một Fragment là vậy. Chúng ta sẽ cùng nhau xem qua cách thức sắp xếp các Fragment lên Activity, và xem sự tương tác giữa chúng với Activity, và giữa các Fragment với nhau sẽ như thế nào ở bài viết kế tiếp nhé.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao ở mỗi bài nếu thấy thích.
– Comment bên dưới mỗi bài nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
Bài Kế Tiếp
Như đã nói, chúng ta sẽ thực hành tạo thêm một Fragment nữa, và cùng hiển thị chúng lên Activity, rồi làm vài thao tác truyền gửi dữ liệu cho nhau, xem như thế nào nhé.
Sao mình gọi getActivity() mà nó hiện đỏ lòm không có sẵn nhỉ
Cấu hình Macbook của bạn như thế nào vậy?
Bạn đang tìm hiểu cấu hình để lập trình phải không. Hiện mình đang dùng con MacBook Pro 16inch, 16GB Ram.