Thêm thực đơn

Thử cách Compose
Jetpack Compose là bộ công cụ giao diện người dùng được đề xuất cho Android. Tìm hiểu cách thêm các thành phần trong Compose.

Trình đơn là một thành phần giao diện người dùng phổ biến trong nhiều loại ứng dụng. Để mang đến trải nghiệm quen thuộc và nhất quán cho người dùng, hãy sử dụng API Menu để trình bày các thao tác của người dùng và các lựa chọn khác trong hoạt động của bạn.

Hình ảnh minh hoạ ví dụ về trình đơn tràn
Hình 1. Một trình đơn được kích hoạt bằng thao tác nhấn vào biểu tượng, xuất hiện bên dưới biểu tượng trình đơn mục bổ sung.

Tài liệu này hướng dẫn cách tạo 3 loại trình đơn hoặc bản trình bày thao tác cơ bản trên mọi phiên bản Android:

Trình đơn tùy chọn và thanh ứng dụng
Trình đơn tuỳ chọn là tập hợp chính các mục trong trình đơn cho một hoạt động. Đây là nơi bạn đặt các thao tác có tác động chung đến ứng dụng, chẳng hạn như "Tìm kiếm", "Soạn email" và "Cài đặt".

Xem phần Tạo trình đơn tuỳ chọn.

Trình đơn theo bối cảnh và chế độ thao tác theo bối cảnh
Trình đơn theo bối cảnh là một trình đơn nổi xuất hiện khi người dùng thực hiện thao tác chạm và giữ một phần tử. API này cung cấp các thao tác ảnh hưởng đến nội dung hoặc khung ngữ cảnh đã chọn.

Chế độ thao tác theo ngữ cảnh hiển thị các mục hành động ảnh hưởng đến nội dung đã chọn trong một thanh ở đầu màn hình và cho phép người dùng chọn nhiều mục.

Xem phần Tạo trình đơn theo bối cảnh.

Trình đơn bật lên
Trình đơn bật lên hiển thị danh sách các mục theo chiều dọc được liên kết với khung hiển thị gọi trình đơn. Bạn nên cung cấp nhiều hành động liên quan đến nội dung cụ thể hoặc cung cấp các lựa chọn cho phần thứ hai của một lệnh. Các thao tác trong trình đơn bật lên không ảnh hưởng trực tiếp đến nội dung tương ứng, đó cũng là mục đích của tính năng thao tác theo ngữ cảnh. Thay vào đó, trình đơn bật lên dành cho các thao tác mở rộng có liên quan đến các khu vực nội dung trong hoạt động của bạn.

Xem phần Tạo trình đơn bật lên.

Xác định trình đơn trong XML

Đối với mọi loại trình đơn, Android đều cung cấp một định dạng XML chuẩn để xác định các mục trong trình đơn. Thay vì tạo một trình đơn trong mã hoạt động, hãy xác định một trình đơn và tất cả các mục thuộc trình đơn đó trong tài nguyên trình đơn XML. Sau đó, bạn có thể tăng cường tài nguyên trình đơn (tải tài nguyên này dưới dạng một đối tượng Menu) trong phân mảnh hoặc hoạt động của bạn.

Việc sử dụng tài nguyên trình đơn là một phương pháp hay vì những lý do sau:

  • Dễ dàng hình ảnh hóa cấu trúc trình đơn ở định dạng XML hơn.
  • Thao tác này tách nội dung trình đơn khỏi mã hành vi của ứng dụng.
  • Công cụ này cho phép bạn tạo cấu hình của trình đơn thay thế cho các phiên bản nền tảng, kích thước màn hình và các cấu hình khác bằng cách tận dụng khung tài nguyên ứng dụng.

Để xác định một trình đơn, hãy tạo một tệp XML bên trong thư mục res/menu/ của dự án và tạo trình đơn bằng các phần tử sau:

<menu>
Xác định Menu, là một vùng chứa các mục trong trình đơn. Phần tử <menu> phải là nút gốc của tệp và có thể chứa một hoặc nhiều phần tử <item><group>.
<item>
Tạo MenuItem, đại diện cho một mục duy nhất trong trình đơn. Phần tử này có thể chứa phần tử <menu> lồng nhau để tạo một trình đơn phụ.
<group>
Một vùng chứa không bắt buộc, không hiển thị cho các phần tử <item>. Công cụ này cho phép bạn phân loại các mục trong trình đơn để chúng có chung những thuộc tính, chẳng hạn như trạng thái hoạt động và khả năng hiển thị. Để biết thêm thông tin, hãy xem phần Tạo nhóm trình đơn.

Trình đơn mẫu có tên là game_menu.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/new_game"
          android:icon="@drawable/ic_new_game"
          android:title="@string/new_game"
          app:showAsAction="ifRoom"/>
    <item android:id="@+id/help"
          android:icon="@drawable/ic_help"
          android:title="@string/help" />
</menu>

Phần tử <item> hỗ trợ một số thuộc tính bạn có thể dùng để xác định giao diện và hành vi của một mục. Các mục trong trình đơn trước đó bao gồm các thuộc tính sau:

android:id
Mã tài nguyên dành riêng cho mục. Mã này cho phép ứng dụng nhận ra khi người dùng chọn mục đó.
android:icon
Tham chiếu đến đối tượng có thể vẽ để sử dụng làm biểu tượng của mục.
android:title
Tham chiếu đến một chuỗi dùng làm tiêu đề mục.
android:showAsAction
Quy cách về thời điểm và cách thức mục này xuất hiện dưới dạng một mục hành động trên thanh ứng dụng.

Đây là những thuộc tính quan trọng nhất mà bạn sử dụng, nhưng còn có nhiều thuộc tính khác. Để biết thông tin về tất cả các thuộc tính được hỗ trợ, hãy xem tài liệu Tài nguyên trình đơn.

Bạn có thể thêm một trình đơn phụ vào một mục trong bất kỳ trình đơn nào bằng cách thêm phần tử <menu> làm phần tử con của <item>. Trình đơn phụ hữu ích khi ứng dụng của bạn có nhiều chức năng có thể được sắp xếp thành các chủ đề, chẳng hạn như các mục trên thanh trình đơn của ứng dụng trên máy tính (như Tệp, Chỉnh sửaXem). Hãy xem ví dụ sau:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/file"
          android:title="@string/file" >
        <!-- "file" submenu -->
        <menu>
            <item android:id="@+id/create_new"
                  android:title="@string/create_new" />
            <item android:id="@+id/open"
                  android:title="@string/open" />
        </menu>
    </item>
</menu>

Để sử dụng trình đơn trong hoạt động, hãy _tăng cường_ tài nguyên trình đơn, chuyển đổi tài nguyên XML thành một đối tượng có thể lập trình bằng cách sử dụng MenuInflater.inflate(). Các phần sau đây cho biết cách mở rộng một trình đơn cho từng loại trình đơn.

Tạo trình đơn tuỳ chọn

Trình đơn tuỳ chọn (như trình đơn trong hình 1) là nơi bạn đưa vào các thao tác và tuỳ chọn khác có liên quan đến ngữ cảnh hoạt động hiện tại, chẳng hạn như "Tìm kiếm", "Soạn email" và "Cài đặt".

Hình ảnh minh hoạ thanh ứng dụng của ứng dụng Google Trang tính
Hình 2. Ứng dụng Google Trang tính, hiển thị một số nút, bao gồm cả nút mục bổ sung thao tác.

Bạn có thể khai báo các mục cho trình đơn tuỳ chọn từ lớp con Activity hoặc lớp con Fragment. Nếu cả hoạt động và phân mảnh đều khai báo các mục cho trình đơn tuỳ chọn, thì các mục sẽ được kết hợp trong giao diện người dùng. Các mục của hoạt động sẽ xuất hiện trước, sau đó là các mục của từng phân mảnh, theo thứ tự mà các phân mảnh được thêm vào hoạt động. Nếu cần, bạn có thể sắp xếp lại các mục trong trình đơn bằng thuộc tính android:orderInCategory trong mỗi <item> cần di chuyển.

Để chỉ định trình đơn tùy chọn cho một Hoạt động, hãy ghi đè onCreateOptionsMenu(). Các phân mảnh cung cấp lệnh gọi lại onCreateOptionsMenu() riêng. Trong phương thức này, bạn có thể tăng cường tài nguyên trình đơn của mình (được xác định trong XML) vào Menu được cung cấp trong lệnh gọi lại. Lệnh này được minh hoạ trong ví dụ sau:

Kotlin

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    val inflater: MenuInflater = menuInflater
    inflater.inflate(R.menu.game_menu, menu)
    return true
}

Java

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.game_menu, menu);
    return true;
}

Bạn cũng có thể thêm các mục trong trình đơn bằng cách sử dụng add() và truy xuất các mục bằng findItem() để sửa đổi thuộc tính của các mục đó bằng API MenuItem.

Xử lý các sự kiện nhấp chuột

Khi người dùng chọn một mục trong trình đơn tùy chọn, bao gồm cả các mục hành động trong thanh ứng dụng, hệ thống sẽ gọi phương thức onOptionsItemSelected() của hoạt động. Phương thức này truyền tham số MenuItem đã chọn. Bạn có thể xác định mục bằng cách gọi getItemId(). Lệnh này trả về mã nhận dạng duy nhất cho mục trong trình đơn, được xác định bằng thuộc tính android:id trong tài nguyên trình đơn hoặc bằng một số nguyên được cấp cho phương thức add(). Bạn có thể so sánh đối chiếu mã này với các mục trong trình đơn đã biết để thực hiện thao tác thích hợp.

Kotlin

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    // Handle item selection.
    return when (item.itemId) {
        R.id.new_game -> {
            newGame()
            true
        }
        R.id.help -> {
            showHelp()
            true
        }
        else -> super.onOptionsItemSelected(item)
    }
}

Java

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle item selection.
    switch (item.getItemId()) {
        case R.id.new_game:
            newGame();
            return true;
        case R.id.help:
            showHelp();
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

Khi xử lý thành công một mục trong trình đơn, hãy trả lại true. Nếu không xử lý mục trong trình đơn, hãy gọi quy trình triển khai lớp cao cấp của onOptionsItemSelected(). Cách triển khai mặc định sẽ trả về giá trị false.

Nếu hoạt động của bạn có chứa các phân mảnh, trước tiên, hệ thống sẽ gọi onOptionsItemSelected() cho hoạt động đó, sau đó gọi cho từng phân mảnh theo thứ tự thêm các phân mảnh, cho đến khi một phân mảnh trả về true hoặc tất cả các phân mảnh đều được gọi.

Thay đổi các mục trong trình đơn trong thời gian chạy

Sau khi gọi onCreateOptionsMenu(), hệ thống sẽ giữ lại một thực thể của Menu mà bạn điền và không gọi lại onCreateOptionsMenu() trừ phi trình đơn bị vô hiệu hoá. Tuy nhiên, chỉ nên sử dụng onCreateOptionsMenu() để tạo trạng thái trình đơn ban đầu và không thực hiện thay đổi trong vòng đời của activity.

Nếu muốn sửa đổi trình đơn tuỳ chọn dựa trên các sự kiện xảy ra trong vòng đời của hoạt động, bạn có thể thao tác như vậy trong phương thức onPrepareOptionsMenu(). Phương thức này chuyển đối tượng Menu vì đối tượng này hiện đang tồn tại để bạn có thể sửa đổi, chẳng hạn như thêm, xoá hoặc vô hiệu hoá các mục. Các mảnh cũng cung cấp một lệnh gọi lại onPrepareOptionsMenu().

Trình đơn tùy chọn luôn mở khi các mục trong trình đơn xuất hiện ở thanh ứng dụng. Khi một sự kiện xảy ra và bạn muốn cập nhật trình đơn, hãy gọi invalidateOptionsMenu() để yêu cầu lệnh gọi hệ thống onPrepareOptionsMenu().

Tạo trình đơn theo bối cảnh

Hình ảnh minh hoạ trình đơn ngữ cảnh nổi
Hình 3. Trình đơn theo bối cảnh nổi.

Trình đơn theo bối cảnh cung cấp các thao tác ảnh hưởng đến một mục hoặc khung ngữ cảnh cụ thể trong giao diện người dùng. Bạn có thể cung cấp một trình đơn ngữ cảnh cho mọi khung hiển thị, nhưng trình đơn này thường được dùng phổ biến nhất cho các mục trong RecylerView hoặc các tập hợp khung hiển thị khác, trong đó người dùng có thể thực hiện các thao tác trực tiếp trên từng mục.

Có hai cách cung cấp thao tác theo ngữ cảnh:

  • Trong trình đơn ngữ cảnh nổi. Một trình đơn sẽ xuất hiện dưới dạng danh sách nổi của các mục trong trình đơn (tương tự như một hộp thoại) khi người dùng thực hiện thao tác nhấn và giữ trên một khung hiển thị để khai báo hỗ trợ cho trình đơn theo bối cảnh. Người dùng có thể thực hiện thao tác theo ngữ cảnh trên một mục tại một thời điểm.
  • chế độ thao tác theo ngữ cảnh. Chế độ này là cách triển khai hệ thống của ActionMode, hiển thị một thanh thao tác theo ngữ cảnh (CAB) ở đầu màn hình cùng với các mục hành động ảnh hưởng đến (các) mục đã chọn. Khi chế độ này hoạt động, người dùng có thể thực hiện một thao tác với nhiều mục cùng lúc, nếu ứng dụng của bạn hỗ trợ thao tác đó.

Lưu ý: Trình đơn theo bối cảnh không hỗ trợ lối tắt và biểu tượng của mục.

Tạo trình đơn ngữ cảnh nổi

Để cung cấp trình đơn ngữ cảnh nổi, hãy làm như sau:

  1. Đăng ký View mà trình đơn ngữ cảnh được liên kết bằng cách gọi registerForContextMenu() và truyền View vào đó.

    Nếu hoạt động của bạn sử dụng RecyclerView và bạn muốn mỗi mục cung cấp cùng một trình đơn ngữ cảnh, hãy đăng ký tất cả các mục cho trình đơn ngữ cảnh bằng cách truyền RecyclerView đến registerForContextMenu().

  2. Triển khai phương thức onCreateContextMenu() trong Activity hoặc Fragment.

    Khi chế độ xem đã đăng ký nhận được một sự kiện chạm và giữ, hệ thống sẽ gọi phương thức onCreateContextMenu() của bạn. Đây là nơi bạn xác định các mục trong trình đơn, thường là bằng cách tăng thêm một tài nguyên của trình đơn, như trong ví dụ sau:

    Kotlin

        override fun onCreateContextMenu(menu: ContextMenu, v: View,
                                menuInfo: ContextMenu.ContextMenuInfo) {
            super.onCreateContextMenu(menu, v, menuInfo)
            val inflater: MenuInflater = menuInflater
            inflater.inflate(R.menu.context_menu, menu)
        }
        

    Java

        @Override
        public void onCreateContextMenu(ContextMenu menu, View v,
                                        ContextMenuInfo menuInfo) {
            super.onCreateContextMenu(menu, v, menuInfo);
            MenuInflater inflater = getMenuInflater();
            inflater.inflate(R.menu.context_menu, menu);
        }
        

    MenuInflater cho phép bạn tăng cường trình đơn theo bối cảnh từ một tài nguyên trình đơn. Các thông số của phương thức gọi lại bao gồm View mà người dùng chọn và đối tượng ContextMenu.ContextMenuInfo cung cấp thêm thông tin về mục đã chọn. Nếu hoạt động có nhiều khung hiển thị, trong đó mỗi khung hiển thị cung cấp một trình đơn ngữ cảnh khác nhau, bạn có thể sử dụng các tham số này để xác định trình đơn ngữ cảnh sẽ tăng cường.

  3. Triển khai onContextItemSelected(), như trong ví dụ sau. Khi người dùng chọn một mục trong trình đơn, hệ thống sẽ gọi phương thức này để bạn có thể thực hiện thao tác phù hợp.

    Kotlin

        override fun onContextItemSelected(item: MenuItem): Boolean {
            val info = item.menuInfo as AdapterView.AdapterContextMenuInfo
            return when (item.itemId) {
                R.id.edit -> {
                    editNote(info.id)
                    true
                }
                R.id.delete -> {
                    deleteNote(info.id)
                    true
                }
                else -> super.onContextItemSelected(item)
            }
        }
        

    Java

        @Override
        public boolean onContextItemSelected(MenuItem item) {
            AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
            switch (item.getItemId()) {
                case R.id.edit:
                    editNote(info.id);
                    return true;
                case R.id.delete:
                    deleteNote(info.id);
                    return true;
                default:
                    return super.onContextItemSelected(item);
            }
        }
        

    Phương thức getItemId() truy vấn mã nhận dạng cho mục trình đơn đã chọn, bạn chỉ định cho mỗi mục trình đơn trong XML bằng cách sử dụng thuộc tính android:id, như minh hoạ trong phần Xác định trình đơn trong XML.

    Khi xử lý thành công một mục trong trình đơn, hãy trả lại true. Nếu bạn không xử lý mục trong trình đơn, hãy chuyển mục đó đến quá trình triển khai lớp cao cấp. Nếu hoạt động của bạn có các phân mảnh, hoạt động trước đó sẽ nhận được lệnh gọi lại này. Bằng cách gọi lớp cao cấp khi không được xử lý, hệ thống sẽ chuyển sự kiện đến phương thức gọi lại tương ứng cho mỗi phân mảnh, lần lượt từng phân mảnh (theo thứ tự thêm từng phân mảnh) cho đến khi true hoặc false được trả về. Các phương thức triển khai mặc định cho Activityandroid.app.Fragment trả về false, vì vậy, hãy luôn gọi lớp cha khi chưa xử lý.

Sử dụng chế độ thao tác theo ngữ cảnh

Chế độ thao tác theo ngữ cảnh là cách triển khai hệ thống của ActionMode tập trung lượt tương tác của người dùng vào việc thực hiện các thao tác theo ngữ cảnh. Khi người dùng bật chế độ này bằng cách chọn một mục, thanh thao tác theo ngữ cảnh sẽ xuất hiện ở đầu màn hình để hiển thị các thao tác mà người dùng có thể thực hiện trên các mục đã chọn. Khi chế độ này được bật, người dùng có thể chọn nhiều mục (nếu ứng dụng của bạn hỗ trợ) và có thể bỏ chọn các mục cũng như tiếp tục điều hướng trong hoạt động. Chế độ thao tác bị vô hiệu hoá và thanh thao tác theo ngữ cảnh sẽ biến mất khi người dùng bỏ chọn tất cả các mục, nhấn nút Quay lại hoặc nhấn thao tác Xong ở bên trái của thanh.

Đối với các khung hiển thị cung cấp thao tác theo ngữ cảnh, bạn thường gọi chế độ thao tác theo ngữ cảnh khi một hoặc cả hai sự kiện sau xảy ra:

  • Người dùng chạm và giữ trên khung hiển thị.
  • Người dùng chọn hộp đánh dấu hoặc thành phần giao diện người dùng tương tự trong chế độ xem.

Cách ứng dụng gọi chế độ thao tác theo ngữ cảnh và xác định hành vi cho từng thao tác tuỳ thuộc vào thiết kế của bạn. Có hai thiết kế:

  • Tùy ý đối với các thao tác theo ngữ cảnh trên chế độ xem riêng lẻ.
  • Đối với các thao tác theo ngữ cảnh theo nhóm (batch) trên các nhóm mục trong RecyclerView, cho phép người dùng chọn nhiều mục và thực hiện thao tác đối với tất cả các mục đó.

Các phần sau đây mô tả chế độ thiết lập cần thiết cho trường hợp đầu tiên.

Bật chế độ thao tác theo ngữ cảnh cho chế độ xem riêng lẻ

Nếu chỉ muốn gọi chế độ thao tác theo ngữ cảnh khi người dùng chọn các khung hiển thị cụ thể, hãy làm như sau:

  1. Triển khai giao diện ActionMode.Callback như trong ví dụ sau. Trong phương thức gọi lại, bạn có thể chỉ định hành động đối với thanh thao tác theo ngữ cảnh, phản hồi các sự kiện nhấp chuột vào mục hành động và xử lý các sự kiện trong vòng đời khác đối với chế độ hành động.

    Kotlin

        private val actionModeCallback = object : ActionMode.Callback {
            // Called when the action mode is created. startActionMode() is called.
            override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
                // Inflate a menu resource providing context menu items.
                val inflater: MenuInflater = mode.menuInflater
                inflater.inflate(R.menu.context_menu, menu)
                return true
            }
    
            // Called each time the action mode is shown. Always called after
            // onCreateActionMode, and might be called multiple times if the mode
            // is invalidated.
            override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
                return false // Return false if nothing is done
            }
    
            // Called when the user selects a contextual menu item.
            override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
                return when (item.itemId) {
                    R.id.menu_share -> {
                        shareCurrentItem()
                        mode.finish() // Action picked, so close the CAB.
                        true
                    }
                    else -> false
                }
            }
    
            // Called when the user exits the action mode.
            override fun onDestroyActionMode(mode: ActionMode) {
                actionMode = null
            }
        }
        

    Java

        private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {
    
            // Called when the action mode is created. startActionMode() is called.
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                // Inflate a menu resource providing context menu items.
                MenuInflater inflater = mode.getMenuInflater();
                inflater.inflate(R.menu.context_menu, menu);
                return true;
            }
    
            // Called each time the action mode is shown. Always called after
            // onCreateActionMode, and might be called multiple times if the mode
            // is invalidated.
            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                return false; // Return false if nothing is done.
            }
    
            // Called when the user selects a contextual menu item.
            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
               switch (item.getItemId()) {
                    case R.id.menu_share:
                        shareCurrentItem();
                        mode.finish(); // Action picked, so close the CAB.
                        return true;
                    default:
                        return false;
                }
            }
    
            // Called when the user exits the action mode.
            @Override
            public void onDestroyActionMode(ActionMode mode) {
                actionMode = null;
            }
        };
        

    Các lệnh gọi lại sự kiện này gần giống như lệnh gọi lại cho trình đơn tuỳ chọn, ngoại trừ việc mỗi lệnh gọi lại này cũng truyền đối tượng ActionMode được liên kết với sự kiện. Bạn có thể dùng API ActionMode để thực hiện nhiều thay đổi đối với CAB, chẳng hạn như sửa đổi tiêu đề và phụ đề bằng setTitle()setSubtitle(). Điều này rất hữu ích khi cho biết số lượng mục đã chọn.

    Mẫu trước đó đặt biến actionMode thành null khi chế độ thao tác bị huỷ. Ở bước tiếp theo, hãy xem cách khởi chạy biến đó và cách lưu biến thành viên trong hoạt động hoặc phân mảnh hữu ích thế nào.

  2. Gọi startActionMode() khi bạn muốn hiện thanh, chẳng hạn như khi người dùng thực hiện thao tác chạm và giữ trên khung hiển thị.

    Kotlin

        someView.setOnLongClickListener { view ->
            // Called when the user performs a touch & hold on someView.
            when (actionMode) {
                null -> {
                    // Start the CAB using the ActionMode.Callback defined earlier.
                    actionMode = activity?.startActionMode(actionModeCallback)
                    view.isSelected = true
                    true
                }
                else -> false
            }
        }
        

    Java

        someView.setOnLongClickListener(new View.OnLongClickListener() {
            // Called when the user performs a touch & hold on someView.
            public boolean onLongClick(View view) {
                if (actionMode != null) {
                    return false;
                }
    
                // Start the CAB using the ActionMode.Callback defined earlier.
                actionMode = getActivity().startActionMode(actionModeCallback);
                view.setSelected(true);
                return true;
            }
        });
        

    Khi bạn gọi startActionMode(), hệ thống sẽ trả về ActionMode đã tạo. Khi lưu sự kiện này vào một biến thành viên, bạn có thể thay đổi thanh thao tác theo ngữ cảnh để phản hồi các sự kiện khác. Trong mẫu trước đó, ActionMode được dùng để đảm bảo rằng bản sao ActionMode sẽ không được tạo lại nếu đã kích hoạt, bằng cách kiểm tra xem thành viên có rỗng hay không trước khi bắt đầu chế độ hành động.

Tạo trình đơn bật lên

Hình ảnh cho thấy một trình đơn bật lên trong ứng dụng Gmail, được gắn vào nút trình đơn ở trên cùng bên phải.
Hình 4. Trình đơn bật lên trong ứng dụng Gmail, được gắn vào nút mục bổ sung ở góc trên bên phải.

PopupMenu là một trình đơn cách thức (modal) liên kết với một View. Công cụ này xuất hiện bên dưới chế độ xem liên kết nếu còn chỗ hoặc phía trên chế độ xem. Công cụ này hữu ích cho những trường hợp sau:

  • Cung cấp trình đơn cách điệu mục bổ sung cho các thao tác liên quan đến nội dung cụ thể, chẳng hạn như tiêu đề email của Gmail, như minh hoạ trong hình 4.
  • Cung cấp phần thứ hai của một câu lệnh, chẳng hạn như nút được đánh dấu là Thêm để tạo một trình đơn bật lên với các lựa chọn Thêm.
  • Cung cấp một trình đơn tương tự như Spinner nhưng không giữ lại lựa chọn liên tục.

Nếu xác định trình đơn trong XML, dưới đây là cách bạn có thể hiển thị trình đơn bật lên:

  1. Tạo bản sao PopupMenu bằng hàm khởi tạo của nó, thao tác này lấy ứng dụng hiện tại ContextView mà bạn liên kết trình đơn.
  2. Sử dụng MenuInflater để phóng to tài nguyên trình đơn vào đối tượng Menu do PopupMenu.getMenu() trả về.
  3. Gọi PopupMenu.show().

Ví dụ: đây là một nút hiển thị trình đơn bật lên:

<ImageButton
    android:id="@+id/dropdown_menu"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:contentDescription="@string/descr_overflow_button"
    android:src="@drawable/arrow_drop_down" />

Sau đó, tác vụ có thể hiển thị trình đơn bật lên như sau:

Kotlin

findViewById<ImageButton>(R.id.dropdown_menu).setOnClickListener {
    val popup = PopupMenu(this, it)
    val inflater: MenuInflater = popup.menuInflater
    inflater.inflate(R.menu.actions, popup.menu)
    popup.show()
}

Java

findViewById(R.id.dropdown_menu).setOnClickListener(v -> {
    PopupMenu popup = new PopupMenu(this, v);
    popup.getMenuInflater().inflate(R.menu.actions, popup.getMenu());
    popup.show();
});

Trình đơn sẽ bị loại bỏ khi người dùng chọn một mục hoặc nhấn ra bên ngoài khu vực trình đơn. Bạn có thể theo dõi sự kiện loại bỏ bằng cách sử dụng PopupMenu.OnDismissListener.

Xử lý các sự kiện nhấp chuột

Để thực hiện một thao tác khi người dùng chọn một mục trong trình đơn, hãy triển khai giao diện PopupMenu.OnMenuItemClickListener và đăng ký giao diện đó với PopupMenu bằng cách gọi setOnMenuItemclickListener(). Khi người dùng chọn một mục, hệ thống sẽ gọi lệnh gọi lại onMenuItemClick() trong giao diện.

Lệnh này được minh hoạ trong ví dụ sau:

Kotlin

fun showMenu(v: View) {
    PopupMenu(this, v).apply {
        // MainActivity implements OnMenuItemClickListener.
        setOnMenuItemClickListener(this@MainActivity)
        inflate(R.menu.actions)
        show()
    }
}

override fun onMenuItemClick(item: MenuItem): Boolean {
    return when (item.itemId) {
        R.id.archive -> {
            archive(item)
            true
        }
        R.id.delete -> {
            delete(item)
            true
        }
        else -> false
    }
}

Java

public void showMenu(View v) {
    PopupMenu popup = new PopupMenu(this, v);

    // This activity implements OnMenuItemClickListener.
    popup.setOnMenuItemClickListener(this);
    popup.inflate(R.menu.actions);
    popup.show();
}

@Override
public boolean onMenuItemClick(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.archive:
            archive(item);
            return true;
        case R.id.delete:
            delete(item);
            return true;
        default:
            return false;
    }
}

Tạo nhóm trình đơn

Nhóm trình đơn là một tập hợp các mục trong trình đơn có chung đặc điểm nhất định. Với một nhóm, bạn có thể làm những việc sau:

Bạn có thể tạo một nhóm bằng cách lồng các phần tử <item> bên trong một phần tử <group> trong tài nguyên trình đơn hoặc bằng cách chỉ định một mã nhóm bằng phương thức add().

Dưới đây là một ví dụ về tài nguyên trình đơn bao gồm một nhóm:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_save"
          android:icon="@drawable/menu_save"
          android:title="@string/menu_save" />
    <!-- menu group -->
    <group android:id="@+id/group_delete">
        <item android:id="@+id/menu_archive"
              android:title="@string/menu_archive" />
        <item android:id="@+id/menu_delete"
              android:title="@string/menu_delete" />
    </group>
</menu>

Các mục trong nhóm xuất hiện cùng cấp với mục đầu tiên – cả ba mục trong trình đơn đồng cấp với nhau. Tuy nhiên, bạn có thể sửa đổi đặc điểm của hai mục trong nhóm bằng cách tham chiếu mã nhóm và sử dụng các phương thức nêu trên. Hệ thống cũng sẽ không bao giờ tách riêng các mục được nhóm lại. Ví dụ: nếu bạn khai báo android:showAsAction="ifRoom" cho mỗi mục, cả hai sẽ xuất hiện trong thanh thao tác hoặc trong mục bổ sung hành động.

Sử dụng các mục trong trình đơn có thể đánh dấu chọn

Hình 5. Một trình đơn phụ có các mục có thể đánh dấu.

Trình đơn có thể hữu ích như khi là một giao diện giúp bật và tắt lựa chọn, sử dụng hộp đánh dấu cho các lựa chọn độc lập hoặc nút chọn cho các nhóm lựa chọn loại trừ lẫn nhau. Hình 5 cho thấy một trình đơn phụ có các mục có thể đánh dấu bằng nút chọn.

Bạn có thể xác định thao tác đánh dấu cho từng mục trong trình đơn bằng cách sử dụng thuộc tính android:checkable trong phần tử <item>, hoặc cho toàn bộ nhóm có thuộc tính android:checkableBehavior trong phần tử <group>. Ví dụ: tất cả các mục trong nhóm trình đơn này đều có thể đánh dấu được bằng nút chọn:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single">
        <item android:id="@+id/red"
              android:title="@string/red" />
        <item android:id="@+id/blue"
              android:title="@string/blue" />
    </group>
</menu>

Thuộc tính android:checkableBehavior chấp nhận một trong những thuộc tính sau:

single
Chỉ có thể đánh dấu chọn một mục trong nhóm, dẫn đến các nút chọn.
all
Bạn có thể đánh dấu vào tất cả các mục, dẫn đến việc xuất hiện hộp đánh dấu.
none
Không có mục nào đánh dấu được.

Bạn có thể áp dụng trạng thái đã đánh dấu mặc định cho một mục bằng thuộc tính android:checked trong phần tử <item> và thay đổi mã đó bằng phương thức setChecked().

Khi chọn một mục có thể đánh dấu, hệ thống sẽ gọi phương thức gọi lại đã chọn cho mục tương ứng, chẳng hạn như onOptionsItemSelected(). Đây là nơi bạn đặt trạng thái của hộp đánh dấu, vì hộp đánh dấu hoặc nút chọn không tự động thay đổi trạng thái của hộp đánh dấu. Bạn có thể truy vấn trạng thái hiện tại của mục (như trước khi người dùng chọn mục) bằng isChecked(), sau đó đặt trạng thái đã đánh dấu là setChecked(). Điều này được thể hiện trong ví dụ sau:

Kotlin

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when (item.itemId) {
        R.id.vibrate, R.id.dont_vibrate -> {
            item.isChecked = !item.isChecked
            true
        }
        else -> super.onOptionsItemSelected(item)
    }
}

Java

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.vibrate:
        case R.id.dont_vibrate:
            if (item.isChecked()) item.setChecked(false);
            else item.setChecked(true);
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

Nếu không đặt trạng thái đánh dấu theo cách này, trạng thái hiển thị của hộp đánh dấu hoặc nút chọn sẽ không thay đổi khi người dùng chọn mục đó. Khi bạn đặt trạng thái, hoạt động này sẽ giữ nguyên trạng thái đã đánh dấu của mục để khi người dùng mở trình đơn vào lúc khác, trạng thái đã đánh dấu mà bạn đặt sẽ hiển thị.

Thêm mục trong trình đơn dựa trên ý định

Đôi khi, bạn muốn một mục trong trình đơn khởi chạy một Hoạt động bằng cách sử dụng Intent, cho dù đó là một Hoạt động trong ứng dụng của bạn hay một ứng dụng khác. Khi biết ý định bạn muốn sử dụng và có một mục cụ thể trong trình đơn khởi chạy ý định đó, bạn có thể thực thi ý định bằng startActivity() trong phương thức gọi lại thích hợp theo mục đã chọn, chẳng hạn như lệnh gọi lại onOptionsItemSelected().

Tuy nhiên, nếu không chắc chắn thiết bị của người dùng có chứa ứng dụng xử lý ý định, thao tác thêm mục trình đơn gọi ứng dụng đó có thể dẫn đến việc một mục trình đơn không hoạt động, vì mục đích có thể không giải quyết được một hoạt động. Để giải quyết vấn đề này, Android cho phép bạn tự động thêm các mục vào trình đơn khi Android tìm thấy các hoạt động trên thiết bị xử lý ý định của bạn.

Để thêm các mục trên trình đơn dựa vào các hoạt động hiện có chấp nhận một ý định, hãy làm như sau:

  1. Xác định một ý định bằng danh mục CATEGORY_ALTERNATIVE hoặc CATEGORY_SELECTED_ALTERNATIVE, hoặc cả hai, cùng với bất kỳ yêu cầu nào khác.
  2. Gọi Menu.addIntentOptions(). Sau đó, Android sẽ tìm kiếm mọi ứng dụng có thể thực hiện ý định và thêm các ứng dụng đó vào trình đơn của bạn.

Nếu không có ứng dụng nào được cài đặt đáp ứng ý định, thì sẽ không có mục trình đơn nào được thêm.

Lệnh này được minh hoạ trong ví dụ sau:

Kotlin

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)

    // Create an Intent that describes the requirements to fulfill, to be
    // included in the menu. The offering app must include a category value
    // of Intent.CATEGORY_ALTERNATIVE.
    val intent = Intent(null, dataUri).apply {
        addCategory(Intent.CATEGORY_ALTERNATIVE)
    }

    // Search and populate the menu with acceptable offering apps.
    menu.addIntentOptions(
            R.id.intent_group,  // Menu group to which new items are added.
            0,                  // Unique item ID (none).
            0,                  // Order for the items (none).
            this.componentName, // The current activity name.
            null,               // Specific items to place first (none).
            intent,             // Intent created above that describes the requirements.
            0,                  // Additional flags to control items (none).
            null)               // Array of MenuItems that correlate to specific items (none).

    return true
}

Java

@Override
public boolean onCreateOptionsMenu(Menu menu){
    super.onCreateOptionsMenu(menu);

    // Create an Intent that describes the requirements to fulfill, to be
    // included in the menu. The offering app must include a category value
    // of Intent.CATEGORY_ALTERNATIVE.
    Intent intent = new Intent(null, dataUri);
    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);

    // Search and populate the menu with acceptable offering apps.
    menu.addIntentOptions(
         R.id.intent_group,         // Menu group to which new items are added.
         0,                         // Unique item ID (none).
         0,                         // Order for the items (none).
         this.getComponentName(),   // The current activity name.
         null,                      // Specific items to place first (none).
         intent,                    // Intent created above that describes the requirements.
         0,                         // Additional flags to control items (none).
         null);                     // Array of MenuItems that correlate to specific items (none).

    return true;
}

Đối với mỗi hoạt động được tìm thấy cung cấp bộ lọc ý định khớp với ý định đã xác định, một mục trong trình đơn sẽ được thêm vào, sử dụng giá trị trong android:label của bộ lọc ý định làm tiêu đề mục trong trình đơn và biểu tượng ứng dụng làm biểu tượng mục trong trình đơn. Phương thức addIntentOptions() trả về số lượng các mục trong trình đơn đã thêm vào.

Cho phép thêm hoạt động của bạn vào các trình đơn khác

Bạn có thể cung cấp dịch vụ về hoạt động của mình cho các ứng dụng khác để có thể đưa ứng dụng vào trình đơn của những ứng dụng khác – đảo ngược vai trò được mô tả trước đó.

Để được đưa vào trình đơn ứng dụng khác, hãy xác định bộ lọc ý định như bình thường, nhưng hãy thêm các giá trị CATEGORY_ALTERNATIVE hoặc CATEGORY_SELECTED_ALTERNATIVE, hoặc cả hai, cho danh mục bộ lọc ý định. Lệnh này được minh hoạ trong ví dụ sau:

<intent-filter label="@string/resize_image">
    ...
    <category android:name="android.intent.category.ALTERNATIVE" />
    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
    ...
</intent-filter>

Đọc thêm về cách viết bộ lọc ý định trong phần Ý định và bộ lọc ý định.