第一章:Android高级UI - UI原理与高级绘制(2)
2.Paint/Cavans高级绘制
画笔,保存了绘制几何图形,文本和维度的样式和颜色信息
1.常用API
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.RED);//设置颜色
mPaint.setARGB(255, 255, 255, 0);//设置Paint对象颜色,范围为0-255
mPaint.setAlpha(200);//设置alpha不透明度,范围为0-255
mPaint.setAntiAlias(true);//抗锯齿
mPaint.setStyle(Paint.Style.STROKE);//描边效果;
mPaint.setStrokeWidth(3);//描边宽度
mPaint.setStrokeJoin(Paint.Join.MITER);//拐角风格
mPaint.setStrokeCap(Paint.Cap.ROUND);//圆角效果
mPaint.setShader(new SweepGradient(200, 200, Color.BLUE, Color.BLACK));//设置环形渲染器
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));//设置图层混合模式
mPaint.setColorFilter(new LightingColorFilter(0x00ffff, 0x00000));//设置颜色过滤器
mPaint.setFilterBitmap(true);//设置双线性过滤
mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.INNER));//设置画笔遮罩滤镜,传入度数和样式
mPaint.setTextScaleX(2);//设置文本缩放倍数
mPaint.setTextSize(23);//设置字体大小
mPaint.setTextAlign(Paint.Align.CENTER);//设置文本对齐方式
mPaint.setUnderlineText(true);//设置下划线
String str = "Android开发";
mPaint = new Paint();
Rect rect = new Rect();
mPaint.getTextBounds(str, 0, str.length(), rect);//测试文本大小,将文本大小信息放在rect中
mPaint.measureText(str);//获取文本的宽
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();//获取字体度量对象
float ascent = fontMetrics.ascent;
Log.e(TAG, "ascent: "+ascent+"---"+
"bottom: "+fontMetrics.bottom+"---"+
"descent: "+fontMetrics.descent+"---" +
"leading: "+fontMetrics.leading+"---" +
"top: "+fontMetrics.top+"---" );
}
以上这些是常用API,部分API的详细记录一下
(1)mPaint.setStyle(Paint.Style.STROKE);//描边效果;
这个主要有这几种效果 下图,,
STROKE :就是下图圆环的效果同时通过mPaint.setStrokeWidth(20)可以设置圆环的宽度;
FILL:就是中间那个圆效果实心圆;
FILL_AND_STROKE:这个看名称就懂了,就是这两个的合体吧所以和FILL效果一样
(2)setStrokeCap //圆角效果
image.png
(3)paint.setStrokeJoin(Paint.Join.MITER);
image.png
(4)setFilterBitmap(true)
设置了这个后为true后我们可以发现打了马赛克样式的图片看着背景变虚化了
image.png
(5)mPaint.getFontMetrics();//获取字体度量对象
String str = "Android开发";
mPaint = new Paint();
Rect rect = new Rect();
mPaint.getTextBounds(str, 0, str.length(), rect);//测试文本大小,将文本大小信息放在rect中
mPaint.measureText(str);//获取文本的宽
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();//获取字体度量对象
float ascent = fontMetrics.ascent;
Log.e(TAG, "ascent: "+ascent+"---"+
"bottom: "+fontMetrics.bottom+"---"+
"descent: "+fontMetrics.descent+"---" +
"leading: "+fontMetrics.leading+"---" +
"top: "+fontMetrics.top+"---" );
image.png
image.png
2.Paint颜色相关
(1)setColor(int color)
(2)setARGB(int a,int r,int g,int b)
(3)setShader(Shader s)参数着色器对象,一般使用shader的几个子类
下面就是shader的几个子类详解
①LinearGradient线性渲染
构造方法:
mLinearGradient = new LinearGradient(0, 0, 500, 500, new int[]{Color.GREEN, Color.RED}, new float[]{0.5f, 1}, Shader.TileMode.CLAMP);
参数比较多
(x0,y0,x1,y1)前四个参数就是两个端点的位置比如 (0,0) (500,500)就是屏幕左上角到(500,500)这个位置这两个点之间的渐变过程 我们想象画一个矩形或者圆形就明白了
int[] colors 渐变颜色值
float[] {0.5f,1} 表示颜色占据的渲染比例(可以为空)
效果图
image.png
②RadialGradient环形渲染
/**
* Create a shader that draws a radial gradient given the center and radius.
*
* @param centerX 辐射中心的X坐标
* @param centerY 辐射中心的Y坐标
* @param radius 辐射半径
* @param colors 辐射渐变颜色数组
* @param stops 辐射渐变位置数组
* @param tileMode 辐射范围之外的着色模式 shader模式
* @param centerColor 中心点渐变颜色
* @param edgeColor 边界渐变颜色
*/
mShader = new RadialGradient(250,250,250,
new int[]{Color.RED,Color.GREEN}, null,Shader.TileMode.CLAMP);
mPaint.setShader(mShader);![image.png](https://img.haomeiwen.com/i1906805/92c02061997639e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
canvas.drawCircle(250, 250, 250, mPaint);
效果如下:
image.png
③扫描渲染
/**
* A Shader that draws a sweep gradient around a center point.
*
* @param cx The x-coordinate of the center 扫描中心x轴
* @param cy The y-coordinate of the center 扫描中心位置y轴
* @param color0 The color to use at the start of the sweep 扫描开始颜色
* @param color1 The color to use at the end of the sweep 扫描终止颜色
*/
mShader = new SweepGradient(250, 250, Color.RED, Color.GREEN);
mPaint.setShader(mShader);
canvas.drawCircle(250, 250, 250, mPaint);
效果图:
image.png
④位图渲染
原本是这样一张图
image.png
/**
* 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轴方向的mode
* @param tileY The tiling mode for y to draw the bitmap in. y轴方向的mode
*/
mShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
mPaint.setShader(mShader);
canvas.drawRect(0, 0, 5000, 5000, mPaint);
添加位图渲染后
image.png image.png
⑤组合渲染
Shader mShaderA = new SweepGradient(250, 250, Color.RED, Color.GREEN);
Shader mShaderB = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
/**
* Create a new compose shader, given shaders A, B, and a combining PorterDuff mode.
* When the mode is applied, it will be given the result from shader A as its
* "dst", and the result from shader B as its "src".
*
* @param shaderA
* @param shaderB
* @param mode 组合两种shader的颜色模式
* @Parm Xfermode 组合两种shader的颜色模式
* ComposeShader有两种构造方法 Xfermode后面说
*/
// mShader = new ComposeShader( Shader shaderA, Shader shaderB, Xfermode mode);
mShader = new ComposeShader(mShaderA, mShaderB, PorterDuff.Mode.ADD);
mPaint.setShader(mShader);
canvas.drawRect(0, 0, 5000, 5000, mPaint);
这里我就把上面的扫描渲染和位图渲染组合在了一起 效果图如下:
image.png
3.lPorterDuff.Mode图层混合模式
先绘制的是dest目标图像 后绘制的是src源图像
//所绘制不会提交到画布上
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),
//取两图层全部区域,交集部分饱和度相加
new PorterDuffXfermode(PorterDuff.Mode.ADD),
//取两图层全部区域,交集部分叠加
new PorterDuffXfermode(PorterDuff.Mode.OVERLAY)
image.png
4.效果相关
image.png
image.png
image.png
4.Canvas高阶使用技巧-变换,状态保存,离屏缓冲,粒子特效
image.png
image.png image.png
项目实战-爆炸粒子效果
public class ParticleView extends View {
private Paint mPaint;
private Bitmap mBitmap;
private float d = 30;
private ValueAnimator mValueAnimator;
private List<Ball> mBalls = new ArrayList<>();
public ParticleView(Context context) {
this(context, null);
}
public ParticleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ParticleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.head, options);
for (int i = 0; i < mBitmap.getWidth(); i++) {
for (int j = 0; j < mBitmap.getHeight(); j++) {
Ball ball = new Ball();
ball.color = mBitmap.getPixel(i, j);
ball.x = i * d + d / 2;
ball.y = j * d + d / 2;
ball.r = d / 2;
ball.vx = (float) (-20 + Math.random() * 40);
ball.vy = (float) (-15 + Math.random() * 50);
mBalls.add(ball);
}
}
mValueAnimator = ValueAnimator.ofFloat(0, 1);
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.setDuration(1000);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateBall();
invalidate();
}
});
}
private void updateBall() {
for (Ball mBall : mBalls) {
mBall.x += mBall.vx;
mBall.y += mBall.vy;
mBall.vx += mBall.ax;
mBall.vy += mBall.ay;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(200, 200);
for (Ball mBall : mBalls) {
mPaint.setColor(mBall.color);
canvas.drawCircle(mBall.x, mBall.y, mBall.r, mPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mValueAnimator.start();
}
return true;
}
}
项目实战-开屏动
public class SplashView extends View {
//旋转圆的画笔
private Paint mPaint;
//扩散圆的画笔
private Paint mHolePaint;
//背景色
private int mBackgroundColor = Color.WHITE;
private int[] mCircleColors;
//圆心坐标
private float mCenterX;
private float mCenterY;
private float mDistance;
//6个小球的半径
private float mCircleRadius = 18;
//旋转圆半径
private float mRotateRadius = 90;
//扩散圆半径
private float mHoleRadius;
private ValueAnimator mValueAnimator;
private SplashState mState;
private float mCurrentRotateAngle;
private float mCurrentRotateRadius = 90;
private float mCurrentHoleRadius;
public SplashView(Context context) {
this(context, null);
}
public SplashView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SplashView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHolePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHolePaint.setStyle(Paint.Style.STROKE);
mHolePaint.setColor(mBackgroundColor);
mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w * 1f / 2;
mCenterY = h * 1f / 2;
mDistance = (float) Math.hypot(w, h) / 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mState == null) {
mState = new RotateState();
}
mState.drawState(canvas);
}
private abstract static class SplashState {
abstract void drawState(Canvas canvas);
}
//1.旋转
private class RotateState extends SplashState {
private RotateState() {
mValueAnimator = ValueAnimator.ofFloat(0, (float) (Math.PI * 2));
mValueAnimator.setRepeatCount(2);
mValueAnimator.setDuration(1200);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentRotateAngle = (float) animation.getAnimatedValue();
invalidate();
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mState = new MerginState();
}
});
mValueAnimator.start();
}
@Override
void drawState(Canvas canvas) {
//绘制背景
drawBackground(canvas);
//绘制小球
drawCircles(canvas);
}
}
private void drawCircles(Canvas canvas) {
float rotateAngle = (float) (Math.PI * 2 / mCircleColors.length);
for (int i = 0; i < mCircleColors.length; i++) {
float angle = i * rotateAngle + mCurrentRotateAngle;
float cx = (float) (Math.cos(angle) * mCurrentRotateRadius + mCenterX);
float cy = (float) (Math.sin(angle) * mCurrentRotateRadius + mCenterY);
mPaint.setColor(mCircleColors[i]);
canvas.drawCircle(cx, cy, mCircleRadius, mPaint);
}
}
private void drawBackground(Canvas canvas) {
if (mCurrentRotateRadius > 0) {
float strokeWidth = mDistance - mCurrentHoleRadius;
float radius = strokeWidth / 2 + mCurrentHoleRadius;
mHolePaint.setStrokeWidth(strokeWidth);
canvas.drawCircle(mCenterX, mCenterY, radius, mHolePaint);
} else {
canvas.drawColor(mBackgroundColor);
}
}
//2.扩散聚合
private class MerginState extends SplashState {
private MerginState() {
mValueAnimator = ValueAnimator.ofFloat(mCircleRadius, mRotateRadius);
mValueAnimator.setDuration(1200);
mValueAnimator.setInterpolator(new OvershootInterpolator(10f));
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentRotateRadius = (float) mValueAnimator.getAnimatedValue();
invalidate();
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mState = new ExpandState();
}
});
mValueAnimator.reverse();
}
@Override
void drawState(Canvas canvas) {
drawBackground(canvas);
drawCircles(canvas);
}
}
//3.水波纹
private class ExpandState extends SplashState {
public ExpandState() {
mValueAnimator = ValueAnimator.ofFloat(mCircleRadius, mDistance);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.setDuration(1200);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentHoleRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
mValueAnimator.start();
}
@Override
void drawState(Canvas canvas) {
drawBackground(canvas);
}
}
}
5.Path
直线
Path path = new Path();
path.moveTo(100,200);
path.lineTo(140,250);
//path.rLineTo(40,50);
添加路径
Path newPath = new Path();
newPath.moveTo(200,200);
newPath.lineTo(300,500);
path.addPath(newPath);
圆角矩形路径
RectF rectF = new RectF(200, 200, 500, 500);
path.addRoundRect(rectF,20,20, Path.Direction.CW);
二阶贝塞尔曲线
path.moveTo(300, 500);
path.quadTo(500, 100, 800, 500);
三阶贝塞尔曲线
path.moveTo(300, 500);
path.cubicTo(500, 100, 600, 1200, 800, 500);
6.PathMeasure
Path path = new Path();
path.lineTo(0, 200);
path.lineTo(200, 200);
path.lineTo(200, 0);
PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(path, false);
path.lineTo(0,0);
pathMeasure.setPath(path, false);
Log.e("TAG_", "onDraw: " + pathMeasure.getLength());
> onDraw: 800.0
image.png
image.png
pathMeasure.getSegment(200, 300, dst, true);最后一个参数为true的时候截取得片段不会发生变化,为false会将片段连起来,形态会发生变化
image.png image.png
小案例:
image.png
image.png image.png