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还有另外两个高级用法则在下一次再来学习了。

posted on 2020-01-10 23:44  cexo  阅读(1371)  评论(0编辑  收藏  举报

导航