目录
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长,这就说明,线冒(笔触)会是增加一定的长度。
- setStrokeJoin(Join join) : 设置两线交界处的样式
提供三种样式:
- Paint.Join.MITER : 默认,锐角
- Paint.Join.BEVEL : 直线
- Paint.Join.ROUND:圆弧
- 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);
}