Radial GradientDrawable兼容問題

Radial GradientDrawable兼容問題

在android中可以使用<shape/>標簽方便實現圖形Drawable,<shape/>標簽對應的class類是GradientDrawable。如果要實現一個圓形的陰影效果,可以使用下面的xml代碼:

<!-- radial shadow drawable -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="oval">
    <gradient
        android:centerX="0.5"
        android:centerY="0.5"
        android:endColor="@android:color/transparent"
        android:gradientRadius="50"
        android:startColor="#2b2b2b"
        android:type="radial"/>
</shape>

<gradient/>標簽實現了漸變效果,android:type="radial"表示圓形類型,另外還有linear,sweeplinear表示線性漸變,sweep則是類似雷達掃描的漸變。這里只探討radial類型的情況。

android:gradientRadius是漸變半徑值,必須設置該值才能實現圓形漸變效果。而不同SDK API版本下從xml解析該屬性值的實現不同,也導致了本文所說的兼容性問題

Drawable Xml資源文件解析過程一文中,xml的解析最后會在drawable.inflate()中完成。下面是GradientDrawable.inflate()方法的實現,這里只關注對android:gradientRadius的解析。

// SDK 19 KITKAT 4.4
// xml解析
TypedValue tv = a.peekValue(com.android.internal.R.styleable.GradientDrawableGradient_gradientRadius);
if (tv != null) {
  boolean radiusRel = tv.type == TypedValue.TYPE_FRACTION;
  st.mGradientRadius = radiusRel ? 
          tv.getFraction(1.0f, 1.0f) : tv.getFloat();
} else if (gradientType == RADIAL_GRADIENT) {
  throw new XmlPullParserException(a.getPositionDescription() 
      + "<gradient> tag requires 'gradientRadius' " 
      + "attribute with radial type");
}
// 繪制
mFillPaint.setShader(new RadialGradient(x0, y0,
                            level * st.mGradientRadius, colors, null,
                            Shader.TileMode.CLAMP));
/*
可以看到,如果type設置為radial,但沒有設置gradientRadius會拋出異常;
而gradientRadius可以使用Fraction、Float格式,并且直接以px單位使用了該值。
雖然可以使用Fraction,但并不支持*%,*%p格式。
*/
// SDK 21 LOLLIPOP 5.0
// xml解析
final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
if (tv != null) {
    final float radius;
    final int radiusType;
    if (tv.type == TypedValue.TYPE_FRACTION) {
        radius = tv.getFraction(1.0f, 1.0f);
        final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT)
                            & TypedValue.COMPLEX_UNIT_MASK;
        if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) {
            radiusType = RADIUS_TYPE_FRACTION_PARENT;
        } else {
            radiusType = RADIUS_TYPE_FRACTION;
        }
    } else {
        radius = tv.getDimension(r.getDisplayMetrics());
        radiusType = RADIUS_TYPE_PIXELS;
    }

    st.mGradientRadius = radius;
    st.mGradientRadiusType = radiusType;
} else if (st.mGradient == RADIAL_GRADIENT) {
        throw new XmlPullParserException(
                        a.getPositionDescription()
                        + "<gradient> tag requires 'gradientRadius' "
                        + "attribute with radial type");
}
// 繪制
float radius = st.mGradientRadius;
if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) {
        radius *= Math.min(st.mWidth, st.mHeight);
} else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) {
        radius *= Math.min(r.width(), r.height());
}
if (st.mUseLevel) {
        radius *= getLevel() / 10000.0f;
}
mGradientRadius = radius;
/*
5.0中gradientRadius可以使用Fraction、Dimension格式,并且真正支持了Fraction百分比格式(*%, *%p)。
但卻不能使用Float格式,Float格式將被解析為Dimension,導致效果不符合預期,5.1中解決了這個問題。
*/
// SDK 22 LOLLIPOP_MR1 5.1
// xml解析
final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
if (tv != null) {
        final float radius;
        final int radiusType;
        if (tv.type == TypedValue.TYPE_FRACTION) {
                radius = tv.getFraction(1.0f, 1.0f);
                final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT)
                            & TypedValue.COMPLEX_UNIT_MASK;
                if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) {
                        radiusType = RADIUS_TYPE_FRACTION_PARENT;
                } else {
                        radiusType = RADIUS_TYPE_FRACTION;
                }
        } else if (tv.type == TypedValue.TYPE_DIMENSION) {
                radius = tv.getDimension(r.getDisplayMetrics());
                radiusType = RADIUS_TYPE_PIXELS;
        } else {
                radius = tv.getFloat();
                radiusType = RADIUS_TYPE_PIXELS;
        }
        st.mGradientRadius = radius;
        st.mGradientRadiusType = radiusType;
} else if (st.mGradient == RADIAL_GRADIENT) {
        throw new XmlPullParserException(
                        a.getPositionDescription()
                        + "<gradient> tag requires 'gradientRadius' "
                        + "attribute with radial type");
}
// 繪制
float radius = st.mGradientRadius;
if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) {
        // Fall back to parent width or height if intrinsic
        // size is not specified.
        final float width = st.mWidth >= 0 ? st.mWidth : r.width();
        final float height = st.mHeight >= 0 ? st.mHeight : r.height();
        radius *= Math.min(width, height);
} else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) {
        radius *= Math.min(r.width(), r.height());
}

/*
5.1解決了5.0的問題,可以支持Float格式數據,并且優化了‘*%’格式下自身size未指定時的情況。
*/

總結:
設置android:gradientRadius屬性值時:
Api 21以下:只能使用Float格式數據。%、%p、dimension格式沒有預期效果;
Api 21時:只能使用Fraction(%,%p)、Dimension格式數據。不能使用Float數據,否則Float會被解析為Dimension,顯示錯誤。drawable自身size未指定時,使用%格式不會顯示;
Api 22及以上:可以正常使用Float、Dimension、%、%p格式。

(float值使用時單位為px)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容