Android矢量圖(二)--VectorDrawable所有屬性全解析

Android矢量圖I--VectorDrawable基礎(chǔ)介紹了VectorDrawable的用法及其常用屬性,掌握上篇文章的基礎(chǔ)知識(shí)差不多就能在項(xiàng)目中使用矢量圖應(yīng)對(duì)一些基本的需求開發(fā)了。這篇文章介紹其余的全部屬性,其中的一些屬性在實(shí)際開發(fā)中可能用到的比較少。

vector屬性

  1. android:alpha:矢量圖的透明度,范圍0-1,默認(rèn)1;
  2. android:tint:矢量圖的顏色,這個(gè)顏色值會(huì)覆蓋所有與color相關(guān)的屬性比如path的fillColor和strokeColor等;這個(gè)屬性會(huì)被API setColorFilter(ColorFilter)覆蓋。
  3. android:tintMode:色彩混合模式,可選值有很多,下面詳細(xì)討論,默認(rèn)src_in。
  4. android:autoMirrored:當(dāng)布局方向變成right-to-left的時(shí)候,矢量圖是否自動(dòng)鏡像,默認(rèn)false,這個(gè)屬性在API>=19才生效。

關(guān)于tintMode,先看下PorterDuff這類的源碼:

public class PorterDuff {
    public PorterDuff() {
        throw new RuntimeException("Stub!");
    }
    public static enum Mode {
        CLEAR, /** [Sa, Sc] */

        DST, /** [Da, Dc] */
        DST_ATOP, /** [Sa, Sa * Dc + Sc * (1 - Da)] */
        DST_IN, /** [Sa * Da, Sa * Dc] */
        DST_OUT, /** [Da * (1 - Sa), Dc * (1 - Sa)] */
        DST_OVER, /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */

        SRC, /** [Sa, Sc] */
        SRC_ATOP, /** [Da, Sc * Da + (1 - Sa) * Dc] */
        SRC_IN, /** [Sa * Da, Sc * Da] */
        SRC_OUT, /** [Sa * (1 - Da), Sc * (1 - Da)] */
        SRC_OVER, /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */

        DARKEN, /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
        XOR, /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
        LIGHTEN, /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
        MULTIPLY, /** [Sa * Da, Sc * Dc] */
        SCREEN, /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
        /** Saturate(S + D) */
        ADD,
        OVERLAY;
        private Mode() {
        }
    }
}

首先類名PorterDuff是什么意思呢?PorterDuff是兩個(gè)人名的組合: Thomas Porter和Tom Duff,他們1984年在ACM SIGGRAPH計(jì)算機(jī)圖形學(xué)發(fā)表論文《Compositing digital images》,最早提出圖形混合概念,極大地推動(dòng)了圖形圖像學(xué)的發(fā)展,有興趣的同學(xué)可以自行查閱資料。

圖1

PorterDuff共有18個(gè)模式可選,但是android:tintMode可選值只有六個(gè):MULTIPLY、SCREEN、ADD、SRC_ATOP、SRC_IN、SRC_OVER,(xml文件只提供這6個(gè)的原因我不知道,請(qǐng)大神留言告知)。當(dāng)然想使用其余12個(gè)tintMode模式也是可以的,需要用代碼調(diào)用API Drawable.setTintMode(PorterDuff.Mode)即可,可以達(dá)到相應(yīng)tintMode的效果。

tint屬性是Android 5.0引入的,Android 6.0又引入了drawableTint的屬性。
Button和TextView等一些組件會(huì)多出下面6個(gè)屬性:


圖2

ImageView會(huì)多出下面6個(gè)屬性:


圖3
圖4

我們對(duì)矢量圖進(jìn)行調(diào)色,先看效果如圖4所示,圖4中紅色文字表示xml允許使用的6個(gè)tintMode。下面貼出代碼:

// poker_a.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="400dp"
    android:height="550dp"
    android:tint="@android:color/holo_purple"
    android:viewportHeight="550"
    android:viewportWidth="400.0">
    <group android:name="poker_diamond_a">
        <path
            android:name="border"
            android:strokeWidth="7"
            android:strokeColor="#96999c"
            android:fillColor="@android:color/white"
            android:pathData="M5 25a20 20 0 0 1 20 -20
            h350a20 20 0 0 1 20 20v500a20 20 0 0 1 -20 20h-350a20 20 0 0 1 -20 -20v-500"/>
        <path android:name="a"
            android:strokeWidth="8"
            android:strokeColor="@android:color/holo_red_dark"
            android:strokeLineJoin="bevel"
            android:pathData="M40 120
            l40 -90
            l40 90
            l-16-35
            h-48"/>
        <path android:name="small_diamond" android:fillColor="@android:color/holo_blue_dark" android:pathData="M80 130l41 41l-41 41l-41 -41z"/>
        <path android:name="big_diamond" android:fillColor="@android:color/holo_green_dark" android:pathData="M260 310l100 100l-100 100l-100 -100z"/>
    </group>
</vector>
<dimen name="width">64dp</dimen>
<dimen name="height">88dp</dimen>
// activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <ImageView
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="10dp"
            android:tint="@android:color/transparent"/>
        <View
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:layout_marginLeft="10dp"
            android:background="@android:color/holo_purple"/>
        <ImageView
            android:id="@+id/CLEAR"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="10dp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">
        <ImageView
            android:id="@+id/DST"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a" />
        <ImageView
            android:id="@+id/DST_ATOP"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
        <ImageView
            android:id="@+id/DST_IN"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
        <ImageView
            android:id="@+id/DST_OUT"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
        <ImageView
            android:id="@+id/DST_OVER"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">
        <ImageView
            android:id="@+id/SRC"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a" />
        <ImageView
            android:id="@+id/SRC_ATOP"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
        <ImageView
            android:id="@+id/SRC_IN"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
        <ImageView
            android:id="@+id/SRC_OUT"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
        <ImageView
            android:id="@+id/SRC_OVER"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">
        <ImageView
            android:id="@+id/DARKEN"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a" />
        <ImageView
            android:id="@+id/XOR"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
        <ImageView
            android:id="@+id/LIGHTEN"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
        <ImageView
            android:id="@+id/MULTIPLY"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
        <ImageView
            android:id="@+id/SCREEN"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">
        <ImageView
            android:id="@+id/ADD"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a" />
        <ImageView
            android:id="@+id/OVERLAY"
            android:layout_width="@dimen/width"
            android:layout_height="@dimen/height"
            android:src="@drawable/poker_a"
            android:layout_marginLeft="5dp" />
    </LinearLayout>
</LinearLayout>

MainActivity.java代碼:

// MainActivity.java
public class MainActivity extends AppCompatActivity {
    static {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ((ImageView) findViewById(R.id.CLEAR)).getDrawable().mutate().setTintMode(PorterDuff.Mode.CLEAR);

        ((ImageView) findViewById(R.id.DST)).getDrawable().mutate().setTintMode(PorterDuff.Mode.DST);
        ((ImageView) findViewById(R.id.DST_ATOP)).getDrawable().mutate().setTintMode(PorterDuff.Mode.DST_ATOP);
        ((ImageView) findViewById(R.id.DST_IN)).getDrawable().mutate().setTintMode(PorterDuff.Mode.DST_IN);
        ((ImageView) findViewById(R.id.DST_OUT)).getDrawable().mutate().setTintMode(PorterDuff.Mode.DST_OUT);
        ((ImageView) findViewById(R.id.DST_OVER)).getDrawable().mutate().setTintMode(PorterDuff.Mode.DST_OVER);

        ((ImageView) findViewById(R.id.SRC)).getDrawable().mutate().setTintMode(PorterDuff.Mode.SRC);
        ((ImageView) findViewById(R.id.SRC_ATOP)).getDrawable().mutate().setTintMode(PorterDuff.Mode.SRC_ATOP);
        ((ImageView) findViewById(R.id.SRC_IN)).getDrawable().mutate().setTintMode(PorterDuff.Mode.SRC_IN);
        ((ImageView) findViewById(R.id.SRC_OUT)).getDrawable().mutate().setTintMode(PorterDuff.Mode.SRC_OUT);
        ((ImageView) findViewById(R.id.SRC_OVER)).getDrawable().mutate().setTintMode(PorterDuff.Mode.SRC_OVER);

        ((ImageView) findViewById(R.id.DARKEN)).getDrawable().mutate().setTintMode(PorterDuff.Mode.DARKEN);
        ((ImageView) findViewById(R.id.XOR)).getDrawable().mutate().setTintMode(PorterDuff.Mode.XOR);
        ((ImageView) findViewById(R.id.LIGHTEN)).getDrawable().mutate().setTintMode(PorterDuff.Mode.LIGHTEN);
        ((ImageView) findViewById(R.id.MULTIPLY)).getDrawable().mutate().setTintMode(PorterDuff.Mode.MULTIPLY);
        ((ImageView) findViewById(R.id.SCREEN)).getDrawable().mutate().setTintMode(PorterDuff.Mode.SCREEN);

        ((ImageView) findViewById(R.id.ADD)).getDrawable().mutate().setTintMode(PorterDuff.Mode.ADD);
        ((ImageView) findViewById(R.id.OVERLAY)).getDrawable().mutate().setTintMode(PorterDuff.Mode.OVERLAY);
    }
}

色彩混合涉及到兩個(gè)對(duì)象,目標(biāo)對(duì)象源對(duì)象,這里的目標(biāo)對(duì)象是poker_a.xml這個(gè)矢量圖,源對(duì)象是tint設(shè)置的顏色值,為啥這樣說呢,記著這個(gè)原則,先繪制的是目標(biāo)對(duì)象,我們就是要對(duì)目標(biāo)對(duì)象進(jìn)行調(diào)色。對(duì)目標(biāo)對(duì)象調(diào)色后的對(duì)象叫做復(fù)合對(duì)象。

有了這兩個(gè)對(duì)象,怎么進(jìn)行調(diào)色呢?這里有很多計(jì)算公式,公式中又有很多元素,先來看下這些元素:

* Sa:Source alpha,源對(duì)象的Alpha通道;
* Sc:Source color,源對(duì)象的顏色;
* Da:Destination alpha,目標(biāo)對(duì)象的Alpha通道;
* Dc:Destination color,目標(biāo)對(duì)象的顏色;
* [a,c]:對(duì)象的ARGB值,a表示alpha,c表示color。

下面對(duì)這18個(gè)tintMode進(jìn)行剖析,該文受到這篇文章的啟發(fā)。
CLEAR
復(fù)合對(duì)象的ARGB值是[0,0],完全透明,相當(dāng)于清除畫布上的圖像了。

DST
[Da, Dc],只保留目標(biāo)對(duì)象的alpha和color值,因此繪制出來的只有目標(biāo)對(duì)象,相當(dāng)于根本就沒有進(jìn)行調(diào)色。
DST_ATOP
[Sa, Sa * Dc + Sc * (1 - Da)],兩者相交處繪制目標(biāo)對(duì)象,不相交的地方繪制源對(duì)象,并且相交處的效果會(huì)受到源對(duì)象和目標(biāo)對(duì)象alpha的影響。
DST_IN
[Sa * Da, Sa * Dc],兩者相交的地方繪制目標(biāo)對(duì)象,不相交的地方不進(jìn)行繪制,并且相交處的效果會(huì)受到源對(duì)象對(duì)應(yīng)地方透明度的影響。
DST_OUT
[Da * (1 - Sa), Dc * (1 - Sa)],兩者不相交的地方繪制目標(biāo)對(duì)象,相交處根據(jù)源對(duì)象alpha進(jìn)行過濾,完全不透明處則完全過濾,完全透明則不過濾。(親測(cè)正確)
DST_OVER
[Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc],目標(biāo)對(duì)象繪制在源對(duì)象的上方。

SRC
[Sa, Sc],只保留源對(duì)象的alpha和color,因此繪制出來只有源對(duì)象。
SRC_ATOP
[Da, Sc * Da + (1 - Sa) * Dc],兩者相交處繪制源對(duì)象,不相交的地方繪制目標(biāo)對(duì)象,并且相交處的效果會(huì)受到源對(duì)象和目標(biāo)對(duì)象alpha的影響。
SRC_IN
[Sa * Da, Sc * Da],兩者相交處繪制源對(duì)象,不相交的地方不進(jìn)行繪制,并且相交處的效果會(huì)受到源對(duì)象對(duì)應(yīng)地方透明度的影響。
SRC_OUT
[Sa * (1 - Da), Sc * (1 - Da)],兩者不相交的地方繪制源對(duì)象,相交處根據(jù)目標(biāo)對(duì)象alpha進(jìn)行過濾,完全不透明處則完全過濾,完全透明則不過濾。(親測(cè)正確)
SRC_OVER
[Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc],源對(duì)象繪制在目標(biāo)對(duì)象的上方。

DARKEN
[Sa + Da - SaDa, Sc(1 - Da) + Dc(1 - Sa) + min(Sc, Dc)],顧名思義,效果會(huì)變暗。進(jìn)行對(duì)應(yīng)像素比較,取較暗值(即較小值),如果色值相同則進(jìn)行混合;如果兩個(gè)對(duì)象都完全不透明,取較暗值,否則使用上面算法進(jìn)行計(jì)算,受到源對(duì)象和目標(biāo)對(duì)象對(duì)應(yīng)色值和alpha值影響。結(jié)果復(fù)合對(duì)象的alpha值會(huì)變大。
XOR
[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc],不相交的地方按原樣繪制源對(duì)象和目標(biāo)對(duì)象,相交的地方受到對(duì)應(yīng)alpha和顏色值影響,按公式進(jìn)行計(jì)算,如果都完全不透明則相交處完全不繪制。
LIGHTEN
[Sa + Da - Sa
Da, Sc(1 - Da) + Dc(1 - Sa) + max(Sc, Dc)],顧名思義,效果會(huì)變亮。進(jìn)行對(duì)應(yīng)像素比較,取較亮值(即較大值),如果色值相同則進(jìn)行混合;如果兩個(gè)對(duì)象都完全不透明,取較亮值,否則使用上面算法進(jìn)行計(jì)算,受到源對(duì)象和目標(biāo)對(duì)象對(duì)應(yīng)色值和alpha值影響。
MULTIPLY
[Sa * Da, Sc * Dc],正片疊底,即查看每個(gè)通道中的顏色信息,目標(biāo)色與源色復(fù)合。結(jié)果色總是較暗的顏色。任何顏色與黑色復(fù)合產(chǎn)生黑色,任何顏色與白色復(fù)合保持不變。(這個(gè)理論貌似和現(xiàn)實(shí)生活的顏色混合的結(jié)果不一致,現(xiàn)實(shí)生活中黃色和白色混合會(huì)是黃白色而不是白色)。
SCREEN
[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],濾色模式,這個(gè)模式與我們所用的顯示屏原理相同,因此也被翻譯成屏幕模式;保留兩個(gè)圖層中較白的部分,較暗的部分被遮蓋,圖層中純黑的部分變成完全透明,純白部分完全不透明,其他的顏色根據(jù)顏色級(jí)別產(chǎn)生半透明的效果。

ADD
Saturate(S + D),飽和度疊加
OVERLAY
算法同時(shí)進(jìn)行進(jìn)行 Multiply(正片疊底)混合還是 Screen(屏幕)混合,是進(jìn)行 Multiply混合還是 Screen混合,取決于目標(biāo)對(duì)象的顏色值,目標(biāo)對(duì)象顏色的高光與陰影部分的亮度等細(xì)節(jié)會(huì)被保留。

這18個(gè)模式終于介紹完了,再次感謝這篇文章的作者。

path屬性

  1. android:strokeLineCap:顧名思義,設(shè)置線條的帽子,round圓角、square正方形、butt臀,默認(rèn)是butt;
  2. android:strokeLineJoin:線條拐彎處的樣式,round圓角、bevel斜角、miter斜切尖角,默認(rèn)是miter;
  3. android:strokeMiterLimit:android:strokeLineJoin為miter的時(shí)候這個(gè)屬性才發(fā)揮作用。設(shè)置miter斜切尖角長(zhǎng)度(用miter_length表示)與線條寬度(用line_width表示)比例值的上限,默認(rèn)是4,strokeMiterLimit = miter_length / line_width,這個(gè)屬性設(shè)定了這個(gè)比例的最大值,超過這個(gè)值的尖角不再顯示尖角而是bevel斜角。
    圖5
    圖6

    如果你希望尖角多一些,就把這個(gè)屬性設(shè)置大一些。在特別尖的拐彎處的點(diǎn),點(diǎn)的這個(gè)比例可能大與strokeMiterLimit,那么就不顯示尖角效果而是類似bevel斜角的效果,這樣看起來不是很突兀,比較美觀。
    圖5和圖6來自文章;
  4. android:trimPathStart:從路徑起始位置截?cái)嗦窂降谋嚷?,取值范?-1,默認(rèn)0;
  5. android:trimPathEnd:從路徑結(jié)束位置截?cái)嗦窂降谋嚷?,取值范?-1,默認(rèn)1;
  6. android:trimPathOffset:設(shè)置路徑截取的偏移比例,取值范圍0-1,默認(rèn)0;
    利用android:trimPathStart和android:trimPathEnd可以做一些入場(chǎng)和出場(chǎng)動(dòng)畫,鏈接
  7. android:fillType:API 24才引入的這個(gè)屬性,取值nonZero和evenOdd,默認(rèn)nonZero。

關(guān)于android:fillType這個(gè)屬性,需要花點(diǎn)篇幅討論下。討論這個(gè)屬性之前,先看下代碼及其對(duì)應(yīng)的效果:

<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="200dp"
        android:height="200dp"
        android:viewportHeight="600"
        android:viewportWidth="600">
    <path
        android:name="noneZero"
        android:strokeWidth="2"
        android:strokeColor="#B32D20"
        android:fillColor="#3C8FC1"
        android:pathData="M20 120 a100 100 0 1 1 200 0 a100 100 0 1 1 -200 0
            M40 120 a80 80 0 1 1 160 0 a80 80 0 1 1 -160 0"/>
    <path
        android:name="evenOdd"
        android:strokeWidth="2"
        android:strokeColor="#B32D20"
        android:fillColor="#3C8FC1"
        android:fillType="evenOdd"
        android:pathData="M260 120 a100 100 0 1 1 200 0 a100 100 0 1 1 -200 0
            M280 120 a80 80 0 1 1 160 0 a80 80 0 1 1 -160 0"/>
</vector>
圖7

代碼中有兩條path,前者的fillType是默認(rèn)的noneZero,后者的fillType是evenOdd,除了這兩個(gè)屬性外,其余屬性一模一樣(name屬性和M指令的起始位置為了顯示的區(qū)別,忽略好吧??)。兩者的效果圖如圖7所示。fillType的原理是什么呢?為啥會(huì)導(dǎo)致這樣的效果呢?

這里需要提一點(diǎn),代碼中每一條path都繪制了兩個(gè)圓,四個(gè)圓的每一個(gè)圓都是通過兩條a指令繪制完成的,如果你不清楚a指令的參數(shù),請(qǐng)仔細(xì)看完Android矢量圖I--VectorDrawable基礎(chǔ)這篇文章并搞明白a指令后再回來看本文。這8條a指令的第5個(gè)參數(shù)都是1表示順時(shí)針,請(qǐng)記住都是順時(shí)針,因?yàn)閒illType屬性值noneZero跟path的方向有很大關(guān)系。

首先來看下默認(rèn)的noneZero值。多看維基百科,那里的解釋最權(quán)威。有一個(gè)多邊形C,我們判斷是否要對(duì)C的子多邊形C1形成的一塊區(qū)域進(jìn)行填充,那么先定義一個(gè)變量value=0,根據(jù)value的值來決定是否對(duì)C1進(jìn)行填充;在這個(gè)C1區(qū)域內(nèi)任意選擇一個(gè)點(diǎn)P,從這個(gè)點(diǎn)P向任意方向發(fā)射一條無限長(zhǎng)的直線L,但是這個(gè)方向需要直線L與C1至少有一個(gè)交點(diǎn),然后找出直線L與C所有的交點(diǎn),每個(gè)交點(diǎn)處的方向是順時(shí)針value++,順時(shí)針value--,如果最后的value不是0,那么就對(duì)C1填充,否則不填充。
如果你不明白上面粗體文字的含義,那就根據(jù)上面代碼名字為noneZero這個(gè)path來分析下。這條path包含4個(gè)a指令,其實(shí)也就是4個(gè)形狀為弧線的子path:弧線AB、弧線BA、弧線CD、弧線DC,相應(yīng)地我們就要判斷四塊區(qū)域(outer_a、outer_b、inner_a、inner_b)是否要進(jìn)行fill(填充,這里的填充默認(rèn)是填充內(nèi)部)。對(duì)于區(qū)域outer_a,我們?cè)谄鋬?nèi)部選擇任意一點(diǎn)P,從P發(fā)射一條無限長(zhǎng)直線,直線需要與子多邊形弧線AB相交至少一個(gè)點(diǎn),該點(diǎn)是P1,直線與多邊形C的交點(diǎn)是P1,是順時(shí)針,value=P1=1,不為0,該區(qū)域填充;對(duì)于區(qū)域inner_a,我們?cè)谄鋬?nèi)部選擇任意一點(diǎn)R,從R發(fā)射一條無限長(zhǎng)直線,直線需要與子多邊形弧線CD相交至少一個(gè)點(diǎn),該點(diǎn)是R1,直線與多邊形C的交點(diǎn)是R1和R2,value=R1+R1=1+1=2,不為0,該區(qū)域填充。如圖8所示,

圖8

為了加深下印象,再來舉個(gè)例子??聪旅娴拇a:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="200dp"
        android:height="200dp"
        android:viewportHeight="600"
        android:viewportWidth="600">
    <path
        android:strokeWidth="2"
        android:strokeColor="#B32D20"
        android:fillColor="#3C8FC1"
        android:pathData="M20 320 a100 100 0 1 0 200 0
            M40 320 a80 80 0 1 1 160 0 a80 80 0 1 1 -160 0"/>
</vector>
圖9

你運(yùn)行上面的代碼,不出問題的矢量圖應(yīng)該如圖9所示。代碼首先從A到B逆時(shí)針繪制橢圓弧,然后從C到D順時(shí)針繪制橢圓弧,最后從D到C順時(shí)針繪制橢圓弧。最終的多邊形C包含三個(gè)子區(qū)域outer_a、inner_a、inner_b。對(duì)于outer_a,其value=P=-1,非0那就填充;對(duì)于inner_a,其value=Q1=1,非0那就填充;對(duì)于inner_b,其value=R1+R2=1+(-1)=0,0那就不填充。

如果你已經(jīng)明白了noneZero,那么evenOdd就非常非常簡(jiǎn)單了,先看維基百科,它與指針方向沒有關(guān)系了,一句話你就能明白:如果虛擬直線與多邊形C的交點(diǎn)數(shù)目為奇數(shù)就填充內(nèi)部,偶數(shù)就不填充內(nèi)部。如果還不明白,就舉個(gè)例子,看代碼:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="200dp"
        android:height="200dp"
        android:viewportHeight="600"
        android:viewportWidth="600">
    <path
        android:strokeWidth="2"
        android:strokeColor="#B32D20"
        android:fillColor="#3C8FC1"
        android:fillType="evenOdd"
        android:pathData="M20 320 a100 100 0 1 0 200 0
            M40 320 a80 80 0 1 1 160 0 a80 80 0 1 1 -160 0"/>
</vector>

上面代碼的結(jié)果也是如圖9所示,那么拿圖9來解釋:對(duì)于outer_a,其只有一個(gè)交點(diǎn)P,奇數(shù)那就填充;對(duì)于inner_a,其只有一個(gè)交點(diǎn)Q,奇數(shù)那就填充;對(duì)于inner_b,有兩個(gè)交點(diǎn)R1和R2,偶數(shù)那就不填充。

懷著緊張的心情,fillType屬性終于介紹了完了,真害怕寫錯(cuò)而誤導(dǎo)讀者,如果有錯(cuò)誤的地方或者有更好的分析方法,請(qǐng)大神批評(píng)指正。這里有兩篇文章,幫助理解fillType:文章1、文章2。再說一點(diǎn),如果你是用tint屬性對(duì)矢量圖進(jìn)行調(diào)色,注意fillType屬性值會(huì)對(duì)結(jié)果造成同樣的影響。
最后,你如果查看Paint類的內(nèi)部類FillType的源碼,會(huì)發(fā)現(xiàn)還有另外兩種fillType:

/**
     * Enum for the ways a path may be filled.
     */
    public enum FillType {
        // these must match the values in SkPath.h
        /**
         * Specifies that "inside" is computed by a non-zero sum of signed
         * edge crossings.
         */
        WINDING         (0),
        /**
         * Specifies that "inside" is computed by an odd number of edge
         * crossings.
         */
        EVEN_ODD        (1),
        /**
         * Same as {@link #WINDING}, but draws outside of the path, rather than inside.
         */
        INVERSE_WINDING (2),
        /**
         * Same as {@link #EVEN_ODD}, but draws outside of the path, rather than inside.
         */
        INVERSE_EVEN_ODD(3);

        FillType(int ni) {
            nativeInt = ni;
        }

        final int nativeInt;
    }

對(duì)這另外兩種fillType,Google程序員的注釋已經(jīng)解釋的很清楚了,我就不解釋了,否則可能越解釋越亂,越亂越糊涂。


這篇文章和上篇文章Android矢量圖I--VectorDrawable基礎(chǔ)一起把VectorDrawable的全部屬性都介紹了,相信使用矢量圖應(yīng)該更加輕松了,??。

后續(xù)文章會(huì)對(duì)矢量動(dòng)畫AnimatedVectorDrawable進(jìn)行研究,敬請(qǐng)期待。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容