Thêm thực đơn

Thử cách sử dụng 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 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 các API Menu để trình bày thao tác của người dùng và các tuỳ chọn khác trong hoạt động của bạn.

Hình ảnh cho thấy ví dụ về trình đơn mục bổ sung
Hình 1. Một trình đơn được kích hoạt khi người dùng 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 trình bày 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 thực hiện các hành động 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à trình đơn nổi xuất hiện khi người dùng nhấn 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ị một danh sách theo chiều dọc gồm các mục được liên kết với khung hiển thị gọi ra trình đơn. Bạn nên cung cấp nhiều thao tác liên quan đến nội dung cụ thể hoặc cung cấp tuỳ 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. Đó là mục đích sử dụng của 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 liên quan đến các khu vực nội dung trong hoạt động của bạn.

Xem mục 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 cung cấp một định dạng XML tiêu chuẩn để xác định các mục trong trình đơn. Thay vì tạo một trình đơn trong mã của hoạt động, hãy xác định một trình đơn và tất cả các mục của trình đơn đó trong một tài nguyên trình đơn XML. Sau đó, bạn có thể tăng cường tài nguyên của trình đơn (tải tài nguyên này dưới dạng đối tượng Menu) trong hoạt động hoặc mảnh 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.
  • Thuộc tính này tách nội dung của trình đơn khỏi mã hành vi của ứng dụng.
  • Thư viện này cho phép bạn tạo các cấu hình trình đơn thay thế cho nhiều 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à xây dựng trình đơn với 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 một 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>. Tính năng này cho phép bạn phân loại các mục trong trình đơn để chúng có chung thuộc tính, chẳng hạn như trạng thái hoạt động và chế độ 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 mà 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 thuộc tính sau:

android:id
Mã tài nguyên dành riêng cho mục, cho phép ứng dụng nhận ra mục 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
Thông số kỹ thuật 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 vẫn còn nhiều thuộc tính khác có sẵn. Để 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 về Tài nguyên trình đơn.

Bạn có thể thêm 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 trong thanh trình đơn của ứng dụng trên máy tính (chẳng hạn như File (Tệp), Edit (Chỉnh sửa) và View (Xem). 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 của bạn, hãy _inflate_ tài nguyên trình đơn, chuyển đổi tài nguyên XML thành đối tượng có thể lập trình bằng MenuInflater.inflate(). Các phần sau đây trình bày cách phóng to 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ư minh hoạ trong hình 1) là nơi bạn đưa các thao tác và tuỳ chọn khá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 thể hiện thanh ứng dụng của ứng dụng Google Trang tính
Hình 2. Ứng dụng Google Trang tính cho thấy một số nút, trong đó 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à 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 đầu tiên, tiếp theo là các mục của từng mảnh, theo thứ tự thêm các mảnh 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 tuỳ chọn cho một hoạt động, hãy ghi đè onCreateOptionsMenu(). Các mảnh cung cấp lệnh gọi lại onCreateOptionsMenu() riêng. Trong phương pháp này, bạn có thể tăng cường tài nguyên trình đơn (đượ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 từ trình đơn tuỳ 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 sẽ 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 cho phương thức add(). Bạn có thể so khớp 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 bạn không xử lý mục trong trình đơn, hãy gọi thao tác triển khai lớp cấp cao của onOptionsItemSelected(). Phương thức triển khai mặc định trả về giá trị false.

Nếu hoạt động có chứa mảnh, trước tiên, hệ thống sẽ gọi onOptionsItemSelected() cho hoạt động đó, sau đó gọi cho mỗi mảnh theo thứ tự thêm các mảnh cho đến khi một mảnh trả về true hoặc tất cả mảnh đượ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 bản sao 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ỉ sử dụng onCreateOptionsMenu() để tạo trạng thái trình đơn ban đầu và không thực hiện các thay đổi trong vòng đời hoạt động.

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 hoạt động, bạn có thể thực hiện việc này trong phương thức onPrepareOptionsMenu(). Phương thức này truyền cho bạ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 đối tượng đó, chẳng hạn như bằng cách thêm, xoá hoặc tắt các mục. Các mảnh cũng cung cấp lệnh gọi lại onPrepareOptionsMenu().

Trình đơn tuỳ chọn được coi là luôn mở khi các mục trong trình đơn hiển thị trê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 hệ thống gọi onPrepareOptionsMenu().

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

Hình ảnh cho thấy một trình đơn theo bối 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 trình đơn theo bối cảnh cho mọi khung hiển thị. Tuy nhiên, các khung hiển thị này thường được dùng nhất cho các mục trong RecylerView hoặc các tập hợp khung hiển thị khác mà người dùng có thể thực hiện 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 gồm các mục trong trình đơn, tương tự như một hộp thoại, khi người dùng nhấn và giữ trên một khung hiển thị khai báo hỗ trợ cho một 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à một 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 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 trên nhiều mục cùng một lúc nếu ứng dụng của bạn hỗ trợ việc đó.

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

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

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

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

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

    Khi khung hiển thị đã đă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(). Đâ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 cường tài nguyên 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 tham 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 nào cần 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 thích 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 trong trình đơn đã chọn mà bạn chỉ định cho mỗi mục trong trình đơn trong XML bằ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 trong trình đơn đến phương thức triển khai lớp cấp cao. Nếu có các mảnh (fragment) thì hoạt động đó sẽ nhận được lệnh gọi lại này trước tiên. Bằng cách gọi lớp cấp cao khi chưa được xử lý, hệ thống sẽ chuyển sự kiện sang phương thức gọi lại tương ứng trong từng mảnh (mỗi lần một mảnh) theo thứ tự thêm từng 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 sẽ trả về false, vì vậy, hãy luôn gọi lớp cấp cao khi không được 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, giúp tập trung 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 di chuyển trong hoạt động. Chế độ thao tác bị tắt 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 vào nút Quay lại hoặc nhấn vào thao tác Xong ở bên trái thanh.

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

  • Người dùng chạm và giữ chế độ xem.
  • 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 sẽ 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 hàng loạt 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 đó.

Phần sau đây mô tả cách thiết lập bắt buộc cho từng trường hợp.

Bật chế độ thao tác theo ngữ cảnh cho từng chế độ xem

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

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

    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 với các 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 liên kết với sự kiện. Bạn có thể sử dụng các 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à tiêu đề phụ bằng setTitle()setSubtitle(). Các API này rất hữu ích trong việc cho biết số mục được chọn.

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

  2. Gọi startActionMode() khi bạn muốn hiện thanh này, chẳng hạn như khi người dùng nhấn 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. Bằng cách lưu sự kiện này vào một biến thành phầ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 thực thể ActionMode sẽ không được tạo lại nếu đã hoạt động, bằng cách kiểm tra xem thành phầ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 neo vào nút mục bổ sung ở trên cùng bên phải.
Hình 4. Trình đơn bật lên trong ứng dụng Gmail được neo vào nút mục bổ sung ở góc trên cùng bên phải.

PopupMenu là một trình đơn phương thức được neo với một View. Chế độ xem này xuất hiện bên dưới chế độ xem liên kết nếu còn khoảng trống hoặc phía trên chế độ xem. Điều này hữu ích cho những mục đích sau:

  • Cung cấp trình đơn kiể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à Add (Thêm) để tạo trình đơn bật lên với các tuỳ chọn Add (Thêm).
  • Cung cấp một trình đơn tương tự như Spinner không giữ lại lựa chọn liên tục.

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

  1. Tạo thực thể cho PopupMenu bằng hàm khởi tạo. Hàm này sẽ lấy ứng dụng hiện tại ContextView liên kết với trình đơn.
  2. Sử dụng MenuInflater để tăng cường 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 cho thấy một 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 bị đóng khi người dùng chọn một mục hoặc nhấn vào bên ngoài khu vực trình đơn. Bạn có thể theo dõi sự kiện đóng 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ẽ thực hiện lệnh gọi lại onMenuItemClick() trong giao diện của bạ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 một 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 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 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à 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 sẽ xuất hiện ở cùng cấp với mục đầu tiên – cả 3 mục trong trình đơn đều là mục đồng cấp. 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 đến mã nhóm và sử dụng các phương thức trước đó. Hệ thống cũng 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, thì cả hai đều xuất hiện trong thanh thao tác hoặc cả hai đều xuất hiện trong mục bổ sung hành động.

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

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ư một giao diện để bật và tắt các tuỳ chọn, sử dụng hộp đánh dấu cho các tuỳ chọn độc lập hoặc nút chọn cho các nhóm tuỳ 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 hành vi có thể đá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 bằng thuộc tính android:checkableBehavior trong phần tử <group>. Ví dụ: bạn có thể kiểm tra tất cả các mục trong nhóm trình đơn này 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 các nội dung sau:

single
Chỉ có thể đánh dấu 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, từ đó tạo thành 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 cách sử dụng thuộc tính android:checked trong phần tử <item> và thay đổi thuộc tính đó trong mã bằng phương thức setChecked().

Khi bạn 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 theo mục tương ứng đã chọn, 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 bằng 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 bạn không đặt trạng thái đánh dấu theo cách này, thì 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 hộp đánh dấu. Khi bạn đặt trạng thái, hoạt động sẽ giữ nguyên trạng thái đã đánh dấu của mục để khi người dùng mở trình đơn sau đó, trạng thái đã đánh dấu mà bạn đã đặt sẽ hiển thị.

Thêm các 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à hoạt động trong ứng dụng của bạn hay một ứng dụng khác. Khi biết ý định mình muốn sử dụng và có một mục cụ thể trong trình đơn để kích hoạt ý định, bạn có thể thực thi ý định với startActivity() trong phương thức phù hợp với mục đã chọn, chẳng hạn như lệnh gọi lại onOptionsItemSelected().

Tuy nhiên, nếu bạn không chắc chắn rằng thiết bị của người dùng có chứa một ứng dụng xử lý ý định, thì việc thêm một mục trong trình đơn gọi ứng dụng đó có thể dẫn đến một mục trong trình đơn không hoạt động vì ý định có thể không phân giải được một hoạt động. Để giải quyết vấn đề này, Android cho phép bạn linh động thêm các mục trong trình đơn 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 trong trình đơn dựa trên các hoạt động có sẵn chấp nhận ý định, hãy làm như sau:

  1. Xác định ý định bằng danh mục CATEGORY_ALTERNATIVE hoặc CATEGORY_SELECTED_ALTERNATIVE hoặc cả hai, cùng với mọi yêu cầu 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 trong 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, 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 mục trong trình đơn đã thêm.

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 để ứng dụng có thể được đưa vào trình đơn của các ứng dụng khác — đảo ngược các vai trò đã mô tả trước đó.

Để được đưa vào các trình đơn ứng dụng khác, hãy xác định một bộ lọc ý định như bình thường, nhưng bao gồ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>

Hãy đọc thêm về cách viết bộ lọc ý định trong bài viết Ý định và bộ lọc ý định.