1.前言
正在实习的公司最近特别忙,加上内分泌失调,顺便TI7也开始了(唯一指定冠军!LGD是不可战胜的!),以上种种,导致断更了……下面进入正文。
学会给女孩子化妆,是你走向人生巅峰的第一步。今天我们就走进堪称亚洲四大邪术之首的化妆术,一起来剖析这门技术的幕后原理。
但是,不要跟我说什么唇膏唇蜜唇釉唇彩!这种东西我不懂!也不需要!作为一名Android程序员,老夫化妆向来都是一把梭!Paint!Martrix!合二为一!变白就走!
2.Paint基本使用
化妆属于Paint的高级技巧,那么在学习之前,我们肯定是要把基础都给夯扎实了。
之前写过一篇Paint基础,内容大多数是枯燥的Api使用,考虑再三就给删了。这些内容完全可以参考官方文档,因此我只选出一些有意思的Api。
Paint的使用主要可以分为两大类,第一是图形、路径的绘制,第二是文字的绘制。我们开始吧~
2.1 绘制图形与路径
1.setStyle(Paint.Style style):设置画笔样式
Paint.Style.FILL :填充内部
Paint.Style.FILL_AND_STROKE :填充内部和描边
Paint.Style.STROKE :仅描边
这些样式不难理解,顾名思义即可
2.setStrokeCap(Paint.Cap cap):设置线冒样式
Paint.Cap.ROUND:圆形线冒
Paint.Cap.SQUARE:方形线冒
Paint.Cap.BUTT:无线冒)
这个不太好理解,我们用代码和图片来说明。
private void drawStrokeCap(Canvas canvas){
Paint paint = new Paint();
paint.setStrokeWidth(80);
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.BUTT);// 无线帽
canvas.drawLine(100,200,400,200,paint);
paint.setStrokeCap(Paint.Cap.SQUARE);// 方形线帽
canvas.drawLine(100,400,400,400,paint);
paint.setStrokeCap(Paint.Cap.ROUND);// 圆形线帽
canvas.drawLine(100,600,400,600,paint);
}
效果图:
冒多出来的那块区域就是线帽,就相当于给原来的直线加上一个帽子一样。
3.setStrokeJoin(Paint.Join join):设置线段连接处样式
Join.MITER:结合处为锐角
Join.ROUND:结合处为圆弧
Join.BEVEL:结合处为直线
代码如下:
private void drawStrokeJoin(Canvas canvas){
...
Path path = new Path();
path.moveTo(100,100);
path.lineTo(450,100);
path.lineTo(100,300);
paint.setStrokeJoin(Paint.Join.MITER);//锐角
canvas.drawPath(path,paint);
path.moveTo(100,400);
path.lineTo(450,400);
path.lineTo(100,600);
paint.setStrokeJoin(Paint.Join.BEVEL);//直线
canvas.drawPath(path,paint);
path.moveTo(100,700);
path.lineTo(450,700);
path.lineTo(100,900);
paint.setStrokeJoin(Paint.Join.ROUND);//圆弧
canvas.drawPath(path,paint);
}
效果图:
4.setPathEffect(PathEffect effect):设置绘制路径的效果
这个方法很牛逼,根据PathEffect的不同,可以实现不同的效果,这里我举两个例子。
1.CornerPathEffect:圆形拐角效果
paint.setPathEffect(new CornerPathEffect(100));
利用半径R=50的圆来代替原来两条直线间的夹角
2.DashPathEffect:虚线效果
paint.setPathEffect(new DashPathEffect(new float[]{20,10,50,100},15));
intervals[]:表示组成虚线的各个线段的长度;整条虚线就是由intervals[]中这些基本线段循环组成的。
phase:表示开始绘制的偏移值
代码如下:
private void drawComposePathEffectDemo(Canvas canvas){
//画原始路径
Paint paint = getPaint();
Path path = getPath();
canvas.drawPath(path,paint);
//仅应用圆角特效的路径
canvas.translate(0,300);
CornerPathEffect cornerPathEffect = new CornerPathEffect(100);
paint.setPathEffect(cornerPathEffect);
canvas.drawPath(path,paint);
//仅应用虚线特效的路径
canvas.translate(0,300);
DashPathEffect dashPathEffect = new DashPathEffect(new float[]{2,5,10,10},0);
paint.setPathEffect(dashPathEffect);
canvas.drawPath(path,paint);
//利用ComposePathEffect先应用圆角特效,再应用虚线特效
canvas.translate(0,300);
ComposePathEffect composePathEffect = new ComposePathEffect(dashPathEffect,cornerPathEffect);
paint.setPathEffect(composePathEffect);
canvas.drawPath(path,paint);
//利用SumPathEffect,分别将圆角特效应用于原始路径,然后将生成的两条特效路径合并
canvas.translate(0,300);
paint.setStyle(Paint.Style.STROKE);
SumPathEffect sumPathEffect = new SumPathEffect(cornerPathEffect,dashPathEffect);
paint.setPathEffect(sumPathEffect);
canvas.drawPath(path,paint);
}
效果图:
(5-8就是我们真正的化妆技术了!等会一个个详细介绍的!这里先留个印象吧)
5.setXfermode(Xfermode xfermode):设置图形重叠时的处理方式
如合并,取交集或并集,经常用来制作橡皮的擦除效果
6.setMaskFilter(MaskFilter maskfilter):实现滤镜的效果
如滤化,立体等
7.setColorFilter(ColorFilter colorfilter):设置颜色过滤器
可以在绘制颜色时实现不用颜色的变换效果
8.setShader(Shader shader):设置图像效果
使用Shader可以绘制出各种渐变效果
2.2 绘制文字
如果有的时候,你想在化妆的同时在你女朋友脸上写字,那么下面的知识点是一定要重点掌握的。
1.Typeface getTypeface() 、Typeface setTypeface(Typeface typeface):获取与设置字体类型。
Android默认有四种字体样式:BOLD(加粗)、BOLD_ITALIC(加粗并倾斜)、ITALIC(倾斜)、NORMAL(正常)。
我们也可以通过Typeface类来自定义个性化字体。这里有一个骚操作,就是将图标直接放到ttf中,用起来很舒服。
2.Paint.Align getTextAlign() 、void setTextAlign(Paint.Align align):获取与设置文本对齐方式
取值为CENTER、LEFT、RIGHT,也就是文字绘制是左边对齐、右边还是局中的。
3.int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth):判断能显示多少文字
private static final String STR = "ABCDEF";
mPaint.setTextSize(50);
float[] value = new float[1];
int ret = mPaint.breakText(STR, true, 200, value);
Log.i(TAG, "breakText="+ret+", STR="+STR.length()+", value="+value[1]);
//输出结果如下:
//breakText=5, STR=8, value=195.0
2.3 绘制文字高级技巧
现在考虑这样一种场景,自定义一个圆形的进度条,在圆形中间显示下载进度。
乍一看感觉很简单,但是动手之后就发现这里面有一个坑——下载进度无法显示在圆心。
为什么呢?这儿就涉及到Android绘制文字的原理了。在Paint中,我们可以找到一个叫FontMetrics
的内部类,从名字上看,它和文字的测量时有关系的,进去看看
public static class FontMetrics {
/**
* The maximum distance above the baseline for the tallest glyph in
* the font at a given text size.
*/
public float top;
/**
* The recommended distance above the baseline for singled spaced text.
*/
public float ascent;
/**
* The recommended distance below the baseline for singled spaced text.
*/
public float descent;
/**
* The maximum distance below the baseline for the lowest glyph in
* the font at a given text size.
*/
public float bottom;
/**
* The recommended additional space to add between lines of text.
*/
public float leading;
}
这里一共定义了5个float类型的变量,我们用图片来直观的体现下他们的关系。
top = top线的y坐标 - baseline线的y坐标,不能超过
bottom = bottom线的y坐标 - baseline线的y坐标,不能超过
ascent = ascent线的y坐标 - baseline线的y坐标,是系统建议的高度,字母上有音标符号时,会超过
desent = desent线的y坐标 - baseline线的y坐标,可能超过
最坑的就是这个baseline,由于它不在文字的中心,而drawText()
中传入的参数是以baseline为基准的,因此在绘制时就会出现文字不在中心的情况。
那么要如何解决呢?拥有数学头脑的同学肯定一眼就看穿,中心点是可以计算出来的!
解:设文字中心到top、bottom的距离分别为A、B,其中A=B;
设文字中心到baseLine的距离为C;
由图可知
A=B = (bottom - top)/2
因为bottom = baseline + FontMetrics.bottom
且top = baseline + FontMetrics.top
所以A = B = (FontMetrics.bottom - FontMetrics.top)/ 2
又因为C = B - (bottom - baseline)= B - FontMetrics.bottom
且C = baseline - center
所以baseline - center = (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom
综上所述
baseline = center +(FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom
计算完毕,是不是有点怀念自己的高中数学老师了?
2.4 实践是检验真理的唯一标准
现在我们知道了文字绘制的原理,又得到了中心点的计算公式,那么就来手写一个圆形的进度条吧。
首先需要在创建attrs资源文件,并且在其中自定义资源类型
<declare-styleable name="CircleProgressBar">
<attr name="progressColor" format="color"/>
<attr name="progressBackgroundColor" format="color"/>
<attr name="progressMax" format="float"/>
<attr name="circleWidth" format="dimension"/>
<attr name="textSize" format="dimension"/>
<attr name="textColor" format="color"/>
</declare-styleable>
接着自定义控件,由于有自定义的资源类型,所以要在构造方法中获取他们,注意最后要typedArray.recycle()
。
public CircleProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar);
mProgressBackgroundColor = typedArray.getColor(R.styleable.CircleProgressBar_progressBackgroundColor, Color.GRAY);
mProgressColor = typedArray.getColor(R.styleable.CircleProgressBar_progressColor, Color.BLUE);
mProgressMax = typedArray.getFloat(R.styleable.CircleProgressBar_progressMax, 100);
mCircleWidth = typedArray.getDimension(R.styleable.CircleProgressBar_circleWidth, 20);
mTextSize = typedArray.getDimension(R.styleable.CircleProgressBar_textSize, 60);
mTextColor = typedArray.getColor(R.styleable.CircleProgressBar_textColor, Color.RED);
typedArray.recycle();
}
代码主要是重写onDraw()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画圆环
mPaint = new Paint();
mPaint.setColor(mProgressBackgroundColor);
mPaint.setStrokeWidth(mCircleWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
int center = getWidth() / 2;
float radius = center - mCircleWidth / 2;
canvas.drawCircle(center, center, radius, mPaint);
//画文字
mPaint = new Paint();
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
mPaint.setStrokeWidth(0);
mPaint.setTypeface(Typeface.DEFAULT_BOLD);
int percent = (int) (mProgress / mProgressMax* 100) ;
String percentStr = percent + "%";
Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
canvas.drawText(percentStr,
center - mPaint.measureText(percentStr) / 2,
center + (fm.bottom - fm.top) / 2 - fm.bottom,
mPaint);
//画圆弧
mPaint=new Paint();
mPaint.setColor(mProgressColor);
mPaint.setStrokeWidth(mCircleWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
RectF oval=new RectF(center-radius,center-radius,center+radius,center+radius);
canvas.drawArc(oval,0,mProgress/mProgressMax*360,false,mPaint);
}
重写onMeasure()
是为了解决wrap_content
的问题。如果没有加上这一段,那么使用wrap_content
与match_parent
就没有区别。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(300,300);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(300,heightSpecSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,300);
}
}
再给调用者提供一个设置进度的方式,万事大吉!
public void setProgress(int progress){
if(progress<0){
throw new IllegalArgumentException("进度不能小于0!");
}
if(progress>mProgressMax){
mProgress= (int) mProgressMax;
}
if(progress<mProgressMax){
mProgress=progress;
//不知道应用层在哪个线程调用
postInvalidate();
}
}
值得注意的是,在这里我们还没有对padding进行处理。认真阅读奶奶系列的同学,自己解决一定是没有问题的,这里给出一个解决思路,只要在onDraw()
中获取这些值,并在canvas.drawXXX()
时加入即可。
int paddingLeft=getPaddingLeft();
int paddingRight=getPaddingRight();
int paddingTop=getPaddingTop();
int paddingBottom=getPaddingBottom();
最后来看看效果图(挺丑的= =)
3.高级渲染
基础的知识点大家应该了解了不少,下面就一起来学习真正的化妆技巧吧。
首先是高级渲染部分。所谓高级渲染,其实就是通过setShader(Shader shader)
方法来设置图像效果。
Shader是着色器的意思,canvas.drawXXX()
画出了具体的形状,而画笔的shader则定义了图形的着色和外观。
3.1.BitmapShader
BitmapShader是指位图图像渲染,即用BitMap对绘制的图形进行渲染着色,简单来说是用图片对图形进行贴图。
在使用Shader时,涉及到一个TileMode的概念,从名字上可以看出,这个参数就是用来设置拉伸模式的。也就是说,当图片小于容器的大小时,多出来的那部分要怎么画。TileMode一共有三种类型:
TileMode.CLAMP 拉伸最后一个像素去铺满剩下的地方
TileMode.MIRROR 通过镜像翻转铺满剩下的地方。
TileMode.REPEAT 重复图片平铺整个画面
具体该怎么使用就直接上代码吧
private void BitmapShaderTest(Canvas canvas) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy2);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scale = Math.max(width, height) / Math.min(width, height);
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
android.graphics.Matrix matrix = new android.graphics.Matrix();
matrix.setScale(scale, scale);
bitmapShader.setLocalMatrix(matrix);
mPaint.setShader(bitmapShader);
canvas.drawRect(0,0,800,800,mPaint);
}
都是一些基本的套路,接着看效果图
注意观察左侧被拉伸的区域,这就是TileMode.CLAMP的作用。剩下2个类型大家可以自己去玩玩看。
那么这玩意儿有什么用处呢?你可以和女朋友装逼说“用什么CircleImageView,直接用Paint画一个不就好了?”。
只要把上面代码最后一行canvas.drawRect(0,0,800,800,mPaint);
替换成canvas.drawCircle(height / 2, height / 2, height / 2, mPaint);
即可。
遗憾的是,如果你女朋友是会玩的并且学习过反装逼,那她就会说,“这算什么,老娘不用你的Paint照样能画出来!”。于是唰唰唰一顿操作,你的代码变成了这样:
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
shapeDrawable.getPaint().setShader(bitmapShader);
shapeDrawable.setBounds(0,0,width,height);
shapeDrawable.draw(canvas);
效果图就不放了,和上面的类似,只不过这次是个椭圆。
3.1.1.BitmapShader放大镜效果
结合上面的知识点,我们来学习第一个化妆技巧——放大妆!
首先,我们需要原图和放大图两张图片。原图直接展示在界面上,放大图通过ShapeDrawable只展示出一块圆形的区域。
public ZoomImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//原图
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy3);
//缩放图
mBitmapScale = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth() * FACTOR
, mBitmap.getHeight() * FACTOR, true);
mBitmapShader = new BitmapShader(mBitmapScale, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mShapeDrawable = new ShapeDrawable(new OvalShape());
mShapeDrawable.getPaint().setShader(mBitmapShader);
mShapeDrawable.setBounds(0,0,RADIUS*2,RADIUS*2);
mMatrix = new Matrix();
}
接着,在ondraw()
方法中进行绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap,0,0,null);
mShapeDrawable.draw(canvas);
}
最后,重写onTouchEvent()
实现自由放大。需要理解的是,当我的手指向右下方移动时,放大图片本身是要向左上移动的,而放大区域则是一直以手指触摸点为圆心。
@Override
public boolean onTouchEvent(MotionEvent event) {
int x= (int) event.getX();
int y= (int) event.getY();
//将放大的图片往相反的方向移动
mMatrix.setTranslate(RADIUS-x*FACTOR,RADIUS-y*FACTOR);
mBitmapShader.setLocalMatrix(mMatrix);
//手指触摸点为圆心
mShapeDrawable.setBounds(x-RADIUS,y-RADIUS,x+RADIUS,y+RADIUS);
invalidate();
return true;
}
放大妆的作用很明显,比如你女朋友牙齿特别好看,那么出门的时候就可以给她来上这个一下,把美丽的牙齿大方的展示出来!我们来看看效果图
3.2.LinearGradient
LinearGradient是线性渲染, 主要通过颜色的变化来进行渲染。
关于基本使用,我们直接上代码来看
private int[] mColors = {Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW};
/**线性渐变
* x0, y0, 起始点
* x1, y1, 结束点
* int[] mColors, 中间依次要出现的几个颜色
* float[] positions,数组大小跟colors数组一样大,中间依次摆放的几个颜色分别放置在那个位置上(参考比例从左往右)
* tile
*/
private void LinearGradientTest(Canvas canvas) {
LinearGradient linearGradient = new LinearGradient(0, 0, 800, 800, mColors, null, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 800, 800, mPaint);
}
效果图这样
3.2.1.LinearGradient霓虹灯文字
下面这个妆就厉害了,是动态的,还会来回转,我们看代码
首先,这个自定义View要继承TextView,这样比较方便
public class LinearGradientTextView extends TextView
接着,通过getPaint()
获取到画笔,并为其设置LinearGradient
public LinearGradientTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//获取屏幕宽度
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mPoint = new Point();
windowManager.getDefaultDisplay().getSize(mPoint);
String text = getText().toString();
//拿到TextView的画笔
mPaint = getPaint();
mTextWidth = (int) mPaint.measureText(text);
int gradientTextSize = mTextWidth / text.length() * 3;
mLinearGradient = new LinearGradient(0, 0, gradientTextSize, 0, mColors, null, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
}
此时静态的效果已经出来了,剩下的就是重写onDraw()
实现动态变换
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Matrix matrix = new Matrix();
mTranslate += DELTAX;
//反复循环
if (mTranslate > mPoint.x || mTranslate < 1) {
DELTAX = -DELTAX;
}
matrix.setTranslate(mTranslate, 0);
mLinearGradient.setLocalMatrix(matrix);
postInvalidate();
}
国际惯例,看一张效果图(gif什么的看上去好麻烦…大家根据代码自己脑补吧)
3.3.SweepGradient
SweepGradient叫渐变渲染或者梯度渲染,其效果与用法都和LinearGradient类似,还是先介绍基本用法
private void SweepGradientTest(Canvas canvas) {
SweepGradient mSweepGradient = new SweepGradient(300, 300, mColors, null);
mPaint.setShader(mSweepGradient);
canvas.drawRect(0, 0, 800,800, mPaint);
}
效果图
3.3.1 SweepGradient雷达扫描
那么这倒霉玩意儿又能干啥呢?SweepGradient经典的应用场景就是实现雷达扫描效果。
来看代码,首先在构造方法中初始化Paint
public RadarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
mRadarBg = new Paint(Paint.ANTI_ALIAS_FLAG); //设置抗锯齿
mRadarBg.setColor(mRadarBgColor); //画笔颜色
mRadarBg.setStyle(Paint.Style.FILL); //画实心圆
mRadarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //设置抗锯齿
mRadarPaint.setColor(mCircleColor); //画笔颜色
mRadarPaint.setStyle(Paint.Style.STROKE); //设置空心的画笔,只画圆边
mRadarPaint.setStrokeWidth(2); //画笔宽度
mRadarShader = new SweepGradient(0, 0, mStartColor, mEndColor);
mMatrix = new Matrix();
}
接着在onDraw()
方法中进行绘制
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mRadarBg.setShader(null);
//将画板移动到屏幕的中心点
canvas.translate(mRadarRadius, mRadarRadius);
//绘制底色,让雷达的线看起来更清晰
canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
//画圆圈
for (int i = 1; i <= mCircleNum; i++) {
canvas.drawCircle(0, 0, (float) (i * 1.0 / mCircleNum * mRadarRadius), mRadarPaint);
}
//绘制雷达基线 x轴
canvas.drawLine(-mRadarRadius, 0, mRadarRadius, 0, mRadarPaint);
//绘制雷达基线 y轴
canvas.drawLine(0, mRadarRadius, 0, -mRadarRadius, mRadarPaint);
//设置颜色渐变从透明到不透明
mRadarBg.setShader(mRadarShader);
canvas.concat(mMatrix);
canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
}
最后来个handler让雷达动起来吧
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mRotate += 3;
postInvalidate();
mMatrix.reset();
mMatrix.preRotate(mRotate, 0, 0);
mHandler.sendEmptyMessageDelayed(MSG_WHAT, DELAY_TIME);
}
};
效果图长这样
3.4.RadialGradient
RadialGradient表示环形渲染,先来看下基本用法
private void RadialGradientTest(Canvas canvas) {
RadialGradient mRadialGradient = new RadialGradient(300, 300, 100, mColors, null, Shader.TileMode.REPEAT);
mPaint.setShader(mRadialGradient);
canvas.drawCircle(300, 300, 300, mPaint);
}
效果图,正常人应该是会晕的
3.4.1 RadialGradient水波纹效果
文章的最后,终于轮到大名鼎鼎的水波纹效果了。
思路大致如下:使用RadialGradient响应触摸事件绘制出初始的圆形波纹,接着使用属性动画将圆形波纹变大。
由于属性动画涉及到对属性的设置,这里我们提供一个setRadius()
方法
public void setRadius(final int radius) {
mCurRadius = radius;
if (mCurRadius > 0) {
mRadialGradient = new RadialGradient(mX, mY, mCurRadius, 0x00FFFFFF, 0xFF58FAAC, Shader.TileMode.CLAMP);
mPaint.setShader(mRadialGradient);
}
postInvalidate();
}
重点都在onTouch()
方法中
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mX != event.getX() || mY != mY) {
mX = (int) event.getX();
mY = (int) event.getY();
setRadius(DEFAULT_RADIUS);
}
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_UP:
{
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
if (mAnimator == null) {
mAnimator = ObjectAnimator.ofInt(this,"radius",DEFAULT_RADIUS, getWidth());
}
mAnimator.setInterpolator(new AccelerateInterpolator());
mAnimator.addListener(new Animator.AnimatorListener() {
...
@Override
public void onAnimationEnd(Animator animation) {
setRadius(0);
}
...
});
mAnimator.start();
}
}
return super.onTouchEvent(event);
}
这里就通过属性动画对radius值进行了改变,关于属性动画,以后会从源码入手好好讲一讲。最后只要在onDraw()里进行简单的绘制即可。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mX, mY, mCurRadius, mPaint);
}
效果光看图片不容易感受,大伙将就着看。
4.总结
这篇文章首先介绍了Paint的一些基础用法,包括图形、路径、文字的绘制。接着将高级渲染进行了细致讲解。其实还有最后一个ComposeShader没有提,这是组合渲染,可以将前面说的4种渲染方式随意组合,大家自由发挥即可。
Paint高级化妆技巧还包括Xfermode以及滤镜的使用,下篇文章咱们就来怼他们俩。
完结撒花~