如何正确绘制属于 ViewGroup 的 Android FloatingActionButton 实例

How to properly draw Android FloatingActionButton instances that are part of a ViewGroup

我正在尝试构建一个 Android 自定义视图,其中包含多个 FloatingActionButton(命名空间 android.support.design.widget)实例,这些实例排列成一个圆圈。

为此,我创建了一个继承自 ViewGroup 的新视图。代码如下所示:

package myapp;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ImageView;

import myapp.R;


public class ButtonOverlayView extends ViewGroup
{
  private final float _radius = 200.0f;
  private int _desiredSize;


  public ButtonOverlayView(Context context)
  {
    super(context);
    initializeViewGroup(context);
  }


  public ButtonOverlayView(Context context, AttributeSet attrs)
  {
    super(context, attrs);
    initializeViewGroup(context);
  }


  public ButtonOverlayView(Context context, AttributeSet attrs, int defStyle)
  {
    super(context, attrs, defStyle);
    initializeViewGroup(context);
  }


  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  {
    _desiredSize = 600;
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(_desiredSize, _desiredSize);
  }


  @Override
  protected void onLayout(boolean b, int i, int i1, int i2, int i3)
  {
    layoutChildren();
  }


  private void initializeViewGroup(Context context)
  {
    createChildren(context, getIconIdentifiers(), getColorIdentifiers());
  }


  private void createChildren(Context context, int[] iconIdentifiers, int[] colorIdentifiers)
  {
    for(int i = 0; i < iconIdentifiers.length; i++)
    {
      final FloatingActionButton button = new FloatingActionButton(context);
      button.setImageResource(iconIdentifiers[i]);
      button.setSize(FloatingActionButton.SIZE_NORMAL);
      button.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
      button.setBackgroundTintList(ColorStateList.valueOf(colorIdentifiers[i]));
      button.setClickable(true);
      addView(button);
    }
  }


  private void layoutChildren()
  {
    int buttonCount      = getChildCount();
    int center           = _desiredSize / 2;
    float angle          = 0.0f;
    float angleIncrement = 360.0f / (buttonCount - 1);

    FloatingActionButton button = (FloatingActionButton)getChildAt(0);
    int halfWidth  = button.getMeasuredWidth()  / 2;
    int halfHeight = button.getMeasuredHeight() / 2;
    button.layout(center - halfWidth, center - halfHeight, center + halfWidth, center + halfHeight);

    for(int i = 1; i < buttonCount; i++)
    {
      button         = (FloatingActionButton)getChildAt(i);
      halfWidth      = button.getMeasuredWidth() / 2;
      halfHeight     = button.getMeasuredHeight() / 2;
      double radians = Math.toRadians(angle);
      int x          = (int)(Math.cos(radians) * _radius) + center;
      int y          = (int)(Math.sin(radians) * _radius) + center;
      button.layout(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight);
      angle += angleIncrement;
    }
  }


  private int[] getIconIdentifiers()
  {
    final TypedArray icons = getResources().obtainTypedArray(R.array.icons);
    int[] iconIdentifiers  = new int[icons.length()];

    try
    {
      for(int i = 0; i < icons.length(); i++)
      {
        iconIdentifiers[i] = icons.getResourceId(i, -1);
      }
    }

    finally
    {
      icons.recycle();
    }

    return iconIdentifiers;
  }


  private int[] getColorIdentifiers()
  {
    final TypedArray colors = getResources().obtainTypedArray(R.array.colors);
    int[] colorIdentifiers  = new int[colors.length()];

    try
    {
      for(int i = 0; i < colors.length(); i++)
      {
        colorIdentifiers[i] = colors.getResourceId(i, -1);
      }
    }

    finally
    {
      colors.recycle();
    }

    return colorIdentifiers;
  }
}

FloatingActionButton 的图标和颜色在专用 xml 文件中提供,其内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <array name="icons">
    <item>@android:drawable/ic_delete</item>
    <item>@android:drawable/ic_input_add</item>
    <item>@android:drawable/ic_menu_call</item>
    <item>@android:drawable/ic_delete</item>
    <item>@android:drawable/ic_input_add</item>
    <item>@android:drawable/ic_menu_call</item>
    <item>@android:drawable/ic_delete</item>
  </array>
  <array name="colors">
    <item>@color/colorPrimary</item>
    <item>@color/colorPrimary</item>
    <item>@color/colorPrimary</item>
    <item>@color/colorPrimary</item>
    <item>@color/colorPrimary</item>
    <item>@color/colorPrimary</item>
    <item>@color/colorAccent2</item>
  </array>
</resources>

从技术上讲,这一切都有效,即它在集成到 Android 应用程序时编译并显示。但是,渲染 FloatingActionButton 个实例时看起来 "strange".

下面的截图说明了我的意思 "strange":

Phone 1 (Android 8.1):

左边的截图显示所有按钮都处于未点击状态,而右边的截图显示右下角的按钮被点击。

Phone 2 (Android 9):

同上,左边为未点击状态,右边为点击右下按钮。

有人能解释为什么这些按钮看起来 "strange" 吗?我该如何解决这个问题?

编辑

受到 this SO 问题的启发,我仔细查看了我正在使用的依赖项,如下所示:

com.android.support:appcompat-v7:25.3.1
com.android.support:support-v4:25.3.1
com.android.support:support-annotations:+
com.android.support:design:25.3.1
com.android.support:support-vector-drawable:25.3.1

因为它可能是那些库中的一个错误,所以我将它们升级到 28.0.0,但光学结果仍然与上面的屏幕截图相同。

好的,我找到问题了。屏幕截图中不太明显的是按钮实际上是透明的。发生这种情况是因为 getColorIdentifiers 没有从 xml 文件中正确检索颜色。

因此,方法 setBackgroundTintList 使按钮变得透明。解决办法是修正方法如下:

private int[] getColorIdentifiers()
{
  return getContext().getResources().getIntArray(R.array.colors);
}