照著“自定義控件其實(shí)很簡(jiǎn)單”博客來(lái)學(xué)自定義view(三)

From AigeStudio(http://blog.csdn.net/aigestudio/article/details/41447349)Power by Aige
根據(jù)這篇文章起甩卓,開(kāi)始重“抄”一遍戏锹。
繼續(xù)Paint 愈污,講怎么“寫(xiě)字”瞬项。
首先要了解Android中一個(gè)和字體相關(guān)的很重要的類FontMetrics双妨。

FontMetrics意為字體測(cè)量吨灭,這么一說(shuō)大家是不是瞬間感受到了這玩意的重要性往枣?那這東西有什么用呢肛根?我們通過(guò)源碼追蹤進(jìn)去可以看到FontMetrics其實(shí)是Paint的一個(gè)內(nèi)部類像鸡,而它里面呢就定義了top,ascent,descent,bottom,leading五個(gè)成員變量活鹰。


Paste_Image.png

??這張圖很簡(jiǎn)單但是也很扼要的說(shuō)明了ascent,descent,leading這三個(gè)參數(shù)。首先我們要知道Baseline基線只估,在Android中志群,文字的繪制都是從Baseline處開(kāi)始的,Baseline往上至字符最高處的距離我們稱之為ascent(上坡度)蛔钙,Baseline往下至字符最底處的距離我們稱之為descent(下坡度)锌云,而leading(行間距)則表示上一行字符的descent到該行字符的ascent之間的距離。
??top和bottom文檔描述地很模糊吁脱,其實(shí)這里我們可以借鑒一下TextView對(duì)文本的繪制桑涎,TextView在繪制文本的時(shí)候總會(huì)在文本的最外層留出一些內(nèi)邊距,為什么要這樣做豫喧?因?yàn)門extView在繪制文本的時(shí)候考慮到了類似讀音符號(hào)石洗,下圖中的A上面的符號(hào)就是一個(gè)拉丁文的類似讀音符號(hào)的東西:



??然而根據(jù)世界范圍內(nèi)已入案的使用語(yǔ)言中能夠標(biāo)注在字符上方或者下方的除了類似的符號(hào)肯定是數(shù)不勝數(shù)的。而top的意思其實(shí)就是除了Baseline到字符頂端的距離外還應(yīng)該包含這些符號(hào)的高度即ascent+leading紧显,bottom的意思也是一樣讲衫,一般情況下我們極少使用到類似的符號(hào)所以往往會(huì)忽略掉這些符號(hào)的存在,但是Android依然會(huì)在繪制文本的時(shí)候在文本外層留出一定的邊距孵班,這就是為什么top和bottom總會(huì)比ascent和descent大一點(diǎn)的原因涉兽。而在TextView中我們可以通過(guò)xml設(shè)置其屬性android:includeFontPadding="false"去掉一定的邊距值但是不能完全去掉。下面我們?cè)贑anvas上繪制一段文本并嘗試打印文本的top,ascent,descent,bottom和leading:

package rcjs.com.customviewdemo.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

/**
 * Created by 仁昌居士 on 2017/5/3.
 * Description:
 */

public class FontView extends View {
    private static final String mContent = "rcjs仁昌居士ξτβбпшㄎㄊěǔぬも┰┠№@↓";
    private Paint mPaint;// 畫(huà)筆
    private Paint.FontMetrics mFontMetrics;// 文本測(cè)量對(duì)象
    private String TAG = "rcjs_zy";

    public FontView(Context context) {
        this(context, null);
    }

    public FontView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 初始化畫(huà)筆
        initPaint();
    }

    /**
     * 初始化畫(huà)筆
     */
    private void initPaint() {
        // 實(shí)例化畫(huà)筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setTextSize(50);
        mPaint.setColor(Color.BLACK);

        mFontMetrics = mPaint.getFontMetrics();

        Log.d(TAG, "ascent:" + mFontMetrics.ascent);
        Log.d(TAG, "top:" + mFontMetrics.top);
        Log.d(TAG, "leading:" + mFontMetrics.leading);
        Log.d(TAG, "descent:" + mFontMetrics.descent);
        Log.d(TAG, "bottom:" + mFontMetrics.bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(mContent, 0, Math.abs(mFontMetrics.top), mPaint);
    }
}

輸出結(jié)果:


Paste_Image.png

注:Baseline上方的值為負(fù)篙程,下方的值為正
??如圖我們得到了top,ascent,descent,bottom和leading的值枷畏,因?yàn)橹挥幸恍形谋舅詌eading恒為0,那么此時(shí)的顯示效果是如何的呢虱饿?上面我們說(shuō)到Android中文本的繪制是從Baseline開(kāi)始的拥诡,在屏幕上的體現(xiàn)便是Y軸坐標(biāo)触趴,所以在

canvas.drawText(TEXT, 0, Math.abs(mFontMetrics.top), mPaint);  

中我們將文本繪制的起點(diǎn)Y坐標(biāo)向下移動(dòng)Math.abs(mFontMetrics.top)個(gè)單位(注:mFontMetrics.top是負(fù)數(shù)),相當(dāng)于把文本的Baseline向下移動(dòng)Math.abs(mFontMetrics.top)個(gè)單位渴肉,此時(shí)文本的頂部剛好會(huì)和屏幕頂部重合:

Paste_Image.png

從代碼中我們可以看到一個(gè)很特別的現(xiàn)象冗懦,在我們繪制文本之前我們便可以獲取文本的FontMetrics屬性值,也就是說(shuō)我們FontMetrics的這些值跟我們要繪制什么文本是無(wú)關(guān)的仇祭,而僅與繪制文本Paint的size和typeface有關(guān)披蕉。那么我們知道這樣的一個(gè)東西有什么用呢?如上所說(shuō)文本的繪制是從Baseline開(kāi)始乌奇,并且Baseline并非文本的分割線没讲,當(dāng)我們想讓文本繪制的時(shí)候居中屏幕或其他的東西時(shí)就需要計(jì)算Baseline的Y軸坐標(biāo),比如我們讓我們的文本居中畫(huà)布:

package rcjs.com.customviewdemo.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import static android.provider.Telephony.Mms.Part.TEXT;

/**
 * Created by 仁昌居士 on 2017/5/3.
 * Description:
 */

public class FontView extends View {
    private static final String mContent = "rcjs仁昌居士ξτβбпшㄎㄊěǔぬも┰┠№@↓";

    private Paint textPaint, centerLinePaint,baseLinePaint;// 文本的畫(huà)筆和中心線的畫(huà)筆

    private int baseX, baseY;// Baseline繪制的XY坐標(biāo)

    public FontView(Context context) {
        this(context, null);
    }

    public FontView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 初始化畫(huà)筆
        initPaint();
    }

    /**
     * 初始化畫(huà)筆
     */
    private void initPaint() {
        // 實(shí)例化畫(huà)筆
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(70);
        textPaint.setColor(Color.BLACK);

        centerLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        centerLinePaint.setStyle(Paint.Style.STROKE);
        centerLinePaint.setStrokeWidth(5);
        centerLinePaint.setColor(Color.RED);

        baseLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        baseLinePaint.setStyle(Paint.Style.STROKE);
        baseLinePaint.setStrokeWidth(5);
        baseLinePaint.setColor(Color.BLUE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 計(jì)算Baseline繪制的起點(diǎn)X軸坐標(biāo)
        baseX = (int) (canvas.getWidth() / 2 - textPaint.measureText(mContent) / 2);

        // 計(jì)算Baseline繪制的Y坐標(biāo)
        baseY = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2));


        canvas.drawText(mContent, baseX, baseY, textPaint);

        // 為了便于理解我們?cè)诋?huà)布中心處繪制一條中線
        canvas.drawLine(0, baseY, canvas.getWidth(), baseY, baseLinePaint);

        // 為了便于理解我們?cè)诋?huà)布中心處繪制一條中線
        canvas.drawLine(0, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight() / 2, centerLinePaint);
    }
}

效果如圖:


Paste_Image.png

??Baseline繪制的起點(diǎn)x坐標(biāo)為畫(huà)布寬度的一半(中點(diǎn)x坐標(biāo))減去文本寬度的一半(這里我們的畫(huà)布大小與屏幕大小一樣)礁苗,這個(gè)很好理解爬凑,而y坐標(biāo)為畫(huà)布高度的一半(中點(diǎn)y坐標(biāo))減去ascent和descent絕對(duì)值之差的一半,這一點(diǎn)很多朋友可能不是很好理解试伙,其實(shí)很簡(jiǎn)單贰谣,如果直接以畫(huà)布的中心為Baseline:

baseY = canvas.getHeight() / 2;  

那么畫(huà)出來(lái)的效果必定是如下的樣子


Paste_Image.png

??也就是說(shuō)Baseline和屏幕中線重合,而這樣子繪制出來(lái)的文本必定不在屏幕中心迁霎,因?yàn)閍scent的距離大于descent的距離(大多數(shù)情況下我們沒(méi)有考慮top和bottom),那么我們就需要將Baseline往下移使繪制出來(lái)的文本能在中心

那么該下移多少呢百宇?這是一個(gè)問(wèn)題考廉,很多童鞋的第一反應(yīng)是下移ascent的一半高度,但是你要考慮到已經(jīng)在中線下方的descent的高度携御,所以我們應(yīng)該先在ascent的高度中減去descent的高度再除以二再讓屏幕的中點(diǎn)Y坐標(biāo)(也就是高度的一半)加上這個(gè)偏移值

baseY = (int) ((canvas.getHeight() / 2) + ((Math.abs(textPaint.ascent()-Math.abs(textPaint.descent()))) / 2));  

這個(gè)公式跟我們上面代碼中的是一樣的昌粤,不信大家可以自己算算這里就不多說(shuō)了。這里我們的需求是讓文本繪制在某個(gè)區(qū)域的中心啄刹,實(shí)際情況中有很多不同的需求不如靠近某個(gè)區(qū)域離某個(gè)區(qū)域需要多少距離等等涮坐,熟練地去學(xué)會(huì)計(jì)算文本測(cè)繪中的各個(gè)值就顯得很有必要了!

Paint有一個(gè)唯一的子類TextPaint就是專門為文本繪制量身定做的“筆”誓军,而這支筆就如API所描述的那樣能夠在繪制時(shí)為文本添加一些額外的信息袱讹,這些信息包括:baselineShift,bgColor,density,drawableState,linkColor,這些屬性都很簡(jiǎn)單大家顧名思義或者自己去嘗試下即可這里就不多說(shuō)了昵时,那么這支筆有何用呢捷雕?最常用的用法是在繪制文本時(shí)能夠?qū)崿F(xiàn)換行繪制
??在正常情況下Android繪制文本是不能識(shí)別換行符之類的標(biāo)識(shí)符的壹甥,這時(shí)候如果我們想實(shí)現(xiàn)換行繪制就得另辟途徑使用StaticLayout結(jié)合TextPaint實(shí)現(xiàn)換行救巷,StaticLayout是android.text.Layout的一個(gè)子類,很明顯它也是為文本處理量身定做的句柠,其內(nèi)部實(shí)現(xiàn)了文本繪制換行的處理浦译,該類不是本系列重點(diǎn)我們不再多說(shuō)直接Look一下它是如何實(shí)現(xiàn)換行的:

package rcjs.com.customviewdemo.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by 仁昌居士 on 2017/5/3.
 * Description:
 */

public class StaticLayoutView extends View {
    private static final String TEXT = "該配合你演出的我演視而不見(jiàn)棒假,在逼一個(gè)最愛(ài)你的人即興表演,什么時(shí)候我們開(kāi)始收起了底線精盅,順應(yīng)時(shí)代的改變看那些拙劣的表演";
    private TextPaint mTextPaint;// 文本的畫(huà)筆
    private StaticLayout mStaticLayout;// 文本布局

    public StaticLayoutView(Context context) {
        this(context, null);
    }

    public StaticLayoutView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 初始化畫(huà)筆
        initPaint();
    }

    /**
     * 初始化畫(huà)筆
     */
    private void initPaint() {
        // 實(shí)例化畫(huà)筆
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(50);
        mTextPaint.setColor(Color.BLACK);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mStaticLayout = new StaticLayout(TEXT, mTextPaint, canvas.getWidth(), Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, false);
        mStaticLayout.draw(canvas);
        canvas.restore();
    }

}

運(yùn)行效果如下:


Paste_Image.png

對(duì)Paint繪制文本的一個(gè)簡(jiǎn)單了解就先到這帽哑,我們來(lái)看看Paint中到底提供了哪些實(shí)用的方法來(lái)繪制文本
ascent()
顧名思義就是返回上坡度的值,我們已經(jīng)用過(guò)了
descent()
同上渤弛,不多說(shuō)了
breakText (CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float[] measuredWidth)
這個(gè)方法讓我們?cè)O(shè)置一個(gè)最大寬度在不超過(guò)這個(gè)寬度的范圍內(nèi)返回實(shí)際測(cè)量值否則停止測(cè)量祝拯,參數(shù)很多但是都很好理解:
text表示我們的字符串;
start表示從第幾個(gè)字符串開(kāi)始測(cè)量她肯;
end表示從測(cè)量到第幾個(gè)字符串為止佳头;
measureForwards表示向前還是向后測(cè)量;
maxWidth表示一個(gè)給定的最大寬度在這個(gè)寬度內(nèi)能測(cè)量出幾個(gè)字符晴氨;
measuredWidth為一個(gè)可選項(xiàng)康嘉,可以為空,不為空時(shí)返回真實(shí)的測(cè)量值籽前。

同樣的方法還有breakText (String text, boolean measureForwards, float maxWidth, float[] measuredWidth)breakText (char[] text, int index, int count, float maxWidth, float[] measuredWidth)亭珍。這些方法在一些結(jié)合文本處理的應(yīng)用里比較常用,比如文本閱讀器的翻頁(yè)效果枝哄,我們需要在翻頁(yè)的時(shí)候動(dòng)態(tài)折斷或生成一行字符串肄梨,這就派上用場(chǎng)了~~~

getFontMetrics (Paint.FontMetrics metrics)
這個(gè)和我們之前用到的getFontMetrics()相比多了個(gè)參數(shù),getFontMetrics()返回的是FontMetrics對(duì)象而getFontMetrics(Paint.FontMetrics metrics)返回的是文本的行間距挠锥,如果metrics的值不為空則返回FontMetrics對(duì)象的值众羡。

getFontMetricsInt()
該方法返回了一個(gè)FontMetricsInt對(duì)象,F(xiàn)ontMetricsInt和FontMetrics是一樣的蓖租,只不過(guò)FontMetricsInt返回的是int而FontMetrics返回的是float
getFontMetricsInt(Paint.FontMetricsInt fmi)
和getFontMetrics (Paint.FontMetrics metrics)理解上類同
getFontSpacing()
返回字符行間距
setUnderlineText(boolean underlineText)
設(shè)置下劃線
setTypeface(Typeface typeface)
設(shè)置字體類型粱侣,上面我們也使用過(guò),Android中字體有四種樣式:
BOLD(加粗)蓖宦;
BOLD_ITALIC(加粗并傾斜)齐婴;
ITALIC(傾斜);
NORMAL(正常)稠茂。
而其為我們提供的字體有五種:
DEFAULT柠偶;
DEFAULT_BOLD;
MONOSPACE睬关;
SANS_SERIF嚣州;
SERIF淮捆。
這些什么類型啊溢陪、字體啊之類的都很簡(jiǎn)單大家自己去試試就知道就不多說(shuō)了。但是系統(tǒng)給我們的字體有限我們可不可以使用自己的字體呢障陶?答案是肯定的藐不!Typeface這個(gè)類中給我們提供了多個(gè)方法去個(gè)性化我們的字體:
defaultFromStyle(int style)
最簡(jiǎn)單的匀哄,簡(jiǎn)而言之就是把上面所說(shuō)的四種Style封裝成Typeface
create(String familyName, int style)和create(Typeface family, int style)
兩者大概意思都一樣秦效,比如

textPaint.setTypeface(Typeface.create("SERIF", Typeface.NORMAL));  
textPaint.setTypeface(Typeface.create(Typeface.SERIF, Typeface.NORMAL));  

兩者效果是一樣的createFromAsset(AssetManager mgr, String path)、createFromFile(String path)和createFromFile(File path)

這三者也是一樣的涎嚼,它們都允許我們使用自己的字體比如我們從asset目錄讀取一個(gè)字體文件:

// 獲取字體并設(shè)置畫(huà)筆字體  
Typeface typeface = Typeface.createFromAsset(context.getAssets(), "kt.ttf");  
textPaint.setTypeface(typeface);  

setTextSkewX(float skewX)
這個(gè)傾斜值沒(méi)有具體的范圍阱州,但是官方推崇的值為-0.25可以得到比較好的傾斜文本效果,值為負(fù)右傾值為正左傾法梯,默認(rèn)值為0
這個(gè)方法可以設(shè)置文本在水平方向上的傾斜苔货,效果類似下圖:

// 設(shè)置畫(huà)筆文本傾斜  
textPaint.setTextSkewX(-0.25F);  
Paste_Image.png

setTextSize (float textSize)
設(shè)置字體大小
setTextScaleX (float scaleX)
將文本沿X軸水平縮放,默認(rèn)值為1立哑,當(dāng)值大于1會(huì)沿X軸水平放大文本夜惭,當(dāng)值小于1會(huì)沿X軸水平縮放文本

// 設(shè)置畫(huà)筆文本縮小 
textPaint.setTextScaleX(0.5F);  
Paste_Image.png
// 設(shè)置畫(huà)筆文本放大  
textPaint.setTextScaleX(1.5F);  

Paste_Image.png

注意事項(xiàng):setTextScaleX不僅放大了文本寬度同時(shí)還拉伸了字符!
setTextLocale (Locale locale)
設(shè)置地理位置铛绰,這個(gè)不講诈茧,我們會(huì)在屏幕適配系列詳解什么是Locale,這里如果你要使用捂掰,直接傳入Locale.getDefault()即可
setTextAlign (Paint.Align align)
設(shè)置文本的對(duì)齊方式敢会,可供選的方式有三種:CENTER,LEFT和RIGHT,其實(shí)從這三者的名字上看我們就知道其意思这嚣,但是問(wèn)題是這玩意怎么用的鸥昏?
我們的文本大小是通過(guò)size和typeface確定的(其實(shí)還有其他的因素但這里影響不大忽略~~),一旦baseline確定姐帚,對(duì)不對(duì)齊好像不相干吧……但是互广,你要知道一點(diǎn),文本的繪制是從baseline開(kāi)始沒(méi)錯(cuò)卧土,但是是從哪邊開(kāi)始繪制的呢?左端還是右端呢像樊?而這個(gè)Align就是為我們定義在baseline繪制文本究竟該從何處開(kāi)始尤莺,上面我們?cè)谶M(jìn)行對(duì)文本的水平居中時(shí)是用Canvas寬度的一半減去文本寬度的一半:

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 計(jì)算Baseline繪制的起點(diǎn)X軸坐標(biāo)
        baseX = (int) (canvas.getWidth() / 2 - textPaint.measureText(mContent) / 2);

        // 計(jì)算Baseline繪制的Y坐標(biāo)
        baseY = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2));


        canvas.drawText(mContent, baseX, baseY, textPaint);

        // 為了便于理解我們?cè)诋?huà)布中心處繪制一條中線
        canvas.drawLine(0, baseY, canvas.getWidth(), baseY, baseLinePaint);

        // 為了便于理解我們?cè)诋?huà)布中心處繪制一條中線
        canvas.drawLine(0, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight() / 2, centerLinePaint);
    }

實(shí)際上我們大可不必這樣計(jì)算,我們只需設(shè)置Paint的文本對(duì)齊方式為CENTER生棍,drawText的時(shí)候起點(diǎn)x = canvas.getWidth() / 2即可:

textPaint.setTextAlign(Align.CENTER);  
canvas.drawText(TEXT, canvas.getWidth() / 2, baseY, textPaint);  

當(dāng)我們將文本對(duì)齊方式設(shè)置為CENTER后就相當(dāng)于告訴Android我們這個(gè)文本繪制的時(shí)候從文本的中點(diǎn)開(kāi)始向兩端繪制颤霎,如果設(shè)置為L(zhǎng)EFT則從文本的左端開(kāi)始往右繪制,如果為RIGHT則從文本的右端開(kāi)始往左繪制:


Paste_Image.png

setSubpixelText (boolean subpixelText)
設(shè)置是否打開(kāi)文本的亞像素顯示涂滴,什么叫亞像素顯示呢友酱?你可以理解為對(duì)文本顯示的一種優(yōu)化技術(shù),如果大家用的是Win7+系統(tǒng)可以在控制面板中找到一個(gè)叫ClearType的設(shè)置柔纵,該設(shè)置可以讓你的文本更好地顯示在屏幕上就是基于亞像素顯示技術(shù)缔杉。具體我們?cè)谠O(shè)計(jì)色彩系列將會(huì)細(xì)說(shuō),這里就不扯了
setStrikeThruText (boolean strikeThruText)
是否添加文本刪除線
setLinearText (boolean linearText)
設(shè)置是否打開(kāi)線性文本標(biāo)識(shí)搁料,這玩意對(duì)大多數(shù)人來(lái)說(shuō)都很奇怪不知道這玩意什么意思或详。想要明白這東西你要先知道文本在Android中是如何進(jìn)行存儲(chǔ)和計(jì)算的系羞。在Android中文本的繪制需要使用一個(gè)bitmap作為單個(gè)字符的緩存,既然是緩存必定要使用一定的空間霸琴,我們可以通過(guò)setLinearText (true)告訴Android我們不需要這樣的文本緩存椒振。
setFakeBoldText (boolean fakeBoldText)
設(shè)置文本仿粗體
measureText (String text),measureText (CharSequence text, int start, int end),measureText (String text, int start, int end),measureText (char[] text, int index, int count)
測(cè)量文本寬度,上面我們已經(jīng)使用過(guò)了梧乘,這四個(gè)方法都是一樣的只是參數(shù)稍有不同澎迎。

下面我們來(lái)看一個(gè)比較深?yuàn)W的東西
setDither(boolean dither)
這玩意用來(lái)設(shè)置我們?cè)诶L制圖像時(shí)的抗抖動(dòng),也稱為遞色选调,那什么叫抗抖動(dòng)呢夹供?在Android中我確實(shí)不好拿出一個(gè)明顯的例子,我就在PS里模擬說(shuō)明一下


大家看到的這張七彩漸變圖是一張RGB565模式下圖片学歧,即便圖片不是很大我們依然可以很清晰地看到在兩種顏色交接的地方有一些色塊之類的東西感覺(jué)很不柔和罩引,因?yàn)樵赗GB模式下只能顯示2^16=65535種色彩,因此很多豐富的色彩變化無(wú)法呈現(xiàn)枝笨,而Android呢為我們提供了抗抖動(dòng)這么一個(gè)方法袁铐,它會(huì)將相鄰像素之間顏色值進(jìn)行一種“中和”以呈現(xiàn)一個(gè)更細(xì)膩的過(guò)渡色:

放大來(lái)看,其在很多相鄰像素之間插入了一個(gè)“中間值”:

抗抖動(dòng)不是Android的專利横浑,是圖形圖像領(lǐng)域的一種解決位圖精度的技術(shù)剔桨。上面說(shuō)了太多理論性的東西,估計(jì)大家都疲憊了徙融,接下來(lái)我們來(lái)瞅瞅一個(gè)比較酷的東西MaskFilter遮罩過(guò)濾器洒缀!在Paint我們有個(gè)方法來(lái)設(shè)置這東西
setMaskFilter(MaskFilter maskfilter)
MaskFilter類中沒(méi)有任何實(shí)現(xiàn)方法,而它有兩個(gè)子類BlurMaskFilter和EmbossMaskFilter欺冀,前者為模糊遮罩濾鏡(比起稱之為過(guò)濾器哥更喜歡稱之為濾鏡)而后者為浮雕遮罩濾鏡树绩,我們先來(lái)看第一個(gè)
BlurMaskFilter
Android中的很多自帶控件都有類似軟陰影的效果,比如說(shuō)Button

它周圍就有一圈很淡的陰影效果隐轩,這種效果看起來(lái)讓控件更真實(shí)饺饭,那么是怎么做的呢?其實(shí)很簡(jiǎn)單职车,使用BlurMaskFilter就可以得到類似的效果

package rcjs.com.customviewdemo.widget;

import android.content.Context;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import rcjs.com.customviewdemo.utils.UIHelper;

/**
 * Created by 仁昌居士 on 2017/5/4.
 * Description:
 */

public class MaskFilterView extends View {
    private static final int RECT_SIZE = 800;
    private Paint mPaint;// 畫(huà)筆
    private Context mContext;// 上下文環(huán)境引用

    private int left, top, right, bottom;//

    public MaskFilterView(Context context) {
        this(context, null);
    }

    public MaskFilterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;

        // 初始化畫(huà)筆
        initPaint();

        // 初始化資源
        initRes(context);
    }

    /**
     * 初始化畫(huà)筆
     */
    private void initPaint() {
        // 實(shí)例化畫(huà)筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(0xFF603811);

        // 設(shè)置畫(huà)筆遮罩濾鏡
        mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.SOLID));
    }

    /**
     * 初始化資源
     */
    private void initRes(Context context) {
        /*
         * 計(jì)算位圖繪制時(shí)左上角的坐標(biāo)使其位于屏幕中心
         */
        left = UIHelper.getWindowWidthHeight(mContext)[0] / 2 - RECT_SIZE / 2;
        top = UIHelper.getWindowWidthHeight(mContext)[1] / 2 - RECT_SIZE / 2;
        right = UIHelper.getWindowWidthHeight(mContext)[0] / 2 + RECT_SIZE / 2;
        bottom = UIHelper.getWindowWidthHeight(mContext)[1] / 2 + RECT_SIZE / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.GRAY);

        // 畫(huà)一個(gè)矩形
        canvas.drawRect(left, top, right, bottom, mPaint);
    }
}

代碼中我們?cè)诋?huà)布中央繪制了一個(gè)正方形瘫俊,并設(shè)置了了它的模糊濾鏡,但是當(dāng)你運(yùn)行后發(fā)現(xiàn)并沒(méi)有任何的效果:


Paste_Image.png

為什么會(huì)這樣呢悴灵?還記得上一節(jié)中我們講的AvoidXfermode么扛芽,在API 16的時(shí)候該類已經(jīng)被標(biāo)注為過(guò)時(shí)了,因?yàn)锳voidXfermode不支持硬件加速积瞒,如果在API 16+上想獲得正確的效果就必需關(guān)閉應(yīng)用的硬件加速川尖,當(dāng)時(shí)我們是在AndroidManifest.xml文件中設(shè)置android:hardwareAccelerated為false來(lái)關(guān)閉的,具體有哪些繪制的方法不支持硬件加速可以參考下圖



但是大家想過(guò)沒(méi)如果在AndroidManifest.xml文件中關(guān)閉硬件加速那么我們整個(gè)應(yīng)用都將不支持硬件加速茫孔,這顯然是不科學(xué)的空厌,如果可以只針對(duì)某個(gè)View關(guān)閉硬件加速那豈不是很好么庐船?當(dāng)然,Android也給我們提供了這樣的功能嘲更,我們可以在View中通過(guò)
setLayerType(LAYER_TYPE_SOFTWARE, null);  

來(lái)關(guān)閉單個(gè)View的硬件加速功能
再次運(yùn)行即可得到正確的效果:

Paste_Image.png

BlurMaskFilter只有一個(gè)含參的構(gòu)造函數(shù)BlurMaskFilter(float radius, BlurMaskFilter.Blur style)筐钟,其中radius很容易理解,值越大我們的陰影越擴(kuò)散赋朦,比如在上面的例子中我將radius改為50:

Paste_Image.png

可以明顯感到陰影的范圍擴(kuò)大了篓冲,這個(gè)很好理解。而第二個(gè)參數(shù)style表示的是模糊的類型宠哄,上面我們用到的是SOLID壹将,其效果就是在圖像的Alpha邊界外產(chǎn)生一層與Paint顏色一致的陰影效果而不影響圖像本身,除了SOLID還有三種毛嫉,NORMAL,OUTER和INNER诽俯。
NORMAL會(huì)將整個(gè)圖像模糊掉:

Paste_Image.png

而OUTER會(huì)在Alpha邊界外產(chǎn)生一層陰影且會(huì)將原本的圖像沿內(nèi)部距離為radius變成透明:


Paste_Image.png

INNER則會(huì)在圖像內(nèi)部產(chǎn)生模糊:

Paste_Image.png

INNER效果其實(shí)并不理想,實(shí)際應(yīng)用中我們使用的也少承粤,我們往往會(huì)使用混合模式和漸變和獲得更完美的內(nèi)陰影效果暴区。如上所說(shuō)BlurMaskFilter是根據(jù)Alpha通道的邊界來(lái)計(jì)算模糊的,如果是一張圖片(注:上面我們說(shuō)過(guò)Android會(huì)把拷貝到資源目錄的圖片轉(zhuǎn)為RGB565辛臊,具體原因具體分析我會(huì)單獨(dú)開(kāi)一篇帖子說(shuō)仙粱,這里就先假設(shè)所有提及的圖片格式為RGB565)你會(huì)發(fā)現(xiàn)沒(méi)有任何效果,那么假使我們需要給圖片加一個(gè)類似陰影的效果該如何做呢彻舰?其實(shí)很簡(jiǎn)單伐割,我們可以嘗試從Bitmap中獲取其Alpha通道,并在繪制Bitmap前先以該Alpha通道繪制一個(gè)模糊效果不就行了刃唤?

package rcjs.com.customviewdemo.widget;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import rcjs.com.customviewdemo.R;
import rcjs.com.customviewdemo.utils.UIHelper;

/**
 * Created by 仁昌居士 on 2017/5/4.
 * Description:
 */

public class BlurMaskFilterView extends View {
    private Paint shadowPaint;// 畫(huà)筆
    private Context mContext;// 上下文環(huán)境引用
    private Bitmap srcBitmap, shadowBitmap;// 位圖和陰影位圖

    private int x, y;// 位圖繪制時(shí)左上角的起點(diǎn)坐標(biāo)

    public BlurMaskFilterView(Context context) {
        this(context, null);
    }

    public BlurMaskFilterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        // 記得設(shè)置模式為SOFTWARE
        setLayerType(LAYER_TYPE_SOFTWARE, null);

        // 初始化畫(huà)筆
        initPaint();

        // 初始化資源
        initRes(context);
    }

    /**
     * 初始化畫(huà)筆
     */
    private void initPaint() {
        // 實(shí)例化畫(huà)筆
        shadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        shadowPaint.setColor(Color.DKGRAY);
        shadowPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL));
    }

    /**
     * 初始化資源
     */
    private void initRes(Context context) {
        // 獲取位圖
        srcBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.nav_header);

        // 獲取位圖的Alpha通道圖
        shadowBitmap = srcBitmap.extractAlpha();

        /*
         * 計(jì)算位圖繪制時(shí)左上角的坐標(biāo)使其位于屏幕中心
         */
        x = UIHelper.getWindowWidthHeight(context)[0] / 2 - srcBitmap.getWidth() / 2;
        y = UIHelper.getWindowWidthHeight(context)[1] / 2 - srcBitmap.getHeight() / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 先繪制陰影
        canvas.drawBitmap(shadowBitmap, x, y, shadowPaint);

        // 再繪制位圖
        canvas.drawBitmap(srcBitmap, x, y, null);
    }
}

如代碼所示我們通過(guò)Bitmap的extractAlpha()方法從原圖中分離出一個(gè)Alpha通道位圖并在計(jì)算模糊濾鏡的時(shí)候使用該位圖生成模糊效果:


Paste_Image.png

這段直接copy,懂就行了
相對(duì)于BlurMaskFilter來(lái)說(shuō)
EmbossMaskFilter
的常用性比較低隔心,倒不是說(shuō)EmbossMaskFilter很沒(méi)用,只是相對(duì)于EmbossMaskFilter實(shí)現(xiàn)的效果來(lái)說(shuō)遠(yuǎn)不及BlurMaskFilter給人的感覺(jué)霸氣尚胞,說(shuō)了半天那么EmbossMaskFilter到底是做什么的呢硬霍?
我們先來(lái)看一張圖:


這么一個(gè)看著像巧克力的東西就是用EmbossMaskFilter實(shí)現(xiàn)了,正如其名辐真,他可以實(shí)現(xiàn)一種類似浮雕的效果,說(shuō)白了就是讓你繪制的圖像感覺(jué)像是從屏幕中“凸”起來(lái)更有立體感一樣(在設(shè)計(jì)軟件中類似的效果稱之為斜面浮雕)崖堤。該類也只有一個(gè)含參的構(gòu)造方法EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius)侍咱,這些參數(shù)理解起來(lái)要比BlurMaskFilter困難得多,如果你沒(méi)有空間想象力的話密幔,首先我們來(lái)看第一個(gè)direction指的是方向楔脯,什么方向呢?光照的方向胯甩!如果大家接觸過(guò)三維設(shè)計(jì)就一定懂昧廷,沒(méi)接觸也沒(méi)關(guān)系堪嫂,我跟你說(shuō)明白。假設(shè)一個(gè)沒(méi)有任何光線的黑屋子里有一張桌子木柬,桌子上有一個(gè)小球皆串,這時(shí)我們打開(kāi)桌子上的臺(tái)燈,臺(tái)燈照亮了小球眉枕,這時(shí)候小球的狀態(tài)與下圖類似:

PS:略草恶复,湊合看
小球最接近光源的地方肯定是最亮的這個(gè)沒(méi)有異議,在參數(shù)中specular就是跟高光有關(guān)的速挑,其值是個(gè)雙向值越小或越大高光越強(qiáng)中間值則是最弱的谤牡,那么再看看什么是ambient呢?同樣我們看個(gè)球姥宝,你會(huì)發(fā)現(xiàn)即便只有一盞燈光翅萤,在球底部跟桌面相接的地方依然不會(huì)出現(xiàn)大片的“死黑”,這是因?yàn)楣饩€在傳播的過(guò)程中碰到物體會(huì)產(chǎn)生反射腊满!這種反射按照物體介質(zhì)的粗糙度可以分為漫反射和鏡面反射套么,而這里我們的小球之所以背面沒(méi)有直接光照但仍能有一定的亮度就是因?yàn)榇罅康穆瓷湓诳臻g傳播讓光線間接照射到小球背面,這種區(qū)別于直接照明的二次照明我們稱之為間接照明糜烹,產(chǎn)生的光線叫做環(huán)境光ambient违诗,參數(shù)中的該值就是用來(lái)設(shè)置環(huán)境光的,在Android中環(huán)境光默認(rèn)為白色疮蹦,其值越大诸迟,陰影越淺,blurRadius則是設(shè)置圖像究竟“凸”出多大距離的很好理解愕乎,最難理解的一個(gè)參數(shù)是direction阵苇,上面我們也說(shuō)了是光照方向的意思,該數(shù)組必須要有而且只能有三個(gè)值即float[x,y,z]感论,這三個(gè)值代表了一個(gè)空間坐標(biāo)系绅项,我們的光照方向則由其定義,那么它是怎么定義的呢比肄?首先x和y很好理解快耿,平面的兩個(gè)維度嘛是吧,上面我們使用的是[1,1]也就是個(gè)45度角芳绩,而z軸表示光源是在屏幕后方還是屏幕前方掀亥,上面我們是用的是1,正值表示光源往屏幕外偏移1個(gè)單位妥色,負(fù)值表示往屏幕里面偏移搪花,這么一說(shuō)如果我把其值改為[1,1,-1]那么我們的巧克力朝著我們的一面應(yīng)該就看不到了對(duì)吧,試試看撒~~這個(gè)效果我就不截圖了,因?yàn)橐黄岷凇悄阋廊荒軌蚩吹揭稽c(diǎn)點(diǎn)灰度就是因?yàn)槲覀兊沫h(huán)境光ambient撮竿!吮便,如果我們把值改為[1,1,2]往屏幕外偏移兩個(gè)單位,那么我們巧克力正面光照將更強(qiáng):

看吧都爆色了幢踏!這里要提醒一點(diǎn)[x,y,z]表示的是空間坐標(biāo)髓需,代表光源的位置,那么一旦這個(gè)位置確定惑折,[ax,ay,az]則沒(méi)有意義授账,也就是說(shuō)同時(shí)擴(kuò)大三個(gè)軸向值的倍數(shù)是沒(méi)有意義的,最終效果還是跟[x,y,z]一樣惨驶!懂了不白热?

public class EmbossMaskFilterView extends View {  
  private static final int H_COUNT = 2, V_COUNT = 4;// 水平和垂直切割數(shù)  
  private Paint mPaint;// 畫(huà)筆  
  private PointF[] mPointFs;// 存儲(chǔ)各個(gè)巧克力坐上坐標(biāo)的點(diǎn)  

  private int width, height;// 單個(gè)巧克力寬高  
  private float coorY;// 單個(gè)巧克力坐上Y軸坐標(biāo)值  

  public EmbossMaskFilterView(Context context) {  
      this(context, null);  
  }  

  public EmbossMaskFilterView(Context context, AttributeSet attrs) {  
      super(context, attrs);  
      // 不使用硬件加速  
      setLayerType(LAYER_TYPE_SOFTWARE, null);  

      // 初始化畫(huà)筆  
      initPaint();  

      // 計(jì)算參數(shù)  
      cal(context);  
  }  

  /** 
   * 初始化畫(huà)筆 
   */  
  private void initPaint() {  
      // 實(shí)例化畫(huà)筆  
      mPaint = new Paint();  
      mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  
      mPaint.setStyle(Paint.Style.FILL);  
      mPaint.setColor(0xFF603811);  

      // 設(shè)置畫(huà)筆遮罩濾鏡  
      mPaint.setMaskFilter(new EmbossMaskFilter(new float[] { 1, 1, 1F }, 0.1F, 10F, 20F));  
  }  

  /** 
   * 計(jì)算參數(shù) 
   */  
  private void cal(Context context) {  
      int[] screenSize = MeasureUtil.getScreenSize((Activity) context);  

      width = screenSize[0] / H_COUNT;  
      height = screenSize[1] / V_COUNT;  

      int count = V_COUNT * H_COUNT;  

      mPointFs = new PointF[count];  
      for (int i = 0; i < count; i++) {  
          if (i % 2 == 0) {  
              coorY = i * height / 2F;  
              mPointFs[i] = new PointF(0, coorY);  
          } else {  
              mPointFs[i] = new PointF(width, coorY);  
          }  
      }  
  }  

  @Override  
  protected void onDraw(Canvas canvas) {  
      super.onDraw(canvas);  
      canvas.drawColor(Color.GRAY);  

      // 畫(huà)矩形  
      for (int i = 0; i < V_COUNT * H_COUNT; i++) {  
          canvas.drawRect(mPointFs[i].x, mPointFs[i].y, mPointFs[i].x + width, mPointFs[i].y + height, mPaint);  
      }  
  }  
}  

上面我們說(shuō)了EmbossMaskFilter的使用面并不是很大,因?yàn)樗f(shuō)其參數(shù)稍復(fù)雜但是其實(shí)現(xiàn)原理是簡(jiǎn)單粗暴的粗卜,簡(jiǎn)而言之就是根據(jù)參數(shù)在圖像周圍繪制一個(gè)“色帶”來(lái)模擬浮雕的效果屋确,如果我們的圖像很復(fù)雜EmbossMaskFilter很難會(huì)正確模擬,所以一般遇到這類圖直接call美工 = = 哈哈哈续扔。

setRasterizer (Rasterizer rasterizer)
設(shè)置光柵攻臀,該方法不支持HW在API 21中被遺棄了
setPathEffect(PathEffect effect)
PathEffect見(jiàn)文知意很明顯就是路徑效果的意思~~那這玩意肯定跟路徑Path有關(guān)咯?那是必須的撒纱昧!PathEffect跟上面的很多類一樣沒(méi)有具體的實(shí)現(xiàn)刨啸,但是其有六個(gè)子類:


這六個(gè)子類分別可以實(shí)現(xiàn)不同的路徑效果:
Paste_Image.png

上圖從上往下分別是沒(méi)有PathEffect、CornerPathEffect识脆、DiscretePathEffect设联、DashPathEffect、PathDashPathEffect灼捂、ComposePathEffect离例、SumPathEffect的效果,代碼的實(shí)現(xiàn)也非常簡(jiǎn)單:

package rcjs.com.customviewdemo.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposePathEffect;
import android.graphics.CornerPathEffect;
import android.graphics.DashPathEffect;
import android.graphics.DiscretePathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathDashPathEffect;
import android.graphics.PathEffect;
import android.graphics.SumPathEffect;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by 仁昌居士 on 2017/5/4.
 * Description:
 */

public class PathEffectView extends View {
    private float mPhase;// 偏移值
    private Paint mPaint;// 畫(huà)筆對(duì)象
    private Path mPath;// 路徑對(duì)象
    private PathEffect[] mEffects;// 路徑效果數(shù)組

    public PathEffectView(Context context, AttributeSet attrs) {
        super(context, attrs);

        /*
         * 實(shí)例化畫(huà)筆并設(shè)置屬性
         */
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.DKGRAY);

        // 實(shí)例化路徑
        mPath = new Path();

        // 定義路徑的起點(diǎn)
        mPath.moveTo(0, 0);

        // 定義路徑的各個(gè)點(diǎn)
        for (int i = 0; i <= 30; i++) {
            mPath.lineTo(i * 35, (float) (Math.random() * 100));
        }

        // 創(chuàng)建路徑效果數(shù)組
        mEffects = new PathEffect[7];
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        /*
         * 實(shí)例化各類特效
         */
        mEffects[0] = null;
        mEffects[1] = new CornerPathEffect(10);
        mEffects[2] = new DiscretePathEffect(3.0F, 5.0F);
        mEffects[3] = new DashPathEffect(new float[]{20, 10, 5, 10}, mPhase);
        Path path = new Path();
        path.addRect(0, 0, 8, 8, Path.Direction.CCW);
        mEffects[4] = new PathDashPathEffect(path, 12, mPhase, PathDashPathEffect.Style.ROTATE);
        mEffects[5] = new ComposePathEffect(mEffects[2], mEffects[4]);
        mEffects[6] = new SumPathEffect(mEffects[4], mEffects[3]);

        /*
         * 繪制路徑
         */
        for (int i = 0; i < mEffects.length; i++) {
            mPaint.setPathEffect(mEffects[i]);
            canvas.drawPath(mPath, mPaint);

            // 每繪制一條將畫(huà)布向下平移250個(gè)像素
            canvas.translate(0, 250);
        }

        // 刷新偏移值并重繪視圖實(shí)現(xiàn)動(dòng)畫(huà)效果
        mPhase += 1;
        invalidate();
    }
}

當(dāng)我們不設(shè)置路徑效果的時(shí)候路徑的默認(rèn)效果就如上圖第一條線那樣直的轉(zhuǎn)折生硬悉稠;而CornerPathEffect則可以將路徑的轉(zhuǎn)角變得圓滑如圖第二條線的效果宫蛆,這六種路徑效果類都有且只有一個(gè)含參的構(gòu)造方法,CornerPathEffect的構(gòu)造方法只接受一個(gè)參數(shù)radius的猛,意思就是轉(zhuǎn)角處的圓滑程度耀盗,我們嘗試更改一下上面的代碼:

mEffects[1] = new CornerPathEffect(50);  
CornerPathEffect(10)
CornerPathEffect(50)

Look Pic是不是更平滑了呢?CornerPathEffect相對(duì)于其他的路徑效果來(lái)說(shuō)最簡(jiǎn)單了卦尊。
DiscretePathEffect離散路徑效果相對(duì)來(lái)說(shuō)則稍微復(fù)雜點(diǎn)叛拷,其會(huì)在路徑上繪制很多“雜點(diǎn)”的突出來(lái)模擬一種類似生銹鐵絲的效果如上圖第三條線,其構(gòu)造方法有兩個(gè)參數(shù)猫牡,第一個(gè)呢指定這些突出的“雜點(diǎn)”的密度胡诗,值越小雜點(diǎn)越密集邓线,第二個(gè)參數(shù)呢則是“雜點(diǎn)”突出的大小淌友,值越大突出的距離越大煌恢。
DashPathEffect的效果相對(duì)與上面兩種路徑效果來(lái)說(shuō)要略顯復(fù)雜,其雖說(shuō)也是包含了兩個(gè)參數(shù)震庭,但是第一個(gè)參數(shù)是一個(gè)浮點(diǎn)型的數(shù)組瑰抵,那這個(gè)數(shù)組有什么意義呢?其實(shí)是這樣的器联,我們?cè)诙x該參數(shù)的時(shí)候只要浮點(diǎn)型數(shù)組中元素個(gè)數(shù)大于等于2即可二汛,也就是說(shuō)上面我們的代碼可以寫(xiě)成這樣的:

mEffects[3] = new DashPathEffect(new float[] {20, 10}, mPhase);  

Paste_Image.png

從圖中我們可以看到我們之前的那種線條變成了一長(zhǎng)一短的間隔線條,而float[] {20, 10}的偶數(shù)參數(shù)20(注意數(shù)組下標(biāo)是從0開(kāi)始哦)定義了我們第一條實(shí)線的長(zhǎng)度拨拓,而奇數(shù)參數(shù)10則表示第一條虛線的長(zhǎng)度肴颊,如果此時(shí)數(shù)組后面不再有數(shù)據(jù)則重復(fù)第一個(gè)數(shù)以此往復(fù)循環(huán),比如我們20,10后沒(méi)數(shù)了渣磷,那么整條線就成了[20,10,20,10,20,10…………………………]這么一個(gè)狀態(tài)婿着,當(dāng)然如果你無(wú)聊,也可以:

mEffects[3] = new DashPathEffect(new float[] {20, 10, 50, 5, 100, 30, 10, 5}, mPhase);  
Paste_Image.png

而DashPathEffect的第二個(gè)參數(shù)我稱之為偏移值醋界,動(dòng)態(tài)改變其值會(huì)讓路徑產(chǎn)生動(dòng)畫(huà)的效果竟宋,上面代碼已給出大家可以自己去試試;PathDashPathEffect和DashPathEffect是類似的形纺,不同的是PathDashPathEffect可以讓我們自己定義路徑虛線的樣式丘侠,比如我們將其換成一個(gè)個(gè)小圓組成的虛線:

Path path = new Path();  
path.addCircle(0, 0, 3, Direction.CCW);  
mEffects[4] = new PathDashPathEffect(path, 12, mPhase, PathDashPathEffect.Style.ROTATE);  
Paste_Image.png

ComposePathEffect和SumPathEffect都可以用來(lái)組合兩種路徑效果,唯一不同的是組合的方式逐样,ComposePathEffect(PathEffect outerpe, PathEffect innerpe)會(huì)先將路徑變成innerpe的效果蜗字,再去復(fù)合outerpe的路徑效果,即:outerpe(innerpe(Path))官研;而SumPathEffect(PathEffect first, PathEffect second)則會(huì)把兩種路徑效果加起來(lái)再作用于路徑秽澳。

Path應(yīng)用的廣泛性注定了PathEffect應(yīng)用的廣泛,所謂一人得道雞犬升天就是這么個(gè)道理戏羽,只要是Path能存在的地方都可以考慮使用担神,下面我們來(lái)模擬一個(gè)類似心電圖的路徑小動(dòng)畫(huà):


Paste_Image.png

這種效果呢也是非常非常地簡(jiǎn)單,說(shuō)白了就是無(wú)數(shù)條短小精悍的小“Path”連接成一條完整的心電路徑:

public class ECGView extends View {  
    private Paint mPaint;// 畫(huà)筆  
    private Path mPath;// 路徑對(duì)象  
  
    private int screenW, screenH;// 屏幕寬高  
    private float x, y;// 路徑初始坐標(biāo)  
    private float initScreenW;// 屏幕初始寬度  
    private float initX;// 初始X軸坐標(biāo)  
    private float transX, moveX;// 畫(huà)布移動(dòng)的距離  
  
    private boolean isCanvasMove;// 畫(huà)布是否需要平移  
  
    public ECGView(Context context, AttributeSet set) {  
        super(context, set);  
  
        /* 
         * 實(shí)例化畫(huà)筆并設(shè)置屬性 
         */  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  
        mPaint.setColor(Color.GREEN);  
        mPaint.setStrokeWidth(5);  
        mPaint.setStrokeCap(Paint.Cap.ROUND);  
        mPaint.setStrokeJoin(Paint.Join.ROUND);  
        mPaint.setStyle(Paint.Style.STROKE);  
        mPaint.setShadowLayer(7, 0, 0, Color.GREEN);  
  
        mPath = new Path();  
        transX = 0;  
        isCanvasMove = false;  
    }  
  
    @Override  
    public void onSizeChanged(int w, int h, int oldw, int oldh) {  
        /* 
         * 獲取屏幕寬高 
         */  
        screenW = w;  
        screenH = h;  
  
        /* 
         * 設(shè)置起點(diǎn)坐標(biāo) 
         */  
        x = 0;  
        y = (screenH / 2) + (screenH / 4) + (screenH / 10);  
  
        // 屏幕初始寬度  
        initScreenW = screenW;  
  
        // 初始X軸坐標(biāo)  
        initX = ((screenW / 2) + (screenW / 4));  
  
        moveX = (screenW / 24);  
  
        mPath.moveTo(x, y);  
    }  
  
    @Override  
    public void onDraw(Canvas canvas) {  
        canvas.drawColor(Color.BLACK);  
  
        mPath.lineTo(x, y);  
  
        // 向左平移畫(huà)布  
        canvas.translate(-transX, 0);  
  
        // 計(jì)算坐標(biāo)  
        calCoors();  
  
        // 繪制路徑  
        canvas.drawPath(mPath, mPaint);  
        invalidate();  
    }  
  
    /** 
     * 計(jì)算坐標(biāo) 
     */  
    private void calCoors() {  
        if (isCanvasMove == true) {  
            transX += 4;  
        }  
  
        if (x < initX) {  
            x += 8;  
        } else {  
            if (x < initX + moveX) {  
                x += 2;  
                y -= 8;  
            } else {  
                if (x < initX + (moveX * 2)) {  
                    x += 2;  
                    y += 14;  
                } else {  
                    if (x < initX + (moveX * 3)) {  
                        x += 2;  
                        y -= 12;  
                    } else {  
                        if (x < initX + (moveX * 4)) {  
                            x += 2;  
                            y += 6;  
                        } else {  
                            if (x < initScreenW) {  
                                x += 8;  
                            } else {  
                                isCanvasMove = true;  
                                initX = initX + initScreenW;  
                            }  
                        }  
                    }  
                }  
            }  
  
        }  
    }  
}  

我們?cè)趏nSizeChanged(int w, int h, int oldw, int oldh)方法中獲取屏幕的寬高始花,該方法的具體用法以后會(huì)寫(xiě)
上面在設(shè)置Paint屬性的時(shí)候我們使用到了一個(gè)setStrokeCap(Paint.Cap cap)方法妄讯。
setStrokeCap(Paint.Cap cap)
該方法用來(lái)設(shè)置我們畫(huà)筆的筆觸風(fēng)格,上面的例子中我使用的是ROUND酷宵,表示是圓角的筆觸亥贸,那么什么叫筆觸呢,其實(shí)很簡(jiǎn)單浇垦,就像我們現(xiàn)實(shí)世界中的筆炕置,如果你用圓珠筆在紙上戳一點(diǎn),那么這個(gè)點(diǎn)一定是個(gè)圓,即便很小朴摊,它代表了筆的筆觸形狀默垄,如果我們把一支鉛筆筆尖削成方形的,那么畫(huà)出來(lái)的線條會(huì)是一條彎曲的“矩形”甚纲,這就是筆觸的意思口锭。除了ROUND,Paint.Cap還提供了另外兩種類型:SQUARE和BUTT介杆。
setStrokeJoin(Paint.Join join)
這個(gè)方法用于設(shè)置結(jié)合處的形態(tài)鹃操,就像上面的代碼中我們雖說(shuō)是花了一條心電線,但是這條線其實(shí)是由無(wú)數(shù)條小線拼接成的春哨,拼接處的形狀就由該方法指定荆隘。
上面的例子中我們還使用到了一個(gè)方法
setShadowLayer(float radius, float dx, float dy, int shadowColor)
該方法為我們繪制的圖形添加一個(gè)陰影層效果:

public class ShadowView extends View {  
    private static final int RECT_SIZE = 800;// 方形大小  
    private Paint mPaint;// 畫(huà)筆  
  
    private int left, top, right, bottom;// 繪制時(shí)坐標(biāo)  
  
    public ShadowView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        // setShadowLayer不支持HW  
        setLayerType(LAYER_TYPE_SOFTWARE, null);  
  
        // 初始化畫(huà)筆  
        initPaint();  
  
        // 初始化資源  
        initRes(context);  
    }  
  
    /** 
     * 初始化畫(huà)筆 
     */  
    private void initPaint() {  
        // 實(shí)例化畫(huà)筆  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        mPaint.setColor(Color.RED);  
        mPaint.setStyle(Style.FILL);  
        mPaint.setShadowLayer(10, 3, 3, Color.DKGRAY);  
    }  
  
    /** 
     * 初始化資源 
     */  
    private void initRes(Context context) {  
        /* 
         * 計(jì)算位圖繪制時(shí)左上角的坐標(biāo)使其位于屏幕中心 
         */  
        left = MeasureUtil.getScreenSize((Activity) context)[0] / 2 - RECT_SIZE / 2;  
        top = MeasureUtil.getScreenSize((Activity) context)[1] / 2 - RECT_SIZE / 2;  
        right = MeasureUtil.getScreenSize((Activity) context)[0] / 2 + RECT_SIZE / 2;  
        bottom = MeasureUtil.getScreenSize((Activity) context)[1] / 2 + RECT_SIZE / 2;  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        // 先繪制位圖  
        canvas.drawRect(left, top, right, bottom, mPaint);  
    }  
}  

radius表示陰影的擴(kuò)散半徑,而dx和dy表示陰影平面上的偏移值赴背,shadowColor就不說(shuō)了陰影顏色臭胜,最后提醒一點(diǎn)setShadowLayer同樣不支持HW哦!



上面我們講MaskFilter的時(shí)候曾用其子類BlurMaskFilter模擬過(guò)類似效果癞尚,跟BlurMaskFilter比起來(lái)這方法是不是更簡(jiǎn)捷呢耸三?但是BlurMaskFilter能做的setShadowLayer卻不一定能做到哦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浇揩,一起剝皮案震驚了整個(gè)濱河市仪壮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胳徽,老刑警劉巖积锅,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異养盗,居然都是意外死亡缚陷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門往核,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)箫爷,“玉大人,你說(shuō)我怎么就攤上這事聂儒』⒚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵衩婚,是天一觀的道長(zhǎng)窜护。 經(jīng)常有香客問(wèn)我,道長(zhǎng)非春,這世上最難降的妖魔是什么柱徙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任缓屠,我火速辦了婚禮,結(jié)果婚禮上护侮,老公的妹妹穿的比我還像新娘藏研。我一直安慰自己,他們只是感情好概行,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著弧岳,像睡著了一般凳忙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上禽炬,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天涧卵,我揣著相機(jī)與錄音,去河邊找鬼腹尖。 笑死柳恐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的热幔。 我是一名探鬼主播乐设,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼绎巨!你這毒婦竟也來(lái)了近尚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤场勤,失蹤者是張志新(化名)和其女友劉穎戈锻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體和媳,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡格遭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了留瞳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拒迅。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖她倘,靈堂內(nèi)的尸體忽然破棺而出坪它,到底是詐尸還是另有隱情,我是刑警寧澤帝牡,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布往毡,位于F島的核電站,受9級(jí)特大地震影響靶溜,放射性物質(zhì)發(fā)生泄漏开瞭。R本人自食惡果不足惜懒震,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嗤详。 院中可真熱鬧个扰,春花似錦、人聲如沸葱色。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)苍狰。三九已至办龄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淋昭,已是汗流浹背俐填。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翔忽,地道東北人英融。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像歇式,于是被迫代替她去往敵國(guó)和親驶悟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容