Android Drawable Resource学习(十二)、ShapeDrawable还是GradientDrawable?

一、发现奇怪的问题?

在研究Android Drawable资源的时候,发现了一个奇怪的问题。在官方API介绍中:

ShapeDrawable 介绍:This object can be defined in an XML file with the <shape> element(这个对象可以用<shape>元素在xml文件中定义)

GradientDrawable 介绍:This object can be defined in an XML file with the <shape> element(这个对象可以用<shape>元素在xml文件中定义)

两者的介绍一模一样,都说可以使用<shape>标签在xml文件中定义。

那么,到底用<shape>标签定义的是什么的呢?经过下面的实验:

在shape.xml中定义:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:startColor="#FFFF0000"
        android:endColor="#80FF00FF"
        android:angle="45"/>
    <padding android:left="7dp"
        android:top="7dp"
        android:right="7dp"
        android:bottom="7dp" />
    <corners android:radius="8dp" />
</shape>

在layout文件中使用:

<TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/gradient_box"
        android:text="测试" />

在java代码中控制:

 TextView textView=(TextView)findViewById(R.id.textView);
    ShapeDrawable gradientDrawable=(ShapeDrawable)textView.getBackground();
结果发现,报错了:
:java.lang.ClassCastException: android.graphics.drawable.GradientDrawable
不能将GradientDrawable转换为ShapeDrawable。

可见,使用<shape>标签定义的是GradientDrawable。


并且,在官方API Drawable Resource中ShapeDrawable介绍到:

compiled resource datatype:
    Resource pointer to a GradientDrawable.
编译资源类型

指向GradientDrawable的指针。

可见,编译的类型是GradientDrawable。


那么,ShapeDrawable是怎么定义的,找了网上的资料,结果硬是没找到如何在XML文件中定义它,只能通过代码的方式实现。如何使用它在后面再介绍。


二、使用GradientDrawable

GradientDrawable的作用在于定于各种样式的渐变。在XML文件中使用<shape>元素定义。
文件位置:
res/drawable/filename.xml
文件名即资源ID
编译资源类型:
指向 GradientDrawable.
资源引用
In Java: R.drawable.filename
In XML: @[package:]drawable/filename
语法
<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape=["rectangle" | "oval" | "line" | "ring"] >
    <corners
        android:radius="integer"
        android:topLeftRadius="integer"
        android:topRightRadius="integer"
        android:bottomLeftRadius="integer"
        android:bottomRightRadius="integer" />
    <gradient
        android:angle="integer"
        android:centerX="integer"
        android:centerY="integer"
        android:centerColor="integer"
        android:endColor="color"
        android:gradientRadius="integer"
        android:startColor="color"
        android:type=["linear" | "radial" | "sweep"]
        android:useLevel=["true" | "false"] />
    <padding
        android:left="integer"
        android:top="integer"
        android:right="integer"
        android:bottom="integer" />
    <size
        android:width="integer"
        android:height="integer" />
    <solid
        android:color="color" />
    <stroke
        android:width="integer"
        android:color="color"
        android:dashWidth="integer"
        android:dashGap="integer" />
</shape>
元素:
<shape>
定义这是一个GradientDrawable,必须作为根元素。

属性:

xmlns:android
String类型。必须的,定义xml文件的命名空间,必须是"http://schemas.a ndroid.com/apk/res/android".
android:shape
关键字。定义shape的值,必须是下面的之一:
描述
"rectangle" 矩阵,这也是默认的shape
"oval" 椭圆
"line" 一条水平的直线。这种shape必须使用 <stroke> 元素来定义这条线的宽度
"ring" 圆环

下面的属性只有当 android:shape="ring"才使用:

android:innerRadius
尺寸。 内环的半径。一个尺寸值(dip等等)或者一个尺寸资源。
android:innerRadiusRatio
Float类型。这个值表示内部环的比例,例如,如果android:innerRadiusRatio = " 5 ",那么内部的半径等于环的宽度除以5。这个值会被android:innerRadius重写。 默认值是9。
android:thickness
尺寸。环的厚度,是一个尺寸值或尺寸的资源。
android:thicknessRatio
Float类型。厚度的比例。例如,如果android:thicknessRatio= " 2 ",然后厚度等于环的宽度除以2。这个值是被android:innerRadius重写, 默认值是3。
android:useLevel
Boolean类型。如果用在 LevelListDrawable里,那么就是true。如果通常不出现则为false。
<corners>
为Shape创建一个圆角,只有shape是rectangle时候才使用。

属性:

android:radius
Dimension。圆角的半径。会被下面每个特定的圆角属性重写。
android:topLeftRadius
Dimension。top-left 圆角的半径。
android:topRightRadius
Dimension。top-right 圆角的半径。
android:bottomLeftRadius
Dimension。 bottom-left圆角的半径。
android:bottomRightRadius
Dimension。bottom-right圆角的半径。

注意:每个圆角半径值都必须大于1,否侧就没有圆角。

             下面的话不明白,我直接设置圆角为0就可以不圆了,其余的设置有圆角,一样的可行。不知道它为什么要这么讲。

(If you want specific cornersto not be rounded, a work-around is to use android:radius to set a default cornerradius greater than 1, but then override each and every corner with the values you reallywant, providing zero ("0dp") where you don't want rounded corners.)

<gradient>
指定这个shape的渐变颜色。

属性:

android:angle
Integer。渐变的角度。 0 代表从 left 到 right。90 代表bottom到 top。必须是45的倍数,默认为0
android:centerX
Float。渐变中心的相对X坐标,在0到1.0之间。
android:centerY
Float。渐变中心的相对Y坐标,在0到1.0之间。
android:centerColor
Color。可选的颜色值。基于startColor和endColor之间。
android:endColor
Color。 结束的颜色。
android:gradientRadius
Float 。渐变的半径。只有在 android:type="radial"才使用
android:startColor
Color。开始的颜色值。
android:type
Keyword。渐变的模式,下面值之一:
描述
"linear" 线形渐变。这也是默认的模式
"radial" 辐射渐变。startColor即辐射中心的颜色
"sweep" 扫描线渐变。
android:useLevel
Boolean。如果在LevelListDrawable中使用,则为true

<padding>

     内容与视图边界的距离

属性:

android:left
Dimension。左边填充距离.
android:top
Dimension。顶部填充距离.
android:right
Dimension。右边填充距离.
android:bottom
Dimension。底部填充距离.
<size>
这个shape的大小。

属性:

android:height
Dimension。这个shape的高度。
android:width
Dimension。这个shape的宽度。

注意:默认情况下,这个shape会缩放到与他所在容器大小成正比。当你在一个ImageView中使用这个shape,你可以使用 android:scaleType="center"来限制这种缩放。

<solid>
填充这个shape的纯色

属性:

android:color
Color。颜色值,十六进制数,或者一个Color资源
<stroke>
这个shape使用的笔画,当android:shape="line"的时候,必须设置改元素。

属性:

android:width
Dimension。笔画的粗细。
android:color
Color。笔画的颜色
android:dashGap
Dimension。每画一条线就间隔多少。只有当 android:dashWidth也设置了才有效。
android:dashWidth
Dimension。每画一条线的长度。只有当 android:dashGap也设置了才有效。
示例:
XML file saved at res/drawable/gradient_box.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:startColor="#FFFF0000"
        android:endColor="#80FF00FF"
        android:angle="45"/>
    <padding android:left="7dp"
        android:top="7dp"
        android:right="7dp"
        android:bottom="7dp" />
    <corners android:radius="8dp" />
</shape>

This layout XML applies the shape drawable to a View:

<TextView
    android:background="@drawable/gradient_box"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content" />

This application code gets the shape drawable and applies it to a View:

Resources res = getResources();
Drawable shape = res. getDrawable(R.drawable.gradient_box);

TextView tv = (TextView)findViewByID(R.id.textview);
tv.setBackground(shape);
参考:
  • ShapeDrawable


下面是API的Demo:

package com.example.shapedrawable;


import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.TextView;

public class MainActivity extends GraphicsActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
   setContentView(new SampleView(this));
  }

  private static class SampleView extends View {
    private Path mPath;
    private Paint mPaint;
    private Rect mRect;
    private GradientDrawable mDrawable;

    public SampleView(Context context) {
      super(context);
      setFocusable(true);

      mPath = new Path();
      mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
      mRect = new Rect(0, 0, 120, 120);

      mDrawable = new GradientDrawable(
          GradientDrawable.Orientation.TL_BR, new int[] { 0xFFFF0000,
              0xFF00FF00, 0xFF0000FF });
      mDrawable.setShape(GradientDrawable.RECTANGLE);
      mDrawable.setGradientRadius((float) (Math.sqrt(2) * 60));
    }

    static void setCornerRadii(GradientDrawable drawable, float r0,
        float r1, float r2, float r3) {
      drawable.setCornerRadii(new float[] { r0, r0, r1, r1, r2, r2, r3,
          r3 });
    }


    //重点的绘制过程
    @Override
    protected void onDraw(Canvas canvas) {

      mDrawable.setBounds(mRect);

      float r = 16;

      canvas.save();
      canvas.translate(10, 10);
      mDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
      setCornerRadii(mDrawable, r, r, 0, 0);
      mDrawable.draw(canvas);
      canvas.restore();

      canvas.save();
      canvas.translate(10 + mRect.width() + 10, 10);
      mDrawable.setGradientType(GradientDrawable.RADIAL_GRADIENT);
      setCornerRadii(mDrawable, 0, 0, r, r);
      mDrawable.draw(canvas);
      canvas.restore();

      canvas.translate(0, mRect.height() + 10);

      canvas.save();
      canvas.translate(10, 10);
      mDrawable.setGradientType(GradientDrawable.SWEEP_GRADIENT);
      setCornerRadii(mDrawable, 0, r, r, 0);
      mDrawable.draw(canvas);
      canvas.restore();

      canvas.save();
      canvas.translate(10 + mRect.width() + 10, 10);
      mDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
      setCornerRadii(mDrawable, r, 0, 0, r);
      mDrawable.draw(canvas);
      canvas.restore();

      canvas.translate(0, mRect.height() + 10);

      canvas.save();
      canvas.translate(10, 10);
      mDrawable.setGradientType(GradientDrawable.RADIAL_GRADIENT);
      setCornerRadii(mDrawable, r, 0, r, 0);
      mDrawable.draw(canvas);
      canvas.restore();

      canvas.save();
      canvas.translate(10 + mRect.width() + 10, 10);
      mDrawable.setGradientType(GradientDrawable.SWEEP_GRADIENT);
      setCornerRadii(mDrawable, 0, r, 0, r);
      mDrawable.draw(canvas);
      canvas.restore();
    }
  }
}

class GraphicsActivity extends Activity {
  // set to true to test Picture
  private static final boolean TEST_PICTURE = false;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  }

  @Override
  public void setContentView(View view) {
    if (TEST_PICTURE) {
      ViewGroup vg = new PictureLayout(this);
      vg.addView(view);
      view = vg;
    }

    super.setContentView(view);
  }
}

class PictureLayout extends ViewGroup {
  private final Picture mPicture = new Picture();

  public PictureLayout(Context context) {
    super(context);
  }

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

  @Override
  public void addView(View child) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }

    super.addView(child);
  }

  @Override
  public void addView(View child, int index) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }

    super.addView(child, index);
  }

  @Override
  public void addView(View child, LayoutParams params) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }

    super.addView(child, params);
  }

  @Override
  public void addView(View child, int index, LayoutParams params) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }

    super.addView(child, index, params);
  }

  @Override
  protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.MATCH_PARENT,
        LayoutParams.MATCH_PARENT);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int count = getChildCount();

    int maxHeight = 0;
    int maxWidth = 0;

    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
      }
    }

    maxWidth += getPaddingLeft() + getPaddingRight();
    maxHeight += getPaddingTop() + getPaddingBottom();

    Drawable drawable = getBackground();
    if (drawable != null) {
      maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
      maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }

    setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
        resolveSize(maxHeight, heightMeasureSpec));
  }

  private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx,
      float sy) {
    canvas.save();
    canvas.translate(x, y);
    canvas.clipRect(0, 0, w, h);
    canvas.scale(0.5f, 0.5f);
    canvas.scale(sx, sy, w, h);
    canvas.drawPicture(mPicture);
    canvas.restore();
  }

  @Override
  protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight()));
    mPicture.endRecording();

    int x = getWidth() / 2;
    int y = getHeight() / 2;

    if (false) {
      canvas.drawPicture(mPicture);
    } else {
      drawPict(canvas, 0, 0, x, y, 1, 1);
      drawPict(canvas, x, 0, x, y, -1, 1);
      drawPict(canvas, 0, y, x, y, 1, -1);
      drawPict(canvas, x, y, x, y, -1, -1);
    }
  }

  @Override
  public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    location[0] = getLeft();
    location[1] = getTop();
    dirty.set(0, 0, getWidth(), getHeight());
    return getParent();
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = super.getChildCount();

    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final int childLeft = getPaddingLeft();
        final int childTop = getPaddingTop();
        child.layout(childLeft, childTop,
            childLeft + child.getMeasuredWidth(),
            childTop + child.getMeasuredHeight());

      }
    }
  }
}

三、ShapeDrawable的使用

ShapeDrawable没有发现如何在XML文件中定义使用,所以应该不属于Drawable Resource。这个留到后面学习Graphics在看。




你可能感兴趣的:(Android Drawable Resource学习(十二)、ShapeDrawable还是GradientDrawable?)