好记性不如烂笔头

1、View里面不同的构造方法

View里面有四种不同的构造方法,分别是:

View(Context context)
 View(Context context, AttributeSet attrs)
 View(Context context, AttributeSet attrs, int defStyleAttr)
 View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

那我们都是什么时候用到这些构造方法呢?

  1. View(Context context)是当代码创建视图的时候使用的构造函数,通过参数中的context我们可以访问当前主题、资源等。
    例如在Activity的setContentView中可以使用new View(this)来将自己的控件显示出来。
  2. View(Context context, AttributeSet attrs)用于layout文件实例化,会把XML内的参数通过AttributeSet带入到View内。就是当写在xml中的控件被实例化的时候会调用该方法。可以通过attrs获取属性。

2、重写onMeasure()方法

那我们为啥要重写onMeasure()呢?

<com.example.lifecircletest.MyView
        android:id="@+id/my_view"
        android:layout_height="100dp"
        android:layout_width="wrap_content"
        android:background="#ff0000"/>

MyView是我们自己继承view的一个类,现在还没有重写onMeasure,此时注意 android:layout_width=“wrap_content” 为wrap_content,就是将大小设置为刚好包裹控件内的内容,运行一下,可以看到。

Android xml布局能用变量引用吗_控件


高度的100dp倒是没啥问题,但是wrap_content咋变成match_parent呢?去看看View里面的onMeasure()方法吧。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

可以看到在getDedaultSize中的AT_MOST情况下没有执行,会继续执行case MeasureSpec.EXACTLY:的内容,返回了specSize的值,那我现在有个疑问了,是谁调用了onMeasure()呢?一般子View的实际宽高是由其父视图和本身决定的,父视图是有义务测量其每一个子View,也就是说子View的measure方法其实是其上层的ViewGroup调用的,详细可以去看measureChild中的getChildMeasureSpec,总结一下就是子View的大小是由其父视图和本身决定的。

下面说一下上文中specMode的三个值是什么意思,我们可以通过MeasureSpec.getMode来获得widthMeasureSpec, heightMeasureSpec中低2位的模式,通过MeasureSpec.getSize获取后30位的值表示大小。模式有三种:

  • MeasureSpec.UNSPECIFIED 是当没有指定大小时使用一个默认的大小
  • MeasureSpec.AT_MOST 最大尺寸,控件的宽高为WRAP_CONTENT,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸
  • MeasureSpec.EXACTLY 精准的尺寸,尺寸的值是多少,那么这个组件的高和宽就是多少。

知道这些后我们就可以重写onMeasure了。如果要实现wrap_content的话需要在onMeasure中进行实现。onMeasure中用setMeasuredDimension来设置大小。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getSize(100,widthMeasureSpec),getSize(100,heightMeasureSpec));	//设置大小的长宽
    }

    private int getSize(int measureSpec, int defaultSize){		//这个方法可以得到一个size或者一个自己设定的默认size
        int mSize = defaultSize;

        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        switch (mode){
            case MeasureSpec.UNSPECIFIED:
                mSize = defaultSize;
                break;
            case MeasureSpec.AT_MOST:
                mSize = size;
                break;
            case MeasureSpec.EXACTLY:
                mSize = size;
                break;
        }
        return mSize;
    }

3、重写onDraw()

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    paint.setColor(Color.BLUE);
    paint.setAntiAlias(true);
    paint.setTextSize(50);
    canvas.drawText("hi man" , 100, 100 , paint);  //绘制一个文字
}

Android xml布局能用变量引用吗_android_02


利用onDraw中的canvas就可以画画了。还有其他很多drawXXX的方法的。

3、获取自定义的属性

3.1设置属性

我们可以在res/values/创建一个attrs.xml的文件来保存我们的属性。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <attr name="color" format="color"/>
    </declare-styleable>
</resources>

declare-styleable中的name就是我们自定义的view,这里的名字是可以随便取的,但是最好和我们的类名字保持一致,在attr中可以申明一个属性的名字和属性所取的格式

Android xml布局能用变量引用吗_xml_03


有如上几种格式,也可以用<attr name="color" format="color||integer"/>来让他有多种格式。设置好属性后就可以在我们的自定义view中来使用了。

首先在布局标签里加入xmlns:hb="http://schemas.android.com/apk/res-auto" hb这个命名空间是可以自己设置的。

接下来就可以在自定义的view中使用了,hb:color="@color/colorPrimary"

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:hb="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.lifecircletest.MyView
        android:id="@+id/my_view"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:background="@drawable/click"
        hb:color="@color/colorPrimary"/>

</LinearLayout>

3.2取出属性
现在我们有了自己的自定义的属性了,像上面的例子,我们就可以不用android:background来设置我们的背景颜色了,那我们接下来该怎么做呢?
还记得第二个构造方法中有一个AttributeSet attrs的参数吗,我们的属性可以从这里面取出来。

private int mColor;
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.MyView);
        mColor = array.getColor(R.styleable.MyView_color,Color.BLUE);   //第二个参数是默认的颜色。就是没设置就用这个颜色。
        array.recycle();    //最后记得recycle它
    }

现在我们的onDraw中的Paint的颜色就可以直接使用上面获取到的mColor了。效果还是和之前一样,但是字体颜色我们可以在xml文件中设置了,如果没设置,就默认是蓝色。

4、onLayout

onlayout的重写在自定义viewGroup可以用到,可以控制child view 的布局。