android高級UI之Draw繪制流程、Paint渲染高級應用


Draw繪制流程:

在上一次https://www.cnblogs.com/webor2006/p/12167825.html對於View的測量布局進行了整體的學習,接下來則需要關注咱們的UI是如何繪制出來的,此時就需要再來分析一下系統源碼【這里以Android 8.1源碼進行分析】來梳理整個的調用流程了,根據上一次的測量流程最終會調用到這進行繪制:

 

而這個方法里面會調用performMeasure,performLayout,performDraw,對於前兩個已經在上一次分析清楚了,接一來重點觀注performDraw()的細節了:

接下來看一下它的具體實現:

private void performDraw() {
        if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
            return;
        } else if (mView == null) {
            return;
        }

        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        mFullRedrawNeeded = false;

        mIsDrawing = true;
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        // For whatever reason we didn't create a HardwareRenderer, end any
        // hardware animations that are now dangling
        if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
            final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
            for (int i = 0; i < count; i++) {
                mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
            }
            mAttachInfo.mPendingAnimatingRenderNodes.clear();
        }

        if (mReportNextDraw) {
            mReportNextDraw = false;

            // if we're using multi-thread renderer, wait for the window frame draws
            if (mWindowDrawCountDown != null) {
                try {
                    mWindowDrawCountDown.await();
                } catch (InterruptedException e) {
                    Log.e(mTag, "Window redraw count down interruped!");
                }
                mWindowDrawCountDown = null;
            }

            if (mAttachInfo.mThreadedRenderer != null) {
                mAttachInfo.mThreadedRenderer.fence();
                mAttachInfo.mThreadedRenderer.setStopped(mStopped);
            }

            if (LOCAL_LOGV) {
                Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
            }

            if (mSurfaceHolder != null && mSurface.isValid()) {
                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
                SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();

                sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
            } else {
                pendingDrawFinished();
            }
        }
    }

其中參數fullRedrawNeeded是由mFullRedrawNeeded成員變量獲取,它的作用是判斷是否需要重新繪制全部視圖,如果是第一次繪制視圖,那么顯然應該繪制所以的視圖,如果由於某些原因,導致了視圖重繪,那么就沒有必要繪制所有視圖。接下來則看一下它的核心邏輯:

其中可以看到有一個fullRedrawNeeded的判斷是否需要重置dirty區域,最后調用了drawSoftware()方法:

接下來這里面就可以看到我們所關心的東東了:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

        // Draw with software renderer.
        final Canvas canvas;
        try {
            final int left = dirty.left;
            final int top = dirty.top;
            final int right = dirty.right;
            final int bottom = dirty.bottom;

            canvas = mSurface.lockCanvas(dirty);

            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }

            // TODO: Do this in native
 canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            Log.e(mTag, "Could not lock surface", e);
            // Don't assume this is due to out of memory, it could be
            // something else, and if it is something else then we could
            // kill stuff (or ourself) for no reason.
            mLayoutRequested = true;    // ask wm for a new surface next time.
            return false;
        }

        try {
            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                        + canvas.getWidth() + ", h=" + canvas.getHeight());
                //canvas.drawARGB(255, 255, 0, 0);
            }

            // If this bitmap's format includes an alpha channel, we
            // need to clear it before drawing so that the child will
            // properly re-composite its drawing on a transparent
            // background. This automatically respects the clip/dirty region
            // or
            // If we are applying an offset, we need to clear the area
            // where the offset doesn't appear to avoid having garbage
            // left in the blank areas.
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }

            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;

            if (DEBUG_DRAW) {
                Context cxt = mView.getContext();
                Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                        ", metrics=" + cxt.getResources().getDisplayMetrics() +
                        ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
            }
            try {
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;

                mView.draw(canvas);

                drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } finally {
                if (!attachInfo.mSetIgnoreDirtyState) {
                    // Only clear the flag if it was not set during the mView.draw() call
                    attachInfo.mIgnoreDirtyState = false;
                }
            }
        } finally {
            try {
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                Log.e(mTag, "Could not unlock surface", e);
                mLayoutRequested = true;    // ask wm for a new surface next time.
                //noinspection ReturnInsideFinallyBlock
                return false;
            }

            if (LOCAL_LOGV) {
                Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
            }
        }
        return true;
    }

可以看出,首先是實例化了Canvas對象,然后鎖定該canvas的區域,由dirty區域決定,接着對canvas進行一系列的屬性賦值,最后調用了mView.draw(canvas)方法,這里就可以清晰的看到,其實平常我們所說的畫布並不是Canvas,而是Surface,通過上面源碼也通清楚的看到,好,接下來則重點分析一下mView.draw(canvas)方法,還記得mView還記得是啥不?

 

好,回到主流程上繼續:

開始看到我們熟悉的東東了,不過在繼續往下分析之前,這句上面還有一個值得看一下:

也就是在我們正式要繪之前先將黑板給擦干凈了,好接下來則重點看一下mView.draw(cavas)的細節了:

解讀一下:

    /*
     * Draw traversal performs several drawing steps which must be executed(繪制遍歷執行必須執行的幾個繪圖步驟)
     * in the appropriate order:(按下列順序)
     *
     *      1. Draw the background(畫背景)
     *      2. If necessary, save the canvas' layers to prepare for fading(必要時,保存畫布的層以准備漸變。)
     *      3. Draw view's content(繪制視圖內容)
     *      4. Draw children(畫子view)
     *      5. If necessary, draw the fading edges and restore layers(如果需要,繪制漸變邊緣並恢復圖層)
     *      6. Draw decorations (scrollbars for instance)(繪制裝飾(例如滾動條))
     */

其中第2步和第5步在接下來的繪制操練中會用到的,這里先忽略,這里直接看第三步:

在第四步的dispatchDraw(canvas);

也是由子類來實現,這里我門是以ViewGroup為例,這個方法我們會發現他在一直迭代子view:

關於這個方法在之后的學習再來分析,至此,View的繪制流程完整的分析完了,我們能夠明白,最終,其實測量,布局,和繪制這三個流程最終都是調用到onMeasure,onLayout,onDraw讓控件自己去完成的。只不過系統組件他實現幫我門已經按照它們自己的規則去完成了自己想要實現的效果,那么我們同樣是根據順序,原理,去施加自己的業務,完成自己想要的自定義控件。

而具體圖形是如何呈現在我門的UI上的,以及我門如何制作一些比較漂亮的特效之類的,則就需要依賴於Paint和Canvas了,接下來則就開始研究這塊的東東。

圖形繪制:

Canvas和Paint的關系:

在正式擼碼操練圖形繪制之前,先來搞清楚Canvas和Paint它們倆之間的關系,我們以畫圓形API為例:

轉到它的父類執行了:

原來最終的繪制是靠底層的c來繪制的【確切說是由底層opengl來畫的,最終是由GPU來進行繪制】,也就是很明顯可以看到Canvas並不是具體的執行者,而是一個傳達者, 在Canvas當中我們會將所有的參數信息設置好,然后交由底層去繪制那么我們可以得到,其實Canvas就是一個圖形繪制的會話。整體畫的流程可以表示成下圖【參考博客:https://www.jianshu.com/p/fdae07efceb4】:

 

那。。Paint是干嘛的呢?平常我們在用Canvas調用繪制api基本上都會帶這個參數:

此時可以打開Paint類看一下:

所以可以看出:我們在繪制圖形的時候,Canvas決定了圖形的位置,形狀等特性, 而Paint決定了色彩和樣式這兩者。所以接下來則會圍繞Canvas和Paint這倆個東東進行詳細的學習,重點是學習它們的高級用法。

Paint基礎應用:

對於Paint的一些基本用法其實在之前的UI學習中也已經使用過了,這里就將Paint的一些基本的API列出來,供之后參考一下,這里就不一一進行操練了,這里重點是要學習高級用法,整個Paint基礎可以划分為如下兩大類:

負責設置獲取圖形繪制、路徑相關:

1.setStyle(Paint.Style style) 
設置畫筆樣式,取值有
Paint.Style.FILL :填充內部
Paint.Style.FILL_AND_STROKE :填充內部和描邊
Paint.Style.STROKE :僅描邊、
注意STROKE、FILL_OR_STROKE與FILL模式下外輪廓的位置會擴大。
2.setStrokeWidth(float width) 
設置畫筆寬度 
3.setAntiAlias(boolean aa) 
設置畫筆是否抗鋸齒 
4.setStrokeCap(Paint.Cap cap) ------demo演示 
設置線冒樣式,取值有Cap.ROUND(圓形線冒)、Cap.SQUARE(方形線冒)、Paint.Cap.BUTT(無線冒) 
注意:冒多出來的那塊區域就是線帽!就相當於給原來的直線加上一個帽子一樣,所以叫線帽 
5.setStrokeJoin(Paint.Join join) ------ demo演示
設置線段連接處樣式,取值有:Join.MITER(結合處為銳角)、Join.Round(結合處為圓弧)、Join.BEVEL(結合處為直線) 
6.setStrokeMiter(float miter) 
設置筆畫的傾斜度,90度拿畫筆與30拿畫筆,畫出來的線條樣式肯定是不一樣的吧。(事實證明,根本看不出來什么區別好嗎……囧……)
void reset() 
清空畫筆復位。
void set(Paint src) 
設置一個外來Paint畫筆。
7.void setARGB(int a, int r, int g, int b) 
int getAlpha() 
void setAlpha(int a) 
int getColor() 
void setColor(int color) 
獲取與設置alpha值、顏色、ARGB等。
final boolean isAntiAlias() 
8.void setAntiAlias(boolean aa) 
獲取與設置是否使用抗鋸齒功能,會消耗較大資源,繪制圖形速度會變慢,一般會開啟。設置后會平滑一些;
final boolean isDither() 
9.void setDither(boolean dither) 
獲取與設定是否使用圖像抖動處理,會使繪制出來的圖片顏色更加平滑和飽滿、圖像更加清晰。      
10.setPathEffect(PathEffect effect);   
* 設置繪制路徑的效果,如點畫線等 
(1)、CornerPathEffect——圓形拐角效果 
    paint.setPathEffect(new CornerPathEffect(100));
    利用半徑R=50的圓來代替原來兩條直線間的夾角
(2)、DashPathEffect——虛線效果            
    //畫同一條線段,偏移值為15  
    paint.setPathEffect(new DashPathEffect(new float[]{20,10,50,100},15));
    intervals[]:表示組成虛線的各個線段的長度;整條虛線就是由intervals[]中這些基本線段循環組成的。比如,我們定義new float[] {20,10};那這個虛線段就是由兩段線段組成的,第一個可見的線段長為20,每二個線段不可見,長度為10;
    phase:
    開始繪制的偏移值            
    .....       
11.setXfermode(Xfermode xfermode);   
設置圖形重疊時的處理方式,如合並,取交集或並集,經常用來制作橡皮的擦除效果       
12.setMaskFilter(MaskFilter maskfilter);   
設置MaskFilter,可以用不同的MaskFilter實現濾鏡的效果,如濾化,立體等        
13.setColorFilter(ColorFilter colorfilter);   
設置顏色過濾器,可以在繪制顏色時實現不用顏色的變換效果
14.setShader(Shader shader);   
設置圖像效果,使用Shader可以繪制出各種漸變效果   
15.setShadowLayer(float radius ,float dx,float dy,int color);   
在圖形下面設置陰影層,產生陰影效果,radius為陰影的角度,dx和dy為陰影在x軸和y軸上的距離,color為陰影的顏色  

負責設置獲取文字相關的:

float getFontSpacing()
獲取字符行間距。
float getLetterSpacing()
void setLetterSpacing(float letterSpacing)
設置和獲取字符間距

final boolean isUnderlineText()
void setUnderlineText(boolean underlineText)
是否有下划線和設置下划線。
final boolean isStrikeThruText()
void setStrikeThruText(boolean strikeThruText)
獲取與設置是否有文本刪除線。

float getTextSize()
void setTextSize(float textSize)
獲取與設置文字大小,注意:Paint.setTextSize傳入的單位是px,TextView.setTextSize傳入的單位是sp,注意使用時不同分辨率處理問題。

Typeface getTypeface()
Typeface setTypeface(Typeface typeface)
獲取與設置字體類型。Android默認有四種字體樣式:BOLD(加粗)、BOLD_ITALIC(加粗並傾斜)、ITALIC(傾斜)、NORMAL(正常),我們也可以通過Typeface類來自定義個性化字體。

float getTextSkewX()
void setTextSkewX(float skewX)
獲取與設置文字傾斜,參數沒有具體范圍,官方推薦值為-0.25,值為負則右傾,為正則左傾,默認值為0。

Paint.Align getTextAlign()
void setTextAlign(Paint.Align align)
獲取與設置文本對齊方式,取值為CENTER、LEFT、RIGHT,也就是文字繪制是左邊對齊、右邊還是局中的。

setSubpixelText(boolean subpixelText)
固定的幾個范圍:320*480,480*800,720*1280,1080*1920等等;那么如何在同樣的分辨率的顯示器中增強顯示清晰度呢?
亞像素的概念就油然而生了,亞像素就是把兩個相鄰的兩個像素之間的距離再細分,再插入一些像素,這些通過程序加入的像素就是亞像素。在兩個像素間插入的像素個數是通過程序計算出來的,一般是插入兩個、三個或四個。
所以打開亞像素顯示,是可以在增強文本顯示清晰度的,但由於插入亞像素是通過程序計算而來的,所以會耗費一定的計算機性能。

int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)
比如文本閱讀器的翻頁效果,我們需要在翻頁的時候動態折斷或生成一行字符串,這就派上用場了~

以上這些有很多沒有用過的,沒關系,這里全部列出來,當api文檔進行查閱。

Paint高級應用: 

概述:

對於Paint的高級應用主要是研究這三個方面:渲染,濾鏡,Xfermode,也是比較難的,這次先來集中對渲染進行一個學習,之后再慢慢將剩下的二項再進行研究。

渲染:

Shader:着色器

要學渲染首先得要明白這個Shader着色器,對於Paint()里面有一個設置着色器的方法:

對於Canvas中的各種drawXXXX()方法都是畫具體形狀的,而Paint的這個Shader定義的就是圖形的着色和外觀,下面來看一下Shader官網的說明:

說實話還是有點抽象,先來看一下它的具體子類有哪些:

下面則具體來學習一下。

BitmapShader:位圖圖像渲染

描述:

使用:

1、平鋪效果:

先看一下它的構造方法:

    /**
     * Call this to create a new shader that will draw with a bitmap.(調用它來創建一個着色器,它將用一個位圖進行繪制)
     *
     * @param bitmap The bitmap to use inside the shader(用在着色器里的位置)
     * @param tileX The tiling mode for x to draw the bitmap in.(x的平鋪模式在其中繪制位圖)
     * @param tileY The tiling mode for y to draw the bitmap in.(y的平鋪模式在其中繪制位圖)
     */
    public BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY) {
        this(bitmap, tileX.nativeInt, tileY.nativeInt);
    }

啥意思?其實這個位圖渲染主要是用在這種場景:

 

而此時圖片只有這么大:

所以在構造方法中就看到有這倆個模式的參數:

那看一下這個枚舉TileMode都有哪些值:

下面各自來試驗一下效果:

1、TileMode.CLAMP:

簡單來說就是拉伸最后一個像素去鋪滿剩下的地方,下面來使用看下效果:

package com.paintgradient.test;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

public class MyGradientView extends View {
    private Paint mPaint;
    private Bitmap mBitMap = null;

    public MyGradientView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mBitMap = ((BitmapDrawable) getResources().getDrawable(R.drawable.xyjy2)).getBitmap();
        mPaint = new Paint();
    }

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

        BitmapShader bitMapShader = new BitmapShader(mBitMap, Shader.TileMode.CLAMP,
                Shader.TileMode.CLAMP);
        mPaint.setShader(bitMapShader);
        mPaint.setAntiAlias(true);
        canvas.drawRect(new Rect(0, 0, 1000, 1600), mPaint);//繪制一個矩形時,用着色器進行渲染
    }
}

其中用到的圖片為大美女:

運行:

確實是在多余的部分以圖片的最后一像素進行平鋪的:

2、TileMode.REPEAT:

試一下效果:

看到效果就秒懂了。

3、TileMode.MIRROR:

 

不多解釋直接看效果:

 

2、繪制圓形圖像:

用它還能做啥呢?其實可以繪制一個圓形圖像,如下:

package com.paintgradient.test;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

public class MyGradientView extends View {
    private Paint mPaint;
    private Bitmap mBitMap = null;
    private int mWidth;
    private int mHeight;

    public MyGradientView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mBitMap = ((BitmapDrawable) getResources().getDrawable(R.drawable.xyjy2)).getBitmap();
        mPaint = new Paint();
        mWidth = mBitMap.getWidth();
        mHeight = mBitMap.getHeight();
    }

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

        BitmapShader bitMapShader = new BitmapShader(mBitMap, Shader.TileMode.MIRROR,
                Shader.TileMode.MIRROR);
        mPaint.setShader(bitMapShader);
        mPaint.setAntiAlias(true);

        canvas.drawCircle(mHeight / 2, mHeight / 2, mHeight / 2, mPaint);
    }
}

 

另外還有一種實現方式:

所以可以看到這個渲染的意思是指對於我們繪制區域進行按照上訴渲染規則進行色彩的填充,另外還有一種用法,可以對其位圖渲染進行一個矩陣縮放,如下:

這里就不演示了,在之后的線程渲染效果中會有用到。

3、放大境效果:

package com.paintgradient.test;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class ZoomImageView extends View {

    //放大倍數
    private static final int FACTOR = 2;
    //放大鏡的半徑
    private static final int RADIUS = 100;
    // 原圖
    private Bitmap mBitmap;
    // 放大后的圖
    private Bitmap mBitmapScale;
    // 制作的圓形的圖片(放大的局部),蓋在Canvas上面
    private ShapeDrawable mShapeDrawable;

    private Matrix mMatrix;

    public ZoomImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy3);
        mBitmapScale = mBitmap;
        //放大后的整個圖片
        mBitmapScale = Bitmap.createScaledBitmap(mBitmapScale, mBitmapScale.getWidth() * FACTOR,
                mBitmapScale.getHeight() * FACTOR, true);
        BitmapShader bitmapShader = new BitmapShader(mBitmapScale, Shader.TileMode.CLAMP,
                Shader.TileMode.CLAMP);

        mShapeDrawable = new ShapeDrawable(new OvalShape());
        mShapeDrawable.getPaint().setShader(bitmapShader);
        // 切出矩形區域,用來畫圓(內切圓)
        mShapeDrawable.setBounds(0, 0, RADIUS * 2, RADIUS * 2);

        mMatrix = new Matrix();
    }

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

        // 1、畫原圖
        canvas.drawBitmap(mBitmap, 0, 0, null);

        // 2、畫放大鏡的圖
        mShapeDrawable.draw(canvas);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        // 將放大的圖片往相反的方向挪動
        mMatrix.setTranslate(RADIUS - x * FACTOR, RADIUS - y * FACTOR);
        mShapeDrawable.getPaint().getShader().setLocalMatrix(mMatrix);
        // 切出手勢區域點位置的圓
        mShapeDrawable.setBounds(x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS);
        invalidate();
        return true;
    }
}

效果:

LinearGradient:線性渲染

描述:

使用:

1、漸變效果:

下面來使用一下,可以使用它來實現一個漸變的效果:

public class MyGradientView extends View {
    private Paint mPaint;
    private Bitmap mBitMap = null;
    private int mWidth;
    private int mHeight;
    private int[] mColors = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};

    public MyGradientView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mBitMap = ((BitmapDrawable) getResources().getDrawable(R.drawable.xyjy2)).getBitmap();
        mPaint = new Paint();
        mWidth = mBitMap.getWidth();
        mHeight = mBitMap.getHeight();
    }

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

        /**線性漸變
         * x0, y0, 起始點
         *  x1, y1, 結束點
         * int[]  mColors, 中間依次要出現的幾個顏色
         * float[] positions,數組大小跟colors數組一樣大,中間依次擺放的幾個顏色分別放置在那個位置上(參考比例從左往右)
         *    tile
         */
        LinearGradient linearGradient = new LinearGradient(0, 0, 800, 800, mColors, null, Shader.TileMode.CLAMP);
        mPaint.setShader(linearGradient);
        canvas.drawRect(0, 0, 800, 800, mPaint);
    }
}

2、跑馬燈效果:

還可以實現類似跑馬燈的效果,如下:

package com.paintgradient.test;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatTextView;
import android.text.TextPaint;
import android.util.AttributeSet;

public class LinearGradientTextView extends AppCompatTextView {
    private TextPaint mPaint;


    //LinearGradient線性渲染,   X,Y,X1,Y1四個參數只定位效果,不定位位置
    private LinearGradient mLinearGradient;
    private Matrix mMatrix;

    private float mTranslate;
    private float DELTAX = 20;

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

    public LinearGradientTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 拿到TextView的畫筆
        mPaint = getPaint();
        String text = getText().toString();
        float textWith = mPaint.measureText(text);

        // 3個文字的寬度
        int gradientSize = (int) (textWith / text.length() * 3);

        // 從左邊-gradientSize開始,即左邊距離文字gradientSize開始漸變
        mLinearGradient = new LinearGradient(-gradientSize, 0, 0, 0, new int[]{
                0x22ffffff, 0xffffffff, 0x22ffffff}, null, Shader.TileMode.CLAMP );

        mPaint.setShader(mLinearGradient);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mTranslate += DELTAX;
        float textWidth = getPaint().measureText(getText().toString());
        //到底部進行返回
        if (mTranslate > textWidth + 1 || mTranslate < 1) {
            DELTAX = -DELTAX;
        }

        mMatrix = new Matrix();
        mMatrix.setTranslate(mTranslate, 0);//每次更改x的位置
        mLinearGradient.setLocalMatrix(mMatrix);
        postInvalidateDelayed(50);//通過它不斷來刷新

    }
}

運行:

 

SweepGradient:漸變渲染/梯度渲染

描述:

使用:

1、漸變效果:

package com.paintgradient.test;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.SweepGradient;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

public class MyGradientView extends View {
    private Paint mPaint;
    private Bitmap mBitMap = null;
    private int mWidth;
    private int mHeight;
    private int[] mColors = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};

    public MyGradientView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mBitMap = ((BitmapDrawable) getResources().getDrawable(R.drawable.xyjy2)).getBitmap();
        mPaint = new Paint();
        mWidth = mBitMap.getWidth();
        mHeight = mBitMap.getHeight();
    }

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

        SweepGradient mSweepGradient = new SweepGradient(300, 300, mColors, null);
        mPaint.setShader(mSweepGradient);
        canvas.drawCircle(300, 300, 300, mPaint);
    }
}

效果:

2、雷達掃描效果:

package com.paintgradient.test;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

public class RadarGradientView extends View {

    private int mWidth, mHeight;

    //五個圓
    private float[] pots = {0.05f, 0.1f, 0.15f, 0.2f, 0.25f};

    private Shader scanShader; // 掃描渲染shader
    private Matrix matrix = new Matrix(); // 旋轉需要的矩陣
    private int scanSpeed = 5; // 掃描速度
    private int scanAngle; // 掃描旋轉的角度

    private Paint mPaintCircle; // 畫圓用到的paint
    private Paint mPaintRadar; // 掃描用到的paint

    public RadarGradientView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        // 畫圓用到的paint
        mPaintCircle = new Paint();
        mPaintCircle.setStyle(Paint.Style.STROKE); // 描邊
        mPaintCircle.setStrokeWidth(1); // 寬度
        mPaintCircle.setAlpha(100); // 透明度
        mPaintCircle.setAntiAlias(true); // 抗鋸齒
        mPaintCircle.setColor(Color.parseColor("#B0C4DE")); // 設置顏色 亮鋼蘭色

        // 掃描用到的paint
        mPaintRadar = new Paint();
        mPaintRadar.setStyle(Paint.Style.FILL_AND_STROKE); // 填充
        mPaintRadar.setAntiAlias(true); // 抗鋸齒

        post(run);
    }

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

        for (int i = 0; i < pots.length; i++) {
            canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth * pots[i], mPaintCircle);
        }

        // 畫布的旋轉變換 需要調用save() 和 restore()
        canvas.save();

        scanShader = new SweepGradient(mWidth / 2, mHeight / 2,
                new int[]{Color.TRANSPARENT, Color.parseColor("#84B5CA")}, null);
        mPaintRadar.setShader(scanShader); // 設置着色器
        canvas.concat(matrix);
        canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth * pots[4], mPaintRadar);

        canvas.restore();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 取屏幕的寬高是為了把雷達放在屏幕的中間
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        mWidth = mHeight = Math.min(mWidth, mHeight);
    }

    private Runnable run = new Runnable() {
        @Override
        public void run() {
            scanAngle = (scanAngle + scanSpeed) % 360; // 旋轉角度 對360取余
            matrix.postRotate(scanSpeed, mWidth / 2, mHeight / 2); // 旋轉矩陣
            invalidate(); // 通知view重繪
            postDelayed(run, 50); // 調用自身 重復繪制
        }
    };

}

運行效果:

具體的代碼待未來真正用到時再細究,這里先有個大概印象既可。
RadialGradient:環形渲染

描述:

 

使用:

package com.paintgradient.test;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

public class MyGradientView extends View {
    private Paint mPaint;
    private Bitmap mBitMap = null;
    private int mWidth;
    private int mHeight;
    private int[] mColors = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};

    public MyGradientView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mBitMap = ((BitmapDrawable) getResources().getDrawable(R.drawable.xyjy2)).getBitmap();
        mPaint = new Paint();
        mWidth = mBitMap.getWidth();
        mHeight = mBitMap.getHeight();
    }

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

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

運行:

ComposeShader:組合渲染

描述:

 

使用:

其實也就是可以任意組合2種渲染器組成新的渲染器,這里以將下面這個心形圖片變成漸變的效果為例:

上面這圖是個純白的所有肉眼看不清是啥,其實它是長這樣的:

 

那如何將這個白心加上漸變的效果呢?上代碼:

package com.paintgradient.test;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposeShader;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

public class MyGradientView extends View {
    private Paint mPaint;
    private Bitmap mBitMap = null;
    private int mWidth;
    private int mHeight;

    public MyGradientView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mBitMap = ((BitmapDrawable) getResources().getDrawable(R.drawable.xyjy2)).getBitmap();
        mPaint = new Paint();
        mWidth = mBitMap.getWidth();
        mHeight = mBitMap.getHeight();
    }

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

        /***************用ComposeShader即可實現心形圖漸變效果*********************************/
        //創建BitmapShader,用以繪制心
        Bitmap mBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.heart)).getBitmap();
        BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        //創建LinearGradient,用以產生從左上角到右下角的顏色漸變效果
        LinearGradient linearGradient = new LinearGradient(0, 0, mWidth, mHeight, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP);
        //bitmapShader對應目標像素,linearGradient對應源像素,像素顏色混合采用MULTIPLY模式
        ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);
        //將組合的composeShader作為畫筆paint繪圖所使用的shader
        mPaint.setShader(composeShader);

        //用composeShader繪制矩形區域
        canvas.drawRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), mPaint);
    }
}

運行:

以上就是關於Paint的渲染高級應用的一個整體學習,主要是對它有個大概的了解,知道它能干啥就可以了,實際工作中有需求的話再細細研究,另外Paint還有另外兩個高級用法則在下一次再來學習了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM