Màu sắc của MenuItem trên Thanh công cụ AppCompat


93

Khi tôi sử dụng các đồ có thể kéo từ AppCompatthư viện cho Toolbarcác mục menu của mình, việc pha màu sẽ hoạt động như mong đợi. Như thế này:

<item
    android:id="@+id/action_clear"
    android:icon="@drawable/abc_ic_clear_mtrl_alpha"  <-- from AppCompat
    android:title="@string/clear" />

Nhưng nếu tôi sử dụng các đồ có thể kéo của riêng mình hoặc thực sự thậm chí sao chép các đồ có thể kéo từ AppCompatthư viện vào dự án của riêng mình thì nó sẽ không có màu gì cả.

<item
    android:id="@+id/action_clear"
    android:icon="@drawable/abc_ic_clear_mtrl_alpha_copy"  <-- copy from AppCompat
    android:title="@string/clear" />

Có một phép thuật đặc biệt nào đó trong chiếc AppCompat Toolbartủ kéo màu duy nhất từ ​​thư viện đó không? Bất kỳ cách nào để làm cho điều này hoạt động với các ngăn kéo của riêng tôi?

Chạy điều này trên thiết bị API Cấp 19 với compileSdkVersion = 21targetSdkVersion = 21và cũng sử dụng mọi thứ từAppCompat

abc_ic_clear_mtrl_alpha_copylà một bản sao chính xác của abc_ic_clear_mtrl_alphapng từAppCompat

Biên tập:

Việc pha màu dựa trên giá trị mà tôi đã đặt cho android:textColorPrimarychủ đề của mình.

Ví dụ: <item name="android:textColorPrimary">#00FF00</item>sẽ cho tôi một màu xanh lá cây.

Ảnh chụp màn hình

Pha màu hoạt động như mong đợi với có thể vẽ từ AppCompat Pha màu hoạt động như mong đợi với có thể vẽ từ AppCompat

Pha màu không hoạt động với có thể vẽ được sao chép từ AppCompat Pha màu không hoạt động với có thể vẽ được sao chép từ AppCompat


Cả hai kiểu đều có cùng cha mẹ? Điều gì sẽ xảy ra nếu bạn mở rộng phong cách hàng đầu với của riêng bạn?
G_V

Không có sự khác biệt trong các phong cách. Sự khác biệt duy nhất là có thể vẽ, cả hai đều là tệp .png
greve

Có thể vẽ giống như một bản sao chính xác của AppCombat gốc có thể vẽ được trong mã?
G_V

Chúng là tệp png, mà tôi đã sao chép. Họ đều giống hệt nhau.
greve

Vậy chính xác thì mã của bạn khác với bản gốc ở đâu nếu nó có cùng kiểu dáng và hình ảnh giống nhau?
G_V

Câu trả lời:


31

Bởi vì nếu bạn nhìn vào mã nguồn của TintManager trong AppCompat, bạn sẽ thấy:

/**
 * Drawables which should be tinted with the value of {@code R.attr.colorControlNormal},
 * using the default mode.
 */
private static final int[] TINT_COLOR_CONTROL_NORMAL = {
        R.drawable.abc_ic_ab_back_mtrl_am_alpha,
        R.drawable.abc_ic_go_search_api_mtrl_alpha,
        R.drawable.abc_ic_search_api_mtrl_alpha,
        R.drawable.abc_ic_commit_search_api_mtrl_alpha,
        R.drawable.abc_ic_clear_mtrl_alpha,
        R.drawable.abc_ic_menu_share_mtrl_alpha,
        R.drawable.abc_ic_menu_copy_mtrl_am_alpha,
        R.drawable.abc_ic_menu_cut_mtrl_alpha,
        R.drawable.abc_ic_menu_selectall_mtrl_alpha,
        R.drawable.abc_ic_menu_paste_mtrl_am_alpha,
        R.drawable.abc_ic_menu_moreoverflow_mtrl_alpha,
        R.drawable.abc_ic_voice_search_api_mtrl_alpha,
        R.drawable.abc_textfield_search_default_mtrl_alpha,
        R.drawable.abc_textfield_default_mtrl_alpha
};

/**
 * Drawables which should be tinted with the value of {@code R.attr.colorControlActivated},
 * using the default mode.
 */
private static final int[] TINT_COLOR_CONTROL_ACTIVATED = {
        R.drawable.abc_textfield_activated_mtrl_alpha,
        R.drawable.abc_textfield_search_activated_mtrl_alpha,
        R.drawable.abc_cab_background_top_mtrl_alpha
};

/**
 * Drawables which should be tinted with the value of {@code android.R.attr.colorBackground},
 * using the {@link android.graphics.PorterDuff.Mode#MULTIPLY} mode.
 */
private static final int[] TINT_COLOR_BACKGROUND_MULTIPLY = {
        R.drawable.abc_popup_background_mtrl_mult,
        R.drawable.abc_cab_background_internal_bg,
        R.drawable.abc_menu_hardkey_panel_mtrl_mult
};

/**
 * Drawables which should be tinted using a state list containing values of
 * {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated}
 */
private static final int[] TINT_COLOR_CONTROL_STATE_LIST = {
        R.drawable.abc_edit_text_material,
        R.drawable.abc_tab_indicator_material,
        R.drawable.abc_textfield_search_material,
        R.drawable.abc_spinner_mtrl_am_alpha,
        R.drawable.abc_btn_check_material,
        R.drawable.abc_btn_radio_material
};

/**
 * Drawables which contain other drawables which should be tinted. The child drawable IDs
 * should be defined in one of the arrays above.
 */
private static final int[] CONTAINERS_WITH_TINT_CHILDREN = {
        R.drawable.abc_cab_background_top_material
};

Điều này có nghĩa là họ có các resourceIds cụ thể được đưa vào danh sách trắng để được nhuộm màu.

Nhưng tôi đoán bạn luôn có thể thấy cách họ nhuộm những hình ảnh đó và làm như vậy. Nó dễ dàng như đặt ColorFilter trên một cái có thể kéo được.


Ugh, đó là điều tôi sợ. Tôi không tìm thấy mã nguồn cho AppCompat trong SDK, đó là lý do tại sao tôi không tự tìm thấy phần này. Đoán tôi sẽ phải duyệt trên googlesource.com sau đó. Cảm ơn!
greve

8
Tôi biết đó là một câu hỏi tiếp tuyến nhưng tại sao lại có một danh sách trắng? Nếu nó có thể pha màu với các biểu tượng này thì tại sao chúng ta không thể pha màu cho các biểu tượng của riêng mình? Ngoài ra, điểm quan trọng trong việc làm cho hầu hết mọi thứ tương thích ngược (với AppCompat) khi bạn bỏ qua một trong những điều quan trọng nhất: có các biểu tượng trên thanh hành động (với màu tùy chỉnh).
Zsolt Safrany

1
Có một vấn đề cho điều này trong theo dõi vấn đề của Google đã được đánh dấu là cố định, nhưng nó không làm việc cho tôi, nhưng bạn có thể theo dõi nó ở đây: issuetracker.google.com/issues/37127128
niknetniko

Họ khẳng định điều này đã được khắc phục, nhưng nó không phải. Chúa ơi, tôi ghét công cụ chủ đề Android, AppCompat và tất cả những thứ vớ vẩn liên quan đến nó. Nó chỉ hoạt động cho các ứng dụng mẫu "Github repo browser".
Martin Marconcini

97

Sau thư viện Hỗ trợ mới v22.1, bạn có thể sử dụng thứ gì đó tương tự như sau:

  @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_home, menu);
        Drawable drawable = menu.findItem(R.id.action_clear).getIcon();

        drawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTint(drawable, ContextCompat.getColor(this,R.color.textColorPrimary));
        menu.findItem(R.id.action_clear).setIcon(drawable);
        return true;
    }

1
Tôi muốn nói rằng trong trường hợp này, cái cũ setColorFilter()là cách tốt hơn.
natario

@mvai, tại sao setColorFilter () có thể cài đặt trước hơn?
wilddev

4
@wilddev ngắn gọn. Tại sao phải bận tâm đến lớp DrawableCompat hỗ trợ khi bạn có thể truy cập menu.findItem (). GetIcon (). SetColorFilter ()? Một lớp lót và rõ ràng.
natario

4
Đối số một lớp không liên quan khi bạn trừu tượng hóa toàn bộ logic vào phương thức TintingUtils.tintMenuIcon (...) của riêng bạn hoặc bất cứ thứ gì bạn muốn gọi nó. Nếu bạn cần thay đổi hoặc điều chỉnh logic trong tương lai, bạn làm điều đó ở một nơi, không phải trên toàn ứng dụng.
Dan Dar3

1
Điều này thật tuyệt!
shariful islam

82

Đặt ColorFilter(tint) trên a MenuItemrất đơn giản. Đây là một ví dụ:

Drawable drawable = menuItem.getIcon();
if (drawable != null) {
    // If we don't mutate the drawable, then all drawable's with this id will have a color
    // filter applied to it.
    drawable.mutate();
    drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
    drawable.setAlpha(alpha);
}

Đoạn mã trên rất hữu ích nếu bạn muốn hỗ trợ các chủ đề khác nhau và bạn không muốn có thêm bản sao chỉ cho màu sắc hoặc độ trong suốt.

Nhấp vào đây để có lớp trợ giúp để đặtColorFiltertrên tất cả các ngăn có thể kéo trong menu, bao gồm cả biểu tượng mục bổ sung.

Trong onCreateOptionsMenu(Menu menu)chỉ gọi MenuColorizer.colorMenu(this, menu, color);sau khi lạm phát đơn và thì đấy bạn; biểu tượng của bạn được nhuộm màu.


Cảm ơn, chắc chắn sẽ thử điều đó!
greve

3
Tôi đã đập đầu vào bàn của mình để cố gắng tìm ra lý do tại sao tất cả các biểu tượng của tôi đang bị ngả màu, cảm ơn bạn đã quan tâm đến drawable.mutate ()!
Scott Cooper

49

app:iconTintthuộc tính được triển khai SupportMenuInflatertừ thư viện hỗ trợ (ít nhất trong 28.0.0).

Đã kiểm tra thành công với API 15 trở lên.

Tệp tài nguyên menu:

<menu
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/menu_settings"
        android:icon="@drawable/ic_settings_white_24dp"
        app:iconTint="?attr/appIconColorEnabled"        <!-- using app name space instead of android -->
        android:menuCategory="system"
        android:orderInCategory="1"
        android:title="@string/menu_settings"
        app:showAsAction="never"
        />

    <item
        android:id="@+id/menu_themes"
        android:icon="@drawable/ic_palette_white_24dp"
        app:iconTint="?attr/appIconColorEnabled"
        android:menuCategory="system"
        android:orderInCategory="2"
        android:title="@string/menu_themes"
        app:showAsAction="never"
        />

    <item
        android:id="@+id/action_help"
        android:icon="@drawable/ic_help_white_24dp"
        app:iconTint="?attr/appIconColorEnabled"
        android:menuCategory="system"
        android:orderInCategory="3"
        android:title="@string/menu_help"
        app:showAsAction="never"
        />

</menu>

(Trong trường hợp này ?attr/appIconColorEnabledlà thuộc tính màu tùy chỉnh trong các chủ đề của ứng dụng và tài nguyên biểu tượng là các vector có thể vẽ được).


5
Đây phải là câu trả lời mới được chấp nhận! Ngoài ra, xin vui lòng lưu ý android:iconTintandroid:iconTintModekhông làm việc, nhưng tiền tố với app:thay cho android:các công trình như một say mê (trên drawables vector riêng tôi, API> = 21)
Sebastiaan Alvarez Rodriguez

Nếu gọi theo chương trình: lưu ý rằng SupportMenuInflatersẽ không áp dụng bất kỳ logic tùy chỉnh nào nếu menu không SupportMenugiống như vậy MenuBuilder, nó chỉ trở lại bình thường MenuInflater.
geekley

Trong trường hợp này, việc sử dụng AppCompatActivity.startSupportActionMode(callback)và các triển khai hỗ trợ thích hợp từ androidx.appcompatsẽ được chuyển vào lệnh gọi lại.
geekley

30

Cá nhân tôi thích cách tiếp cận này từ liên kết này

Tạo một bố cục XML như sau:

<?xml version="1.0" encoding="utf-8"?>
<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/ic_action_something"
    android:tint="@color/color_action_icons_tint"/>

và tham khảo có thể kéo này từ menu của bạn:

<item
    android:id="@+id/option_menu_item_something"
    android:icon="@drawable/ic_action_something_tined"

2
Mặc dù liên kết này có thể trả lời câu hỏi, nhưng tốt hơn hết bạn nên đưa các phần thiết yếu của câu trả lời vào đây và cung cấp liên kết để tham khảo. Các câu trả lời chỉ có liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi.
tomloprod

Cảm ơn bạn đã bình luận của bạn, tôi đã chỉnh sửa câu hỏi. @tomloprod
N Jay

4
Đây là giải pháp ưa thích của tôi. Tuy nhiên, điều quan trọng cần lưu ý là hiện tại, giải pháp này dường như không hoạt động khi bạn đang sử dụng các loại vector có thể vẽ mới làm nguồn.
Michael De Soto

1
@haagmm giải pháp này cần API> = 21. Nó cũng hoạt động cho các vectơ.
Chất dẫn truyền thần kinh

1
Và nó sẽ không hoạt động với các vectơ, thẻ gốc bitmap. Có nhiều cách khác để tô màu vectơ. Có lẽ ai đó có thể thêm vector màu ở đây quá ...
milosmns

11

Hầu hết các giải pháp trong chủ đề này đều sử dụng API mới hơn hoặc sử dụng phản chiếu hoặc sử dụng tra cứu chế độ xem chuyên sâu để đi đến thông tin tăng cao MenuItem.

Tuy nhiên, có một cách tiếp cận thanh lịch hơn để làm điều đó. Bạn cần có Thanh công cụ tùy chỉnh, vì trường hợp sử dụng "áp dụng màu tùy chỉnh" của bạn không hoạt động tốt với API tạo kiểu / chủ đề công khai.

public class MyToolbar extends Toolbar {
    ... some constructors, extracting mAccentColor from AttrSet, etc

    @Override
    public void inflateMenu(@MenuRes int resId) {
        super.inflateMenu(resId);
        Menu menu = getMenu();
        for (int i = 0; i < menu.size(); i++) {
            MenuItem item = menu.getItem(i);
            Drawable icon = item.getIcon();
            if (icon != null) {
                item.setIcon(applyTint(icon));
            }
        }
    }
    void applyTint(Drawable icon){
        icon.setColorFilter(
           new PorterDuffColorFilter(mAccentColor, PorterDuff.Mode.SRC_IN)
        );
    }

}

Chỉ cần đảm bảo rằng bạn gọi trong mã Hoạt động / Phân đoạn của mình:

toolbar.inflateMenu(R.menu.some_menu);
toolbar.setOnMenuItemClickListener(someListener);

Không phản chiếu, không tra cứu chế độ xem và không có quá nhiều mã, phải không?

Và bây giờ bạn có thể bỏ qua những điều vô lý onCreateOptionsMenu/onOptionsItemSelected.


Về mặt kỹ thuật, bạn đang thực hiện tra cứu lượt xem. Bạn đang lặp lại các chế độ xem và đảm bảo chúng không rỗng. ;)
Martin Marconcini

Bạn chắc chắn đúng theo một cách nào đó :-) Tuy nhiên, Menu#getItem()độ phức tạp là O (1) trong thanh công cụ, vì các mục được lưu trữ trong ArrayList. Khác với View#findViewByIdtraversal (mà tôi gọi là tra cứu chế độ xem trong câu trả lời của mình), độ phức tạp của nó là không đổi :-)
Drew

Đồng ý, trên thực tế, tôi đã làm một điều rất tương tự. Tôi vẫn đang bị sốc rằng Android đã không sắp xếp tất cả điều này sau nhiều năm ...
Martin Marconcini

Làm cách nào để thay đổi màu sắc của biểu tượng tràn và biểu tượng bánh hamburger với cách tiếp cận này?
Sandra

8

Đây là giải pháp mà tôi sử dụng; bạn có thể gọi nó sau onPrepareOptionsMenu () hoặc nơi tương đương. Lý do cho mutate () là nếu bạn tình cờ sử dụng các biểu tượng ở nhiều vị trí; không có đột biến, tất cả chúng sẽ có cùng một tông màu.

public class MenuTintUtils {
    public static void tintAllIcons(Menu menu, final int color) {
        for (int i = 0; i < menu.size(); ++i) {
            final MenuItem item = menu.getItem(i);
            tintMenuItemIcon(color, item);
            tintShareIconIfPresent(color, item);
        }
    }

    private static void tintMenuItemIcon(int color, MenuItem item) {
        final Drawable drawable = item.getIcon();
        if (drawable != null) {
            final Drawable wrapped = DrawableCompat.wrap(drawable);
            drawable.mutate();
            DrawableCompat.setTint(wrapped, color);
            item.setIcon(drawable);
        }
    }

    private static void tintShareIconIfPresent(int color, MenuItem item) {
        if (item.getActionView() != null) {
            final View actionView = item.getActionView();
            final View expandActivitiesButton = actionView.findViewById(R.id.expand_activities_button);
            if (expandActivitiesButton != null) {
                final ImageView image = (ImageView) expandActivitiesButton.findViewById(R.id.image);
                if (image != null) {
                    final Drawable drawable = image.getDrawable();
                    final Drawable wrapped = DrawableCompat.wrap(drawable);
                    drawable.mutate();
                    DrawableCompat.setTint(wrapped, color);
                    image.setImageDrawable(drawable);
                }
            }
        }
    }
}

Điều này sẽ không quan tâm đến việc tràn, nhưng đối với điều đó, bạn có thể làm điều này:

Bố trí:

<android.support.v7.widget.Toolbar
    ...
    android:theme="@style/myToolbarTheme" />

Kiểu dáng:

<style name="myToolbarTheme">
        <item name="colorControlNormal">#FF0000</item>
</style>

Điều này hoạt động như appcompat v23.1.0.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.