Android自定义View中padding与wrap_content的问题

来源:互联网 发布:webshell教程 编辑:程序博客网 时间:2024/05/29 18:03

    很多时候,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" />

看下效果
这里写图片描述

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

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

这里写图片描述

    发现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;@Overrideprotected 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);    }}

看下效果
这里写图片描述

wrap_content已经生效了

现在我们设置一个padding

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

这里写图片描述

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

@Overrideprotected 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中允许自定义属性来达到我们的目的
首先,在res/valus目录下新建attrs.xml值文件
这里写图片描述
新建一条自定义属性,比如说圆的颜色

<?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"/>

这里写图片描述

成功
关于自定义属性中的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);        }    }}
阅读全文
0 0
原创粉丝点击