从0开始学自定义View -1

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
描边+填充,同时绘制轮廓和填充内容。
Cap简介
Paint.Cap.BUTT 无线帽,也是默认类型。
Paint.Cap.SQUARE 以线条宽度为大小,在开头和结尾分别添加半个正方形。
Paint.Cap.ROUND 以线条宽度为直径,在开头和结尾分别添加一个半圆。
 
下面我们只画一个最简单的
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的事件分发机制。

原文地址:https://www.cnblogs.com/cmusketeer/p/12837076.html