PS:好久没有写博客了,之前的东西有所忘记,百度一下竟然查到了自己的写过的博客,访问量还可以,一开始的写博客的初衷是把自己不会的记录下来,现在没想到也有博友会关注我,这就给了我动力,工作之余把零零碎碎的东西总结一下,供大家参考。下面的博文是我自己的总结再加上博友的一些见解整合而成,共同进步。
初识View
在安卓中所有的样式都可以说是一个视图,TextView,Button,ImageView…这些官方已经给出的view已经无法满足我们的日常生活所需了,这个时候,我们就可以自定义View,随心所欲创建属于我们自己的View。那么我们应该怎么去做呢,首先要干嘛,其次又干嘛,最后干嘛呢,这都是过程中的一个节点。下面呢我们就从第一步开始。
一张图认识View
在这张图中坐标系和我们数学中的不一样,这里的Y轴下方是正数,X轴右方是正数,其中的View(浅蓝色背景)为我们自定义的View,MotionEvent是手指点击的位置,我们对View进行移动,也是根据MotionEvent返回的xy坐标点进行绘制的。
View的坐标系
注意:View的坐标系统是相对于父控件而言的.
getTop(); //获取子View左上角距父View顶部的距离 getLeft(); //获取子View左上角距父View左侧的距离 getBottom(); //获取子View右下角距父View顶部的距离 getRight(); //获取子View右下角距父View左侧的距离
MotionEvent中 get 和 getRaw 的区别
event.getX(); //触摸点相对于其所在组件坐标系的坐标 event.getY(); event.getRawX(); //触摸点相对于屏幕默认坐标系的坐标 event.getRawY();
这里只是讲解了一些View的独有关于坐标的方法,因为在平常自定义的时候了解这些方法就已经可以对View进行操作了,比如说拖拽,拉伸,收缩等。
自定义View实战
自定义View如何做,怎么做,往往都是第一步比较难,之后对View美化就相对来说比较简单了。下面我写了几个步骤
- 继承View
- MyView extends View重写里面重要的3-4个构造方法
- onMeasure测量控件
- onMeasure(int widthMeasureSpec, int heightMeasureSpec) 获取屏幕,自定义View父组件尺寸等。
- onDraw绘制View
- onDraw(Canvas canvas)使用canvas去绘制View,并展示出来
我们就根据上面步骤一一解答
继承View
我这里写了三个构造方法,也可以写四个,但如果只写一个会出现问题,比如说在XML文件中使用会报错
public MyView(Context context) { super(context); init(); } public MyView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); this.context = context; init(); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); }
onMeasure测量控件
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //以下大小均为获取父布局尺寸 measureWidth = MeasureSpec.getSize(widthMeasureSpec); measureHeight = MeasureSpec.getSize(heightMeasureSpec); maxWidth = getMaxWidth(context); maxHeight = getMaxHeight(context); Log.e("measure", maxWidth + " -- " + maxHeight); measureWidthMode = MeasureSpec.getMode(widthMeasureSpec); measureHeightMode = MeasureSpec.getMode(heightMeasureSpec); Log.e("measure", "measureWidth:" + measureWidth + "measureHeight:" + measureHeight + "measureWidthMode:" + measureWidthMode + "measureHeightMode:" + measureHeightMode); } // 获取最大宽度 public int getMaxWidth(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); return dm.widthPixels; } // 获取最大高度 public int getMaxHeight(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); return dm.heightPixels; }
widthMeasureSpec,heightMeasureSpec 这两个参数不是简单的整数类型,而是2位整数(模式类型)和30位整数(实际数值) 的组合,所以我们要通过MeasureSpec 获取模式int值 和 获取数值int值。这个时候才是我们想要的尺寸,比如说屏幕是1080 * 1920,获取的值也就是1080 * 1920。
onDraw绘制View
这个方法就厉害了,所有的绘制工作都是里面的canvas去完成,canvas翻译过来是帆布的意思,对我们来说就是画布,画布有了,还差画笔,有笔有布有多彩墨水才能画出大好河山嘛,这里先介绍画笔Paint
1:初始画笔 —– Paint的使用
canvas.drawCircle(100, 100, 50, paint)
这是一个要绘制圆形图片的代码,两个100分别是XY轴坐标,50是半径,paint是画笔,他的意思是说,我要画一个圆点是(100,100)半径是50的圆。样式是paint,这个时候我们就要对paint去绘制,官方文档上画笔有 100 个左右的公开方法,常用方法标出颜色。下图是网页提供的表格。
返回值 | 简介 |
int | getFlags() 获取画笔相关的一些设置(标志)。 |
int | getFlags() 获取画笔相关的一些设置(标志)。 |
void | setFlags(int flags) 设置画笔的标志位。 |
void | set(Paint src) 复制 src 的画笔设置。 |
void | reset() 将画笔恢复为默认设置。 |
int | getAlpha() 只返回颜色的alpha值。 |
void | setAlpha(int a) 设置透明度。 |
int | getColor() 返回画笔的颜色。 |
void | setColor(int color) 设置颜色。 |
void | setARGB(int a, int r, int g, int b) 设置带透明通道的颜色。 |
float | getStrokeWidth() 返回描边的宽度。 |
void | setStrokeWidth(float width) 设置线条宽度。 |
Paint.Style | getStyle() 返回paint的样式,用于控制如何解释几何元素(除了drawBitmap,它总是假定为FILL_STYLE)。 |
void | setStyle(Paint.Style style) 设置画笔绘制模式(填充,描边,或两者均有)。 |
Paint.Cap | getStrokeCap() 返回paint的Cap,控制如何处理描边线和路径的开始和结束。 |
void | setStrokeCap(Paint.Cap cap) 设置线帽。 |
Paint.Join | getStrokeJoin() 返回画笔的笔触连接类型。 |
void | setStrokeJoin(Paint.Join join) 设置连接方式。 |
float | getStrokeMiter() 返回画笔的笔触斜接值。用于在连接角度锐利时控制斜接连接的行为。 |
void | setStrokeMiter(float miter) 设置画笔的笔触斜接值。用于在连接角度锐利时控制斜接连接的行为。 |
PathEffect | getPathEffect() 获取画笔的 patheffect 对象。 |
PathEffect | setPathEffect(PathEffect effect) 设置 Path 效果。 |
boolean | getFillPath(Path src, Path dst) 将任何/所有效果(patheffect,stroking)应用于src,并将结果返回到dst。 结果是使用此画笔绘制绘制 src 将与使用默认画笔绘制绘制 dst 相同(至少从几何角度来说是这样的)。 |
Style | 简介 |
Paint.Style.FILL | 填充内容,也是画笔的默认模式。 |
Paint.Style.STROKE | 描边,只绘制图形轮廓。 |
Paint.Style.FILL_AND_STROKE | 描边+填充,同时绘制轮廓和填充内容。 |
下面我们只画一个最简单的
paint = new Paint(); paint.setColor(Color.BLACK);//画笔颜色 paint.setStrokeWidth(5f);//边的宽度 paint.setStyle(Paint.Style.STROKE);//描边
上面canvas.drawCircle(100, 100, 50, paint)和paint的创建 都是写在onDraw方法里的。上整体代码
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //样式一 canvas.drawCircle(200, 200, 100, paint); //样式二 Path arcPath = new Path(); arcPath.addArc(new RectF(100, 100, 500, 500), 120, 300); // Paint paint = new Paint(); // paint.setStyle(Paint.Style.STROKE); // canvas.drawPath(arcPath, paint); Path borderPath = new Path(); Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeWidth(100); paint.getFillPath(arcPath, borderPath); // getFillPath // 测试画笔,注意设置为 STROKE Paint testPaint = new Paint(); testPaint.setStyle(Paint.Style.STROKE); testPaint.setStrokeWidth(2); testPaint.setAntiAlias(true); // 绘制通过 getFillPath 获取到的 Path canvas.drawPath(borderPath, testPaint); }
好了,到这里对canvas画简单图案是告一段落了,那么我们之前获取到的尺寸是干嘛用的呢,下面我们对拖拽进行讲解,拖拽其实就是down,move,up对着三者的一个解析,当我们手指按下的时候将会出发down,手指一动时触发move,手指抬起时触发up。那么我们怎么去监听他的,有方法,那就是onTouchEvent,触摸方法的事件分发机制我们下节讲。这里我们直接上代码
拖拽图案
@Override public boolean onTouchEvent(MotionEvent event) { //手指按下 int action = event.getAction(); //获取手机触摸的坐标 int x = (int) event.getX(); int y = (int) event.getY(); switch (action){ case MotionEvent.ACTION_DOWN://按下,获取小球初始的位置 startLeft = getLeft(); startRight = getRight(); startTop = getTop(); startBottom = getBottom(); lastX = x; lastY = y; break; case MotionEvent.ACTION_MOVE://移动,小球跟随手指的移动 Log.e("event","getRawX():"+event.getRawX()+" getRawY():"+event.getRawY()); Log.e("event","getX():"+event.getX()+" getY():"+event.getY()); int offsetX = x - lastX; int offsetY = y - lastY; layout(getLeft()+offsetX,getTop()+offsetY, getRight()+offsetX,getBottom()+offsetY); break; case MotionEvent.ACTION_UP://当手指抬起时,回到小球初始的位置 // layout(startLeft, startTop, startRight, startBottom); break; } return true; }
好了,到这里,就可以拖拽你画的图案了。下一篇讲解View的事件分发机制。