Giải pháp chuyển đổi số trọn gói cho doanh nghiệp Việt
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.
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é.
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.
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.
|
1
2
3
4
5
6
7
8
9
10
11
|
<android.support.v4.widget.DrawerLayout 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.
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.
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.
Đâ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.
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é.
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.
|
1
2
3
4
5
6
7
8
9
10
11
12
|
<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> |
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é.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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.
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à.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<?xml version="1.0" encoding="utf-8"?> 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.
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à đâ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.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
<?xml version="1.0" encoding="utf-8"?> 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" </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á.
Đế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.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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ó.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
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é.
Ở bước thêm thư viện com.android.support:design sẽ có lỗi không tương thích do AndroidX đã hỗ trợ rồi lên có thể bỏ bước này để tránh lỗi.
Ở bước thêm NavigationView sẽ có lỗi ở đoạn (android.support.design.widget.NavigationView) để hết lỗi thì sửa (android.support.design.widget.NavigationView) thành (com.google.android.material.navigation.NavigationView)