Android底部导航栏:一个选项卡,具有不同的未选中/选中颜色。

8

我正在尝试匹配这样的设计...

enter image description here

请注意,“选定标签颜色色调”是蓝色的,但中心标签的图标始终应该是中间带有白色时钟的绿色圆圈。
我尝试了很多方法。首先尝试通过使用一个具有绿色圆圈和时钟PNG资源的层列表XML资源来进行编程,但根本没有起作用。然后我只是让设计师给我完整的图标(时钟和绿色圆圈),但现在我遇到了这个问题...
(未选择)

enter image description here

(已选择)

enter image description here

我在谷歌上搜索相关术语以解决问题时失败了。

最终,我需要所选标签的颜色为蓝色,但是中心标签图标必须始终保持实际图标而没有其他着色(基本上它需要看起来与 .png 图片完全相同)。

PS:我正在使用 Xamarin.Forms、FreshMvvm 和 FreshTabbedFONavigationContainer。然而,通过 Renderer,我可以直接访问 BottomNavigationView 和所有其他本机 Android 组件。因此,解决方案不必是 Xamarin 解决方案。Java/Kotlin 解决方案也可以,我只需将其转换为 Xamarin。

======================

编辑:

======================

所以我使用Andres Castro下面的代码取得了更多进展,但我仍然遇到了之前的问题。使用Andres的代码,我切换回使用FontAwesome作为图标(这非常好用),但这样做意味着我需要使用LayerDrawable来创建圆形/图标中心选项卡图标。
所以这是我目前拥有的... 未选中的中心图标

enter image description here

选择的中心图标

enter image description here

如您所见,当中心图标未被选中时,它仍然是灰色的;而当被选中时,它会变成蓝色(与其他四个图标的正确选中/未选中颜色相同)。

以下是我目前针对中心图标的代码:

UpdateTabbedIcons

private void UpdateTabbedIcons()
{
    for (var i = 0; i < Element.Children.Count; i++) {
        var tab = _bottomNavigationView.Menu.GetItem(i);

        var element = Element.Children[i];
        if (element is NavigationPage navigationPage) {
            //if the child page is a navigation page get its root page
            element = navigationPage.RootPage;
        }

        UpdateTabIcon(tab, element);
    }
}

UpdateTabIcon

public void UpdateTabIcon(IMenuItem menuItem, Page page)
{
    var icon = page?.Icon;
    if (icon == null) return;

    var drawable = new IconDrawable(Context, icon, "fa-regular-pro-400.ttf");

    var element = Element.CurrentPage;
    if (element is NavigationPage navigationPage) {
        //if the child page is a navigation page get its root page
        element = navigationPage.RootPage;
    }

    if (page is DoNowTabPage) { //Page for center icon
        drawable.Color(Helpers.Resources.White.ToAndroid());
        var finalDrawable = GetCombinedDrawable(drawable);
        menuItem.SetIcon(finalDrawable);
        return;
    } else if (element == page) {
        drawable.Color(BarSelectedItemColor.ToAndroid());
    } else {
        drawable.Color(BarItemColor.ToAndroid());
    }

    menuItem.SetIcon(drawable);
}

获取组合绘制对象

private Drawable GetCombinedDrawable(IconDrawable iconDrawable)
{
    var displayMetrics = Resources.DisplayMetrics;

    GradientDrawable circleDrawable = new GradientDrawable();
    circleDrawable.SetColor(Helpers.Resources.Green.ToAndroid());
    circleDrawable.SetShape(ShapeType.Oval);
    circleDrawable.SetSize((int)TypedValue.ApplyDimension(ComplexUnitType.Dip, 500, displayMetrics), (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, 500, displayMetrics));
    circleDrawable.Alpha = 1;

    var inset = (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, 140, displayMetrics);
    var bottomInset = (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, 40, displayMetrics);
    LayerDrawable finalDrawable = new LayerDrawable(new Drawable[] { circleDrawable, iconDrawable });
    finalDrawable.SetLayerHeight(1, iconDrawable.IntrinsicHeight);
    finalDrawable.SetLayerWidth(1, iconDrawable.IntrinsicWidth);
    finalDrawable.SetLayerInset(1, inset, inset, inset, inset + bottomInset);
    finalDrawable.SetLayerInsetBottom(0, bottomInset);
    finalDrawable.ClearColorFilter();

    return finalDrawable;
}

正如您在我创建的GradientDrawable中所看到的那样,我将其颜色设置为我的绿色(我有一个名为Resources的自定义类,不是Android的Resources类)。

这就是我卡住的地方。我将圆形可绘制对象设置为绿色,但一旦进入BottomNavigationView,它的颜色总是与其他图标的未选中/选中颜色匹配。

希望能解决这个最后的问题。感谢任何帮助。


一个简单的解决方案是在它们之间使用一个空白项,并在BottomNavigationView上放置一个单独的ImageView,使用绿色图像。 - Shivam Yadav
1
为什么不根据底部导航视图的索引编程更改所选的色调/颜色? - Saamer
1
@Saamer,您的评论看起来很简单哈。能否将其发布为答案,以便我能授予奖励?这是我使用的代码... _bottomNavigationView.ItemIconTintList = null; - Ryan Alford
这对你有用吗?BottomNavigationView 比其 iOS 实现更加困难。我进行了一些研究,看看你所要求的是否可能,在我看到 Android 原生时,开始考虑实现的方法。 - Saamer
1
是的,它起作用了。我不确定,但似乎我的更改会重置所有项目图标色调,但实际上并没有。因为我每次都手动设置它们,所以所有其他选项卡仍然有效,而中心在选择和取消选择时都是绿色的。 - Ryan Alford
5个回答

2

由于您可以访问底部导航视图,因此每次切换页面时都可以“重新绘制”底部工具栏。我们做了类似的事情,并没有注意到任何性能问题。

首先,您需要通过订阅OnElementChanged内的页面更改来监视页面更改。

Element.CurrentPageChanged += ElementOnCurrentPageChanged;

然后在ElementOnCurrentPageChanged中,您可以遍历每个页面并获取该页面的菜单项。

private void UpdateTabbedIcons()
{
    for (var i = 0; i < Element.Children.Count; i++)
    {
        var tab = _bottomNavigationView.Menu.GetItem(i);

        var element = Element.Children[i];
        if (element is NavigationPage navigationPage)
        {
            //if the child page is a navigation page get its root page
            element = navigationPage.RootPage;
        }

        UpdateTabIcon(tab, element);
    }
}

在我们的情况下,我们使用了字体图标,因此每次绘制图标并设置颜色。

public void UpdateTabIcon(IMenuItem menuItem, Page page)
{
    var icon = page?.Icon?.File;
    if (icon == null) return;

    var drawable = new IconDrawable(Context, "FontAwesome.ttf", icon).SizeDp(20);

    var element = Element.CurrentPage;
    if (element is NavigationPage navigationPage)
    {
        //if the child page is a navigation page get its root page
        element = navigationPage.RootPage;
    }

    if (element == page)
    {
        drawable.Color(BarSelectedItemColor.ToAndroid());
    }
    else
    {
        drawable.Color(BarItemColor.ToAndroid());
    }

    menuItem.SetIcon(drawable);
}

我们还需要重写OnAttachedToWindow方法,以确保在从不同状态返回应用程序时重新绘制图标。
protected override void OnAttachedToWindow()
{
    UpdateTabbedIcons();

    base.OnAttachedToWindow();
}

你需要根据你的使用情况进行一些修改,但我认为这种方法应该能够实现你想要的功能。


太棒了。我今晚会试一下。如果你有使用FontAwesome(或其他矢量图标字体)为BottomNavigationView编写的工作代码,你应该在某个地方发布一篇博客文章。我找不到任何示例,我的尝试也无法显示图标。 - Ryan Alford

1

BottomNavigationView比其iOS实现困难得多。我进行了一些研究,看看你所问的是否可能,当我在Android原生中看到它时,开始思考如何实现它。

要实现您的最后一个挑战,您需要根据底部导航视图的索引每次以编程方式更改所选的色调/颜色。


1
你可以使用SVG图像,并创建自己的颜色属性,为中心图标以及其他底部导航视图图标制作可绘制选择器,如下所示:
在colors.xml文件中。
    <color name="color_bottom_selected">#44C8F5</color>
 <color name="color_bottom_unselected">#c0c0c0</color>

在attrs.xml文件中。
   <attr name="color_bottom_unselected" format="color" />
    <attr name="color_bottom_selected" format="color" />

在SVG图像文件中,用您的属性替换硬编码的颜色值。
android:fillColor="#fff" to android:fillColor="?attr/color_bottom_unselected"

不要忘记将选择器设置为可绘制的。

1
尝试使用色彩模式DST,它将简单地忽略色彩。
将此行添加到您的方法GetCombinedDrawable()中。
circleDrawable.setTintMode(PorterDuff.Mode.DST);

如果这不起作用,请尝试这个:

finalDrawable.ClearColorFilter();
finalDrawable.setTintMode(PorterDuff.Mode.DST);

我实际上尝试了所有的PorterDuff.Mode选项,但都没有起作用。 - Ryan Alford

0

我可以帮你处理这个问题:

public class HomeMenuTabLayout extends TabLayout {

public static final int HOME_MENU_TABLAYOUT_COUNT = 5;

public static final int HOME_MENU_LIVE_INDEX = 0;
public static final int HOME_MENU_TEAM_INDEX = 1;
public static final int HOME_MENU_ADS_INDEX = 2;
public static final int HOME_MENU_WALLET_INDEX = 3;
public static final int HOME_MENU_POST_INDEX = 4;

public int selectedIndex = 0;
private TextView unread;

public HomeMenuTabLayout(Context context) {
    super(context);
    initItems(context);
}

public HomeMenuTabLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initItems(context);
}

public HomeMenuTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initItems(context);
}

public void initItems(Context context) {
    for (int i = 0; i < HOME_MENU_TABLAYOUT_COUNT; i++) {
        addTab(newTab());
    }

    for (int i = 0; i < HOME_MENU_TABLAYOUT_COUNT; i++) {
        TabLayout.Tab tab = this.getTabAt(i);
        tab.setCustomView(getTabView(context, i, false));
    }
}

public void setTagIndex(Context context, int index) {
    getTabView(context, selectedIndex, false);
    selectedIndex = index;
    getTabView(context, selectedIndex, true);
}

private View getTabView(Context context, int index, boolean selected) {
    View v = null;
    TabLayout.Tab tab = this.getTabAt(index);
    if (tab != null) {
        v = tab.getCustomView();
        if (v == null) {
            v = LayoutInflater.from(context).inflate(R.layout.activity_main_tab_layout, null);
        }
    }

    if (v == null) {
        return null;
    }

    ImageView img = v.findViewById(R.id.tablayout_image);
    int color = 0;
    int color2 = 0;
    if (selected) {
        color = getResources().getColor(R.color.corn_flower_blue);
        color2 = getResources().getColor(R.color.dark_sky_blue_three);
        TmlyUtils.displayViewWithZoom(img);
    } else {
        color = getResources().getColor(R.color.battleship_grey_dark);
        color2 = getResources().getColor(R.color.battleship_grey_dark);
    }

    switch (index) {
        case HOME_MENU_ADS_INDEX:
            Bitmap offers = StyleKit.imageOfTabbarSearchActive(color, color2);
            img.setImageBitmap(offers);
            break;
        case HOME_MENU_LIVE_INDEX:

            Bitmap live = StyleKit.imageOfTabbarHomeActive(color, color2);
            img.setImageBitmap(live);
            unread = v.findViewById(R.id.tablayout_unread);
            break;
        case HOME_MENU_TEAM_INDEX:
            Bitmap team = StyleKit.imageOfTabbarSocialActive(color, color2);
            img.setImageBitmap(team);
            break;
        case HOME_MENU_WALLET_INDEX:
            Bitmap wallet = StyleKit.imageOfTabbarCaddyActive(color, color2);
            img.setImageBitmap(wallet);
            break;
        case HOME_MENU_POST_INDEX:
            Bitmap chat = StyleKit.imageOfTabbarPlusActive(getResources().getColor(R.color.white), getResources().getColor(R.color.white));
            img.setImageBitmap(chat);
            View cirle = v.findViewById(R.id.tablayout_circle);
            cirle.setVisibility(View.VISIBLE);
            break;
        default:
            break;
    }
    return v;
}
}

这是一个自定义的TabLayout,你可以看到我扩展了TabLayout类,当TabLayout被创建时,我调用initItems方法添加标签并设置自定义视图。

getTabView返回充气的视图,就像你可以看到的这样

 LayoutInflater.from(context).inflate(R.layout.activity_main_tab_layout, null);

如果你需要的话
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="@dimen/tab_bar_height">

    <ImageView
        android:id="@+id/tablayout_circle"
        android:layout_width="@dimen/tab_bar_height"
        android:layout_height="@dimen/tab_bar_height"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:background="@drawable/circle_blue_gradient"
        android:visibility="gone"
        tools:visibility="visible" />

    <ImageView
        android:id="@+id/tablayout_image"
        android:layout_width="@dimen/tab_bar_icon_favorite_height"
        android:layout_height="@dimen/tab_bar_icon_favorite_height"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />

</RelativeLayout>

在填充视图之后,您可以使用以下方法获取视图元素:

  ImageView img = v.findViewById(R.id.tablayout_image);

您可以使用布尔值selected来检查视图是否被选中,对于您的情况,当索引为中心时,需要忽略颜色切换。

只有一件事,当您点击TabLayout图标时,请不要忘记调用

setTagIndex();

如果不这样做,图像将不会重新绘制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接