Android中的自定义View

自定义View

自定义View三步走:

  • mesure: View的测量流程
  • layout: 布局流程
  • draw: 绘制流程

mesure:用来测量View的宽和高
layout:确定View在父容器中放置位置
draw:负责将View绘制在屏幕上

理解MeasureSpec

MeasureSpec字面意思:“测量规格”
测量过程中,系统会将View的LayoutParams**根据父容器所施加的规则**转化成对应的MeasureSpec,在根据这个MeasureSpec来测量View的宽和高。

  • 这里面说的根据父容器施加的规则到底是什么?

MeasureSpec代表32位int,高两位代表SpecMode,低30位代表SpecSize。

SpecMode有三种:

  • UNSPECIFIED
    不对View任何限制要多大有多大(一般不常用)

  • EXACTLY
    这种模式称为精确模式,父容器已经检测出View所需要的精确大小,它对应的就是LayouParams中的match_parent和具体数值这两种模式。(View最终大小就是SpecSize)

  • AT_MOST
    这种模式称为最大模式,父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体什么值看不同View的具体实现。它对应LayoutParams中的wrap_content。

普通View来说,MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams来共同决定,此外还和View的margin及padding有关

普通View的MeasureSpec的创建规则

子元素的MeasureSpec是由父容器的MeasureSpec和自身LayoutParams共同决定!

父容器不同,View的LayoutParams不同,View九游多种MeasureSpec。

表格说明:

  • 当View采用固定宽/高(dp/px),不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式,大小遵循LayoutParams中的大小。

  • 当View的宽/高是match_parent时:

    • 父容器是EXACTLY(精确模式),这时候View也是精确模式,大小是父容器剩余空间。
    • 父容器是AT_MOST(最大模式),这时候View也是最大模式,大小不会超过父容器剩余空间。
  • 当View的宽/高是wrap_content时:不管父容器是模式是精确还是最大化,View的模式总是最大化,大小不能超过父容器剩余空间。

View的工作流程

measure过程

  • measure确定View的测量过程
  • layout确定View的最终宽高,和四个顶点位置
  • draw将View绘制到屏幕上
View的measure过程

结论:直接继承View的自定义控件需要重写onMeasure,并且还要设置是wrap_content时的自身大小(就是布局中如果是wrap_content时,需要设置View的大小)否则在布局中使用wrap_content就相当于match_parent.

原因:在布局中使用wrap_content,那么它的specMode是AT_MOST模式,它的宽/高都是父布局中可使用的大小,这时候的specSize是parentSize(父布局当前剩余的空间大小)。效果和在布局中使用match_parent一样!

怎样解决:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widrhSpecSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(200, 200);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(200, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widrhSpecSize, 200);
    }
}

这里给View指定一个默认的内部宽/高(自己设置大小,没有固定依据,自己去灵活处理),并在wrap_content时设置设置此宽高即可。对于非wrap_content沿用系统测量的即可。

继承View重写onDraw()方法

实现过程必须考虑到wrap_content模式以及padding。还可以对外提供自定义属性。
margin属性有父容器控制,不用自定义View特殊处理。

例如:布局文件设置了padding属性,onDraw里面解决方式:

<com.example.pakar.viewdemo.CustomView
  android:layout_width="wrap_content"
  android:layout_height="100dp"
  android:layout_margin="20dp"
  android:background="#000000"
  android:padding="20dp"
  my:my_color="@color/colorAccent" />

布局中设置了padding。

CustomView里面onDraw需要注意:

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

    final int paddingLeft = getPaddingLeft();
    final int paddingRight = getPaddingRight();
    final int paddingTop = getPaddingTop();
    final 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);

}

这时候padding效果才会显示出来。

当然这里的布局文件也使用了wrap_content,onMeasure里面也需要处理。(onMeasure方法里面上面代码有),不然和match_parent一样了。

自定义属性提供给外界

这是套路都这样写:

在values目录下面创建自定义属性XML,比如attrs.xml,也可以类似于attrs_circle_view.xml等,这种attrs_开头的,可以随意取名。

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="CustomView">

        <attr name="my_color" format="color" />

    </declare-styleable>
</resources></pre>

View构造方法解析自定义属性

public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mColor = array.getColor(R.styleable.CustomView_my_color, Color.RED);
    array.recycle();
}

第三步:在布局文件中使用自定义属性

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

    <com.example.pakar.viewdemo.CustomView
  android:layout_width="wrap_content"
  android:layout_height="100dp"
  android:layout_margin="20dp"
  android:background="#000000"
  android:padding="20dp"
  my:my_color="@color/colorAccent" />

</LinearLayout>

* xmlns:my=”http://schemas.android.com/apk/res-auto”*

这里的my是自定义属性的前缀,随便。

apk/res后面可以附加应用的包名,不过/apk/res-auto更方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值