day05-自定义View
MeasureSpec的使用
- 获取测量模式(Mode) int specMode = MeasureSpec.getMode(measureSpec)
- 获取测量大小(Size) int specSize = MeasureSpec.getSize(measureSpec)
自定义View属性:
自定义View的自定义属性,为了能让自定义View在xml文件中编写时可以设置自己特有的属性值
创建attrs.xml文件
在res/values/中创建一个attrs.xml。
创建declare-styleable节点
在根节点resources内添加declare-styleable节点,declare-styleable节点只有一个name属性且为必填,name可以自由定义。
View生命周期
onWindowVisibilityChanged(改变可见性)——》创建(构造方法)——》onFinishInflate(初始化xml【当所有子控件全部完成xml映射是触发】})——》onAttachedToWindow(当view附着一个窗口时触发)——》onMeasure(确定自己的大小)——》onSizeChenged(当view大小变化是触发)——》onLayout(确定所有子控件的大小和位置)——》onDeaw(view绘制)——》onDetackedFromWindow(当View离开附着的窗口时触发,比如在Activity调用onDestroy方法时View就会离开窗口。和一开始的AttachedToWindow相对,都只会被调用一次)
view的onMeasure和onLayout问什么会执行两次?
api25-24:执行2次onMeasure、2次onLayout、1次onDraw,理论上执行三次测量,但由于测量优化策略,第三次不会执行onMeasure。
api23-21:执行3次onMeasure、2次onLayout、1次onDraw,forceLayout标志位,离奇被置为true,导致无测量优化。
api19-16:执行2次onMeasure、2次onLayout、1次onDraw,原因第一次performTranversals中只会执行measureHierarchy中的performMeasure,forceLayout标志位,离奇被置位true,导致无测量优化。
总之,造成这个现象的根本原因是performTranversal函数在View的测量流程中会执行2次。
遍历Activity的根布局DecorView里(或者其它窗口比如Dialog)的每一个View
画笔属性:
1、setARGB(int a,int r,int g,int b); 设置绘制的颜色,a代表透明度,r,g,b代表颜色值。
2、setAlpha(int a); 设置绘制图形的透明度。
3、 setColor(int color); 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。
4、setAntiAlias(boolean aa); 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
5、setDither(boolean dither); 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
6、setFilterBitmap(boolean filter); 如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示 速度, 本设置项依赖于dither和xfermode的设置
7、setMaskFilter(MaskFilter maskfilter); 设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等 8、8、setColorFilter(ColorFilter colorfilter); 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果
9、setPathEffect(PathEffect effect); 设置绘制路径的效果,如点画线等
10、setShader(Shader shader); 设置图像效果,使用Shader可以绘制出各种渐变效果
11、setShadowLayer(float radius ,float dx,float dy,int color); 在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色
12、setStyle(Paint.Style style); 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE
13、setStrokeCap(Paint.Cap cap); 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式 Cap.ROUND,或方形样式Cap.SQUARE setSrokeJoin(Paint.Join join); 设置绘制时各图形的结合方式,如平滑效果等
14、setStrokeWidth(float width); 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度
15、setXfermode(Xfermode xfermode); 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果
16、setFakeBoldText(boolean fakeBoldText); 模拟实现粗体文字,设置在小字体上效果会非常差
17、setSubpixelText(boolean subpixelText); 设置该项为true,将有助于文本在LCD屏幕上的显示效果
18、setTextAlign(Paint.Align align); 设置绘制文字的对齐方向 setTextScaleX(float scaleX); 设置绘制文字x轴的缩放比
例,可以实现文字的拉伸的效果
setTextSize(float textSize); 设置绘制文字的字号大小 setTextSkewX(float skewX); 设置斜体文字,skewX为倾斜弧度 setTypeface(Typeface typeface); 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
setUnderlineText(boolean underlineText); 设置带有下划线的文字效果 setStrikeThruText(boolean strikeThruText); 设置带有删除线的效果
案例
填充View效果
代码
public class MyView extends View {
private Context context;
private int myWidth,myHeight;
private int width;
private int height;
private Paint p1,p2,p3,p4;
private Path path1,path2,path3,path4;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.MyView);
myWidth = typedArray.getInteger(R.styleable.MyView_myWidth,0);
myHeight = typedArray.getInteger(R.styleable.MyView_myHeight,0);
initPaint();
}
private void initPaint() {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
width = displayMetrics.widthPixels;
height = displayMetrics.heightPixels;
p1 = new Paint();
p2 = new Paint();
p3 = new Paint();
p4 = new Paint();
p1.setStrokeWidth(5);
p1.setColor(Color.RED);
p1.setAntiAlias(true);
p1.setStyle(Paint.Style.STROKE);
p2.setStrokeWidth(5);
p2.setColor(Color.BLUE);
p2.setAntiAlias(true);
p2.setStyle(Paint.Style.STROKE);
p3.setStrokeWidth(5);
p3.setColor(Color.GREEN);
p3.setAntiAlias(true);
p3.setStyle(Paint.Style.STROKE);
p4.setStrokeWidth(5);
p4.setColor(Color.YELLOW);
p4.setAntiAlias(true);
p4.setStyle(Paint.Style.STROKE);
path1 = new Path();
path2 = new Path();
path3 = new Path();
path4 = new Path();
path1.moveTo(0,0);
path1.lineTo(width/2,height/2);
path1.lineTo(width,0);
path2.moveTo(0,0);
path2.lineTo(width/2,height/2);
path2.lineTo(0,height);
path3.moveTo(0,height);
path3.lineTo(width/2,height/2);
path3.lineTo(width,height);
path4.moveTo(width,0);
path4.lineTo(width/2,height/2);
path4.lineTo(width,height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path1,p1);
canvas.drawPath(path2,p2);
canvas.drawPath(path3,p3);
canvas.drawPath(path4,p4);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
fill(x,y);
fill1(x,y);
fill2(x,y);
fill3(x,y);
break;
}
invalidate();
return true;
}
private void fill3(float x, float y) {
RectF rectF = new RectF();
path4.computeBounds(rectF,true);
Region region = new Region();
region.setPath(path4,new Region((int)rectF.left,(int)rectF.top,(int)rectF.right,(int)rectF.bottom));
boolean contains = region.contains((int) x, (int) y);
if (contains){
p4.setStyle(Paint.Style.FILL);
invalidate();
}
}
private void fill2(float x, float y) {
RectF rectF = new RectF();
path3.computeBounds(rectF,true);
Region region = new Region();
region.setPath(path3,new Region((int)rectF.left,(int)rectF.top,(int)rectF.right,(int)rectF.bottom));
boolean contains = region.contains((int) x, (int) y);
if (contains){
p3.setStyle(Paint.Style.FILL);
invalidate();
}
}
private void fill1(float x, float y) {
RectF rectF = new RectF();
path2.computeBounds(rectF,true);
Region region = new Region();
region.setPath(path2,new Region((int)rectF.left,(int)rectF.top,(int)rectF.right,(int)rectF.bottom));
boolean contains = region.contains((int) x, (int) y);
if (contains){
p2.setStyle(Paint.Style.FILL);
invalidate();
}
}
private void fill(float x, float y) {
RectF rectF = new RectF();
path1.computeBounds(rectF,true);
Region region = new Region();
region.setPath(path1,new Region((int)rectF.left,(int)rectF.top,(int)rectF.right,(int)rectF.bottom));
boolean contains = region.contains((int) x, (int) y);
if (contains){
p1.setStyle(Paint.Style.FILL);
invalidate();
}
}}
擦除View 效果
代码
public class EraserView extends View {
private Context context;
//屏幕的宽高
private int weight;
private int height;
private Paint paint;
//前景bitmap
private Bitmap qbitmap;
//前景画布canvas
private Canvas mcanvas;
//背景bitmap
private Bitmap bbitmap;
//背景canvas
//手指移动的路径
private Path path;
//设置一个全局的起点
private float picX;
private float picY;
//移动的最小值
private float MIN_MOVE_DIS = 10;
public EraserView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
initPaint();
}
//初始化
private void initPaint() {
//获取屏幕宽高
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
weight = displayMetrics.widthPixels;
height = displayMetrics.heightPixels;
paint = new Paint();
paint.setAntiAlias(true); //抗锯齿
paint.setDither(true); //抗抖动
// paint.setColor(Color.GREEN); //设置画笔颜色
// paint.setStrokeWidth(5); //设置画笔宽度
paint.setARGB(128,255,0,0); //设置透明度
//设置混合模式
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
paint.setStyle(Paint.Style.STROKE); //设置描边
paint.setStrokeJoin(Paint.Join.ROUND); //设置路径结合处样式
paint.setStrokeCap(Paint.Cap.ROUND); // 设置笔触类型
paint.setStrokeWidth(40); //设置宽度
//生成前景的bitmap 设置图片的质量参数
qbitmap = Bitmap.createBitmap(weight,height,Bitmap.Config.ARGB_8888);
//将其注入画布
mcanvas = new Canvas(qbitmap);
//灰色
mcanvas.drawColor(0XFF808080);
//生成背景图片
bbitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.xuexxx);
bbitmap = Bitmap.createScaledBitmap(bbitmap,weight,height,true);
path = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置背景
canvas.drawBitmap(bbitmap,0,0,null);
//设置前景
canvas.drawBitmap(qbitmap,0,0,null);
//设置前景
mcanvas.drawPath(path,paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//获取当前手指所在位置的点
float x = event.getX();
float y = event.getY();
switch (event.getAction()){
//手指落下
case MotionEvent.ACTION_DOWN:
//清空上一次的path路径
path.reset();
path.moveTo(x,y);
//赋值全局的起点
picX = x;
picY = y;
break;
case MotionEvent.ACTION_MOVE:
float absX = Math.abs(x - picX);
float absY = Math.abs(y - picY);
if (absX > MIN_MOVE_DIS || absY > MIN_MOVE_DIS){
//存储路径的线路
path.quadTo(picX,picY,x,y);
}
//赋值到起点
picX = x;
picY = y;
break;
}
invalidate();
return true;
}}