Paint进阶

目录

目录

Android已经为我们提供了大量的View供我们使用,但是可能有时候这些组件不能满足我们的开发中的需求,这时候就需要自定义控件了,而自定义控件主要是调用系统提供的API来实现我们需要的样式。本文主要介绍Paint(画笔)相关API的使用。

下文API的说明都是以Demo示例来说明,有关Paint的基础Api这里不再阐述,统一设置Paint的基础样式如下:

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
mPaint.setColor(Color.parseColor("#FF4081"));//设置画笔的颜色
mPaint.setStyle(Paint.Style.STROKE);//设置样式为描边
mPaint.setStrokeWidth(42);//设置描边的宽度,这里为演示方便,直接使用px

API

  • setStrokeCap(Cap cap) : 设置线冒(或者笔触)的样式

什么是笔触?其实很简单,就像我们现实世界中的笔,如果你用圆珠笔在纸上戳一点,那么这个点一定是个圆,即便很小,它代表了笔的笔触形状,如果我们把一支铅笔笔尖削成方形的,那么画出来的线条会是一条弯曲的“矩形”,这就是笔触的意思。提供以下三种样式:

  • Paint.Cap.BUTT : 默认,没有线冒
  • Paint.Cap.SQUARE :方形
  • Paint.Cap.ROUND : 圆形

注意:从下面的示例看出BUTT和SQUARE的效果是一样的,但是他们的长度是不一样的,在代码中画的线的长度都是一样的,但最终呈现的长度SQUARE比BUTT长,这就说明,线冒(笔触)会是增加一定的长度。

Cap
  • setStrokeJoin(Join join) : 设置两线交界处的样式

提供三种样式:

  • Paint.Join.MITER : 默认,锐角
  • Paint.Join.BEVEL : 直线
  • Paint.Join.ROUND:圆弧
Join
  • setDither(boolean dither) : 设置是否使用图片抖动处理,会使绘制的图片更加清晰和颜色更加的饱满

文本相关

  • measureText(text,start,end) : 测量文本的宽度
  • breakText(text,start,end,,boolean measureForwards, float maxWidth, float[] measuredWidth) :测量文本的宽度

measureForwards :是否从前往后测量
maxWidth:最大的测量宽度,一般给个较大的值即可。
measuredWidth:该参数是一个数据,接受返回的测量结果

这两个API都是测量文本的宽度,经测试,测量结果是一样的

  • getTextBounds(String text, int start, int end, Rect bounds) : 测量文本的矩形区域,通过该API可以获取文本的高度

    Rect rect = new Rect();
    mPaint.getTextBounds(mText,0,mText.length(),rect);
    int textHeight = rect.height();//获取测量文本的高度
    int textWidth = rect.width();//测量的宽度有误差
    

注意:虽然getTextBounds也可以获取文本的宽度,但是测量的宽度结果是有误差的。后面的示例会说明这一点。

在View正中间绘制文本

第一种方式:

绘制文本跟绘制其他的图形是不同的,canvas绘制文本是从左下角开始绘制的,而并不是左上角,所以要想让文本显示在View的正中间,就必须正确的计算出左下角的坐标(相对于View本身)。如下图所示:我们先假设View的宽度为viewWidth,高度为viewHeight;文本的宽度为textWidth,高度为textHeight,如果文本想居于正中间位置,那么它们之间的关系为:
x = (viewWidth - textWidth) / 2
y = (viewHeight + textHeight) / 2
所以我们只要知道View的宽高和文本的宽高就可以准确的计算出左下角的坐标(x,y),而View的宽高是在布局中指定的,我们直接获取即可,文本的宽高我们用上述的API即可获取。

 @Override
 protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    float textWidth = mPaint.measureText(mText, 0, mText.length());//测量文本宽度
    Rect rect = new Rect();
    mPaint.getTextBounds(mText,0,mText.length(),rect);//测量文本区域,获取高度
    int textHeight = rect.height();
    //计算做下角的坐标
    float x = (width - textWidth) * 1.0f / 2;
    float y = (height + textHeight) * 1.0f / 2;
    mPaint.setColor(Color.parseColor("#FF4081"));
    canvas.drawCircle(width * 1.0f / 2,height * 1.0f / 2,width * 1.0f / 2 ,mPaint);//绘制背景
    mPaint.setColor(Color.parseColor("#FFFFFF"));
    canvas.drawText(mText,x,y,mPaint);//绘制文本,左下角开始
    //绘制中心轴线,便于观察文本是否居中
    canvas.drawLine(0,height * 1.0f / 2,width,height * 1.0f / 2,mPaint);
    canvas.drawLine(width * 1.0f / 2,0,width * 1.0f / 2,height,mPaint);
}
效果图

注意:如果使用getTextBounds来获取文本的宽度,是有误差的,如下图所示:

第二种方式:

drawText(String text, float x, float y, Paint paint),该方法中的y指的是文本基线(baseline)的纵坐标,何为基线?其实基线并不真实存在,只是一种抽象的概念。超过基线向下的部分称为降部(descent);基线向上到文本的最顶端称为升部(ascent);要注意的是:基线向下的值为正值,向上的值为负值。Paint 中提供了相应的API来获取这些值。

基线
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
// 负值
float top = fontMetrics.top; 
float ascent = fontMetrics.ascent;
//正值
float bottom = fontMetrics.bottom; 
float descent = fontMetrics.descent;

结合上图,假设我们让文本居于正中间,灰色线位置,那么我们只要求对应的基线位置即可。根据图中标注所示,各个尺寸之间的关系为:
baselineY = centerY + a的绝对高度
a = (bottom - top) / 2 - bottom // 这是减去top是应为top的值为负值,其实就是取top得绝对值。
这样我们就能求出基线的位置了。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(Color.parseColor("#FF4081"));
    canvas.drawCircle(width * 1.0f / 2,height * 1.0f / 2,width * 1.0f / 2 ,mPaint);//绘制背景
    //绘制中心轴线,便于观察文本是否居中
    mPaint.setColor(Color.parseColor("#FFFFFF"));
    canvas.drawLine(0,height * 1.0f / 2,width,height * 1.0f / 2,mPaint);
    canvas.drawLine(width * 1.0f / 2,0,width * 1.0f / 2,height,mPaint);
    float textWidth = mPaint.measureText(mText, 0, mText.length());//测量文本宽度
    Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
    float bottom = fontMetrics.bottom;
    float top = fontMetrics.top;
    float x = (width - textWidth) / 2;
    float y = height * 1.0f / 2 + ((bottom - top) / 2 - bottom );
    canvas.drawText(mText,x,y,mPaint);//绘制文本
}
效果图

图像相关

图像处理

其实就是不同的颜色矩阵对图像的处理效果

图像分析-RGBA模型

R:红色;G:绿色;B:蓝色;A:透明度 (Android中使用ARGB(透明度在前))
色相/色调:物体传递的颜色(RGB)
饱和度:颜色的纯度,从0(灰)到100%来描述
亮度/明度:颜色的相对明暗程度

//设置图片的色调,饱和度和亮度
public static Bitmap handleImageEffect(Bitmap bm, float hue, float saturation, float lum) {
  //Android不能对图像直接处理,我们需要创建图像的副本对其操作 
  Bitmap bmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas =   new Canvas(bmp);//创建一个和传出的图像一样大的画布 
  Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//创建一个画笔 
  //设置色调
  ColorMatrix hueMatrix = new ColorMatrix();
  hueMatrix.setRotate(0, hue); //0:代表红色
  hueMatrix.setRotate(1, hue); 
   hueMatrix.setRotate(2, hue); 
   //设置饱和度
  ColorMatrix saturationMatrix = new ColorMatrix();
  saturationMatrix.setSaturation(saturation); 
  //设置亮度 
  ColorMatrix lumMatrix = new ColorMatrix(); 
  lumMatrix.setScale(lum, lum, lum, 1); 
  //将以上设置的参数集合起来 
  ColorMatrix imageMatrix = new ColorMatrix();
  imageMatrix.postConcat(hueMatrix);
  imageMatrix.postConcat(saturationMatrix); 
  imageMatrix.postConcat(lumMatrix); 
  //将设置的参数传递给画笔 
  paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
  //使用我们设置好的画笔将传入的图像绘制在画布上
  canvas.drawBitmap(bm, 0, 0, paint); 
  return bmp; 
} 

switch (seekBar.getId()) { 
  case R.id.seekbarHue: //经验值,这样的效果最好 
  mHue = (progress - MID_VALUE) * 1.0F / MID_VALUE * 180; 
  break;
  case R.id.seekbarSaturation: //经验值,这样的效果最好 
  mStauration = progress * 1.0F / MID_VALUE; 
  break; 
  case R.id.seekbatLum: //经验值,这样的效果最好
   mLum = progress * 1.0F / MID_VALUE; 
  break; 
} 
  mImageView.setImageBitmap(ImageHelper.handleImageEffect(bitmap, mHue, mStauration, mLum));
  方法内部改变的还是颜色矩阵

颜色矩阵

初始化颜色矩阵是不改变ARGB的值得
颜色矩阵中的第五列代表颜色的偏移量:改变相应的值就会改变相应的颜色的比重
颜色矩阵分量:每个像素颜色的表示

//通过改变颜色矩阵来改变图片的颜色
private void setImageMatrix() {
    Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
            Bitmap.Config.ARGB_8888);
    android.graphics.ColorMatrix colorMatrix = new android.graphics.ColorMatrix();
    //设置颜色矩阵
    colorMatrix.set(mColorMatrix); //一维数组,大小:20

    Canvas canvas = new Canvas(bmp);
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
    canvas.drawBitmap(bitmap, 0, 0, paint);
    mImageView.setImageBitmap(bmp);
}

画笔风格

Android定义:先绘制的图片称为Dst,后绘制的图片称为Src

//禁止硬件加速,因为Xfermode的很多属性不支持硬件加速,会显示不出想要的效果
setLayerType(LAYER_TYPE_SOFTWARE, null);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1);
mOut = Bitmap.createBitmap(mBitmap.getWidth(),
        mBitmap.getHeight(),
        Bitmap.Config.ARGB_8888);
//将副本图片关联到画布
Canvas canvas = new Canvas(mOut);
//抗锯齿
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// Android中将在画布上先画的图片定义为:Dst
//根据Xfermode中要想将一个图片变为圆角矩形,采用SrcIn模式:在下面先画一个圆角矩形,再在上面画一个传入
//的图片,两个图片混合就可以将传入的图片变为圆角矩形了
//绘制圆角矩形
canvas.drawRoundRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(),
        50, 50, mPaint);
//设置Xfermode的模式
//PorterDuff.Mode:内部的计算公式为:[Sa*Da ,Sc*Da] :a:代表A通道(透明度通道) c:代表颜色通道
//如果传入的图片不支持A通道,设置是不起作用的
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 后画的定义为:Src
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
mPaint.setXfermode(null);
//BitmapShader的使用
@Override protected void onDraw(Canvas canvas) { 
  mPaint = new Paint(); 
  mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test); 
  // Shader.TileMode有三种模式:重复,拉伸,镜像 
  mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);         
  mPaint.setShader(mBitmapShader); 
  //在画布上画一个圆,内部用传入的图片填充
  canvas.drawCircle(300, 200, 200, mPaint); 
}

//使用线性渐变和矩阵来实现镜像的效果
 private void initView() {
 mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test); 
 Matrix matrix = new Matrix();
 //在矩阵的缩放中将坐标的缩放大小不变,只改变y坐标的符号就可以实现将图片倒置的效果     
 matrix.setScale(1, -1); 
 mRefBitmap = Bitmap.createBitmap(mSrcBitmap, 0, 0, mSrcBitmap.getWidth(), mSrcBitmap.getHeight(), matrix, true); 
 mPaint = new Paint(); 
//设置线性渐变 mPaint.setShader(new LinearGradient(0, mSrcBitmap.getHeight(), 0, mSrcBitmap.getHeight() * 1.4F, 0XDD000000, 0X10000000, Shader.TileMode.CLAMP));
//设置Xfermode模式
 mPaint.setXfermode(
   new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); 
 } 

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

推荐阅读更多精彩内容