此前自定义View中用的比较多的是对view位置的控制和功能性的融合,视觉上和动画上的使用要少一些,因此最近心血来潮准备用原生的view绘制些动画效果出来。
这里就懒得去查资料了,大致靠记忆写一下,依次是 onCreate->onMeasure->onLayout->onDraw。
首先,我们常见的View无非两种,一种是View(独立的控件,不能存在子控件),一种是ViewGroup(大多是充当容器的作用,可以包含子空间,例如:XXLayout)。但其实从本质上来说所有View都是继承于View
,包括ViewGroup也仅仅是种“可以包含view的View”,可能看上去比较拗口,谁让我是一个偏科的工科男,当然是选择原谅我啦o(>﹏<)o。
View的构造方法通常根据传入参数个数不同会有三个,分别是Context context, AttributeSet attrs, int defStyleAttr
。
onMeasure用来向父容器声明自己的大小,这个方法很重要也很常用,大致就是告诉父容器自己有多大,是以什么属性进行放置,会在父容器的onLayout中参与计算。
onLayout用于设置子View的Layout位置,因此,只有ViewGroup才会执行这个方法。
onDraw中对View进行具体的绘画操作,决定View最终的展示效果。
draw(Canvas canvas)`,会传入一个Canvas对象,该对象即是整个View画布,所以我们需要画任何效果都是通过canvas.drawXXX()来实现的,这里以绘制一条动态显示的心电图为例进行讲解。
心电图属于一种不规则的线条图形,其实就是由多条线段组成而线又可以由点组成,因此实现方式有很多种,最简单的是通过Canvas.drawPath(Path,Paint)进行绘制。
drawPath用于路径的绘制,路径也就是由多个点连接而成的不规则图形,可以相交成闭合的多边形,也可以不相交成为一段线段,该方法有两个参数(Path,Paint)。
Paint,画笔对象,面向对象编程有个特点也是优点,那就是十分贴近我们的生活,既然我们已经有了Canvas画布,那么按照现实生活中的逻辑,我们就还需要用笔在画布上进行绘画才能真正显示出图像,因此我们就需要生成一个Paint对象。
其常见的方法如下:
setColor(int color); 设置颜色(为画笔设置一种纯色,这个没什么好说的)
setFlags(int flags); 预设一些属性,如ANTI_ALIAS_FLAG
抗锯齿。
setStyle(Style style); 设置画笔填充类型,共三种:FILL
填充,STROKE
描边,FILL_AND_STROKE
填充并描边。如果设置填充则画出来的图形是实心的,**不仅仅是针对封闭图形,线段同样有效。**相反,设置描边则仅画出外边框即轮廓。
setStrokeWidth(float witdh);设置画笔宽度(粗细)。
setAntiAlias(boolean aa); 设置抗锯齿,和setFlags(ANTI_ALIAS_FLAG)效果相同。
setDither(boolean d); 设置防抖动,开启后会让图像颜色显示更加平滑,和抗锯齿一样会效果更多的性能,同样也可以通过setFlag设置。
setShadowLayer(float radius, float dx, float dy, int shadowColor); 设置阴影,第一个参数为阴影效果因数,越大效果越明显,0则没有,而且设置0还会报错(Excuse me?),第二第三个参数为阴影的偏移量,第三个是阴影的颜色。
setShader(Shader shader); 设置着色器,可以看成setColor的豪华升级版。通过这个方法可以设置出很多炫酷的色彩效果。
BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY); 顾名思义,它是将bitmap作为画笔颜色,需要传入三个对象,第一个不用说,后面两个分别是X轴和Y轴的处理模式。一共有三种模式:CLAMP、MIRROR和REPETA。
Blend
类似,有兴趣自己去玩玩儿,这里也不具体介绍。setPathEffect(PathEffect effect); 从名字上可以看出是专门给Path设置效果的一个方法,(具体是不是只针对Path有效没具体验证过- -!)。
DashPathEffect
类似,不过前者始终是以线的表现形式,而PathDashPathEffect可以通过传入Path的不同,定义间断出来的形状如圆点,方块等等。ComposePathEffect
类似,会把两种路径效果加起来再作用于路径。通过上面的介绍,大致流程是这样的:
先通过drawPath画出心电图类似的上下折线图形。
然后设置Paint的Shader属性,通过LinearGradient为Paint添加一个带有透明的线性渐变的特效。
最后通过一个线程循环定时改变线性颜色开始和结束的偏移量达到动态绘制心电图的效果。
同时为了提升显示效果可以为Paint设置一些别的辅助效果和参数。
接下来就是 ShowTime :
下面直接上代码:
public class MyView extends View {
private Paint mPaint;
private int mWindowWidth;
private int mWindowHeight;
private int mOffset;
private Handler mHandler = new Handler();
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mOffset = 0;
mPaint = new Paint();
//设置空心
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
//设置线宽
mPaint.setStrokeWidth(15f);
//设置抗锯齿
mPaint.setAntiAlias(true);
//设置防抖动
mPaint.setDither(true);
//设置阴影
// mPaint.setShadowLayer(25f, 5f, 10f, Color.BLACK);
//初始化渐变颜色,因为要达到真正的透明效果,所以使用两个透明渐变到红色
final int[] colors = new int[]{Color.argb(0, 0, 0, 0), Color.argb(0, 0, 0, 0), Color.RED};
//启用一根新线程进行定时刷新
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
mOffset += 5;
//添加离散效果,让线条变得更加曲折
DiscretePathEffect discretePathEffect = new DiscretePathEffect(3f, 5f);
//添加转角圆滑
CornerPathEffect cornerPathEffect = new CornerPathEffect(90f);
//设置组合PathEffect
mPaint.setPathEffect(new ComposePathEffect(cornerPathEffect, discretePathEffect));
//设置线性渐变
mPaint.setShader(new LinearGradient(mOffset, 0, 800 + mOffset, 15f, colors, null, Shader.TileMode.REPEAT));
//刷新视图
mHandler.post(new Runnable() {
@Override
public void run() {
invalidate();
}
});
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
//获得屏幕尺寸
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
mWindowWidth = displayMetrics.widthPixels;
mWindowHeight = displayMetrics.heightPixels;
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
//设置折线路径
Path path = new Path();
//起始路径
path.moveTo(0, mWindowHeight / 2);
//经过路径
path.lineTo(50, mWindowHeight / 2 + 50);
path.lineTo(100, mWindowHeight / 2 - 50);
path.lineTo(150, mWindowHeight / 2 + 100);
path.lineTo(200, mWindowHeight / 2 - 100);
path.lineTo(250, mWindowHeight / 2 + 150);
path.lineTo(300, mWindowHeight / 2 - 150);
path.lineTo(350, mWindowHeight / 2 + 150);
path.lineTo(400, mWindowHeight / 2 - 150);
path.lineTo(450, mWindowHeight / 2 + 150);
path.lineTo(500, mWindowHeight / 2 - 150);
path.lineTo(550, mWindowHeight / 2 + 100);
path.lineTo(600, mWindowHeight / 2 - 100);
path.lineTo(650, mWindowHeight / 2 + 50);
path.lineTo(700, mWindowHeight / 2 - 50);
path.lineTo(mWindowWidth, mWindowHeight / 2);
//drawPath
canvas.drawPath(path, mPaint);
}
}