在前几篇文章中都是讲的基础,大家应该会觉得非常熟悉,但往往我们可能对于基础某些细节认识不够彻底或贯穿不全,我一直认为基础都是比较难的,那么本章节终于到进阶啦,主要讲的是View 的相关知识,在前面《Android 面试题总结之Android 基础 (六)》 中已经对View有了一定的了解,由于内容较多且也是面试必考题,所以将分两篇继续深入的理解View。

在阅读过程中有任何问题,请及时联系。如需转载请注明 fuchenxuan de Blog
本章系《Android 之美 从0到1 – 高手之路》Android进阶(一)自定义View的过程

[TOC]

#掌握 1. 什么是View? 2. View 坐标的基本概念 3. View的生命周期 4. 如何自定义View

#什么是View? android.app.View 就是手机的UI,View 负责绘制UI,处理事件(evnet),Android 利用 View 打造出所 Widgets,利用 Widget 可打造出互动式的使用者介面,每个View 负责一定区域的绘制。

一张图理解常用控件层级关系

这里写图片描述

#View 坐标的基本概念 View的宽高是有top、left、right、bottom参数决定的 而X,Y和translationX,和translationY则负责View位置的改变。 >从Android3.0开始,加入了translation的概念,即相对于父容器的偏移量以及X,Y坐标的概念,X,Y代表左上顶点的横纵坐标。当View在发生平移时,getX,getY,setX,setY get/setTranslationX/Y来获得当前左上点的坐标。

X=left+translationX Y同理。 注意:在View发生改变的过程中,top,left等值代表原始位置,是不会改变的。改变的只有X,Y,translationX/Y。

一张图理解View的坐标概念 这里写图片描述

#View的生命周期 | Category | Methods | Description | | ————- |:————-:| —–:| | Creation | Constructors | 几个View的构造函数| | | onFinishInflate() |当系统解析完View之后调用onFinishInflate方法 | | Layout | onMeasure(int, int) | 确定所有子View的大小 | | | onLayout(boolean, int, int, int, int) | 当ViewGroup分配所有的子View的大小和位置时触发 | | | onSizeChanged(int, int, int, int) |当view的大小发生变化时触发| | Drawing | onDraw(android.graphics.Canvas) |view渲染内容的细节 | | Event processing | onKeyDown(int, KeyEvent) | 有按键按下后触发 | | | onKeyUp(int, KeyEvent) |有按键按下后弹起时触发 | | |onTrackballEvent(MotionEvent)|轨迹球事件| | |onTouchEvent(MotionEvent)|触屏事件| |Focus|onFocusChanged(boolean, int, android.graphics.Rect)|当View获取或失去焦点时触发| ||onWindowFocusChanged(boolean)|当窗口包含的view获取或失去焦点时触发| |Attaching|onAttachedToWindow()|当view被附着到一个窗口时触发| ||onDetachedFromWindow()|当view离开附着的窗口时触发,该方法和 onAttachedToWindow() 是相反| ||onWindowVisibilityChanged(int)|当窗口中包含的可见的view发生变化时触发|

对实现自定义View,不需要重写所有这些方法。事实上,你可以只onDraw(android.graphics.Canvas)

##View 的几个构造函数 * public MyView(Context context)
java代码直接new一个Custom View实例的时候,会调用第一个构造函数

  • public MyView(Context context, AttributeSet attrs)
    在xml创建但是没有指定style的时候被调用.多了一个AttributeSet类型的参数,自定义属性,在通过布局文件xml创建一个view时,会把XML内的参数通过AttributeSet带入到View内。

  • public MyView(Context context, AttributeSet attrs, int defStyleAttr)
    构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或Activity所用的Theme中的默认Style,且只有在明确调用的时候才会调用

  • @TargetApi(Build.VERSION_CODES.LOLLIPOP) public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    该构造函数是在api21的时候才添加上的

http://blog.csdn.net/vfush

##View 的几个重要方法 * requestLayout
View重新调用一次layout过程

  • invalidate
    View重新调用一次draw过程

  • forceLayout
    标识View在下一次重绘,需要重新调用layout过程。

  • postInvalidate
    这个方法与invalidate方法的作用是一样的,都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。

#自定义View ##简单理解View的绘制 这里我们先简单理解View 的绘制,后续文章我们会深入理解。
1.测量——onMeasure():决定View的大小

2.布局——onLayout():决定View在ViewGroup中的位置

3.绘制——onDraw():如何绘制这个View。

这里写图片描述

##自定义View的分类 * 继承View * 继承ViewGroup * 继承系统控件(Button,LinearLayout…)

##自定义View的过程 1. 自定义 View 首先要实现一个继承自 View 的类

  1. 添加类的构造方法,通常是三个构造方法,不过从 Android5.0 开始构造方法已经添加到 4 个了

  2. override 父类的方法,如 onDraw,(onMeasure)

  3. 自定义属性,需要在 values 下建立 attrs.xml 文件,在其中定义属性 >通过context.obtainStyledAttributes将构造函数中的attrs进行解析出来,就可以拿到相对应的属性.
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView); mColor = typedArray.getColor(R.styleable.MyView_myColor, 0XFF00FF00);

【注意】三个函数获取尺寸的区别: getDimension()是基于当前DisplayMetrics进行转换,获取指定资源id对应的尺寸
getDimensionPixelSize()getDimension()功能类似,不同的是将结果转换为int,并且小数部分四舍五入
getDimensionPixelOffset()getDimension()功能类似,不同的是将结果转换为int,取整去除小数。举个例子 列如getDimension()返回结果是20.5f,那么getDimensionPixelSize()返回结果就是 21,getDimensionPixelOffset()返回结果就是20。

  1. 打开布局文件我们可以看到有很多的以xmlns开头的字段。其实这个就是XML name space 的缩写。我们可以使用res-atuo命名空间,就不用在添加自定义View全类名。 xmlns:app="http://schemas.android.com/apk/res-auto"
/**
 * Created by fuchenxuan on 16/6/4.
 */

public class MyView extends View {
	private int mRadius=200;
	private int mColor;

    public MyView(Context context) {
        this(context,null);
    }

    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //read custom attrs
        TypedArray t = context.obtainStyledAttributes(attrs,
                R.styleable.rainbowbar, 0, 0);
       mRadius = t.getDimensionPixelSize(R.styleable.coutom_radius, (int) hSpace);
        t.getDimensionPixelOffset(R.styleable.coutom_at1, (int) vSpace);
   		mColor=t.getColor(R.styleable.color, barColor);
        t.recycle();   // we should always recycle after used
     
     
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //set size
        setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? (int) mRadius * 3 : widthSize, heightMode == MeasureSpec.AT_MOST ? (int) mRadius * 3 : heightSize);
    }


    //draw be invoke clire.
    int index = 0;
    @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);
 	     mPaint = new Paint();
        mPaint.setColor(mColor);
        mPaint.setAntiAlias(true);
		 canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
     }
}

这里是一个普通的自定义View,里面画了圆,根据不同的模式设置了父View的大小。

关于View重写onMeasure()时机: 如果用了wrap_content。那么在onMeasure()中就要调用setMeasuredDimension(), 来指定view的宽高。如果使用的是match_parent或者一个具体的dp值。那么直接使用super.onMeasure()即可。

##自定义ViewGroup ##自定义ViewGroup的过程 1. 自定义 ViewGroup 和自定义View 一样,只是继承自 ViewGroup 的类,和必须实现onLayout()函数

 /**
 * Created by fuchenxuan on 16-6-6.
 */
public class CostumViewGroup extends ViewGroup {


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

    public CostumViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
            }
        }
    }

}

这里是一个简单的自定义ViewGroup,实现类似LinearLayout 横向排放子View位置。这就是一个简单的ViewGroup过程。

###彻底理解MeasureSpec三种模式 View的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般会自己进行测量。他们是由 mode+size两部分组成的。widthMeasureSpec和heightMeasureSpec转化成二进制数字表示,他们都是30位的。前两位代表mode(测量模 式),后面28位才是他们的实际数值(size);MeasureSpec.getMode()获取模式,MeasureSpec.getSize()获取尺寸 测量View大小使用的是onMeasure函数,所以我们需要了解三种测量模式:

  • EXACTLY:一般是设置了明确的值(100dp)或者是MATCH_PARENT
  • AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
  • UNSPECIFIED:表示子布局想要多大就多大,很少使用

关于ViewGroup重写onMeasure()时机

  • 首先要先测量子View的宽高: getChildAt(int index)可以拿到index上的子view。
    getChildCount()得到子view的个数,再循环遍历出子view。

  • 使用子view自身的测量方法 childView.measure(int wSpec, int hSpec);

使用viewGroup的测量子view的方法

  • measureChild(subView, int wSpec, int hSpec); 测量某一个子view,多宽,多高, 内部加上了viewGroup的padding值

  • measureChildren(int wSpec, int hSpec); 测量所有子view 都是 多宽,多高, 内部调用了measureChild方法
  • measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed); 测量某一个子view,多宽,多高, 内部加上了viewGroup的padding值、margin值和传入的宽高wUsed、hUsed

##问题总结 1. getWidth()和getMeasuredWidth()的区别? getMeasuredWidth():只要一执行完 setMeasuredDimension() 方法,就有值了,并且不再改变。 getWidth():必须执行完 onMeasure() 才有值,可能发生改变。
如果 onLayout 没有对子 View 实际显示的宽高进行修改,那么 getWidth() 的值 == getMeasuredWidth() 的值。

  1. onLayout() 和Layout()的区别?
    onLayout() ViewGroup中子View的布局方法,layout()是子View布局的方法

  2. View 里面的 onSavedInstanceState和onRestoreInstanceState的作用?
    View和Activity一样的,每个View都有onSavedInstanceState和onRestoreInstanceState这两个方法,可用于保存和恢复view的状态。

    在本章节中我们知道什么是View?,View 坐标的基本概念,理解了View的生命周期,学习了如何自定义View?虽然全是理论知识总结,在后续我们会一起来自定义View的实战学习。不管有没有任何疑问,欢迎在下方留言吧。

更多Android 面试题总结,请点击下方图片哦。 > 水平有限,若有错漏,欢迎指正,批评,如需转载,请注明出处–http://blog.csdn.net/vfush,谢谢! 这里写图片描述