没想到现在在大学开发APP这么挣钱,作为一名大学生的我为了有一天能够存到足够的钱去留学重操旧业,不过我快一年多没学Android了,没想到忘记得这么快,果然还是太菜了我哈哈哈哈,废话不多说,开始今天得内容。现在开发APP如果没有一个美观得UI已经不能够算得上优秀得APP了。所以重操就业得我打算先从基础开始复习。首先就是自定义View。

(1)继承View类创建自定义View

public class MyView extends View {

   

    public MyView(Contextcontext) {

        super(context);

    }

    public MyView(Contextcontext, @Nullable AttributeSet attrs) {

        super(context,attrs);

    }

    public MyView(Contextcontext, @Nullable AttributeSet attrs, int defStyleAttr) {

        super(context, attrs, defStyleAttr);

    }

}

这里我们创建了类MyView并实现了其三个构造函数,我们自定义View时最主要的就是实现onMeasure和onDraw,首先onMeasure函数主要用于测量view的宽和高的尺寸,为什么要测量宽高尺寸呢?毕竟我们可以在XML总设置width和height。这是一个非常复杂的问题,这里我会按照自己的理解来归纳一遍。

@Override

    protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width =MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.UNSPECIFIED?100:MeasureSpec.getSize(widthMeasureSpec );

        int height = MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED?100:MeasureSpec.getSize(heightMeasureSpec);

       setMeasuredDimension(width, height);

    }

这是onMeasure的一个一般的实现,我们来看看这个onMeasure,这个函数在什么时候会被调用呢?答案是ViewGroup给 子View分配空间的时候。相当于ViewGroup询问子View需要多大的空间,这时ViewGroup会调用子view的onMeasure方法并传入widthMeasureSpec和heightMeasureSpec两个参数。这两个参数中包含的mode是关键所在,我们看看下面代码

int width =MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.UNSPECIFIED?100:MeasureSpec.getSize(widthMeasureSpec);

这里的意思是调用MeasureSpec.getMode()方法获得widthMeasureSpec的模式(mode),如果等于UNSPECIFIED(即是未定义),就将width设置为100,否则就将其保留原widthMeasureSpec中的size。这里的否则是其他两种情况,就是MeasureSpec.AT_MOST(最大)和MeasureSpec.EXACTLY(固定值)。

 那怎么确定三种mode的选择呢?

当父控件对子控件不加任何束缚,子元素可以得到任意想要的大小时,mode为UNSPECIFIED,这里是指size未指定,这种情况很少,只有当ViewGroup在height或者width上具有无限延伸的空间并且子View的width或height为wrap_content或者match_parent时才会出现,例如我们使用ScrollView时,无论子view的height设置为多大,都能滚动显示,这种时候就会出现传入的widthMeasureSpec和heightMeasureSpec的mode为UNSPECIFIED的情况。

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.drw.myapplication.MainActivity">
<com.drw.myapplication.MyView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
 android:background="#000"/>
</ScrollView>

这里的MyView的onMeasure就是上面我写的一般实现,这里的ScrollView的height和width能无限延伸,我们使用MyView并设置width和height为wrap_content时就会触发

int width =MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.UNSPECIFIED?100:MeasureSpec.getSize(widthMeasureSpec);


int height =MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED?100:MeasureSpec.getSize(heightMeasureSpec);

 使height和width变为100,最终运行结果会是下面左图,如果将ScrollView改为其他不能延伸的ViewGroup如linearLayout时运行结果就会如下右图

Android自定义View三种测量 android自定义view书籍推荐_Android自定义View三种测量

    

Android自定义View三种测量 android自定义view书籍推荐_自定义_02


 当ViewGroup已经知道为子View有个确定值时,例如子View的width和height为固定值,或子View的height(width)为match_parent并且ViewGroup的height(width)不具有无限延伸属性时,mode为EXACTLY,如下代码

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.drw.myapplication.MainActivity">

   <com.drw.myapplication.MyView
       android:layout_width="match_parent"
       android:layout_height="100dp"
       android:background="#000" />

</LinearLayout>

 当ViewGroup为子View指定最大参考尺寸,希望子View的尺寸不要超过这个尺寸时mode为AT_MOST,一般是子View的布局参数采用wrap_content的时候。而ViewGroup对应的布局参数上不具有延伸属性。如下代码

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.drw.myapplication.MainActivity">
   <com.drw.myapplication.MyView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:background="#000" />
</LinearLayout>

 这时LinearLayout会把剩余最大空间作为传入参数的size,在本例中时全屏,运行结果如下

Android自定义View三种测量 android自定义view书籍推荐_android_03

 好了,到这里就把onMeasure的基本知识点讲完了,现在我们来看看onDraw函数,在onMeasure中我们已经知道了自定义View的尺寸了,那么onDraw自然而然就是我们要实现的视觉效果了,例如是什么形状,什么颜色等等。onDraw函数传入参数是一个canvas即画布,显然就是在这个画布上画出我们要实现的效果。这里我们画园

@Override
    protected voidonDraw(Canvas canvas) {
  super.onDraw(canvas);
  int radius = getMeasuredWidth() / 2;
  int centerX = getLeft() + radius;//得到圆形的圆心x坐标
//每个view都可以通过调用getLeft(),getRight()得到View的左边的坐标和view的右边的坐标,同理getTop和getBottom也一样
  int centerY = getTop() + radius;//得到圆形的圆心x坐标
  Paint paint = new Paint();
  paint.setColor(Color.RED);
  canvas.drawCircle(centerX, centerY, radius, paint);
    }

 运行结果如下,width和height设置为100dp

Android自定义View三种测量 android自定义view书籍推荐_自定义_04


 下面po上全代码

<?xml version="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.drw.myapplication.MainActivity">
   <com.drw.myapplication.MyView
       android:layout_width="100dp"
       android:layout_height="100dp"
       android:background="#000"/>
</LinearLayout>


package *************;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**

 * Created by DRW on2018/2/8.

 */
 
public class MyView extends View { 
    public MyView(Contextcontext) {
        super(context);
    }

    public MyView(Contextcontext, @Nullable AttributeSet attrs) {
        super(context,attrs);
    }

    public MyView(Contextcontext, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context,attrs, defStyleAttr);
    }
 
    @Override
    protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
        int width =MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.UNSPECIFIED?100:MeasureSpec.getSize(widthMeasureSpec);
        int height =MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED?100:MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    protected voidonDraw(Canvas canvas) {
       super.onDraw(canvas);
       int radius =getMeasuredWidth() / 2;
       int centerX =getLeft() + radius;
       int centerY =getTop() + radius;
       Paint paint = newPaint();
       paint.setColor(Color.RED);
       canvas.drawCircle(centerX,centerY, radius, paint);
    }
}

我是菜鸟,多多指教(DRW)