Android基础知识之view基础知识与自定义View

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/c_he_n/article/details/85285662

什么是View

View是Android所有控件的基类。常见的View,如button,textview,imageview等等,用于展示数据和信息的。除了View还有ViewGroup,它是容纳View的容器,既然它是视图。说到view不得不说自己他的坐标系,这样才能更好的理解view和开发view 相关的功能。从下图可以看出,Android视图的坐标系,与我们在数学中学到的二位坐标有点不一样,左上角就是坐标的原点,向右为X轴的正向,向下为Y轴的正向。

坐标及相关属性

在这里插入图片描述
如何确定一个view的位置呢,既然是有坐标系,那么一个view的位置就好确定了。其实这么多获取位置,很容混淆的。

  • 获取view的位置:

getTop():获取到的是view自身的顶边到其父布局顶边的距离
getLeft():获取到的是view自身的左边到其父布局左边的距离
getRight():获取到的是view自身的右边到其父布局左边的距离
getBottom():获取到的是view自身底边到其父布局顶边的距离
根据视图的关系,获取View的宽度和长度
height = getbottom()-getTop();
width = getRight()-getLeft();

  • 获取手指位置
    MotionEvent提供的方法:

getX(): 获取点击事件距离控件左边的距离,即视图坐标
getY(): 获取点击事件距离控件顶边的距离,即视图坐标
getRawX():获取到的是点击事件距离整个屏幕左边的距离,即绝对坐标
getRawY():获取到的是点击事件距离整个屏幕顶边的距离,即绝对坐标

自定义view

为了实现自定义view,必须继承View并重写方法,比如 onMeasure(int, int),onLayout(boolean, int, int, int, int), onDraw(android.graphics.Canvas) 等等,但是构建函数必须要重写的。详见View的api
下面是自定义view常用的重写方法

方法 描述
构建函数 当从代码创建或者从布局文件中创建时调用
onMeasure(int, int) 测量这个view以及它的所有子view(必须要实现的方法)
onLayout(boolean, int, int, int, int) 当view给子view分配大小和位置时会调用
onDraw(android.graphics.Canvas) 当view渲染内容时会调用
onSizeChanged(int, int, int, int) view大小发生变化时调用
onTouchEvent(MotionEvent) 屏幕触摸事件发生时触发
  • 重写View时,有四个构造函数可以重写,该如何选择,有什么区别?

    • View(Context context) 在代码创建时,执行这个构造函数;
    • View(Context context, @Nullable AttributeSet attrs) ,View(Context context, @Nullable AttributeSet attrs), View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) 从布局文件加载时会执行构造函数。
  • size,padding,margins

    • View的大小时通过width,height来定义,但是有两套的width,height的值,第一套:getMeasuredWidth(),getMeasuredHeight(),获取的原始组件的宽高,在配置文件或者代码中设置的大小;第二套,width和height是在屏幕的实际的尺寸。这两个值大部分是相等的。比如说,在父布局的onLayout()方法或者该View的onDraw()方法里调用measure(0, 0),二者的结果可能会不同(measure中的参数可以自己定义)。
    • 在测量他的大小时,还要考虑padding,通过getPadding(int ,int ,int ,int)或者getPaddingLeft()…等等,但是view是没有Margins,ViewGroup提供。
  • 布局
    布局有两个过程,Measure和layout两个过程

    • measure过程实现了 measure(int, int),自顶向下的遍历view树进行测量。
    • onLayout(int ,int ,int,int) 自顶向下遍历的,父view负责放置每个子view位置,通过measure得到的测量数据。
  • 在设置view大小时,一般有三种方式:具体大小、match_parent、wrap_content。View.MeasureSpec类告诉父view怎么测量和放置view。

    • EXACTLY:具体大小,保证所有继承的view都符合这个尺寸,对应的是具体大小、match_parent。
    • UNSPECIFIED:父view不对子view做限制,它想多大就多大,有特殊的测量需求。
    • AT_MOST:对应于wrap_content。
  • 绘制
    To force a view to draw, call invalidate().

下面自定义view,实现可拖动功能,分别设置具体大小,match_parent,wrap_content三种模式,从日志中就能看出他们的对应关系。

public class CustomView extends View {
    private static final String TAG = CustomView.class.getSimpleName();
    private Paint mPaint = null;
    private int dx;
    private int dy;
    private int startX;
    private int startY;
    private Canvas mCanvas;

    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.BLUE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.i(TAG, "onMeasure");
        setMeasuredDimension(getDefaultSize(widthMeasureSpec), getDefaultSize(heightMeasureSpec));
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                startX = (int) event.getX();
                startY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                dx = (int) event.getX() - startX;
                dy = (int) event.getY() - startY;
                layout(getLeft() + dx, getTop() + dy, getRight() + dx, getBottom() + dy);
                invalidate();//onDraw
//                requestLayout();//onMeasure->onLayout
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.i(TAG, "onLayout");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mCanvas = canvas;
        canvas.drawCircle(300, 300, 300, mPaint);
        Log.i(TAG, "onDraw");
    }

    private int getDefaultSize(int measureSpec) {
        int defaultSize = 100;
        int measureMode = MeasureSpec.getMode(measureSpec);
        int measureSize = MeasureSpec.getSize(measureSpec);

        switch (measureMode) {
            case MeasureSpec.UNSPECIFIED:
                Log.i(TAG, "MeasureSpec.UNSPECIFIED");
                return defaultSize;
            case MeasureSpec.AT_MOST:
                Log.i(TAG, "MeasureSpec.AT_MOST");
                return measureSize;
            case MeasureSpec.EXACTLY:
                Log.i(TAG, "MeasureSpec.EXACTLY");
                return measureSize;
        }
        return -1;
    }
}

  • invalidate,postInvalidate,requestLayout区别
    • invalidate,postInvalidate这个两个其实是一样的,只是使用场景不一样而已,invalidate必须在UI线程中使用,postInvalidate在非UI线程中使用。
      用invalidate刷新view时,view只进行:onDraw
    • requestLayout :
      刷新时进行散步操作:onMeasure->onLayout,在代码我注释掉了,解注释后,根据日志就能看出刷新的机制。

参考

view

猜你喜欢

转载自blog.csdn.net/c_he_n/article/details/85285662