当你准备自定义view的时候

前言

一直以来 写一些小demo没什么总结性,今天先放个坑,谈谈对自定义的理解 也作为自己的网络笔记。
讲讲自己对常用方法的理解,后续会补上相关实际的实例
先分享一些我已经发过的比较简单的自定义view

简易实现Listview滑动删除 (通用任意view)
Android DIY之路 (一) 指定区域多图片合成 放大 缩小 镜像 旋转 等
Android DIY之路 (三) 手绘 仅在限定区域留下痕迹 并再现这一过程
自定义Toast效果,windows层添加view,多个Toast效果
自定义edittext 手机格式 银行卡格式
手势密码实用demo
Android DIY之路 (四)拖拽替换,一个view发送其他所有view绑定即可监听到。拖拽排序的核心
都是日常的积累,大家可以讨论,比如再现手绘过程,都还没写完善。

进入正题

自定义view核心无非3点

测量==== 布局==== 绘制

*自定义事件

那么根据这个思路,我们先直接继承一个View啥也不写,打印出可能会用到的方法,扔在Activity里面进行研究。

public class ExView extends View {
    private String exViewMethodInturn = "exViewMethodInturn----->";

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

    public ExView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ExView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public String getExViewMethodInturn() {
        return exViewMethodInturn;
    }

    private void init() {
        exViewMethodInturn += "init-->";
        Log.i("rex", "init");
    }
    //重写常用的方法,进行打印
}
<com.justforview.view.ExView
    android:id="@+id/exView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp" android:background="#DEDEDE"
/>

调用顺序:

init(构造方法)
—–>onFinishInflate
—–>onAttachedToWindow
—–>onMeasure
—–>onSizeChanged
—–>onLayout
—–>onMeasure( -2147482978,-2147482656)
—–>onLayout
—–>onDraw
—–>dispatchDraw

(经过打印,和手机上画面显示,我们发现有些方法被调用了多次,onMeasure 数值什么鬼?,wrap无效的现象。下面我们来一一探讨)

*废话*:官方的方法名,一般起的 都能理解个一二,以后回忆起来也好记,measure测量 哪里绘制,draw诸如此类。(学好英语很重要)

构造方法

一般用作(AttributeSet attrs),自定义属性的获取,和一些初始化, new Paint() 啊 TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.XXXiew); a.getColor啥的; 暂不讨论。

1.onFinishInflate

Inflate 很面熟吧 我就记“打气”比如给娃娃充气,使她感到充实,成为一个完整的个体。用一个XML源填充view,加上finish,那就是完事。
常用:LayoutInflater.from(getContext()).inflate() / View.inflate()
所以finish了把xml打气成一起view,但要注意的是,每个部位都充气充好,才是真正的完事。
即当View中所有的子控件均被映射成xml后触发

2.onAttachedToWindow

Attach(附在)Window(窗体)上
View和Window缠绵在一起(绑定时)就会调用这个函数(那么这个函数的作用 你懂得)

按再打印出生命周期onCreate->onStart->onResume->onAttachedToWindow

Activity的onResume生命周期后就能获取DecorView的LayoutParam,进而可以设置高度和宽度了。根据上面贴出的生命周期图,onResume()后面是onAttachedToWindow(),并且onAttachedToWindow只会调用一次,不会受用户操作行为影响。所以
在onAttachedToWindow中进行窗口尺寸的修改再合适不过了
DecorView的LayoutParams是在ActivityThread的handleResumeActivity中设置的,并且该函数会调用Activity的onResume生命周期,所以在onResume之后可以设置窗体尺寸

3.onMeasure

测量相信多多少会提到,比如获取宽高的时候为0啊。测量也非常重要,那它是干嘛的,怎么测量,自定义的时候有什么作用。包括上述例子中经典现象wrap_content是无效的(填具体数值有效)。widthMeasureSpec, heightMeasureSpec -2147482978又是什么。咋还被多次调用呢?这方法被重写的几率高吗?其实不高。大多需求影响没有那么大(那说个卵…)

好的,那就开始说个卵,你不重写它,wrap_content是无效的,简言之,你不告诉它包裹的内容是啥,它就默认干别的去了比如漫不经心的充满布局啥的。TextView 告诉了它包裹的是字,在哪里告诉的 就是onMeasure。可能到这里好像卵用性还不够。

好了,正经的说View在屏幕上显示出来要先经过measure(计算)宽高 和 layout(布局)位置
3.1 onMeasure/setMeasuredDimension/specSize

为方便研究,我们直接在将宽高设置成,200,100px(不设置dp是为了更明显看他的作用,因为数据实际传递是生成的px,as里面可以设置px)
我们先看widthMeasureSpec(heightMeasureSpec类似)是什么

大小+类型
(多余的解释)
一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。
MeasureSpec是通过将一个int(32)的数组成而成的,本质是一个int数,MeasureSpec由两部分组成:SepcMode 和 SpecSize 。其中SpecMode为MeasueSpec的高2位,SpecSize为MeasureSpec的低30位。

{
//经查阅方法转化
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
<----->MeasureSpec.makeMeasureSpec

//得出来的恰好是200 specMode 先不变。
//说明通过layout-width=200经过上面两个方法之后 到了view的onMeasure的经过MeasureSpec形成边界参数widthMeasureSpec传进来

//那么我们除以2 再给super.onmesure试试 
  int newwidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec) / 2, MeasureSpec.getMode(widthMeasureSpec));
        super.onMeasure(newwidthMeasureSpec, heightMeasureSpec);
        }
//而实际宽度是  super.onMeasure(newwidthMeasureSpec, heightMeasureSpec)中的newwidthMeasureSpec

到这里就解释了一个疑惑xml中layout-width为什么不是width,一如layout-gravity和gravity关系?(当然这只是我的推测)layout-width是告诉父布局,我想要多大的宽高,父布局surper再给你实际大小。貌似很有道理,我们还是看看源码莫意淫。

源码就一个setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
这个估计就是直接告诉爹地我要多大地盘。我们一试。根据他这个getDefaultSize推断这里是实际宽度,而不是一长串什么鬼(控件可获得的空间以及关于这个空间描述的元数据)。
直接 setMeasuredDimension(100,100);注释掉super依然是个100x100正方形.其实不注释也没关系。实际就是两次的setMeasuredDimension取最后一个效果。
那么ok 从layout-width到widthMeasureSpec最后到再到实际效果的setMeasuredDimension。形成最终的width
网上的一个解释是父布局给你一个限制,你可以遵守或者不遵守。
我觉得是当子view还在xml里面的时候告诉了父亲自己想要多大的空间,等到终于有一天,自己被inflate成为view的时候,父亲将这个值还给了子view,子view可以选择遵从父亲留存当时最初的梦想,也可以变心自己去任性setMeasuredDimension。
上段都是废话。核心就是能得到xml里面(或者inflate之前)的值,你可以选择改或者不改(setMeasuredDimension/super.measuer—->setMeasuredDimension)。

如果你不改就会取默认的。


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

(默认填充父布局?)现在大小我们知道了,再看看最后一个specMode 是什么。

3.2 specMode

specMode 根据数值(上面的方法也可以看到):
1. EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
3.3 wrap_content无效的原因

由上面两个方法得出默认是没有处理wrap_content,且默认效果是填充父容器

(详细原因)
在默认实现中,AT_MOST和EXACTLY两种模式都会被设置成specSize。我们也知道AT_MOST对应于wrap_content,而EXACTLY对应于match_parent和具体数值情况。也就说默认情况下wrap_content和match_parent是具有相同的效果的。那有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢? 下面讲一下View的绘制过程: View的绘制首先起于ViewRootImpl,并且View的三个流程也是通过ViewRootImpl来完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建viewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。之后,View的绘制过程从ViewRootImpl的performTraversals方法开始,它经过measure、layout、draw三个过程最终将一个View绘制出来,由于DecorView是Android的一个页面的顶级View,所以绘制过程首先会从DecorView开始,又因为DecorView是一个ViewGroup,它会遍历绘制所有的子View,我们注意到在protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法中会有两个参数,其实这两个参数是在ViewGroup中传入进去的,最初是在DecorView中赋值的,且其值就是屏幕的宽度和高度。.也就是下面的windowSize

private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
关于wrap_content无效的可参考博客
https://my.oschina.net/ccqy66/blog/616662
3.4很多次的onmeasure
一个界面的展示可能会涉及到很多次的measure,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小

4.onSizeChanged

它的方法名已经告诉我们了,这个方法会在这个view的大小发生改变是被系统调用,我们要记住的就是view大小变化,这个方法就被执行就可以了.我们啥也没变它也调用了?0开始变的嘛。参数名int w, int h, int oldw, int oldh。就不再赘述。

5.onLayout

该方法是View的放置方法,在View类实现。调用该方法需要传入放置View的矩形空间左上角left、top值和右下角right、bottom值。这四个值是相对于父控件而言的。例如传入的是(10, 10, 100, 100),则该View在距离父控件的左上角位置(10, 10)处显示,显示的大小是宽高是90(参数r,b是相对左上角的),这有点像绝对布局。
平常开发所用到RelativeLayout、LinearLayout、FrameLayout…这些都是继承ViewGroup的布局。这些布局的实现都是通过都实现ViewGroup的onLayout方法,只是实现方法不一样而已

  // 动态获取子View实例
  for (int i = 0, size = getChildCount(); i < size; i++) {
  View view = getChildAt(i);
  // 放置子View,宽高都是100
  view.layout(l, t, l + 100, t + 100);
  l += 100 + padding;
  }

onMeasure在整个界面上需要放置一样东西或拿掉一样东西时会调用。比如addView就是放置,removeview就是拿掉,另外比较特殊的是,child设置为gone会触发onMeasure,但是invisible不会触发onMeasure。一旦执行过onMeasure,往往就会执行onLayout来重新布局

5.onDraw/dispatchDraw ()

由于onMeasure大佬太占位置,写了半天才写到能体现自定义view的精髓的Draw.这也是自定义view最有成就的地方
调用流程 :
mView.draw()开始绘制,draw()方法实现的功能如下:
1 、绘制该View的背景
2 、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)
3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)
值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类
函数实现具体的功能。

dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个
地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能
实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

但我曾经写到高亮引导(或者二维码中间扣一块透明里面有用到dispatchDraw )
自定义高亮区域 作用户引导的思路

    @Override
    protected void dispatchDraw(Canvas canvas) {
        //核心先后顺序
        if (rects.size() != 0) {
            drawRect(canvas);//绘制高亮区域
            super.dispatchDraw(canvas);//绘制子view -->如提示文字 箭头
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制半透明背景

    }

*invalidate()方法

说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”
视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。

 一般引起invalidate()操作的函数如下:
        1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
        2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
        3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,
                 继而绘制该View。
        4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。

*requestLayout()方法

会导致调用measure()过程 和 layout()过程 。
说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制
任何视图包括该调用者本身。

一般引起invalidate()操作的函数如下:
     1、setVisibility()方法:
         当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。
同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。

requestFocus()函数说明:

      说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。

下面一篇讲述关于绘制过程比较深刻的微博 虽年代久远,但依然获益匪浅。

http://blog.csdn.net/qinjuning/article/details/7110211/

持续更新…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值