Được chỉnh sửa ngày 24/10/2019.
Chào mừng các bạn đã đến với bài học Android số 25, bài học về cách xây dựng Navigation Drawer. Đây là bài học trong chuỗi bài về lập trình ứng dụng Android bằng Java của Yellow Code Books.
Với bài học hôm trước, bạn đã “nâng cấp” một tí cho TourNote thông qua việc tạo cho ứng dụng này một ActionBar. Hôm nay chúng ta lại sẽ nâng cái cấp này lên một tí ti nữa, thông qua việc xây dựng thêm cho nó một Navigation Drawer.
Giới Thiệu Về Navigation Drawer
Có lẽ cái tên Navigation Drawer, hay nhiều bạn vẫn gọi là Left Menu, hoặc Slide Menu, không có gì xa lạ với chúng ta cả. Ai cũng biết về nó, ai cũng sử dụng nó hằng ngày. Navigation Drawer được Google giới thiệu vào năm 2013, ngay sau khi ActionBar được trình làng khoảng 2 năm. Navigation Drawer này chỉ đơn giản là một thanh menu được ẩn đi về phía bên trái màn hình. Nó được hiển thị ra khi người dùng nhấn vào icon menu trên Action Bar. Giới thiết kế gọi cái icon menu này bằng một cái tên khá hay: “icon Hamburger”, bởi vì trông nó giống như một cái hamburger… bạn tưởng tượng đi nào.
Cũng giống như ActionBar, Navigation Drawer mong muốn đem lại cho user một trải nghiệm rõ ràng hơn trên các ứng dụng phức tạp. Khi đó các chức năng chính của ứng dụng dường như được để hết cả lên thanh này (và cả ở ActionBar nữa, nhưng thường thì ActionBar chỉ chứa các chức năng quan trọng và được truy xuất cực kỳ nhiều mà thôi). Và cũng không quá khi nói rằng Navigation Drawer này mới chính thức là “xương sống” rõ ràng nhất cho ứng dụng.
Ngoài các thông tin về Navigation Drawer của mình trên đây, bạn có thể vào link này của Google để đọc một số thông tin hữu ích về mặt thiết kế liên quan đến thành phần này nhé.
Tìm Hiểu Các Layout Và Thành Phần Mới
Nếu như bạn còn nhớ, mình đã dành một bài để nói nhiều nhất có thể các layout được dùng phổ biến trong Android, trong bài viết đó, FrameLayout, LinearLayout, RelativeLayout và TableLayout đã được nhắc đến. Và cuối bài viết, mình có nói rằng là sẽ còn nhiều layout khác liên quan đến các giao diện đặc trưng khác chứ không riêng gì các layout phổ biến này. Và đến bài học hôm nay bạn đã có dịp làm quen với một trong số các layout đặc trưng cho Navigation Drawer. Mình xin giới thiệu, một layout mới mang tên DrawerLayout.
DrawerLayout
Như đã nói, DrawerLayout là một layout đặc biệt, nó chuyên dùng để tạo Navigation Drawer. Layout này nằm trong gói support-v4 và support-v13 để hỗ trợ sự tương thích ngược đến với các hệ điều hành cũ hơn. Nếu bạn thắc mắc tại sao lại có nhiều gói support-vx này thì hãy yên tâm, mình dự định dành một bài viết riêng để nói rõ về các thư viện support này nhé.
Và bởi vì layout này đặc biệt là dùng kèm theo Navigation Drawer, nên nó hầu như chẳng có nhiều nguyên tắc lắm đâu. Việc của bạn chỉ cần khai báo một DrawerLayout ở gốc của một Activity nào đó, rồi khai báo bên trong layout đó hai thành phần con, đại loại như thế này, là được.
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Thành phần con thứ nhất -->
<!-- Thành phần con thứ hai -->
</android.support.v4.widget.DrawerLayout>
Mình xin nói rõ một chút về vai trò của hai thành phần con trong DrawerLayout.
- Thành phần con thứ nhất, chính là giao diện chính của ứng dụng. Một lát nữa khi thực hành xây dựng ứng dụng TourNote, bạn phải thay đổi một chút cấu trúc của màn hình chính TourNote, sao cho trở thành thành phần con thứ nhất này.
- Thành phần con thứ hai, chính là thành phần của left menu. Thành phần này như đã nói, bình thường sẽ bị ẩn đi, đến khi người dùng nhấn vào icon hamburger hoặc vuốt từ bên trái màn hình sang phải, sẽ được hiển thị. Sau đó nếu người dùng nhấn lại vào icon hamburger, hoặc nhấn vào vùng ngoài left menu, hoặc vuốt từ bên phải màn hình sang trái, thì lại ẩn đi.
Chúng ta sẽ hiểu rõ hơn về DrawerLayout và từng thành phần con của nó khi tiến hành xây dựng Navigation Drawer cho TourNote ở bên dưới đây.
Xây Dựng Navigation Drawer
Trước khi đi vào chi tiết cách xây dựng Navigation Drawer, mình xin được nói rõ rằng, sẽ có hai cách để bạn có thể xây dựng thành phần này cho ứng dụng.
Cách thứ nhất, chẳng tốn công sức gì cả, khi bạn tạo mới bất kỳ project Android nào, đến bước chọn một template (bạn có thể xem lại bài 3 để hiểu các khái niệm liên quan đến tạo mới một project Android), thì bạn cứ chọn Navigation Drawer Activity như hình dưới. Tùy chọn này giúp bạn tạo một ứng dụng có cả Navigation Drawer lẫn Floating Action Button. Thật là đơn giản.
Cách thứ hai, là cách bạn phải bỏ công sức ra xây dựng từng bước như dưới đây chúng ta sẽ làm với TourNote. Mình thích cách trên kia, nhưng với các bài học của mình thì mình muốn cùng các bạn đi theo hướng gian khổ ở cách này. Bởi vì sau khi xây dựng thủ công mọi thứ, chắc chắn các bạn sẽ hiểu rõ hơn về thành phần giao diện này, và các kiến thức liên quan. Sau đó, tự bạn có thể tùy chỉnh, thêm thắt vài thứ vào giao diện một cách dễ dàng hơn.
Thực Hành Xây Dựng Navigation Drawer Cho TourNote
Đây là thiết kế ban đầu về Navigation Drawer của TourNote.
Như những gì bạn đã đọc qua trên đây, chắc bạn cũng đã hình dung ra các bước mà bài thực hành này sẽ xây dựng. Bao gồm, thay đổi activity_main.xml sao cho layout gốc chính là DrawerLayout nè, rồi xây dựng các thành phần bên trong DrawerLayout nè, và cuối cùng là xây dựng các Java code liên quan nè. Chúng ta cùng vào chi tiết.
Download Resource
Do có xuất hiện thêm một vài icon nữa, bao gồm icon hình dấu chấm than và chấm hỏi, nên bạn có thể vào Android Asset Studio để tự tìm hai icon này.
Hoặc bạn có thể vào link này để down file zip về rồi giải nén và để các ảnh vừa down vào các alternative resource tương ứng nhé.
Thêm Một Số Thông Số Vào dimens.xml
Chúng ta sẽ tuân thủ các yêu cầu về mặt thiết kế ở link này của Google. Nên bước này chúng ta nên định nghĩa thêm một số kích thước cho Navigation Drawer vào file dimens.xml, bạn chú ý các dòng được tô sáng.
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<!-- Main Activity components -->
<dimen name="empty_icon_width">60dp</dimen>
<dimen name="empty_icon_height">60dp</dimen>
<dimen name="navigation_header_height">160dp</dimen>
<dimen name="navigation_item_height">48dp</dimen>
<dimen name="navigation_item_icon_size">24dp</dimen>
</resources>
Khai Báo Thêm Thư Viện Trong build.gradle
Chúng ta khoan hãy bắt tay vào xây dựng Navigation Drawer ngay, vì nếu bắt tay vào xây dựng, chắc chắn sẽ có báo lỗi xảy ra với project, là do các thành phần trong Navigation Drawer ở các bước sau đòi hỏi project phải có thư viện com.android.support:design bên trong nó.
Chắc hẳn bạn sẽ không hoang mang và hiểu rõ những gì mình nói trên đây nếu đã đọc qua bài 11 của mình. Nếu đã đọc và hiểu rồi, thì bạn hãy mở build.gradle ở cấp độ module lên, và thêm vào dòng được tô sáng sau vào khối dependencies của file này nhé.
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.yellowcode.tournote"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:design:28.0.0'
}
Bạn nên sync lại project sau khi thêm vào dòng trên kia, rồi hẵn qua bước tiếp theo bên dưới.
Xây Dựng DrawerLayout
Nào chúng ta bắt đầu xây dựng Navigation Drawer, bằng cách xây dựng Drawer Layout.
Chúng ta cùng mở lại giao diện chính của TourNote ra, chính là file activity_main.xml. Tại đây, như các bài thực hành từ trước giờ, thẻ gốc của file này vẫn là ConstraintLayout.
Với thay đổi ở bước này, bạn chỉ chuyển toàn bộ giao diện gốc của TourNote này thành thành phần thứ nhất. Như vậy giao diện gốc xưa kia giờ đây nằm trong một thẻ có tên android.support.v4.widget.DrawerLayout. Bạn chú ý ghi đầy đủ đường dẫn đến gói support.v4.widget luôn nhé, vì đây là layout được lấy từ thư viện support như mình có nói đến trên kia mà.
<?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 -->
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/activity_main_tv_empty"
style="@style/InformationTextView"
android:text="@string/mainscreen_empty_note"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/empty_icon_width"
android:layout_height="@dimen/empty_icon_height"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/activity_main_tv_empty"
app:srcCompat="@drawable/empty_note" />
</android.support.constraint.ConstraintLayout>
</android.support.v4.widget.DrawerLayout>
Bạn khoan run ứng dụng lên vội, vì chưa có thay đổi gì đâu.
Xây Dựng Thành Phần Con Thứ Hai
Bạn đã biết thành phần con thứ nhất trong DrawerLayout chính là ConstraintLayout cũ được chúng ta nhét vào trong DrawerLayout rồi đúng không nào. Vậy nên bước này chúng ta chỉ cần xây dựng thành phần con thứ hai, chính là giao diện cho left menu. Dưới đây là một vài ý quan trọng cho thành phần này.
- Với layout ở thành phần con thứ hai này, bạn xây dựng chúng bằng bất cứ layout nào cũng được. Nhưng mình khuyên bạn nên dùng NavigationView. Đây cũng là một layout được chuyên dùng trong Navigation Drawer, nó giúp định nghĩa ra một menu layout chuẩn, với các màu sắc và kích cỡ mặc định, và khi sử dụng nó, bạn không cần phải lo lắng canh chỉnh chiều rộng của left menu này như thế nào.
- Bởi vì NavigationView là con cháu của FrameLayout nên nó không giỏi lắm trong việc sắp xếp các thành phần UI con vào trong nó. Tốt nhất bạn nên xây dựng thêm một layout linh động hơn vào con của NavigationView này. Như code dưới đây mình thêm LinearLayout.
- Dù bạn có dùng layout nào cho thành phần này, cũng đừng quên khai báo thuộc tính android:layout_gravity=”start”, nó giúp neo layout này vào bên trái của thành phần con thứ nhất trên kia.
Và đây là code của toàn bộ giao diện activity_main.xml. Code mới ở mục này được thêm vào như những dòng được tô sáng dưới đây.
<?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 -->
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/activity_main_tv_empty"
style="@style/InformationTextView"
android:text="@string/mainscreen_empty_note"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/empty_icon_width"
android:layout_height="@dimen/empty_icon_height"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/activity_main_tv_empty"
app:srcCompat="@drawable/empty_note" />
</android.support.constraint.ConstraintLayout>
<!-- Left Menu -->
<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="3dp"
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="32dp"
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="32dp"
android:text="@string/menu_item_help" />
</LinearLayout>
</LinearLayout>
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>
Chú ý rằng bên trong NavigationView này mình đã lồng vào một LinearLayout với mục đích như đã nói đến trên đây. Bên trong LinearLayout này mình xây dựng một header để hiển thị thông tin người dùng. Phía dưới header là hai item của left menu, đó là item info và item help. Hai item này tốt nhất nên nằm trong một ListView, nhưng vì chúng ta chưa được học qua ListView, nên mình chỉ xây dựng tạm như thế này để nhìn cho đẹp mắt thôi chứ chưa có logic gì cho việc click lên ẻm đâu nhá.
Chỉnh Sửa MainActivity.java
Đến bước này, nếu bạn thực thi chương trình, cũng sẽ không có bất cứ giao diện cho left menu nào xuất hiện đâu nhé. Thực chất chúng đã nằm sẵn ở bên tay trái màn hình thiết bị nhờ vào việc sắp xếp của DrawerLayout. Việc tiếp theo của chúng ta là xây dựng một số hành động bên MainActivity.java nữa để menu này có cơ hội được xuất hiện.
Thứ nhất, bạn phải đảm bảo cho icon hamburger xuất hiện trên ActionBar bằng hai dòng lệnh sau.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.search:
Toast.makeText(this, "Search button selected", Toast.LENGTH_SHORT).show();
return true;
case R.id.about:
Toast.makeText(this, "About button selected", Toast.LENGTH_SHORT).show();
return true;
case R.id.help:
Toast.makeText(this, "Help button selected", Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
}
Sau đó, dĩ nhiên rồi, bạn khai báo DrawerLayout trong Java code, để thiết lập vài logic cho nó.
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawerLayout = findViewById(R.id.activity_main_drawer);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.search:
Toast.makeText(this, "Search button selected", Toast.LENGTH_SHORT).show();
return true;
case R.id.about:
Toast.makeText(this, "About button selected", Toast.LENGTH_SHORT).show();
return true;
case R.id.help:
Toast.makeText(this, "Help button selected", Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
}
Cuối cùng, chúng ta lại dùng đến một thành phần có tên là ActionBarDrawerToggle. Thành phần này được gắn vào ActionBar, làm nhiệm vụ điều khiển việc đóng mở DrawerLayout cho chúng ta. Sau đây là các đoạn code khai báo, gắn vào ActionBar, và đồng bộ thành phần ActionBarDrawerToggle này với Activity. Đoạn code khai báo sau cần sự định nghĩa thêm hai resource string, đó là navigation_drawer_open và navigation_drawer_close, bạn có thể xem thêm định nghĩa string này ở link project ở cuối bài học hôm nay.
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
private ActionBarDrawerToggle drawerToggle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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);
}
@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:
Toast.makeText(this, "About button selected", Toast.LENGTH_SHORT).show();
return true;
case R.id.help:
Toast.makeText(this, "Help button selected", Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
}
Giờ thì bạn có thể thực thi ứng dụng lên xem được rồi nhé.
Download Source Code Mẫu
Bạn có thể download source code mẫu của bài này ở đây.
Vậy là chúng ta vừa xem xong cách thức xây dựng một Navigation Drawer cho TourNote. Bạn đừng nên học thuộc lòng các bước xây dựng giao diện trong bài hôm nay. Nội dung bài học được mình tham khảo ở link gốc này của Google, và nó cũng không hoàn toàn khớp với link gốc, nó dựa trên sự tham khảo và tổng hợp từ nhiều nguồn khác nhau. Quan trọng là bạn hiểu các thành phần và tìm cách kết hợp chúng với nhau là được rồi.
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
Trước giờ chúng ta đã làm quen nhiều cách thao tác ở MainActivity.java. Và bạn cũng đã biết một Activity như vậy chịu trách nhiệm hiển thị một giao diện lên màn hình thiết bị rồi đúng không nào. Vậy thì còn việc hiển thị nhiều màn hình trong một ứng dụng thì sao. Và còn bất kỳ thông tin nào khác liên quan đến khái niệm Activity hay không. Bài sau chúng ta cùng tìm hiểu qua nhé.
Cảm ơn Ad đã post bài tiếp, mong tiếp tục được xem các bài viết của Ad
Bai nay khi minh an vao cac item tren menu sao phai viet ham` gi de no chay
Bạn Tuấn ơi. Code này của mình chưa tốt, nên mình chưa làm gì với sự kiện nhấn trên item left menu cả. Tuy nhiên bạn vẫn có thể set click listener lên từng widget hoặc layout để bắt sự kiện nhấn của user (bạn có thể xem việc bắt sự kiện click ở đây https://yellowcodebooks.com/2016/11/08/tim-hieu-cac-widget-co-ban/#Nhan_Su_Kien_Qua_Viec_Lang_Nghe_OnClickListener) rồi khi user nhấn vào đó bạn có thể mở Activity khác chẳng hạn (bạn xem ở bài 26 về thao tác với Activity). Nhưng như mình có nói, sau này khi xây dựng left menu là ListView sẽ dễ thao tác hơn, và mình dành sự nâng cấp này cho các bài sau. Nếu bạn có thắc mắc gì thêm thì có thể gởi mail cho mình nhé (yellowcode.books@gmail.com).
A cho e hỏi e làm Switch trong DrawerLayout. Nhưng Switch đó nằm trong 1 item (activelayout = switch). Lúc làm sự kiện thì chỉ find id đc của cái item chứa Switch đó. Bây giờ e muốn tìm đến cái switch đó để viết sự kiện khi thay đổi trạng thái thì phải làm thế nào ạ? (Sự kiện cho item chứa switch đó thì làm vẫn ok ạ, nhưng chưa làm đc sự kiện khi switch change trạng thái
)
Chào bạn, thực sự mình đã thử xem vấn đề của bạn là gì nhưng vẫn chưa thực sự hiểu lắm, bạn có thể liên lạc với mình qua chat trên facebook Yellow Code Books để mình dễ xác định vấn đề hơn nhé.
Bạn cho mình hỏi trong cái ActionBarDrawerToggle(….) co 2 thông số là string open và close,mình chưa hiểu chổ string đó có tác dụng ji
Well, cái vụ này, thực ra mình chưa được biết rõ tường tận. Nhưng theo mình đã từng tìm hiểu thì, một số component của Android mà user không thể đọc được, như Image, và bây giờ bạn biết thêm có việc open và close của Navigation Drawer. Các component này đòi hỏi các developer chúng ta phải định nghĩa description cho nó. Để làm chi? Nó có thể dùng trong một số chức năng muốn hỗ trợ các người dùng đặc biệt, một trong những chức năng đó là text-to-speech, tức là hệ điều hành sẽ đọc to lên đoạn text mà developer chúng ta đã định nghĩa.
Cho mình hỏi đoạn code trong method onPostCreate và onConfigurationChanged có tác dụng gì vậy? Và khi nào thì 2 method đó được gọi ạ?
Hai phương thức override onPostCreate() và onConfigurationChanged() ở bài học hôm nay là do mình copy từ đoạn code mẫu trên trang Android Developer. Tuy nhiên chúng ta cũng nên hiểu là onPostCreate() thì thường dùng cho hệ thống hơn, ở đây chúng ta override phương thức này để gọi phương thức đồng bộ của drawerToggle ngay khi Activity được tạo xong, một yêu cầu của Navigation Drawer mà chúng ta cần tuân thủ thôi. Còn onConfigurationChanged() được gọi khi Activity được reset lại (hủy và tạo lại ngay bởi hệ thống), và chúng ta cũng code bên trong phương thức này theo yêu cầu của Navigation Drawer luôn, với mong muốn được cập nhật trạng thái của nó. onPostCreate() thì mình sẽ không bao giờ nói đến, còn với onConfigurationChange() thì mình sẽ có bài viết nói rõ hơn về nó nhé.
Cho mình hỏi, sao mình copy các đoạn code vào sau đó Run nó trên máy áo thì cứ thông báo “Tournote has stop” không mở ứng dụng được…
Bạn có thể xem logcat để biết báo lỗi thế nào nhé. Bạn có thể copy lỗi đó lên đây. Nhưng mình đoán có thể MainActivity của bạn không kế thừa từ AppCompatActivity mà kế thừa từ Activity luôn, nên các phương thức getSupportActionBar() gây ra crash.
Cảm ơn bạn đã phản hồi. Mỗi khi thêm thẻ này vào thì “run” chương trình nó báo 1 dọc lỗi mình nhìn vào không hiểu:
07-18 20:37:51.803 12491-12491/vn.test.test E/AndroidRuntime: FATAL EXCEPTION: main
Process: vn.test.test, PID: 12491
java.lang.RuntimeException: Unable to start activity ComponentInfo{vn.test.test/vn.test.test.MainActivity}: android.view.InflateException: Binary XML file line #38: Binary XML file line #38: Error inflating class android.support.design.widget.NavigationView
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: android.view.InflateException: Binary XML file line #38: Binary XML file line #38: Error inflating class android.support.design.widget.NavigationView
Caused by: android.view.InflateException: Binary XML file line #38: Error inflating class android.support.design.widget.NavigationView
Caused by: java.lang.ClassNotFoundException: Didn’t find class “android.support.design.widget.NavigationView” on path: DexPathList[[zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/base.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_dependencies_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_resources_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_0_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_1_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_2_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_3_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_4_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_5_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_6_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_7_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_8_apk.apk”, zip file “/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_slice_9_apk.apk”],nativeLibraryDirectories=[/data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/lib/x86, /system/lib, /vendor/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:125)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at android.view.LayoutInflater.createView(LayoutInflater.java:606)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:790)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
at android.support.v7.app.AppCompatDelegateImplV9.setContentView(AppCompatDelegateImplV9.java:287)
at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:139)
at vn.test.test.MainActivity.onCreate(MainActivity.java:24)
at android.app.Activity.performCreate(Activity.java:7009)
at android.app.Activity.performCreate(Activity.java:7000)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
07-18 20:37:51.805 12491-12491/vn.test.test E/AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Suppressed: java.io.IOException: No original dex files found for dex location /data/app/vn.test.test-cgz4lg_A-s04KwjAyUYyoQ==/split_lib_resources_apk.apk
at dalvik.system.DexFile.openDexFileNative(Native Method)
at dalvik.system.DexFile.openDexFile(DexFile.java:353)
at dalvik.system.DexFile.(DexFile.java:100)
at dalvik.system.DexFile.(DexFile.java:74)
at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374)
at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337)
at dalvik.system.DexPathList.(DexPathList.java:157)
at dalvik.system.BaseDexClassLoader.(BaseDexClassLoader.java:65)
at dalvik.system.PathClassLoader.(PathClassLoader.java:64)
at com.android.internal.os.ClassLoaderFactory.createClassLoader(ClassLoaderFactory.java:73)
at com.android.internal.os.ClassLoaderFactory.createClassLoader(ClassLoaderFactory.java:88)
at android.app.ApplicationLoaders.getClassLoader(ApplicationLoaders.java:69)
at android.app.ApplicationLoaders.getClassLoader(ApplicationLoaders.java:35)
at android.app.LoadedApk.createOrUpdateClassLoaderLocked(LoadedApk.java:693)
at android.app.LoadedApk.getClassLoader(LoadedApk.java:727)
at android.app.LoadedApk.getResources(LoadedApk.java:954)
at android.app.ContextImpl.createAppContext(ContextImpl.java:2270)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5639)
at android.app.ActivityThread.-wrap1(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1656)
… 6 more
Thẻ mình nói khi thêm vào bị lỗi là: android.support.design.widget.NavigationView.
Ok cảm ơn bạn… mình đã sửa được!
mình cũng bị lỗi như bạn, bạn sửa ntn vậy ạ ?
bạn kiểm tra xem đã add thư viện support design vào project chưa nhé. vì các thư viện mạc định không hỗ trợ NavigationView.
Tks bạn. Mong bạn tiếp tục viết những bài viết ý nghĩa và hay như này <3
(like)
Chào anh, em hiện tại đang thực hành tạo Navigation Drawer, trong đó anh có đề cập code Left Menu ngay bên dưới đoạn code android.support.v4.widget.DrawerLayout. Nhưng khi em thực hành, thì báo lỗi Multible Root Tags. Có cách nào sửa không anh, có cần tạo một cái layout xml khác không anh?
À, em tìm ra lỗi rùi. Phải đặt left Menu vào trong thẻ android.support.v4.widget.DrawerLayout. Chứ không phải đặt ở cuối code. Đáng lẽ ra em nên coi kỹ hơn, xin lỗi vì em hơi mất bình tĩnh
Em có gửi hình qua Skype của anh. Em nghĩ như thế sẽ dễ hiểu hơn
Em chào anh. Anh có thể làm một bài hướng dẫn về hamburger icon sẽ thay đổi thành back arrow khi đi vào những fragment nằm trong fragment lớn được không ạ. E cảm ơn
Em bị lỗi như này trên Android Studio
Binary XML file line #2: Error inflating class android.support.v4.widget.DrawerLayout
mà em search mạng mãi người ta bảo vào là như này
Right click on Project
Build Path -> Configure Build Path
Tab: Order and Export
Make sure your Android and Android Dependencies libraries are checked
Clean & Build your Project
Cơ mà em chả thấy cái build path chỗ nào luôn. ai giúp em với
Bạn chẳng cần phải mở build path gì đó xa xôi, cứ tìm file build.gradle (cấp độ module) và check xem có xuất khai báo đủ các thư viện trong khối dependencied chưa là dc. Như các khai báo sau của mình.
dependencies {
implementation fileTree(dir: ‘libs’, include: [‘*.jar’])
implementation ‘com.android.support:appcompat-v7:28.0.0’
implementation ‘com.android.support.constraint:constraint-layout:1.1.3’
testImplementation ‘junit:junit:4.12’
androidTestImplementation ‘com.android.support.test:runner:1.0.2’
androidTestImplementation ‘com.android.support.test.espresso:espresso-core:3.0.2’
}
ad ơi, cho e hỏi ngơ 1 câu là cái nào là gói android.support.v4.widget v ạ?
Cái hàm onConfigurationChanged(Configuration newConfig) dùng để làm gì ad nhỉ?
onConfigurationChanged() được gọi khi Activity bị hủy và khởi tạo lại. Một trong số tình huống có thể gọi đến phương thức này là khi user xoay màn hình. Khi đó Activity gần như reset lại và phương thức này có thể được gọi đến. Nên trong code ví dụ của mình có khai báo phương thức này để khi hệ thống gọi đến sẽ tiến hành nói drawerToggle biết mà xử lý các tác vụ của riêng nó.
Chào anh!
Em làm theo hướng dẫn và đã làm được nhưng khi em tạo các activity khác extends activity có NavigationView (em làm ở MainActivity) nó không gọi NavigationView được khi click vào biểu tượng menu ở góc trên bên trái mặt dù các menu góc trên bên phải thì chạy tốt
Hi, bạn. Mình test vài lần rồi, có mấy thắc mắc sau bạn biết thì cùng trao đổi nha:
– Mình thấy cái NavigationView, thanh kéo ra bên trái ấy, có lúc nó tràn lên cả trên, nhung có đôi lần nó lại không tràn được lên trên thanh Toolbar, bạn có biết nguyên nhân là vì sao không? Mình cứ tưởng là do thuộc tính, nhưng mình thấy có lần được, có lần mình set không được. Vậy android:fitsSystemWindows=”true” phải set ở đâu là chắc chắn?
– Ngoài ra khi trên androidx, thư viên mới này ấy, mình thấy cái icon Humbuger nó không chuyển thành mũi tên khi được nhấn vào nó nữa, mà nó cứ icon humbeger mãi thôi.
Bạn biết vì sao hay có sự thay đổi gì mới không?
Thanks!
Chào Admin và cộng đồng đang học lập trình Android!
Đúng thực sự là công nghệ đang ngày càng phát triển nhanh và các thư viện Android hỗ trợ cũng phát triển hơn. Mình đã đọc và làm theo code như trên của Ad nhưng đã xảy ra một số lỗi do nó không còn hỗ trợ các thư viện như Ad đưa ra và sử dụng ở trên. Nên mình sẽ góp ý và bổ sung cho phù hợp (chỉ bổ sung ở phần thực hành với app TourNote)
1. Đối với khi Khai Báo Thêm Thư Viện Trong build.gradle
– Nếu như khai báo thêm giống Ad với thư viện support thì tại thời điểm viết cmt này thì Android đã khuyến khích nên chuyển qua dùng thư viện AndroidX
– Khi mình thêm thì đã gặp lỗi: Version 28 (intended for Android Pie and below) is the last version of the legacy support library, so we recommend that you migrate to AndroidX libraries when using Android Q and moving forward. Và bài viết mình tìm được để fix: https://stackoverflow.com/questions/50782435/android-design-support-library-for-api-28-p-not-working
– Cách khắc phục: thay vì implementation như trên ta thay bằng
implementation ‘androidx.cardview:cardview:1.0.0’
– Sau đó sync lại project
2. Xây Dựng DrawerLayout
– Như đã nói thì Android đã không còn hỗ trợ thư viện support nên thay vì dùng android.support.v4.widget.DrawerLayout ta thay bằng androidx.drawerlayout.widget.DrawerLayout
– android.support.constraint.ConstraintLayout thay bằng androidx.constraintlayout.widget.ConstraintLayout
– android.support.design.widget.NavigationView thay bằng com.google.android.material.navigation.NavigationView
3. Chỉnh Sửa MainActivity.java
– Cũng thay vì import
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
Thì sẽ thay bằng
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.drawerlayout.widget.DrawerLayout;
Trên đây là góp ý của mình, có gì sai sót mong mọi người đóng góp