MenuItem tinting บน AppCompat Toolbar


95

เมื่อฉันใช้ drawables จากAppCompatไลบรารีสำหรับToolbarรายการเมนูของฉันการย้อมสีจะทำงานตามที่คาดไว้ แบบนี้:

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

แต่ถ้าฉันใช้ drawables ของฉันเองหรือแม้แต่คัดลอก drawables จากAppCompatไลบรารีไปยังโปรเจ็กต์ของฉันเองมันจะไม่ย้อมสี

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

มีเวทมนตร์พิเศษบางอย่างในAppCompat Toolbarสีที่วาดได้จากห้องสมุดนั้นหรือไม่? มีวิธีใดบ้างที่จะทำให้สิ่งนี้ใช้งานได้กับ drawables ของฉันเอง?

เรียกใช้สิ่งนี้บนอุปกรณ์ API ระดับ 19 ด้วยcompileSdkVersion = 21และtargetSdkVersion = 21และยังใช้ทุกอย่างจากAppCompat

abc_ic_clear_mtrl_alpha_copyเป็นสำเนาที่ถูกต้องของabc_ic_clear_mtrl_alphapng จากAppCompat

แก้ไข:

การย้อมสีจะขึ้นอยู่กับค่าที่ฉันตั้งไว้android:textColorPrimaryในธีมของฉัน

เช่น<item name="android:textColorPrimary">#00FF00</item>จะให้สีโทนเขียวแก่ฉัน

ภาพหน้าจอ

การย้อมสีทำงานตามที่คาดไว้ด้วย AppCompat การย้อมสีทำงานตามที่คาดไว้ด้วย AppCompat

การย้อมสีไม่ทำงานกับ drawable ที่คัดลอกมาจาก AppCompat การย้อมสีไม่ทำงานกับ drawable ที่คัดลอกมาจาก AppCompat


ทั้งสองสไตล์มีพ่อแม่เดียวกันหรือไม่? จะเป็นอย่างไรถ้าคุณขยายสไตล์ด้านบนด้วยของคุณเอง?
G_V

ไม่มีความแตกต่างในรูปแบบ ข้อแตกต่างเพียงอย่างเดียวคือสิ่งที่วาดได้ซึ่งเป็นไฟล์. png ทั้งคู่
greve

สิ่งที่วาดได้ดูเหมือนสำเนาที่แน่นอนของ AppCombat ดั้งเดิมที่วาดได้ในรหัส?
G_V

เป็นไฟล์ png ซึ่งฉันคัดลอกมา พวกเขาเหมือนกันทุกประการ
greve

แล้วโค้ดของคุณแตกต่างจากต้นฉบับตรงไหนถ้ามีรูปแบบเดียวกันและรูปภาพเดียวกัน?
G_V

คำตอบ:


31

เพราะถ้าคุณดูซอร์สโค้ดของ TintManager ใน AppCompat คุณจะเห็น:

/**
 * 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
};

ซึ่งค่อนข้างมากหมายความว่าพวกเขามี resourceIds เฉพาะที่จะย้อมสี

แต่ฉันเดาว่าคุณสามารถเห็นได้เสมอว่าพวกเขากำลังย้อมสีภาพเหล่านั้นอย่างไรและทำแบบเดียวกัน ง่ายพอ ๆ กับการตั้งค่า ColorFilter บน drawable


ฮึนั่นคือสิ่งที่ฉันกลัว ฉันไม่พบซอร์สโค้ดสำหรับ AppCompat ใน SDK นั่นคือสาเหตุที่ฉันไม่พบส่วนนี้ด้วยตัวเอง เดาว่าฉันจะต้องเข้าไปดูใน googlesource.com ขอบคุณ!
greve

8
ฉันรู้ว่ามันเป็นคำถามที่จับต้องได้ แต่ทำไมถึงมีไวท์ลิสต์? หากสามารถย้อมสีด้วยไอคอนเหล่านี้ทำไมเราไม่สามารถย้อมสีไอคอนของเราเองได้? นอกจากนี้จุดสำคัญในการทำให้เกือบทุกอย่างเข้ากันได้แบบย้อนหลัง (ด้วย AppCompat) เมื่อคุณละทิ้งสิ่งที่สำคัญที่สุดอย่างหนึ่งนั่นคือการมีไอคอนแถบการกระทำ (พร้อมสีที่กำหนดเอง)
Zsolt Safrany

1
มีปัญหานี้ในเครื่องมือติดตามปัญหาของ Google ที่ทำเครื่องหมายว่าแก้ไขแล้ว แต่ไม่ได้ผลสำหรับฉัน แต่คุณสามารถติดตามได้ที่นี่: issueetracker.google.com/issues/37127128
niknetniko

พวกเขาอ้างว่าสิ่งนี้ได้รับการแก้ไขแล้ว แต่ไม่ใช่ เอ้ยฉันเกลียดเอ็นจิ้นธีม Android, AppCompat และอึทั้งหมดที่เกี่ยวข้องกับมัน ใช้งานได้กับแอปตัวอย่าง "Github repo browser" เท่านั้น
Martin Marconcini

98

หลังจากไลบรารี Support v22.1 ใหม่คุณสามารถใช้สิ่งที่คล้ายกับสิ่งนี้:

  @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
ฉันจะบอกว่าในกรณีนี้ความเก่าsetColorFilter()เป็นวิธีที่ดีกว่า
Natario

@mvai ทำไม setColorFilter () ถึงสั่งล่วงหน้าได้มากกว่า?
wilddev

4
@wilddev กะทัดรัด ทำไมต้องกังวลกับคลาส DrawableCompat ที่สนับสนุนเมื่อคุณสามารถไปที่ menu.findItem (). getIcon (). setColorFilter ()? ซับหนึ่งเส้นและชัดเจน
นาตาริโอ

5
อาร์กิวเมนต์หนึ่งซับไม่เกี่ยวข้องเมื่อคุณแยกตรรกะทั้งหมดออกเป็นวิธี TintingUtils.tintMenuIcon (... ) ของคุณเองหรืออะไรก็ตามที่คุณต้องการเรียกมัน หากคุณจำเป็นต้องเปลี่ยนหรือปรับตรรกะในอนาคตคุณทำได้ในที่เดียวไม่ใช่ทั้งหมดในแอปพลิเคชัน
Dan Dar3

ไม่จำเป็นต้องใช้ลอจิกที่แตกต่างกันในการย้อมสีและไม่จำเป็นต้องไม่เปลี่ยนแปลงในอนาคต
Jemshit Iskenderov

84

การตั้งค่าColorFilter(tint) บน a MenuItemนั้นง่ายมาก นี่คือตัวอย่าง:

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);
}

โค้ดด้านบนมีประโยชน์มากหากคุณต้องการรองรับธีมที่แตกต่างกันและคุณไม่ต้องการมีสำเนาเพิ่มเติมเพียงเพื่อให้ได้สีหรือความโปร่งใส

คลิกที่นี่เพื่อดูคลาสตัวช่วยเพื่อตั้งค่าColorFilterบน drawables ทั้งหมดในเมนูรวมถึงไอคอนโอเวอร์โฟลว์

ในonCreateOptionsMenu(Menu menu)เพียงโทรMenuColorizer.colorMenu(this, menu, color);หลังจากพองเมนูและ voila ของคุณ ไอคอนของคุณถูกย้อมสี


4
ฉันเอาหัวโขกกับโต๊ะทำงานพยายามหาสาเหตุว่าทำไมไอคอนทั้งหมดของฉันถึงถูกย้อมสีขอบคุณที่แจ้งให้ทราบเกี่ยวกับ drawable.mutate ()!
Scott Cooper

51

app:iconTintแอ็ตทริบิวต์ถูกนำมาใช้SupportMenuInflaterจากไลบรารีการสนับสนุน (อย่างน้อยใน 28.0.0)

ทดสอบสำเร็จด้วย API 15 ขึ้นไป

ไฟล์ทรัพยากรเมนู:

<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>

(ในกรณีนี้?attr/appIconColorEnabledคือแอตทริบิวต์สีที่กำหนดเองในธีมของแอปและทรัพยากรไอคอนเป็นแบบเวกเตอร์ที่วาดได้)


5
นี่ควรเป็นคำตอบใหม่ที่ได้รับการยอมรับ! นอกจากนี้โปรดทราบandroid:iconTintและandroid:iconTintModeอย่าทำงาน แต่ใช้คำนำหน้าapp:แทนandroid:งานเหมือนเสน่ห์ (บนเวกเตอร์ drawables ของฉันเอง API> = 21)
Sebastiaan Alvarez Rodriguez

หากเรียกโปรแกรม: ทราบว่าSupportMenuInflaterจะไม่ใช้ตรรกะกำหนดเองใด ๆ ถ้าเป็นเมนูที่ไม่SupportMenuเหมือนก็แค่อยู่กลับไปยังปกติMenuBuilder MenuInflater
geekley

ในกรณีนี้การใช้งานAppCompatActivity.startSupportActionMode(callback)และการใช้งานการสนับสนุนที่เหมาะสมจากandroidx.appcompatจะถูกส่งไปยังการเรียกกลับ
geekley

30

ฉันชอบแนวทางนี้เป็นการส่วนตัวจากลิงค์นี้

สร้างเค้าโครง XML ดังต่อไปนี้:

<?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"/>

และอ้างอิงจากเมนูของคุณ:

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

2
แม้ว่าลิงก์นี้อาจตอบคำถามได้ แต่ควรรวมส่วนสำคัญของคำตอบไว้ที่นี่และระบุลิงก์เพื่อการอ้างอิง คำตอบแบบลิงก์เท่านั้นอาจไม่ถูกต้องหากหน้าที่เชื่อมโยงเปลี่ยนไป
tomloprod

ขอบคุณสำหรับความคิดเห็นของคุณฉันได้แก้ไขคำถามแล้ว @tomloprod
N Jay

4
นี่คือทางออกที่ฉันต้องการ อย่างไรก็ตามสิ่งสำคัญคือต้องทราบว่าในตอนนี้โซลูชันนี้ดูเหมือนจะไม่ได้ผลเมื่อคุณใช้ประเภทเวกเตอร์ที่วาดได้ใหม่เป็นแหล่งที่มา
Michael De Soto

1
@haagmm โซลูชันนี้ต้องการ API> = 21 นอกจากนี้ยังใช้ได้กับเวกเตอร์ด้วย
Neurotransmitter

1
bitmapและก็ไม่ควรทำงานกับเวกเตอร์แท็กราก มีวิธีอื่นในการระบายสีเวกเตอร์ บางทีอาจมีใครบางคนเพิ่มสีเวกเตอร์ที่นี่ด้วยก็ได้ ...
milosmns

11

ที่สุดของการแก้ปัญหาในหัวข้อนี้อย่างใดอย่างหนึ่งใช้ API MenuItemใหม่หรือการใช้การสะท้อนหรือใช้การค้นหามุมมองอย่างเข้มข้นที่จะได้รับที่สูงเกินจริง

อย่างไรก็ตามมีวิธีการที่หรูหรากว่าในการทำเช่นนั้น คุณต้องมีแถบเครื่องมือที่กำหนดเองเนื่องจากกรณีการใช้งาน "ใช้โทนสีที่กำหนดเอง" ของคุณไม่สามารถใช้งานได้ดีกับ API รูปแบบ / ธีมสาธารณะ

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)
        );
    }

}

ตรวจสอบให้แน่ใจว่าคุณโทรในรหัสกิจกรรม / ชิ้นส่วน:

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

ไม่มีการสะท้อนไม่มีการค้นหามุมมองและรหัสไม่มากนักใช่มั้ย?

และตอนนี้คุณสามารถเพิกเฉยต่อสิ่งไร้สาระonCreateOptionsMenu/onOptionsItemSelectedได้


ในทางเทคนิคคุณกำลังทำการค้นหามุมมอง คุณกำลังทำซ้ำมุมมองและตรวจสอบให้แน่ใจว่าไม่มีค่าว่าง ;)
Martin Marconcini

คุณคิดถูกอย่างแน่นอน :-) อย่างไรก็ตามMenu#getItem()ความซับซ้อนคือ O (1) ในแถบเครื่องมือเนื่องจากรายการถูกเก็บไว้ใน ArrayList ซึ่งแตกต่างจากการส่งView#findViewByIdผ่าน (ซึ่งฉันเรียกว่าการดูการค้นหาในคำตอบของฉัน) ความซับซ้อนซึ่งห่างไกลจากค่าคงที่ :-)
Drew

เห็นด้วยที่จริงฉันทำสิ่งที่คล้ายกันมาก ฉันยังคงตกใจที่ Android ไม่ได้ปรับปรุงประสิทธิภาพทั้งหมดนี้หลังจากผ่านไปหลายปี…
Martin Marconcini

ฉันจะเปลี่ยนไอคอนโอเวอร์โฟลว์และสีของไอคอนแฮมเบอร์เกอร์ด้วยวิธีนี้ได้อย่างไร
Sandra

8

นี่คือวิธีแก้ปัญหาที่ฉันใช้ คุณสามารถเรียกมันหลังจาก onPrepareOptionsMenu () หรือสถานที่ที่เทียบเท่า สาเหตุของการกลายพันธุ์ () คือถ้าคุณใช้ไอคอนในตำแหน่งมากกว่าหนึ่งแห่ง หากไม่มีการกลายพันธุ์พวกเขาทั้งหมดจะใช้สีเดียวกัน

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);
                }
            }
        }
    }
}

สิ่งนี้จะไม่ดูแลการล้น แต่คุณสามารถทำได้:

เค้าโครง:

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

รูปแบบ:

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

ใช้งานได้กับ appcompat v23.1.0


2

สิ่งนี้ใช้ได้ผลสำหรับฉัน:

override fun onCreateOptionsMenu(menu: Menu?): Boolean {

        val inflater = menuInflater
        inflater.inflate(R.menu.player_menu, menu)

        //tinting menu item:
        val typedArray = theme.obtainStyledAttributes(IntArray(1) { android.R.attr.textColorSecondary })
        val textColor = typedArray.getColor(0, 0)
        typedArray.recycle()

        val item = menu?.findItem(R.id.action_chapters)
        val icon = item?.icon

        icon?.setColorFilter(textColor, PorterDuff.Mode.SRC_IN);
        item?.icon = icon
        return true
    }

หรือคุณสามารถใช้ tint ใน xml ที่วาดได้:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:tint="?android:textColorSecondary"
    android:viewportWidth="384"
    android:viewportHeight="384">
    <path
        android:fillColor="#FF000000"

        android:pathData="M0,277.333h384v42.667h-384z" />
    <path
        android:fillColor="#FF000000"
        android:pathData="M0,170.667h384v42.667h-384z" />
    <path
        android:fillColor="#FF000000"
        android:pathData="M0,64h384v42.667h-384z" />
</vector>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.