1. 程式人生 > >Android Drawable Resource學習(十二)、ShapeDrawable還是GradientDrawable?

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。

compiled resource datatype:
    Resource pointer to a GradientDrawable.
編譯資源型別

指向GradientDrawable的指標。

可見,編譯的型別是GradientDrawable。

那麼,ShapeDrawable是怎麼定義的,找了網上的資料,結果硬是沒找到如何在XML檔案中定義它,只能通過程式碼的方式實現。如何使用它在後面再介紹。

二、使用GradientDrawable

GradientDrawable的作用在於定於各種樣式的漸變。在XML檔案中使用<shape>元素定義。
檔案位置:
res/drawable/filename.xml
檔名即資源ID
編譯資源型別:
資源引用
In Java: R.drawable.filename
In XML: @[package:]drawable/filename
語法
<?xml version="1.0" encoding="utf-8"?><shapexmlns:android="http://schemas.android.com/apk/res/android"android:shape=["rectangle" | "oval" | "line" | "ring"] ><cornersandroid:radius="integer"android:topLeftRadius="integer"android:topRightRadius="integer"android:bottomLeftRadius="integer"android:bottomRightRadius="integer"/><gradientandroid: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"] /><paddingandroid:left="integer"android:top="integer"android:right="integer"android:bottom="integer"/><sizeandroid:width="integer"android:height="integer"/><solidandroid:color="color"/><strokeandroid:width="integer"android:color="color"android:dashWidth="integer"android:dashGap="integer"/></shape>
元素:
<shape>
定義這是一個GradientDrawable,必須作為根元素。

屬性:

xmlns:android
String型別。必須的,定義xml檔案的名稱空間,必須是"http://schemas.android.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型別。如果用在 裡,那麼就是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"?><shapexmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle"><gradientandroid:startColor="#FFFF0000"android:endColor="#80FF00FF"android:angle="45"/><paddingandroid:left="7dp"android:top="7dp"android:right="7dp"android:bottom="7dp"/><cornersandroid:radius="8dp"/></shape>

This layout XML applies the shape drawable to a View:

<TextViewandroid: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 =;Drawable shape = res.(R.drawable.gradient_box);TextView tv =(TextView)findViewByID(R.id.textview);
tv.setBackground(shape);
參考:

下面是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在看。