绘图中的其他知识点总结

Draw

Canvas 可以绘制的对象:弧线(arcs)、填充颜色(argb和color)、圆(circle和oval)、Bitmap、点(point)、线(line)、矩形(Reat)、图片(Picture)、圆形矩形(RoundRect)、文本(text)、顶点(Vertices)、路径(path)。

Canvas不仅仅可以draw一些图形、图片,其本身也提供了可操作的方法:rorate(旋转)、scale(压缩)、translate(平移)、skew(扭曲)等。

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

 for (int i = 0; i < 360; i = i + 6) {
     canvas.save();
     canvas.rotate(i, 100, 100);
     canvas.drawLine(100, 0, 100, 10, new Paint());
     canvas.restore();
 }
}
效果

Matrix

canvas 类当中drawBitmap(Bitmap bitmap,Matrix matrix,Paint paint)方法当中有个参数是matrix类型,它是个3x3矩阵。Matrix在android中的主要作用是图像变换、如平移、旋转、缩放、扭曲等。
Matrix提供了Translate(平移)、Scale(缩放)、Rotate(旋转)、Skew(扭曲)四中变换操作,这4种操作实质上都是调用了setValues方法来设置矩阵数组来达到效果。除translate外,scale、rotate、skew 都是围绕一个中心点来进行的,如果不指定,默认情况下是围绕(0,0)来进行相应的变换的。
Matrix提供的四种操作,每一种都有pre、set、post三种形式。原因是矩阵乘法不满足乘法交换律,因此左乘还是右乘最终的效果都不一样。我们可以把Matrix变换想象成一个队列,队列里面包含了若干个变换操作,队列中每个操作按照先后顺序操作变换目标完成变换,pre相当于向队首增加一个操作,post相当于向队尾增加一个操作,set相当于清空当前队列重新设置。
下面的例子和解释很重要。
例1

//这段代码只有translate(100, 100)生效,因为第二个set会把之前队列中的操作清除。
Matrix m = new Matrix();
m.setRotate(100); 
m.setTranslate(100, 100);```
例2

//这段代码先执行translate(100, 100),后执行rotate(100)
Matrix m = new Matrix();
m.setTranslate(100, 100);
m.postRotate(100);```
例3

//这段代码先执行rotate(100),后执行translate(100, 100)
Matrix m = new Matrix();
m.setTranslate(100, 100);
m.preRotate(100);```
例4

//这段代码的执行顺序:translate(100f, 100f) -> scale(2f, 2f) -> scale(0.5f, 0.5f) -> translate(50f, 50f)
Matrix m = new Matrix();
m.preScale(2f, 2f);
m.preTranslate(100f, 100f);
m.postScale(0.5f, 0.5f);
m.postTranslate(50f, 50f);```
例5

//这段代码的执行顺序:translate(50f, 50f) -> scale(0.8f, 0.8f) -> scale(3f, 3f)
Matrix m = new Matrix();
m.postTranslate(100, 100);   
m.preScale(0.5f, 0.5f);
m.setScale(0.8f, 0.8f);   
m.postScale(3f, 3f);
m.preTranslate(50f, 50f);```
Matrix 映射方法
Matrix提供了mapPoints(),mapRects(),mapVectors()等映射方法,用来获取经Matrix映射后的值。

//这段代码的作用是获取经过平移后该bitmap四个点的坐标
Matrix m = new Matrix();
m.postTranslate(100f, 100f);

float[] src = {
0, 0,
0, bitmap.getHeight(),
bitmap.getWidth(), 0,
bitmap.getWidth(), bitmap.getHeight()
};
float[] dst = new float[8];

m.mapPoints(dst, src);


### Xfermode
XferMode主要是将2张图片合在一起,由用户自己决定是选中图片重叠的部分还是非重叠的部分,可以参考Android官方提供的图片,图中 圆为Dst,正方形为Src
![](http://upload-images.jianshu.io/upload_images/2154124-796ead1b6739ea1b?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

Xfermode的类型有16种:
private static final Xfermode[] sModes = {
new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
new PorterDuffXfermode(PorterDuff.Mode.SRC),
new PorterDuffXfermode(PorterDuff.Mode.DST),
new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
new PorterDuffXfermode(PorterDuff.Mode.XOR),
new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
new PorterDuffXfermode(PorterDuff.Mode.SCREEN)
};

如果我们想要实现一个圆心图像的话可以使用图中dstin的方式,即在我们的原图上面再画一个实心圆形图。首先我们需要生成iyge圆形的bitmap:

private Bitmap mCircleBitmap;

//生成一个实心圆形Bitmap,这个Bitmap宽高要与当前的View的宽高相同
private Bitmap getCircleBitmap() {
    if (mCircleBitmap == null) {
        mCircleBitmap = Bitmap.createBitmap(2 * mRadius, 2 * mRadius,
                Config.ARGB_8888);
        Canvas canvas = new Canvas(mCircleBitmap);

        mPaint.reset();
        mPaint.setStyle(Style.FILL);
        canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
    }
    return mCircleBitmap;

}

在将这个圆形图片盖在原图上面

//将两张图片以XferMode(DST_IN)的方式组合到一张照片中
private Bitmap combineBitmap(Drawable drawable, Bitmap maskBitmap) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
// 将drawable转bitmap
Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
//将图片自动放缩到View的宽高,即2倍的半径
drawable.setBounds(0, 0, mRadius2, mRadius2);
drawable.draw(canvas);

    // 先将XferMode设置好,然后将盖在上面的bitmap绘制出来
    mPaint.reset();
    mPaint.setXfermode(xfermode);
    canvas.drawBitmap(maskBitmap, 0, 0, mPaint);
    mPaint.setXfermode(null);

    return bitmap;
}

//然后将组合得到的bitmaptoin通过canvas绘制到界面上
@Override
protected void onDraw(Canvas canvas) {
//获取设置的src图片
Drawable drawable = getDrawable();
//获取盖在src上面的实心圆形Bitmap
Bitmap circleBitmap = getCircleBitmap();

    //两张图片以XferMode(DST_IN)的方式组合
    Bitmap bitmap = combineBitmap(drawable, circleBitmap);

    //将最终的bitmap画到画板上面
    canvas.drawBitmap(bitmap, 0, 0, mPaint);

}
完整的类代码

package com.hc.circleimage;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class XfermodeCircleImage extends ImageView {
private int mRadius;
private Paint mPaint;
private Xfermode xfermode;
private Bitmap mCircleBitmap;

public XfermodeCircleImage(Context context) {
    super(context);
    init();
}

public XfermodeCircleImage(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

private void init() {
    mPaint = new Paint();
    xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = getMeasuredWidth();
    int height = getMeasuredHeight();

    if (width > height) {
        mRadius = height / 2;
    } else {
        mRadius = width / 2;
    }

    setMeasuredDimension(mRadius * 2, mRadius * 2);
}

//生成一个实心圆形Bitmap,这个Bitmap宽高要与当前的View的宽高相同
private Bitmap getCircleBitmap() {
    if (mCircleBitmap == null) {
        mCircleBitmap = Bitmap.createBitmap(2 * mRadius, 2 * mRadius,
                Config.ARGB_8888);
        Canvas canvas = new Canvas(mCircleBitmap);

        mPaint.reset();
        mPaint.setStyle(Style.FILL);
        canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
    }
    return mCircleBitmap;
}

//将两张图片以XferMode(DST_IN)的方式组合到一张照片中
private Bitmap combineBitmap(Drawable drawable, Bitmap maskBitmap) {
    int width = drawable.getIntrinsicWidth();
    int height = drawable.getIntrinsicHeight();
    // 将drawable转bitmap
    Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    //将图片自动放缩到View的宽高,即2倍的半径
    drawable.setBounds(0, 0, mRadius*2, mRadius*2);
    drawable.draw(canvas);

    // 先将XferMode设置好,然后将盖在上面的bitmap绘制出来
    mPaint.reset();
    mPaint.setXfermode(xfermode);
    canvas.drawBitmap(maskBitmap, 0, 0, mPaint);
    mPaint.setXfermode(null);

    return bitmap;
}

@Override
protected void onDraw(Canvas canvas) {
    //获取设置的src图片
    Drawable drawable = getDrawable();
    //获取盖在src上面的实心圆形Bitmap
    Bitmap circleBitmap = getCircleBitmap();

    //两张图片以XferMode(DST_IN)的方式组合
    Bitmap bitmap = combineBitmap(drawable, circleBitmap);

    //将最终的bitmap画到画板上面
    canvas.drawBitmap(bitmap, 0, 0, mPaint);

}

}

这样就可以将一个方形的图片,变成圆形了。

### BitmapShader

BitmapShader是Shader的子类,可以通过Paint.setShader(Shader shader)进行设置、,BitmapShader有啥作用呢,它可以根据你设置的方式(下面介绍)将图片铺满你所选的区域,有哪几种方式“铺”呢?有以下几种:
(1)CLAMP:拉伸,在x方向上是图片的最后一列像素重复平铺,而y方向是最后一行往下拉伸(当bitmap比要绘制的图形小时拉伸位图的最后一个像素;当bitmap比要绘制的图形大时,根据绘制图形剪裁bitmap )
(2)REPEAT: 重复,很容易理解,图片重复平铺过去(当绘画的区域比图片本身要大时会重复,从设置的x或者y轴方向复制bitmap)
(3)MIRROR:镜像,就是将图片翻转。
CLAMP的方式:![](http://upload-images.jianshu.io/upload_images/2154124-401f6c0f76db8d57?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
REPEAT方式:![](http://upload-images.jianshu.io/upload_images/2154124-10d19cc1356a10be?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
MIRROR方式:![](http://upload-images.jianshu.io/upload_images/2154124-e469691be7d7e470?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

使用BitmapShader制作圆形图片的方法非常简单,只需通过Bitmap构造出一个BitmapShader,并将这个BitmapShader设置到当前的Paint当中,用这个Paint绘制一个圆就可以了

package com.hc.circleimage;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class ShaderCircleImage extends ImageView {

private int mRadius;
private Paint mPaint;

public ShaderCircleImage(Context context) {
    super(context);
    init();
}

public ShaderCircleImage(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

private void init() {
    mPaint = new Paint();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = getMeasuredWidth();
    int height = getMeasuredHeight();

    if (width > height) {
        mRadius = height / 2;
    } else {
        mRadius = width / 2;
    }

    setMeasuredDimension(mRadius * 2, 2 * mRadius);
}

private Bitmap drawableToBitmap(Drawable drawable) {
    if (drawable instanceof BitmapDrawable) {
        return ((BitmapDrawable) drawable).getBitmap();
    } else {
        int width = drawable.getIntrinsicWidth();
        int height = drawable.getIntrinsicHeight();
        Bitmap bitmap = Bitmap.createBitmap(width, height,
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, mRadius*2, mRadius*2);
        drawable.draw(canvas);
        return bitmap;
    }
}

@Override
protected void onDraw(Canvas canvas) {
    // 将Drawable转为Bitmap
    Bitmap bmp = drawableToBitmap(getDrawable());
    // 通过Bitmap和指定x,y方向的平铺方式构造出BitmapShader对象
    BitmapShader mBitmapShader = new BitmapShader(bmp, TileMode.CLAMP,
            TileMode.CLAMP);
    // 将BitmapShader设置到当前的Paint对象中
    mPaint.setShader(mBitmapShader);
    // 绘制出一个圆
    canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);

}

}

之前我的文章当中介绍的一个圆形图像也就是用这种方式实现的[CircleImageView](http://www.jianshu.com/p/ef6c5b871db5)
### ClipPath
ClipPath 也可以用来绘制圆形

package com.hc.circleimage;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class ClipCircleImage extends ImageView {
private int mRadius;
private Paint mPaint;

public ClipCircleImage(Context context) {
    super(context);
    init();
}

public ClipCircleImage(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

private void init() {
    mPaint = new Paint();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = getMeasuredWidth();
    int height = getMeasuredHeight();

    if (width > height) {
        mRadius = height / 2;
    } else {
        mRadius = width / 2;
    }

    setMeasuredDimension(mRadius * 2, 2 * mRadius);
}

private Bitmap drawableToBitmap(Drawable drawable) {
    if (drawable instanceof BitmapDrawable) {
        return ((BitmapDrawable) drawable).getBitmap();
    } else {
        int width = drawable.getIntrinsicWidth();
        int height = drawable.getIntrinsicHeight();
        Bitmap bitmap = Bitmap.createBitmap(width, height,
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, mRadius*2, mRadius*2);
        drawable.draw(canvas);
        return bitmap;
    }
}

@Override
protected void onDraw(Canvas canvas) {
    // 将Drawable转为Bitmap
    Bitmap bmp = drawableToBitmap(getDrawable());
    Path path = new Path(); 
    //按照逆时针方向添加一个圆
    path.addCircle(mRadius, mRadius, mRadius, Direction.CCW);
    //先将canvas保存
    canvas.save();
    //设置为在圆形区域内绘制
    canvas.clipPath(path);
    //绘制Bitmap
    canvas.drawBitmap(bmp, 0, 0, mPaint);
    //恢复Canvas
    canvas.restore();
}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容