Thứ Ba, 14 tháng 3, 2017

Tạo Navigation Drawer - Menu kéo - DrawerLayout

Navigation drawer là một menu kéo hiện thị như một mảng ô để điều hướng ứng dụng ở cạnh biên trái màn hình. Nó luôn ẩn, nhưng nó sẽ xuất hiện khi người dùng vuốt cạnh trái hoặc chạm vào biểu tượng ứng dụng trên thanh công cụ. Giờ tạo một ứng dụng với Menu kéo như vậy, chi chọn menu kéo thì hiện thị ảnh tương ứng với của các hành tinh. Từ Android Studio tạo một project mới với tên project là Planets như hình: tao project android moi Tại bước Add an Active for Mobile chọn Empty Activity. Tiếp tục để mặc định Activity name là MainActivity và Layout Name là activity_main.

Tạo một Drawer Layout

Để thêm một navigation drawer, bạn cần định nghĩa giao diện với một DrawerLayout như là view gốc trước. Sau đó trong DrawerLayout, thêm một view có chứa nội dung chính của layout (Nộ dung này vẫn hiện thi khi drawer ẩn).
Ví dụ tạo layout sử dụng DrawerLayout với 2 view con: một sử dụng FrameLayout để chứa nội dung chính và một sử dụng ListView để tạo ra navigation drawer. Từ các file của Project mở file activity_main và thay phần tử layout gốc thành android.support.v4.widget.DrawerLayout, và đặt id của nó là: drawer_layout. Kết quả như file dưới đây:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</android.support.v4.widget.DrawerLayout>

Tiếp tục thêm 2 view con vào layout trên, một view đầu tiên sử dụng FrameLayout với id là content_frame và view thứ 2 có dùng ListView với id là left_drawer. View đầu để hiện nội dung cửa sổ chính, view 2 chính là Menu drawer. Kết quả file activity_main như sau:

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    tools:openDrawer="start"
    android:layout_height="match_parent">
    <!-- The main content view -->
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- The navigation drawer -->
    <ListView android:id="@+id/left_drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#111"/>
</android.support.v4.widget.DrawerLayout>

Các chú ý quan trọng khi tạo Drawer Layout

  • View hiện thị nội dung chính (như trên là view FrameLayout ) phải xếp ở thứ tự đầu tiên.
  • View hiện thị nội dung chính phải thiết lập rộng cao tương ứng với parent bởi vì nó hiện thị giao diện chính khi navigation drawer ẩn.
  • Drawer phải chỉnh định thuôc canh ngang với android:layout_gravity . Với giá trị strart nó sẽ trượt từ biên trái ra.
  • Drawer phải chỉnh định độ rộng với đơn vị dp và cao tương ứng với view cha. Và chiều rộng không vượt quá 320dp nhằm đảm bảo người dung luôn nhìn thấy nội dung chính.

Khởi tạo cho Drawer

Giờ mở file MainActivity ra, và cập nhật các nội dung sau:
Thêm vào các biến:
    private DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;
    private String[] mPlanetTitles;
Trong Activity, việc đầu tiên bạn cần làm là khởi tạo drawer (ví dụ trên là danh sách). Làm việc này phụ thuộc vào nội dung của ứng dụng, thường thì một navigation thường chứa một ListView. Nếu đã dùng ListView ở đây khởi tạo để hiện thị danh sách tên một số hành tinh.
ListView ở đây các phần tử trong nó được hiện thị là một TextView nên để có mẫu hiện thị cần thiết phải định nghĩa mẫu để ListView sử dụng. Định nghĩa bằng cách tạo một layout có tên là drawer_list_item. Nhấn phải chuột vào thư mục layout chọn New->layout resourece file. Sau đó điền các thông số như hình để tạo:

Sau đó cập nhật nội dung file drawer_list_item.xml như sau:
<?xml version="1.0" encoding="utf-8"?>

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceListItemSmall"
    android:gravity="center_vertical"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:textColor="#fff"
    android:background="?android:attr/activatedBackgroundIndicator"
    android:minHeight="?android:attr/listPreferredItemHeightSmall"
    >
</TextView>
Trở lại MainActivity cập nhật đến thời điểm này như sau:
package com.ichte.planets;

import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {
    private DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;
    private String[] mPlanetTitles;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTitle = mDrawerTitle = getTitle();
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); //Lấy layout của Activity
        mDrawerList = (ListView) findViewById(R.id.left_drawer); //Lấy ListView
        mPlanetTitles = getResources().getStringArray(R.array.planets_array); //Lấy mảng tên Hành tin định nghĩa trong strings.xml

        // Thiết lập nội dung của ListView và lắng nghe sự kiện click
        mDrawerList.setAdapter(new ArrayAdapter<String>(this,
                R.layout.drawer_list_item, mPlanetTitles));
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

    }
}
Gọi hàm setOnItemClickListener() của ListView như trên để nhận sự kiện khi bấm vào một mục trong danh sách. Các sự kiến này được gửi đến và sử lý bởi lớp DrawerItemClickListener.
Đến đây bạn cần định nghĩa lớp DrawerItemClickListener bên trong MainActivity để nó xử lý sự kiện click như sau (thêm vào MainActivity):
/* Lớp lắng nghe sự kiện click trên navigation drawer*/
private class DrawerItemClickListener implements ListView.OnItemClickListener {

@Override
 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  selectItem(position);
 }
}

private void selectItem(int position) {
 //Cập nhật tiêu đề của mục chọ sau đóng đóng Drawer lại
 mDrawerList.setItemChecked(position, true);
 setTitle(mPlanetTitles[position]);
 mDrawerLayout.closeDrawer(mDrawerList);

@Override
public void setTitle(CharSequence title) {
 mTitle = title;
 getSupportActionBar().setTitle(mTitle);
} 
Ở đây hàm selectItem được gọi khi sự kiện click gửi đến, trong hàm này còn cần xử lý sẽ hiện thị ảnh của Planet tương ứng, code này sẽ thêm vào sau.
Giờ cần hiện thị nút bấm trên ActionBar (vì action bar của ứng dụng đang chỉ hiện thị tên ứng dụng) bằng cách thêm vào onCreate của MainActivity:
 getSupportActionBar().setDisplayHomeAsUpEnabled(true);
 getSupportActionBar().setHomeButtonEnabled(true);
Giờ chạy ứng dụng đã có nút Home Up. Nhưng bấm chưa thấy gì!?

Lắng nghe sự kiện Open và Close (khi bấm nút trên Action Bar)

Để lắng nghe sự kiện đóng và mở (khi bấm nút trên ActionBar), gọi hàm setDrawerListener() trong DrawerLayout. Khi gọi hàm này nó sẽ chỉ định đối tượng chuyên xử lý sự kiện đóng/mở. Đối tượng này được dùng ở đây là lớp ActionBarDrawerToggle, vậy bạn cần khởi tạo một đối tượng ActionBarDrawerToggle sau đó định nghĩa hàm callback onDrawerOpened và onDrawerClosed trong nó, rồi dùng nó làm tham số cho setDrawerListener.
Trước tiên bạn thêm dữ liệu sau vào string.xml đã:
<string name="navigation_drawer_open">Mở Navigation Drawer</string>
<string name="navigation_drawer_close">Đóng Navigation Drawer</string>
<string name="drawer_open">Mở navigation drawer</string>
<string name="drawer_close">Đóng navigation drawer</string>
<string name="action_settings">Settings</string>
<string name="action_websearch">Web search</string>
<string name="app_not_available">Sorry, there\'s no web browser available</string>
Đoạn mã thêm vào onCreate của MainActivity như sau
mDrawerToggle = new ActionBarDrawerToggle(
  this,                  /* host Activity */
  mDrawerLayout,         /* DrawerLayout */
  R.string.drawer_open,  /* "open drawer" trợ giúp mô tả */
  R.string.drawer_close  /* "close drawer" trợ giúp mô tả */
);
mDrawerLayout.setDrawerListener(mDrawerToggle);

//Chọn planet mặc định khi ứng dụng chạy
if (savedInstanceState == null) {
 selectItem(0);
};
Đến đây đã lắng nghe được sự kiến khi bấm nút Home trên ActionBar, mỗi khi bấm nút nó sẽ gọi tới hàm onOptionsItemSelected, như vậy cần phải Override hàm này để ẩn hiện Drawer. Thêm vào như sau:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
 if (mDrawerToggle.onOptionsItemSelected(item)) {
  return true;
 }
 return super.onOptionsItemSelected(item);
}
Đến đây có thể chạy ứng dụng kiểm tra đóng/mở của Drawer. Lưu ý khi sử dụng ActionBarDrawerToggle thì bạn phải gọi hàm syncState và hàm onConfigurationChanged của nó trong hàm onPostCreate và onConfigurationChanged để cập nhậ trạng thái bình thường khi sử dụng ứng dụng.
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);        
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Pass any configuration change to the drawer toggls
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

Tạo và sử dụng Fragment hiện thị ảnh mỗi khi chọn Drawer item

Trong thư mục layout tạo một layout có tên fragment_planet sau đó mở file fragment_planet.xml cập nhật thành:
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/image"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:gravity="center"
    android:padding="32dp" />

Sau đó xây dựng class có tên PlanetFragment kế thừa từ Fragment sử dụng layout trên để hiện thị ảnh. PlanetFragment thêm vào MainActivity như sau:
public static class PlanetFragment extends Fragment {
 public static final String ARG_PLANET_NUMBER = "planet_number";

 public PlanetFragment() {
  // Empty constructor required for fragment subclasses
 }

 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
  View rootView = inflater.inflate(R.layout.fragment_planet, container, false);
  int i = getArguments().getInt(ARG_PLANET_NUMBER);
  String planet = getResources().getStringArray(R.array.planets_array)[i];

  int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()),
    "drawable", getActivity().getPackageName());
  ((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId);
  getActivity().setTitle(planet);
  return rootView;
 }
}

Sau đó quay lại hàm selectItem ở trên thêm vào đoạn mã xử lý hiện thị, chèn, thay thế Fragment này mỗi khi chọn menu. Hàm selectItem giờ có nội dung như sau:
    private void selectItem(int position) {

        Fragment fragment = new PlanetFragment();
        Bundle args = new Bundle();
        args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
        fragment.setArguments(args);

        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();

        //Cập nhật tiêu đề của mục chọ sau đóng đóng Drawer lại
        mDrawerList.setItemChecked(position, true);
        setTitle(mPlanetTitles[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

Như vậy mỗi khi chọn Menu thì Fragment lấy tên ảnh tương ứng, imageId để hiện thị. Ảnh này nằm trong folder drawable và có tên tương ứng với tên menu item. Đó là các file: jupiter.jpg, mars.jpg... Bạn có thể copy các file ảnh với tên tương ứng bên ngoài vào drawable để hiện thị.
Copy các ảnh đó ở đây: https://github.com/ichte/Android-Learning/tree/master/res/drawable


Sử dụng DrawerLayout trong Android

DrawerLayout như là một trình chứa cho các cửa sổ nội dung, nó cho phép "kéo" các đối tượng view ra từ biên trái hay phải của cửa sổ.
Vị trí của Drawer (hiểu như các ngăn kéo được kéo và đẩy ra từ cạnh biên của cửa sổ) và layout được điều khiển bằng sử dụng thuộc tính android:layout_gravity trên các view con tương ứng với cạnh mà Drawer muốn kết hợp: trái hay phải. Chú ý bạn chỉ có duy nhất mọt drawer cho mỗi cạnh của cửa sổ.
Để sử dụng DrawerLayout, định vị view chính đầu tiên với rộng và cao có thuộc tính match_parent và không dùng layout_gravity. Thêm các drawer như là view con sau khi view chính và thiết lập layout_gravity được chính xác.
DrawerLayout.DrawerListener được dùng để giám sát trạng thái dịch chuyển của các view.



Danh mục Gửi liên hệ

Loading...