初识View

一张图认识View

从0开始学自定义View -1-LMLPHP
在这张图中坐标系和我们数学中的不一样,这里的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 个左右的公开方法,常用方法标出颜色。下图是网页提供的表格。
下面我们只画一个最简单的
paint = new Paint();
paint.setColor(Color.BLACK);//画笔颜色
paint.setStrokeWidth(5f);//边的宽度
paint.setStyle(Paint.Style.STROKE);//描边

从0开始学自定义View -1-LMLPHP

上面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);
    }

从0开始学自定义View -1-LMLPHP

好了,到这里对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的事件分发机制。

05-07 00:33