Chào mừng các bạn đã đến với bài học Android thứ 32, bài học về Fragment (phần tiếp theo). Đâ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, kết thúc bài học hôm trước, chúng ta đã có trong tay hai Fragment. Fragment thứ nhất có tên FirstFragment đang hiển thị hai Button. Fragment thứ hai dĩ nhiên là có tên SecondFragment rồi, Fragment này đang chứa một TextView sẽ hiện thị nội dung nào đó tùy theo user sẽ nhấn Button nào ở FirstFragment.
Bài học tiếp theo này chúng ta sẽ tìm cách hiển thị hai Fragment này lên Activity, và thực hiện truyền thông tin qua lại giữa chúng nữa. Bạn sẽ thấy rằng, sử dụng Fragment không khó như bạn nghĩ đâu.
Nào hãy cùng mình xem và thực hành từng bước thông qua các kiến thức của bài học hôm nay nhé.
Các Cách Thức Hiển Thị Fragment
Trước khi chính thức thực hiện việc hiển thị, chúng ta cần hiểu xem có các cách hiển thị nào.
Bạn cũng nên biết rằng, hiện tại mình dùng từ Hiển thị. Nhưng bạn có thể hiểu chúng ta đang tìm cách sắp xếp các Fragment lên Activity, hiển thị chúng, và tương tác với chúng nữa. Tất cả những hành động trên đây mình gom lại thành hai từ Hiển thị mà thôi.
Chúng ta có hai cách để hiển thị các Fragment. Cách thứ nhất là hiển thị Fragment theo kiểu tĩnh. Cách thứ hai đương nhiên là cách hiển thị Fragment theo kiểu động rồi. Vậy thì hai cách này khác nhau ra sao?
Với cách hiển thị Fragment theo kiểu tĩnh. Nghe qua cái tên bạn đã thấy có gì đó mang ý nghĩa cố định rồi. Cố định ở đây có nghĩa là bạn phải chỉ định trước các Fragment cần hiển thị lên màn hình. Khi ứng dụng thực thi, sẽ không có chuyện từ một vùng màn hình đang hiển thị Fragment A, có thể được thay thế nó bởi Fragment B hay bất kỳ Fragment nào khác. Sự linh động của các Fragment đối với cách hiển thị như thế này không cao. Thường thì nếu bạn biết được Activity đó cần hiển thị những Fragment nào và ở vị trí nào, khi đó bạn có thể định nghĩa tĩnh các Fragment như này. Cách hiển thị này có ưu điểm là giúp bạn thiết kế ra các ứng dụng rất nhanh, vì không cần quá nhiều các dòng code. Chắc chắn bạn sẽ được làm quen với cách thức hiển thị Fragment theo kiểu này ở mục bên dưới.
Còn cách hiển thị Fragment theo kiểu động thì đương nhiên sẽ linh động hơn kiểu tĩnh rồi. Nếu bạn sử dụng cách này để hiển thị các Fragment, thông qua việc quản lý bằng Java code, bạn có thể thoải mái đặt Fragment nào đó vào một vùng không gian đã được chỉ định sẵn, hoặc lấy Fragment đó ra khỏi vùng không gian đó, hoặc thay thế bằng một Fragment khác. Cách hiển thị này giúp bạn đạt tới “cảnh giới” cao nhất của sự linh động trong việc sử dụng Fragment. Và dĩ nhiên chúng ta cũng sẽ cùng nhau thực hành hiển thị Fragment theo kiểu này ở bài học hôm nay.
Làm Quen Với Cách Hiển Thị Fragment Theo Kiểu Tĩnh
Chúng ta sẽ hiển thị giao diện như trên đây.
Cách này rất nhanh chóng. Bạn chỉ cần sử dụng một layout có tên là fragment để hiển thị một Fragment mà bạn mong muốn.
Layout fragment này cũng cần bạn chỉ định các thuộc tính android:layout_width và android:layout_height như các layout khác. Chính vì vậy bạn có thể thiết kế bao nhiêu fragment vào trong giao diện của Activity đều được, và đặt chúng vào vào bất cứ vị trí nào bạn muốn. Chính thuộc tính android:name của thẻ fragment này sẽ giúp bạn chỉ định Fragment nào cần hiển thị.
Bạn xem code sau, mình sẽ chỉnh sửa ở file activity_main.xml của TourNote. Mình tìm đến layout chứa TextView và ImageView mà chúng ta đã thực hành, và thay thế hai widget này bằng hai thẻ fragment. Các UI còn lại liên quan đến Navigation Drawer mình vẫn giữ như cũ.
<!-- Content --> <LinearLayout android:id="@+id/activity_main_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.yellowcode.tournote.MainActivity"> <fragment android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:name="com.yellowcode.tournote.FirstFragment"/> <fragment android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2" android:name="com.yellowcode.tournote.SecondFragment"/> </LinearLayout>
Bạn có thể tự tạo một project mới rồi sửa chữa trên layout chính của project đó cũng được nhé. Nhớ là phải đảm bảo có sẵn hai Fragment đã được tạo ra ở bài hôm trước.
Rất nhanh. Bạn đã hiển thị hai Fragment vào trong một LinearLayout rồi đấy. Bạn để ý xem các giá trị android:name=”com.yellowcode.tournote.FirstFragment và android:name=”com.yellowcode.tournote.SecondFragment giúp hệ thống xác định Fragment nào cần hiển thị lên layout fragment nào.
Có lẽ bạn đang rất nôn nóng được xem diện mạo hai Fragment như thế nào. Bình thường với một số Fragment chuyên hiển thị dữ liệu, như SecondFragment, thì đến bước này bạn đã có thể thực thi ứng dụng để kiểm thử được rồi. Nhưng vì FirstFragment có định nghĩa interface OnFirstFragmentListener, và ở phương thức onAttach() của FirstFragment có kiểm tra nếu Activity chứa nó không implement interface này, thì sẽ báo lỗi. Chính vì vậy bạn phải thêm các dòng code sau vào MainActivity.java trước khi thực thi để tránh lỗi.
public class MainActivity extends AppCompatActivity implements FirstFragment.OnFirstFragmentListener { // Các thuộc tính... @Override protected void onCreate(Bundle savedInstanceState) { // Code như các bài học trước... } @Override protected void onPostCreate(Bundle savedInstanceState) { // Code như các bài học trước... } @Override public void onConfigurationChanged(Configuration newConfig) { // Code như các bài học trước... } @Override public boolean onCreateOptionsMenu(Menu menu) { // Code như các bài học trước... } @Override public boolean onOptionsItemSelected(MenuItem item) { // Code như các bài học trước... } @Override public void onItemPressed(String content) { // Implement phương thức trừu tượng của OnFirstFragmentListener } }
Giờ thì bạn có thể thực thi ứng dụng được rồi. Đây là thành quả của chúng ta.
Bạn có thể thấy phần không gian màu trắng chứa hai Button ITEM 1 và ITEM 2 chính là giao diện của FirstFragment đấy. Còn phần không gian màu xám chứa một nội dung “Content” thì chính là giao diện của SecondFragment.
Chúng ta chỉ thực hành hiển thị các Fragment lên màn hình ở mục này thôi, chứ không đi sâu hơn với sự tương tác khi user touch lên bất kỳ Button nào ở FirstFragment. Vì thông thường cách hiển thị tĩnh như này chỉ nên dùng khi bạn muốn tổ chức nhanh các Fragment mà không đòi hỏi quá nhiều vào logic của chúng. Nếu bạn không thích cách hiển thị có phần thụ động này, thì hãy đọc tiếp mục sau nhé.
Làm Quen Với Cách Hiển Thị Fragment Theo Kiểu Động
Với cách hiển thị này thì giao diện của màn hình cũng sẽ không đổi. Như hình trên. Vậy sẽ động hơn như thế nào, bạn xem.
Nếu như với cách hiển thị tĩnh trên kia, bạn phải chỉ định thẻ fragment nào sẽ chứa đựng Fragment nào một cách cố định. Thì với cách hiển thị động này, bạn chỉ cần khai báo một vùng không gian nào đó sẽ chứa đựng Fragment, vùng không gian đó được khai báo bằng một FrameLayout.
Nào, vậy chúng ta cần phải thay đổi tí giao diện của activity_main.xml sao cho chứa đựng hai FrameLayout như sau.
<!-- Content --> <LinearLayout android:id="@+id/activity_main_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.yellowcode.tournote.MainActivity"> <FrameLayout android:id="@+id/firstFrame" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <FrameLayout android:id="@+id/secondFrame" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2"/> </LinearLayout>
Giờ chúng ta sẽ qua MainActivity.java để thiết lập logic hiển thị và tương tác giữa các Fragment.
Chúng ta vẫn phải giữ dòng code implements FirstFragment.OnFirstFragmentListener của MainActivity và implement phương thức onItemPressed() mà bạn đã thêm vào ở mục trên. Vì như mình đã có nói, bất cứ khi nào mà bạn có thêm FirstFragment vào Activity, bạn buộc phải có các dòng code này.
Trước khi tiến hành add Fragment vào các FrameLayout mà bạn đã xây dựng sẵn, chúng ta cần làm quen một số lớp quan trọng liên quan sau.
FragmentManager
Đây là một lớp dùng để quản lý các Fragment, lớp này được tích hợp vào trong mỗi Activity để giúp các Activity có thể dễ dàng để thêm (add), xóa (remove) hoặc thay thế (replace) các Fragment ra khỏi một vùng không gian một cách linh động.
Nếu bạn có đọc kỹ về sự tương thích ngược của Fragment, thì có thể thấy có hai lớp FragmentManager mà bạn có thể cân nhắc sử dụng.
Mình xin nhắc lại tóm tắt cách chọn các lớp tương thích ngược như sau.
– FragmentManager ở gói android.app – Được gọi thông qua phương thức getFragmentManager() của Activity. Nếu project của bạn khai báo minSdkVersion từ 11 trở lên thì cứ thoải mái sử dụng em này.
– FragmentManager ở gói android.support.v4.app – Được gọi thông qua phương thức getSupportFragmentManager() của Activity. Nếu project của bạn khai báo minSdkVersion nhỏ hơn 11 thì bạn buộc phải dùng lớp quản lý này để có thể mang Fragment đến với các hệ điều hành trước Android 3.0.
Lớp này có một phương thức rất hữu dụng mà mình xin được nói trước như sau.
– findFragmentById() – Khi dùng phương thức này bạn sẽ truyền vào cho nó một ID. ID này có thể là ID của thẻ fragment như với mục hiển thị tĩnh trên kia. Hoặc ID của FrameLayout như bạn mới vừa làm quen ở code trên đây. Kết quả của phương thức này sẽ trả về cho bạn một Fragment được chứa trong layout có ID mà bạn vừa cung cấp.
FragmentTransaction
Khi đã có FragmentManager, bạn bắt đầu thực hiện việc thêm, xóa, thay đổi thoải mái các Fragment dựa vào FragmentTransaction này. Bạn có thể “triệu hồi” FragmentTransaction thông qua phương thức beginTransaction() từ FragmentManager.
FragmentTransaction có các phương thức thú vị để bạn “chơi” với Fragment như sau.
– add() – Khi FrameLayout còn rỗng, tức chưa chứa đựng bất kỳ Fragment nào, thì bạn có thể dùng phương thức này để add Fragment vào cho FrameLayout đó. Như bạn sắp làm quen với code add một FirstFragment sau đây.
– replace() – Khi bạn muốn thay thế một Fragment đang có sẵn ở FrameLayout bằng một Fragment nào đó khác.
– remove() – Khi bạn muốn gỡ bỏ Fragment ra khỏi một FrameLayout nào đó.
– addToBackStack() – Khi bạn quản lý Fragment bởi các phương thức replace() hay remove() trên đây, bạn có thể sử dụng thêm phương thức addToBackStack() này. Nếu bạn gọi đến phương thức này trước khi gọi commit() sẽ được nói ở dưới, thì hệ thống sẽ đưa Fragment ở transaction hiện tại vào Back Stack. Điều này có ý nghĩa là, Fragment bị thay thế hay bị gỡ ra khỏi FrameLayout ở transaction này sẽ không bị xóa khỏi hệ thống mà vẫn còn được quản lý bên trong Back Stack (bạn sẽ được làm quen với Back Stack của Fragment ở bài học sau). Và do Fragment không bị hủy khỏi Back Stack, nên nếu user nhấn nút back sau đó, họ hoàn toàn có thể quay trở lại với Fragment trước đó đã bị gỡ ra.
– commit() – Cho dù bạn có quản lý hiển thị Fragment bằng add(), replace() hay remove() thì bạn cũng phải gọi commit() cuối cùng, để FragmentTransaction biết sẽ bắt đầu thực hiện các transaction mà bạn đã ra lệnh đó.
Nếu như thông tin về FragmentManager và FragmentTransaction trên đây khó hiểu quá, thì bạn cứ tiếp tục thực hành tiếp phần sau đây. Bạn sẽ nhanh chóng hiểu rõ hơn thôi.
Chúng ta bắt đầu add FirstFragment vào trong FrameLayout có ID là firstFrame ngay khi MainActivity được hiển thị như sau.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = (DrawerLayout) findViewById(R.id.activity_main_drawer); drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawerLayout.addDrawerListener(drawerToggle); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); FirstFragment firstFragment = new FirstFragment(); FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(R.id.firstFrame, firstFragment); fragmentTransaction.commit(); }
Các dòng code add FirstFragment vào firstFrame là các dòng được tô sáng trên đây. Dòng đầu tiên là dòng khai báo FirstFragment. Kế tiếp bạn nên khai báo FragmentManager, vì Fragment chúng ta đang dùng đã được hệ thống tạo sẵn nằm trong gói android.support.v4.app, nên FragmentManager cũng sẽ được tạo ra thông qua phương thức getSupportFragmentManager(). Sau đó FragmentTransaction được tạo ra, và chúng ta dùng phương thức add() để add firstFragment vào firstFrame. Cuối cùng bạn phải nhớ phương thức commit(). Khi này nếu thực thi ứng dụng, bạn đã có thể trông thấy FirstFragment hiển thị rồi đấy.
Tiếp theo, do bạn đã implement phương thức trừu tượng onItemPresses() ở MainActivity, nên bạn sẽ nhận được một String khi user nhấn vào một Button nào đó ở FirstFragment. Đây là hình ảnh gợi nhớ lại các dòng code truyền một String mà bạn đã xây dựng bên FirstFragment ở bài hôm trước.
Do đó, mình sẽ căn cứ vào phương thức onItemPressed() này mà thực hiện việc khởi tạo SecondFragment và add vào FrameLayout có ID là secondFrame như sau.
@Override public void onItemPressed(String content) { SecondFragment secondFragment = SecondFragment.newInstance(content); FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.secondFrame, secondFragment); fragmentTransaction.commit(); }
Bạn thấy, SecondFragment được tạo mới không thông qua từ khóa new như với FirstFragment, vì ở bài hôm trước chúng ta đã xây dựng (thực ra là sửa lại) phương thức khởi tạo newInstance() có kèm các value truyền vào rồi, nên bài này chúng ta sử dụng thôi. Các khởi tạo cho FragmentManager và FragmentTransaction thì vẫn như cách làm với FirstFragment trên kia. Lần này thay vì gọi phương thức add(), chúng ta dùng replace() sẽ hợp lý hơn, vì user có thể sẽ nhấn nhiều lần lên các Button ở FirstFragment, mỗi lần như vậy chúng ta cần thay thế Fragment cũ bằng Fragment mới tại secondFrame này. Và cuối cùng, phương thức commit() luôn phải được gọi.
Dưới đây là tất cả code của activity_main.xml và MainActivity.java mà bạn có thể tham khảo.
– activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main_drawer" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Content --> <LinearLayout android:id="@+id/activity_main_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.yellowcode.tournote.MainActivity"> <FrameLayout android:id="@+id/firstFrame" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <FrameLayout android:id="@+id/secondFrame" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2"/> </LinearLayout> <android.support.design.widget.NavigationView android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:orientation="vertical"> <!-- Header --> <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/navigation_header_height" android:background="@color/colorPrimary" android:gravity="bottom" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:theme="@style/ThemeOverlay.AppCompat.Dark"> <ImageView android:id="@+id/activity_main_imv_avatar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="@dimen/activity_vertical_margin" app:srcCompat="@mipmap/ic_launcher_round" /> <TextView android:id="@+id/activity_main_tv_user_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/activity_vertical_margin" android:text="Yellow Code" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <TextView android:id="@+id/activity_main_tv_email" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="yellowcode.books@gmail.com" /> </LinearLayout> <!-- Item Info --> <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/navigation_item_height" android:layout_marginTop="@dimen/padding_tiny_plus_one" android:background="@android:color/white" android:gravity="center_vertical" android:orientation="horizontal" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin"> <ImageView android:layout_width="@dimen/navigation_item_icon_size" android:layout_height="@dimen/navigation_item_icon_size" android:src="@drawable/ic_action_info" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/padding_extra_extra_large" android:text="@string/menu_item_about_app" /> </LinearLayout> <!-- Item Help --> <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/navigation_item_height" android:background="@android:color/white" android:gravity="center_vertical" android:orientation="horizontal" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin"> <ImageView android:layout_width="@dimen/navigation_item_icon_size" android:layout_height="@dimen/navigation_item_icon_size" android:src="@drawable/ic_action_help" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/padding_extra_extra_large" android:text="@string/menu_item_help" /> </LinearLayout> </LinearLayout> </android.support.design.widget.NavigationView> </android.support.v4.widget.DrawerLayout>
– MainActivity.java
public class MainActivity extends AppCompatActivity implements FirstFragment.OnFirstFragmentListener { private DrawerLayout drawerLayout; private ActionBarDrawerToggle drawerToggle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = (DrawerLayout) findViewById(R.id.activity_main_drawer); drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawerLayout.addDrawerListener(drawerToggle); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); FirstFragment firstFragment = new FirstFragment(); FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(R.id.firstFrame, firstFragment); fragmentTransaction.commit(); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. drawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); drawerToggle.onConfigurationChanged(newConfig); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_actions, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if(drawerToggle.onOptionsItemSelected(item)) { return true; } switch (item.getItemId()) { case R.id.search: Toast.makeText(this, "Search button selected", Toast.LENGTH_SHORT).show(); return true; case R.id.about: Intent intent = new Intent(this, ContactActivity.class); Bundle bundle = new Bundle(); bundle.putString(ContactActivity.KEY_SHOW_WHAT, ContactActivity.VALUE_SHOW_ABOUT); intent.putExtras(bundle); startActivity(intent); return true; case R.id.help: intent = new Intent(this, ContactActivity.class); bundle = new Bundle(); bundle.putString(ContactActivity.KEY_SHOW_WHAT, ContactActivity.VALUE_SHOW_HELP); intent.putExtras(bundle); startActivity(intent); return true; } return super.onOptionsItemSelected(item); } @Override public void onItemPressed(String content) { SecondFragment secondFragment = SecondFragment.newInstance(content); FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.secondFrame, secondFragment); fragmentTransaction.commit(); } }
Giờ đây bạn có thể thực thi ứng dụng, và nhấn thoải mái lên các Button để kiểm chứng được rồi nhé.
Làm Quen Với Cảnh Giới Cao Nhất Của Hiển Thị Fragment Theo Kiểu Động
Nếu bạn đã thành công với các bước hiển thị Fragment một cách linh động như trên đây, thì xin chúc mừng bạn, bạn đã hiểu rõ về bản chất của sử dụng Fragment rồi đó.
Tuy nhiên, cách sử dụng hoàn hảo Fragment sẽ được mình trình bày tiếp theo ở mục này.
Các bước sau đây chúng ta sẽ giúp cho MainActivity “thông minh” hơn. Khi biết được nếu thiết bị đang đặt xoay ngang (landscape), sẽ hiển thị hai Fragment lên cùng một màn hình, với FirstFragment bên trái và SecondFragment bên phải, khi click vào một item ở FirstFragment (hiện tại chúng ta đang giả lập là Button) thì sẽ cập nhật nội dung ở SecondFragment, kiểu hiển thị này giống như ví dụ ở mục trên thôi. Còn khi thiết bị đang được đặt đứng (portrait), thì sẽ hiển thị FirstFragment lên toàn màn hình, khi click vào một item ở FirstFragment, Activity sẽ hiển thị nội dung ở SecondFragment cũng toàn màn hình. Tất cả sẽ giống như hình minh họa trên đây.
Xây Dựng Giao Diện Cho Các Màn Hình Ngang/Dọc
Đầu tiên, chúng ta cần chỉnh sửa lại activity_main.xml. Đây là layout mặc định để hiển thị giao diện đứng cho thiết bị, nên chúng ta chỉ cần một Fragment toàn màn hình.
<!-- Content --> <LinearLayout android:id="@+id/activity_main_content" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.yellowcode.tournote.MainActivity"> <FrameLayout android:id="@+id/contentFrame" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
Sau đó bạn nên tạo thêm một thư mục layout cho màn hình ngang, thư mục mới này có tên và đường dẫn là res/layout-land/. Tại sao lại có tên như vậy thì bạn có thể xem lại trong cách sử dụng Alternative Resource của Android ở bài viết này nhé. Khi bạn tạo xong thư mục layout cho màn hình ngang, thì hãy copy file activity_main.xml hiện tại vào đó.
Như đã nói, chúng ta cần chỉnh sửa lại sao cho layout ở thư mục này sao cho chứa đựng hai Fragment.
<!-- Content --> <LinearLayout android:id="@+id/activity_main_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" tools:context="com.yellowcode.tournote.MainActivity"> <FrameLayout android:id="@+id/firstFrame" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"/> <FrameLayout android:id="@+id/secondFrame" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2"/> </LinearLayout>
Bạn chú ý là các ID của các FrameLayout của cả hai layout ngang và đứng không giống nhau đâu nhé.
Hiển Thị FirstFragment
Không như mục trên kia, nhảy vào onCreate() là bạn có thể add FirstFragment vào firstFrame ngay. Bởi vì chúng ta có hai activity_main.xml, mỗi layout này chứa cách hiển thị Fragment khác nhau, do đó chúng ta phải kiểm tra xem layout hiện tại mà thiết bị đang dùng là layout ngang hay đứng, rồi mới add các Fragment tương ứng vào đúng FrameLayout. Code của onCreate() giờ đây như sau.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = (DrawerLayout) findViewById(R.id.activity_main_drawer); drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawerLayout.addDrawerListener(drawerToggle); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); FirstFragment firstFragment = new FirstFragment(); if (findViewById(R.id.contentFrame) != null) { // Found the ID of only one Fragment ==> Portrait mode // Remove the existing fragment before add new one if (savedInstanceState != null) { getSupportFragmentManager().executePendingTransactions(); Fragment fragmentById = getSupportFragmentManager().findFragmentById(R.id.contentFrame); if (fragmentById != null) { getSupportFragmentManager().beginTransaction().remove(fragmentById).commit(); } } // Add new one getSupportFragmentManager().beginTransaction().add(R.id.contentFrame, firstFragment).commit(); } else { // Landscape mode // Remove the existing fragments before add new one if (savedInstanceState != null) { getSupportFragmentManager().executePendingTransactions(); Fragment firstFragmentById = getSupportFragmentManager().findFragmentById(R.id.firstFrame); if (firstFragmentById != null) { getSupportFragmentManager().beginTransaction().remove(firstFragmentById).commit(); } Fragment secondFragmentById = getSupportFragmentManager().findFragmentById(R.id.secondFrame); if (secondFragmentById != null) { getSupportFragmentManager().beginTransaction().remove(secondFragmentById).commit(); } } // Add new one getSupportFragmentManager().beginTransaction().add(R.id.firstFrame, firstFragment).commit(); } }
Bạn có thể thấy, dòng if (findViewById(R.id.contentFrame) != null) sẽ giúp kiểm tra xem có tìm thấy layout với ID là contentFrame không, nếu có thì đích thị là activity_main.xml cho màn hình đứng rồi, khi đó chúng ta nên gọi phương thức remove() để gỡ Fragment ra khỏi FrameLayout trước khi add FirstFragment vào. Trường hợp thiết bị đang hiển thị kiểu ngang, thì nên gỡ cả hai Fragment ra khỏi hai FrameLayout của màn hình này rồi mới add FirstFragment vào. Các khởi tạo cho FragmentManager và FragmentTransaction mình viết ngắn gọn hơn. Code không có gì quá khó đúng không bạn.
Tương tự, ở sự kiện onItemPressed(), chúng ta cũng kiểm tra, nếu là màn hình đứng, thì sẽ thay thế FirstFragment bằng SecondFragment cho contentFrame. Nếu là màn hình ngang, thì hành xử giống như cách hiển thị hai Fragment cùng lúc ở mục trên kia thôi. Có một điều cần lưu ý rằng ở cách hiển thị với màn hình đứng, cần phải có thêm phương thức addToBackStack() để cho phép người dùng nhấn nút back ở thiết bị thì quay về Fragment trước đó, như mình có nói đến ở trên kia.
@Override public void onItemPressed(String content) { SecondFragment secondFragment = SecondFragment.newInstance(content); if (findViewById(R.id.contentFrame) != null) { // Found the ID of only one Fragment ==> Portrait mode FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.contentFrame, secondFragment); fragmentTransaction.addToBackStack(null); fragmentTransaction.commit(); } else { // Landscape mode getSupportFragmentManager().beginTransaction().replace(R.id.secondFrame, secondFragment).commit(); } }
Đây là code đầy đủ của MainActivity.java cho bạn tham khảo.
public class MainActivity extends AppCompatActivity implements FirstFragment.OnFirstFragmentListener { private DrawerLayout drawerLayout; private ActionBarDrawerToggle drawerToggle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = (DrawerLayout) findViewById(R.id.activity_main_drawer); drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawerLayout.addDrawerListener(drawerToggle); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); FirstFragment firstFragment = new FirstFragment(); if (findViewById(R.id.contentFrame) != null) { // Found the ID of only one Fragment ==> Portrait mode // Remove the existing fragment before add new one if (savedInstanceState != null) { getSupportFragmentManager().executePendingTransactions(); Fragment fragmentById = getSupportFragmentManager().findFragmentById(R.id.contentFrame); if (fragmentById != null) { getSupportFragmentManager().beginTransaction().remove(fragmentById).commit(); } } // Add new one getSupportFragmentManager().beginTransaction().add(R.id.contentFrame, firstFragment).commit(); } else { // Landscape mode // Remove the existing fragments before add new one if (savedInstanceState != null) { getSupportFragmentManager().executePendingTransactions(); Fragment firstFragmentById = getSupportFragmentManager().findFragmentById(R.id.firstFrame); if (firstFragmentById != null) { getSupportFragmentManager().beginTransaction().remove(firstFragmentById).commit(); } Fragment secondFragmentById = getSupportFragmentManager().findFragmentById(R.id.secondFrame); if (secondFragmentById != null) { getSupportFragmentManager().beginTransaction().remove(secondFragmentById).commit(); } } // Add new one getSupportFragmentManager().beginTransaction().add(R.id.firstFrame, firstFragment).commit(); } } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. drawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); drawerToggle.onConfigurationChanged(newConfig); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_actions, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if(drawerToggle.onOptionsItemSelected(item)) { return true; } switch (item.getItemId()) { case R.id.search: Toast.makeText(this, "Search button selected", Toast.LENGTH_SHORT).show(); return true; case R.id.about: Intent intent = new Intent(this, ContactActivity.class); Bundle bundle = new Bundle(); bundle.putString(ContactActivity.KEY_SHOW_WHAT, ContactActivity.VALUE_SHOW_ABOUT); intent.putExtras(bundle); startActivity(intent); return true; case R.id.help: intent = new Intent(this, ContactActivity.class); bundle = new Bundle(); bundle.putString(ContactActivity.KEY_SHOW_WHAT, ContactActivity.VALUE_SHOW_HELP); intent.putExtras(bundle); startActivity(intent); return true; } return super.onOptionsItemSelected(item); } @Override public void onItemPressed(String content) { SecondFragment secondFragment = SecondFragment.newInstance(content); if (findViewById(R.id.contentFrame) != null) { // Found the ID of only one Fragment ==> Portrait mode FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.contentFrame, secondFragment); fragmentTransaction.addToBackStack(null); fragmentTransaction.commit(); } else { // Landscape mode getSupportFragmentManager().beginTransaction().replace(R.id.secondFrame, secondFragment).commit(); } } }
Khi này nếu thực thi chương trình, bạn hãy thử nghiệm sự thông minh của ứng dụng khi xoay màn hình thiết bị ngang dọc nhé.
Chúng ta vừa xem qua các kiến thức về việc hiển thị các Fragment lên cùng một Activity theo dạng tĩnh và động. Chúng ta vẫn sẽ cần phải nói nhiều hơn về Fragment ở bài học sau.
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 bên dưới 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
Bài sau chúng ta sẽ nói về vòng đời của Fragment, và chính thức mang Fragment lên TourNote nhé.
mình học tới bài 32 rồi bạn dạy rất hay, mong rằng thời gian tới bạn sẽ làm 1 tập sách như kiểu sách Java bạn đã làm, thân ái !
Cảm ơn bạn, nhất định mình sẽ dành thời gian tạo hết các ebook cho các bài viết của mình, bạn đón xem nhé 😉
Các bài bạn viết thật chi tiết, nhưng bị dừng lại ở bài 36 rồi ạ, mong bạn sớm ra những chủ đề mới
Cho mình hỏi, sao mình thêm đoạn này vào implements FirstFragment.OnFirstFragmentListener là nó báo lỗi thế này: “error: cannot find symbol class OnFirstFragmentListener”
À mình tìm ra lỗi rồi cảm ơn bạn…^^
Bạn có thể giải thích rõ hơn ở phần code: if(saveInstance != null) và executePendingTransactions() được không ạ. mình cảm ơn
Bạn xem câu trả lời của mình với bạn Danh Tran ở bình luận dưới nhé.
Cho mình hỏi là bạn có lưu giá trị vào cho saveInstance đâu mà phải phải kiểm tra nó khác null, với executePendingTransactions() có ý nghĩa gì z. Cảm ơn bạn.
Bạn xem câu trả lời của mình với bạn Danh Tran ở bình luận dưới nhé.
Cho mình hỏi là bạn có lưu giá trị vào cho saveInstance đâu mà phải phải kiểm tra nó khác null, với executePendingTransactions() có ý nghĩa gì z. Cảm ơn bạn.
– Về việc tại sao phải check saveInstanceState != null: Bạn nên biết rằng saveInstanceSate là một Bundle do hệ thống xây dựng ra để chúng ta, và cả hệ thống nữa, tận dụng khi cần lưu giá trị của người dùng, hay lưu trạng thái của một số component của hệ thống. Việc lưu này đôi khi được thực hiện một cách tự động. Một điển hình cho việc lưu này là khi bạn xoay màn hình thiết bị, sau khi xoay xong, nếu có debug, bạn sẽ thấy giá trị saveInstanceState này ở onCreate() khi này sẽ khác null. Do đó việc kiểm tra khác null để gỡ các Fragment đã add trước đó để thêm vào một fragment khác, là mục đích của các dòng code này.
– Về phương thức executePendingTransactions(). Theo mình biết thì sau khi gọi FragmentTransaction.commit() thì hệ thống sẽ đưa transaction này vào hàng đợi, và vì vậy sẽ có độ trễ khi xử lý transaction. Muốn tránh độ trễ này thì mình sẽ gọi executePendingTransactions() để hệ thống xử lý transaction của mình theo kiểu bất đồng bộ luôn. Mà tình huống bài thực hành là chúng ta phải gỡ Fragment cũ ra khỏi Activity càng nhanh càng tốt, trước khi gắn Fragment khác vào, nên chúng ta mới phải sử dụng phương thức này.
Hay quá anh, rất chi tiết nhưng lại simple. Em công nhận sự đầu tư của anh cho những bài viết này là rất lớn. Em rất cảm ơn và sẽ giới thiệu trang của anh đến với bạn bè nhiều hơn. ^^
anh cho em hỏi, em làm giống anh ở phần hiển thị Fragment động.
nhưng ở SecondFragment của e nó không hiển thị lên bất kỳ nội dung nào.
có thể cho em biết có thể rơi vào những nguyên nhân nào mà khiến SecondFragment không hiển thị được không ạ.