很多时候,android系统提供的控件不能满足我们的业务需求,此时,需要我们自定义一个View,来展示我们的内容。
    我们先来实现一个最简单的自定义View,画一个圆

package com.test.customview;

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;

public class CircleView extends View {

    private int mColor = Color.RED;
    private Paint mPaint;

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

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setColor(mColor);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(width / 2, height / 2, radius, mPaint);
    }
}

在布局中使用它

<com.test.customview.CircleView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

看下效果

android paddingtop 无效_android

修改下布局,再看下效果,为了更明显看到控件大小,我们设置一个背景色

<com.test.customview.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:background="#afafaf"/>

android paddingtop 无效_ide_02

    发现wrap_content并没有起作用,控件的高度依然是和父容器一样,这相当于match_parent
    我们看下View中onMeasure的源码

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

看下getDefaultSize的实现:

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;
    }

MeasureSpec代表一个32位的int值,高两位代表SpecMode ,是测量模式,低30位代表SpecSize ,是在某种测量模式下的规格大小。

SpecMode 有三种:

  • UNSPECIFIED 父容器不对View做任何限制,一般用于系统内部
  • AT_MOST 父容器指定一个可用大小SpecSize ,View不能大于这个值,对应LayoutParams中的wrap_content
  • EXACTLY 父容器已经测出View所需的精确大小,View的大小就是SpecSize ,对应LayoutParams的match_parent和具体数值这两中模式
    可以看出,wrap_content和match_parent采用了同样的处理方式,会直接占满父容器的剩余空间。解决办法是重写onMeasure方法,并指定一个AT_MOST时的默认值
private int defaultWidth = 100;
private int defaultHeight = 100;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
    if (widthSpaceMode == MeasureSpec.AT_MOST && heightSpaceMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(defaultWidth, defaultHeight);
    } else if (widthSpaceMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(defaultWidth, heightSpaceSize);
    } else if (heightSpaceMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSpaceSize, defaultHeight);
    }
}

看下效果

android paddingtop 无效_自定义view_03

wrap_content已经生效了

现在我们设置一个padding

<com.test.customview.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#afafaf"
        android:padding="10dp"/>

android paddingtop 无效_自定义view_03

没有生效,处理也简单,在onDraw方法中,考虑到padding并进行相应处理即可

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    int paddingLeft = getPaddingLeft();
    int paddingRight = getPaddingRight();
    int paddingTop = getPaddingTop();
    int paddingBottom = getPaddingBottom();

    int width = getWidth() - paddingLeft - paddingRight;
    int height = getHeight() - paddingTop - paddingBottom;
    int radius = Math.min(width, height) / 2;

    canvas.drawCircle(paddingLeft + width / 2, 
            paddingTop + height / 2, radius, mPaint);
}

android paddingtop 无效_android_05

    我们现在只有红色,如果想要画其他颜色的圆需要怎么办呢,Android中允许自定义属性来达到我们的目的

首先,在res/valus目录下新建attrs.xml值文件

android paddingtop 无效_控件_06


新建一条自定义属性,比如说圆的颜色

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

在CircleView中获取属性,在构造方法中调用

private void initAttrs(AttributeSet attrs) {
    TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.CircleView);
    mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
    a.recycle();
}

在布局文件中使用

<com.test.customview.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#afafaf"
        android:padding="10dp"
        app:circle_color="@color/colorPrimary"/>

android paddingtop 无效_自定义view_07

成功
关于自定义属性中的format可以

package com.test.customview;

import android.content.Context;
import android.content.res.TypedArray;
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;

public class CircleView extends View {

    private Context mContext;

    private int mColor;
    private Paint mPaint;

    private int defaultWidth = 100;
    private int defaultHeight = 100;

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

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        initAttrs(attrs);
        init();
    }

    private void initAttrs(AttributeSet attrs) {
        TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
        a.recycle();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setColor(mColor);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;
        int radius = Math.min(width, height) / 2;

        canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpaceMode == MeasureSpec.AT_MOST && heightSpaceMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(defaultWidth, defaultHeight);
        } else if (widthSpaceMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(defaultWidth, heightSpaceSize);
        } else if (heightSpaceMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpaceSize, defaultHeight);
        }
    }
}