教你用Paint給女朋友化妝(上)!

1.前言

正在實(shí)習(xí)的公司最近特別忙诫舅,加上內(nèi)分泌失調(diào)羽利,順便TI7也開(kāi)始了(唯一指定冠軍!LGD是不可戰(zhàn)勝的?浮)这弧,以上種種娃闲,導(dǎo)致斷更了……下面進(jìn)入正文。

學(xué)會(huì)給女孩子化妝皇帮,是你走向人生巔峰的第一步。今天我們就走進(jìn)堪稱亞洲四大邪術(shù)之首的化妝術(shù)逞频,一起來(lái)剖析這門(mén)技術(shù)的幕后原理柒巫。
但是,不要跟我說(shuō)什么唇膏唇蜜唇釉唇彩!這種東西我不懂拴鸵!也不需要聘芜!作為一名Android程序員,老夫化妝向來(lái)都是一把梭庐扫!Paint萨醒!Martrix囤踩!合二為一涣仿!變白就走钧汹!

2.Paint基本使用

化妝屬于Paint的高級(jí)技巧,那么在學(xué)習(xí)之前嗤形,我們肯定是要把基礎(chǔ)都給夯扎實(shí)了叶组。

之前寫(xiě)過(guò)一篇Paint基礎(chǔ)臣淤,內(nèi)容大多數(shù)是枯燥的Api使用橄霉,考慮再三就給刪了。這些內(nèi)容完全可以參考官方文檔邑蒋,因此我只選出一些有意思的Api姓蜂。

Paint的使用主要可以分為兩大類,第一是圖形医吊、路徑的繪制钱慢,第二是文字的繪制。我們開(kāi)始吧~

2.1 繪制圖形與路徑

1.setStyle(Paint.Style style):設(shè)置畫(huà)筆樣式

Paint.Style.FILL :填充內(nèi)部
Paint.Style.FILL_AND_STROKE :填充內(nèi)部和描邊
Paint.Style.STROKE :僅描邊
這些樣式不難理解遮咖,顧名思義即可

2.setStrokeCap(Paint.Cap cap):設(shè)置線冒樣式

Paint.Cap.ROUND:圓形線冒
Paint.Cap.SQUARE:方形線冒
Paint.Cap.BUTT:無(wú)線冒)

這個(gè)不太好理解滩字,我們用代碼和圖片來(lái)說(shuō)明。

 private void drawStrokeCap(Canvas canvas){
        Paint paint = new Paint();
        paint.setStrokeWidth(80);
        paint.setAntiAlias(true);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);

        paint.setStrokeCap(Paint.Cap.BUTT);// 無(wú)線帽
        canvas.drawLine(100,200,400,200,paint);

        paint.setStrokeCap(Paint.Cap.SQUARE);// 方形線帽
        canvas.drawLine(100,400,400,400,paint);

        paint.setStrokeCap(Paint.Cap.ROUND);// 圓形線帽
        canvas.drawLine(100,600,400,600,paint);

    }

效果圖:

線帽類型

冒多出來(lái)的那塊區(qū)域就是線帽御吞,就相當(dāng)于給原來(lái)的直線加上一個(gè)帽子一樣麦箍。

3.setStrokeJoin(Paint.Join join):設(shè)置線段連接處樣式

Join.MITER:結(jié)合處為銳角
Join.ROUND:結(jié)合處為圓弧
Join.BEVEL:結(jié)合處為直線

代碼如下:

 private void drawStrokeJoin(Canvas canvas){
        ...
        Path path  = new Path();
        path.moveTo(100,100);
        path.lineTo(450,100);
        path.lineTo(100,300);
        paint.setStrokeJoin(Paint.Join.MITER);//銳角
        canvas.drawPath(path,paint);

        path.moveTo(100,400);
        path.lineTo(450,400);
        path.lineTo(100,600);
        paint.setStrokeJoin(Paint.Join.BEVEL);//直線
        canvas.drawPath(path,paint);

        path.moveTo(100,700);
        path.lineTo(450,700);
        path.lineTo(100,900);
        paint.setStrokeJoin(Paint.Join.ROUND);//圓弧
        canvas.drawPath(path,paint);
    }

效果圖:

連接樣式

4.setPathEffect(PathEffect effect):設(shè)置繪制路徑的效果
這個(gè)方法很牛逼,根據(jù)PathEffect的不同陶珠,可以實(shí)現(xiàn)不同的效果挟裂,這里我舉兩個(gè)例子。

1.CornerPathEffect:圓形拐角效果 
paint.setPathEffect(new CornerPathEffect(100));
利用半徑R=50的圓來(lái)代替原來(lái)兩條直線間的夾角

2.DashPathEffect:虛線效果 
paint.setPathEffect(new DashPathEffect(new float[]{20,10,50,100},15));
intervals[]:表示組成虛線的各個(gè)線段的長(zhǎng)度揍诽;整條虛線就是由intervals[]中這些基本線段循環(huán)組成的诀蓉。
phase:表示開(kāi)始繪制的偏移值 

代碼如下:

private void drawComposePathEffectDemo(Canvas canvas){
        //畫(huà)原始路徑
        Paint paint = getPaint();
        Path path = getPath();
        canvas.drawPath(path,paint);

        //僅應(yīng)用圓角特效的路徑
        canvas.translate(0,300);
        CornerPathEffect cornerPathEffect = new CornerPathEffect(100);
        paint.setPathEffect(cornerPathEffect);
        canvas.drawPath(path,paint);

        //僅應(yīng)用虛線特效的路徑
        canvas.translate(0,300);
        DashPathEffect dashPathEffect = new DashPathEffect(new float[]{2,5,10,10},0);
        paint.setPathEffect(dashPathEffect);
        canvas.drawPath(path,paint);

        //利用ComposePathEffect先應(yīng)用圓角特效,再應(yīng)用虛線特效
        canvas.translate(0,300);
        ComposePathEffect composePathEffect = new ComposePathEffect(dashPathEffect,cornerPathEffect);
        paint.setPathEffect(composePathEffect);
        canvas.drawPath(path,paint);

        //利用SumPathEffect,分別將圓角特效應(yīng)用于原始路徑,然后將生成的兩條特效路徑合并
        canvas.translate(0,300);
        paint.setStyle(Paint.Style.STROKE);
        SumPathEffect sumPathEffect = new SumPathEffect(cornerPathEffect,dashPathEffect);
        paint.setPathEffect(sumPathEffect);
        canvas.drawPath(path,paint);

    }

效果圖:


PathEffect

(5-8就是我們真正的化妝技術(shù)了!等會(huì)一個(gè)個(gè)詳細(xì)介紹的暑脆!這里先留個(gè)印象吧)

5.setXfermode(Xfermode xfermode):設(shè)置圖形重疊時(shí)的處理方式
如合并渠啤,取交集或并集,經(jīng)常用來(lái)制作橡皮的擦除效果

6.setMaskFilter(MaskFilter maskfilter):實(shí)現(xiàn)濾鏡的效果
如濾化添吗,立體等

7.setColorFilter(ColorFilter colorfilter):設(shè)置顏色過(guò)濾器
可以在繪制顏色時(shí)實(shí)現(xiàn)不用顏色的變換效果

8.setShader(Shader shader):設(shè)置圖像效果
使用Shader可以繪制出各種漸變效果

2.2 繪制文字

如果有的時(shí)候娜膘,你想在化妝的同時(shí)在你女朋友臉上寫(xiě)字沃暗,那么下面的知識(shí)點(diǎn)是一定要重點(diǎn)掌握的衣陶。

1.Typeface getTypeface() 樟氢、Typeface setTypeface(Typeface typeface):獲取與設(shè)置字體類型。
Android默認(rèn)有四種字體樣式:BOLD(加粗)、BOLD_ITALIC(加粗并傾斜)、ITALIC(傾斜)、NORMAL(正常)辰如。
我們也可以通過(guò)Typeface類來(lái)自定義個(gè)性化字體。這里有一個(gè)騷操作贵试,就是將圖標(biāo)直接放到ttf中琉兜,用起來(lái)很舒服。

2.Paint.Align getTextAlign() 锡移、void setTextAlign(Paint.Align align):獲取與設(shè)置文本對(duì)齊方式
取值為CENTER呕童、LEFT、RIGHT淆珊,也就是文字繪制是左邊對(duì)齊夺饲、右邊還是局中的。

3.int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth):判斷能顯示多少文字

private static final String STR = "ABCDEF";
mPaint.setTextSize(50);
float[] value = new float[1];
int ret = mPaint.breakText(STR, true, 200, value);
Log.i(TAG, "breakText="+ret+", STR="+STR.length()+", value="+value[1]);
//輸出結(jié)果如下:
//breakText=5, STR=8, value=195.0

2.3 繪制文字高級(jí)技巧

現(xiàn)在考慮這樣一種場(chǎng)景施符,自定義一個(gè)圓形的進(jìn)度條往声,在圓形中間顯示下載進(jìn)度。
乍一看感覺(jué)很簡(jiǎn)單戳吝,但是動(dòng)手之后就發(fā)現(xiàn)這里面有一個(gè)坑——下載進(jìn)度無(wú)法顯示在圓心浩销。

為什么呢?這兒就涉及到Android繪制文字的原理了听哭。在Paint中慢洋,我們可以找到一個(gè)叫FontMetrics的內(nèi)部類,從名字上看陆盘,它和文字的測(cè)量時(shí)有關(guān)系的普筹,進(jìn)去看看

public static class FontMetrics {
        /**
         * The maximum distance above the baseline for the tallest glyph in
         * the font at a given text size.
         */
        public float   top;
        /**
         * The recommended distance above the baseline for singled spaced text.
         */
        public float   ascent;
        /**
         * The recommended distance below the baseline for singled spaced text.
         */
        public float   descent;
        /**
         * The maximum distance below the baseline for the lowest glyph in
         * the font at a given text size.
         */
        public float   bottom;
        /**
         * The recommended additional space to add between lines of text.
         */
        public float   leading;
    }

這里一共定義了5個(gè)float類型的變量,我們用圖片來(lái)直觀的體現(xiàn)下他們的關(guān)系隘马。

FontMetrics

top = top線的y坐標(biāo) - baseline線的y坐標(biāo)太防,不能超過(guò)
bottom = bottom線的y坐標(biāo) - baseline線的y坐標(biāo),不能超過(guò)
ascent = ascent線的y坐標(biāo) - baseline線的y坐標(biāo)酸员,是系統(tǒng)建議的高度蜒车,字母上有音標(biāo)符號(hào)時(shí),會(huì)超過(guò)
desent = desent線的y坐標(biāo) - baseline線的y坐標(biāo)幔嗦,可能超過(guò)
最坑的就是這個(gè)baseline酿愧,由于它不在文字的中心,而drawText()中傳入的參數(shù)是以baseline為基準(zhǔn)的邀泉,因此在繪制時(shí)就會(huì)出現(xiàn)文字不在中心的情況嬉挡。

那么要如何解決呢叛氨?擁有數(shù)學(xué)頭腦的同學(xué)肯定一眼就看穿,中心點(diǎn)是可以計(jì)算出來(lái)的棘伴!

解:設(shè)文字中心到top、bottom的距離分別為A屁置、B焊夸,其中A=B;
設(shè)文字中心到baseLine的距離為C蓝角;
由圖可知
A=B = (bottom - top)/2
因?yàn)閎ottom = baseline + FontMetrics.bottom
且top = baseline + FontMetrics.top
所以A = B = (FontMetrics.bottom - FontMetrics.top)/ 2

又因?yàn)镃 =  B - (bottom - baseline)=  B - FontMetrics.bottom
且C = baseline - center      
所以baseline - center = (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom

綜上所述
baseline = center +(FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom        

計(jì)算完畢阱穗,是不是有點(diǎn)懷念自己的高中數(shù)學(xué)老師了?

2.4 實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)

現(xiàn)在我們知道了文字繪制的原理使鹅,又得到了中心點(diǎn)的計(jì)算公式揪阶,那么就來(lái)手寫(xiě)一個(gè)圓形的進(jìn)度條吧。

首先需要在創(chuàng)建attrs資源文件患朱,并且在其中自定義資源類型

 <declare-styleable name="CircleProgressBar">
        <attr name="progressColor" format="color"/>
        <attr name="progressBackgroundColor" format="color"/>
        <attr name="progressMax" format="float"/>
        <attr name="circleWidth" format="dimension"/>
        <attr name="textSize" format="dimension"/>
        <attr name="textColor" format="color"/>
    </declare-styleable>

接著自定義控件鲁僚,由于有自定義的資源類型,所以要在構(gòu)造方法中獲取他們裁厅,注意最后要typedArray.recycle()冰沙。

 public CircleProgressBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar);
        mProgressBackgroundColor = typedArray.getColor(R.styleable.CircleProgressBar_progressBackgroundColor, Color.GRAY);
        mProgressColor = typedArray.getColor(R.styleable.CircleProgressBar_progressColor, Color.BLUE);
        mProgressMax = typedArray.getFloat(R.styleable.CircleProgressBar_progressMax, 100);
        mCircleWidth = typedArray.getDimension(R.styleable.CircleProgressBar_circleWidth, 20);
        mTextSize = typedArray.getDimension(R.styleable.CircleProgressBar_textSize, 60);
        mTextColor = typedArray.getColor(R.styleable.CircleProgressBar_textColor, Color.RED);
        typedArray.recycle();
    }

代碼主要是重寫(xiě)onDraw()

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //畫(huà)圓環(huán)
        mPaint = new Paint();
        mPaint.setColor(mProgressBackgroundColor);
        mPaint.setStrokeWidth(mCircleWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        int center = getWidth() / 2;
        float radius = center - mCircleWidth / 2;
        canvas.drawCircle(center, center, radius, mPaint);
        //畫(huà)文字
        mPaint = new Paint();
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColor);
        mPaint.setStrokeWidth(0);
        mPaint.setTypeface(Typeface.DEFAULT_BOLD);
        int percent = (int) (mProgress / mProgressMax* 100) ;
        String percentStr = percent + "%";
        Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
        canvas.drawText(percentStr,
                center - mPaint.measureText(percentStr) / 2,
                center + (fm.bottom - fm.top) / 2 - fm.bottom,
                mPaint);
        //畫(huà)圓弧
        mPaint=new Paint();
        mPaint.setColor(mProgressColor);
        mPaint.setStrokeWidth(mCircleWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        RectF oval=new RectF(center-radius,center-radius,center+radius,center+radius);
        canvas.drawArc(oval,0,mProgress/mProgressMax*360,false,mPaint);
    }

重寫(xiě)onMeasure()是為了解決wrap_content的問(wèn)題。如果沒(méi)有加上這一段执虹,那么使用wrap_contentmatch_parent就沒(méi)有區(qū)別拓挥。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
        if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(300,300);
        }else if(widthSpecMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(300,heightSpecSize);
        }else if(heightSpecMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize,300);
        }
    }

再給調(diào)用者提供一個(gè)設(shè)置進(jìn)度的方式,萬(wàn)事大吉袋励!

public void setProgress(int progress){
        if(progress<0){
            throw new IllegalArgumentException("進(jìn)度不能小于0侥啤!");
        }
        if(progress>mProgressMax){
            mProgress= (int) mProgressMax;
        }
        if(progress<mProgressMax){
            mProgress=progress;
            //不知道應(yīng)用層在哪個(gè)線程調(diào)用
            postInvalidate();
        }
    }

值得注意的是,在這里我們還沒(méi)有對(duì)padding進(jìn)行處理茬故。認(rèn)真閱讀奶奶系列的同學(xué)盖灸,自己解決一定是沒(méi)有問(wèn)題的,這里給出一個(gè)解決思路均牢,只要在onDraw()中獲取這些值糠雨,并在canvas.drawXXX()時(shí)加入即可。

int paddingLeft=getPaddingLeft();
int paddingRight=getPaddingRight();
int paddingTop=getPaddingTop();
int paddingBottom=getPaddingBottom();

最后來(lái)看看效果圖(挺丑的= =)


圓形進(jìn)度條

3.高級(jí)渲染

基礎(chǔ)的知識(shí)點(diǎn)大家應(yīng)該了解了不少徘跪,下面就一起來(lái)學(xué)習(xí)真正的化妝技巧吧甘邀。

首先是高級(jí)渲染部分。所謂高級(jí)渲染垮庐,其實(shí)就是通過(guò)setShader(Shader shader)方法來(lái)設(shè)置圖像效果松邪。
Shader是著色器的意思,canvas.drawXXX()畫(huà)出了具體的形狀哨查,而畫(huà)筆的shader則定義了圖形的著色和外觀逗抑。

3.1.BitmapShader

BitmapShader是指位圖圖像渲染,即用BitMap對(duì)繪制的圖形進(jìn)行渲染著色,簡(jiǎn)單來(lái)說(shuō)是用圖片對(duì)圖形進(jìn)行貼圖邮府。

在使用Shader時(shí)荧关,涉及到一個(gè)TileMode的概念,從名字上可以看出褂傀,這個(gè)參數(shù)就是用來(lái)設(shè)置拉伸模式的忍啤。也就是說(shuō),當(dāng)圖片小于容器的大小時(shí)仙辟,多出來(lái)的那部分要怎么畫(huà)同波。TileMode一共有三種類型:

TileMode.CLAMP 拉伸最后一個(gè)像素去鋪滿剩下的地方
TileMode.MIRROR 通過(guò)鏡像翻轉(zhuǎn)鋪滿剩下的地方。
TileMode.REPEAT 重復(fù)圖片平鋪整個(gè)畫(huà)面

具體該怎么使用就直接上代碼吧

private void BitmapShaderTest(Canvas canvas) {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy2);
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scale = Math.max(width, height) / Math.min(width, height);
        BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        android.graphics.Matrix matrix = new android.graphics.Matrix();
        matrix.setScale(scale, scale);
        bitmapShader.setLocalMatrix(matrix);
        mPaint.setShader(bitmapShader);
        canvas.drawRect(0,0,800,800,mPaint);
    }

都是一些基本的套路叠国,接著看效果圖


BitmapShader_Rectangle

注意觀察左側(cè)被拉伸的區(qū)域未檩,這就是TileMode.CLAMP的作用。剩下2個(gè)類型大家可以自己去玩玩看粟焊。

那么這玩意兒有什么用處呢冤狡?你可以和女朋友裝逼說(shuō)“用什么CircleImageView,直接用Paint畫(huà)一個(gè)不就好了吆玖?”筒溃。
只要把上面代碼最后一行canvas.drawRect(0,0,800,800,mPaint);替換成canvas.drawCircle(height / 2, height / 2, height / 2, mPaint);即可。

BitmapShader_Cirlce

遺憾的是沾乘,如果你女朋友是會(huì)玩的并且學(xué)習(xí)過(guò)反裝逼怜奖,那她就會(huì)說(shuō),“這算什么翅阵,老娘不用你的Paint照樣能畫(huà)出來(lái)歪玲!”。于是唰唰唰一頓操作掷匠,你的代碼變成了這樣:

ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
shapeDrawable.getPaint().setShader(bitmapShader);
shapeDrawable.setBounds(0,0,width,height);
shapeDrawable.draw(canvas);

效果圖就不放了滥崩,和上面的類似,只不過(guò)這次是個(gè)橢圓讹语。

3.1.1.BitmapShader放大鏡效果

結(jié)合上面的知識(shí)點(diǎn)钙皮,我們來(lái)學(xué)習(xí)第一個(gè)化妝技巧——放大妝!
首先顽决,我們需要原圖和放大圖兩張圖片短条。原圖直接展示在界面上,放大圖通過(guò)ShapeDrawable只展示出一塊圓形的區(qū)域才菠。

public ZoomImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //原圖
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy3);
        //縮放圖
        mBitmapScale = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() * FACTOR
                , mBitmap.getHeight() * FACTOR, true);
        mBitmapShader = new BitmapShader(mBitmapScale, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mShapeDrawable = new ShapeDrawable(new OvalShape());
        mShapeDrawable.getPaint().setShader(mBitmapShader);
        mShapeDrawable.setBounds(0,0,RADIUS*2,RADIUS*2);
        mMatrix = new Matrix();
    }

接著茸时,在ondraw()方法中進(jìn)行繪制

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(mBitmap,0,0,null);
        mShapeDrawable.draw(canvas);
    }

最后,重寫(xiě)onTouchEvent()實(shí)現(xiàn)自由放大赋访。需要理解的是可都,當(dāng)我的手指向右下方移動(dòng)時(shí)缓待,放大圖片本身是要向左上移動(dòng)的,而放大區(qū)域則是一直以手指觸摸點(diǎn)為圓心渠牲。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x= (int) event.getX();
        int y= (int) event.getY();
        //將放大的圖片往相反的方向移動(dòng)
        mMatrix.setTranslate(RADIUS-x*FACTOR,RADIUS-y*FACTOR);
        mBitmapShader.setLocalMatrix(mMatrix);
        //手指觸摸點(diǎn)為圓心
        mShapeDrawable.setBounds(x-RADIUS,y-RADIUS,x+RADIUS,y+RADIUS);
        invalidate();
        return true;
    }

放大妝的作用很明顯旋炒,比如你女朋友牙齒特別好看,那么出門(mén)的時(shí)候就可以給她來(lái)上這個(gè)一下签杈,把美麗的牙齒大方的展示出來(lái)国葬!我們來(lái)看看效果圖

放大鏡效果

3.2.LinearGradient

LinearGradient是線性渲染, 主要通過(guò)顏色的變化來(lái)進(jìn)行渲染芹壕。
關(guān)于基本使用,我們直接上代碼來(lái)看

private int[] mColors = {Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW};
 /**線性漸變
     * x0, y0, 起始點(diǎn)
     * x1, y1, 結(jié)束點(diǎn)
     * int[]  mColors, 中間依次要出現(xiàn)的幾個(gè)顏色
     * float[] positions,數(shù)組大小跟colors數(shù)組一樣大接奈,中間依次擺放的幾個(gè)顏色分別放置在那個(gè)位置上(參考比例從左往右)
     * tile
     */
    private void LinearGradientTest(Canvas canvas) {
        LinearGradient linearGradient = new LinearGradient(0, 0, 800, 800, mColors, null, Shader.TileMode.CLAMP);
        mPaint.setShader(linearGradient);
        canvas.drawRect(0, 0, 800, 800, mPaint);
    }

效果圖這樣

LinearGradient基本使用.png

3.2.1.LinearGradient霓虹燈文字

下面這個(gè)妝就厲害了踢涌,是動(dòng)態(tài)的,還會(huì)來(lái)回轉(zhuǎn)序宦,我們看代碼
首先睁壁,這個(gè)自定義View要繼承TextView,這樣比較方便

public class LinearGradientTextView extends TextView

接著互捌,通過(guò)getPaint()獲取到畫(huà)筆潘明,并為其設(shè)置LinearGradient

public LinearGradientTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //獲取屏幕寬度
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mPoint = new Point();
        windowManager.getDefaultDisplay().getSize(mPoint);
        String text = getText().toString();
        //拿到TextView的畫(huà)筆
        mPaint = getPaint();
        mTextWidth = (int) mPaint.measureText(text);
        int gradientTextSize = mTextWidth / text.length() * 3;
        mLinearGradient = new LinearGradient(0, 0, gradientTextSize, 0, mColors, null, Shader.TileMode.CLAMP);
        mPaint.setShader(mLinearGradient);
    }

此時(shí)靜態(tài)的效果已經(jīng)出來(lái)了,剩下的就是重寫(xiě)onDraw()實(shí)現(xiàn)動(dòng)態(tài)變換

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Matrix matrix = new Matrix();
        mTranslate += DELTAX;
        //反復(fù)循環(huán)
        if (mTranslate > mPoint.x || mTranslate < 1) {
            DELTAX = -DELTAX;
        }
        matrix.setTranslate(mTranslate, 0);
        mLinearGradient.setLocalMatrix(matrix);
        postInvalidate();
    }

國(guó)際慣例秕噪,看一張效果圖(gif什么的看上去好麻煩…大家根據(jù)代碼自己腦補(bǔ)吧)

文字霓虹燈.png

3.3.SweepGradient

SweepGradient叫漸變渲染或者梯度渲染钳降,其效果與用法都和LinearGradient類似,還是先介紹基本用法

private void SweepGradientTest(Canvas canvas) {
        SweepGradient mSweepGradient = new SweepGradient(300, 300, mColors, null);
        mPaint.setShader(mSweepGradient);
        canvas.drawRect(0, 0, 800,800, mPaint);
    }

效果圖

SweepGradient基本使用.png

3.3.1 SweepGradient雷達(dá)掃描

那么這倒霉玩意兒又能干啥呢腌巾?SweepGradient經(jīng)典的應(yīng)用場(chǎng)景就是實(shí)現(xiàn)雷達(dá)掃描效果遂填。
來(lái)看代碼,首先在構(gòu)造方法中初始化Paint

public RadarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);

        mRadarBg = new Paint(Paint.ANTI_ALIAS_FLAG);     //設(shè)置抗鋸齒
        mRadarBg.setColor(mRadarBgColor);                  //畫(huà)筆顏色
        mRadarBg.setStyle(Paint.Style.FILL);           //畫(huà)實(shí)心圓

        mRadarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);     //設(shè)置抗鋸齒
        mRadarPaint.setColor(mCircleColor);                  //畫(huà)筆顏色
        mRadarPaint.setStyle(Paint.Style.STROKE);           //設(shè)置空心的畫(huà)筆澈蝙,只畫(huà)圓邊
        mRadarPaint.setStrokeWidth(2);                      //畫(huà)筆寬度
        mRadarShader = new SweepGradient(0, 0, mStartColor, mEndColor);
        mMatrix = new Matrix();
    }

接著在onDraw()方法中進(jìn)行繪制

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mRadarBg.setShader(null);
        //將畫(huà)板移動(dòng)到屏幕的中心點(diǎn)
        canvas.translate(mRadarRadius, mRadarRadius);
        //繪制底色吓坚,讓雷達(dá)的線看起來(lái)更清晰
        canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
        //畫(huà)圓圈
        for (int i = 1; i <= mCircleNum; i++) {
            canvas.drawCircle(0, 0, (float) (i * 1.0 / mCircleNum * mRadarRadius), mRadarPaint);
        }
        //繪制雷達(dá)基線 x軸
        canvas.drawLine(-mRadarRadius, 0, mRadarRadius, 0, mRadarPaint);
        //繪制雷達(dá)基線 y軸
        canvas.drawLine(0, mRadarRadius, 0, -mRadarRadius, mRadarPaint);
        //設(shè)置顏色漸變從透明到不透明
        mRadarBg.setShader(mRadarShader);
        canvas.concat(mMatrix);
        canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
    }

最后來(lái)個(gè)handler讓雷達(dá)動(dòng)起來(lái)吧

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mRotate += 3;
            postInvalidate();
            mMatrix.reset();
            mMatrix.preRotate(mRotate, 0, 0);
            mHandler.sendEmptyMessageDelayed(MSG_WHAT, DELAY_TIME);
        }
    };

效果圖長(zhǎng)這樣

雷達(dá)掃描.png

3.4.RadialGradient

RadialGradient表示環(huán)形渲染,先來(lái)看下基本用法

 private void RadialGradientTest(Canvas canvas) {
        RadialGradient mRadialGradient = new RadialGradient(300, 300, 100, mColors, null, Shader.TileMode.REPEAT);
        mPaint.setShader(mRadialGradient);
        canvas.drawCircle(300, 300, 300, mPaint);
    }

效果圖灯荧,正常人應(yīng)該是會(huì)暈的

RadialGradient基本使用.png

3.4.1 RadialGradient水波紋效果

文章的最后礁击,終于輪到大名鼎鼎的水波紋效果了。
思路大致如下:使用RadialGradient響應(yīng)觸摸事件繪制出初始的圓形波紋逗载,接著使用屬性動(dòng)畫(huà)將圓形波紋變大哆窿。
由于屬性動(dòng)畫(huà)涉及到對(duì)屬性的設(shè)置,這里我們提供一個(gè)setRadius()方法

public void setRadius(final int radius) {
        mCurRadius = radius;
        if (mCurRadius > 0) {
            mRadialGradient = new RadialGradient(mX, mY, mCurRadius, 0x00FFFFFF, 0xFF58FAAC, Shader.TileMode.CLAMP);
            mPaint.setShader(mRadialGradient);
        }
        postInvalidate();
    }

重點(diǎn)都在onTouch()方法中

@Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mX != event.getX() || mY != mY) {
            mX = (int) event.getX();
            mY = (int) event.getY();
            setRadius(DEFAULT_RADIUS);
        }
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                return true;
            case MotionEvent.ACTION_UP:
            {
                if (mAnimator != null && mAnimator.isRunning()) {
                    mAnimator.cancel();
                }
                if (mAnimator == null) {
                    mAnimator = ObjectAnimator.ofInt(this,"radius",DEFAULT_RADIUS, getWidth());
                }
                mAnimator.setInterpolator(new AccelerateInterpolator());
                mAnimator.addListener(new Animator.AnimatorListener() {
                   ...
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        setRadius(0);
                    }
                    ...
                });
                mAnimator.start();
            }
        }
        return super.onTouchEvent(event);
    }

這里就通過(guò)屬性動(dòng)畫(huà)對(duì)radius值進(jìn)行了改變撕贞,關(guān)于屬性動(dòng)畫(huà)更耻,以后會(huì)從源碼入手好好講一講。最后只要在onDraw()里進(jìn)行簡(jiǎn)單的繪制即可捏膨。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mX, mY, mCurRadius, mPaint);
    }

效果光看圖片不容易感受秧均,大伙將就著看食侮。


水波紋效果.png

4.總結(jié)

這篇文章首先介紹了Paint的一些基礎(chǔ)用法,包括圖形目胡、路徑锯七、文字的繪制。接著將高級(jí)渲染進(jìn)行了細(xì)致講解誉己。其實(shí)還有最后一個(gè)ComposeShader沒(méi)有提眉尸,這是組合渲染,可以將前面說(shuō)的4種渲染方式隨意組合巨双,大家自由發(fā)揮即可噪猾。

Paint高級(jí)化妝技巧還包括Xfermode以及濾鏡的使用,下篇文章咱們就來(lái)懟他們倆筑累。

完結(jié)撒花~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末袱蜡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子慢宗,更是在濱河造成了極大的恐慌坪蚁,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镜沽,死亡現(xiàn)場(chǎng)離奇詭異敏晤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)缅茉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)嘴脾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蔬墩,你說(shuō)我怎么就攤上這事统阿。” “怎么了筹我?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵扶平,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蔬蕊,道長(zhǎng)结澄,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任岸夯,我火速辦了婚禮麻献,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘猜扮。我一直安慰自己勉吻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布旅赢。 她就那樣靜靜地躺著齿桃,像睡著了一般惑惶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上短纵,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天带污,我揣著相機(jī)與錄音,去河邊找鬼香到。 笑死鱼冀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悠就。 我是一名探鬼主播千绪,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼梗脾!你這毒婦竟也來(lái)了翘紊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤藐唠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鹉究,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宇立,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年自赔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妈嘹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绍妨,死狀恐怖润脸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情他去,我是刑警寧澤毙驯,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站灾测,受9級(jí)特大地震影響爆价,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜媳搪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一铭段、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秦爆,春花似錦序愚、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)芬膝。三九已至,卻和暖如春拗胜,著一層夾襖步出監(jiān)牢的瞬間蔗候,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工埂软, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锈遥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓勘畔,卻偏偏與公主長(zhǎng)得像所灸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子炫七,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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

  • 一爬立、概述 1. 四線格與基線 小時(shí)候,我們?cè)趧傞_(kāi)始學(xué)習(xí)寫(xiě)字母時(shí)万哪,用的本子是四線格的侠驯,我們必須把字母按照規(guī)則寫(xiě)在四線...
    addapp閱讀 7,663評(píng)論 2 17
  • 導(dǎo)航 Android Paint之顏色過(guò)濾器 Paint之shader(圖像渲染) Paint之PathEffec...
    侯蛋蛋_閱讀 4,584評(píng)論 0 5
  • 1.版本 家庭版:免費(fèi) 專業(yè)版:收費(fèi)吟策、無(wú)限的并發(fā)連接 2.下載地址 http://www.tenable.com/...
    cybeyond閱讀 453評(píng)論 0 0
  • 工作兩年半,比起工作半年的人的止,你的核心競(jìng)爭(zhēng)力在哪里檩坚? 產(chǎn)品工作的核心是什么?我對(duì)自己工作的邊界定義是什么诅福? 了解產(chǎn)...
    一念佛魔分閱讀 140評(píng)論 0 0
  • 今天我們?nèi)チ舜笥矶韶椅:密囈院笪覀兊嚼锩妫吹搅艘豢煤艽蟮陌貥?shù)氓润,叫作神柏赂乐。有4000多年了。 然后我們?nèi)チ瞬A颍?..
    木一創(chuàng)客閱讀 275評(píng)論 0 1