自定义View(一)---View的基础概念、工作流程以及生命周期的理解

转载请注明出处:From李诗雨—-http://blog.csdn.net/cjm2484836553/article/details/54358072

不诗意的女程序猿不是好厨师~

:最近在工作中使用到了各种自定义控件,也更深刻的理解了自定义控件的重要性,所以就建了一个专栏来专门整理自定义控件的相关知识。我打算先从理论知识说起,然后再把项目中使用的自定义控件整理后写为博客发表,并且源码也会一并上传。理论知识部分,个人觉得整理的还是很详细的而且重点分明,无论是对面试还是对代码的理解都能起到很好的辅助作用。
:本文于2017/4/30号进行了更新。增加了一些新内容,又绘制了一些新的图形,更有助于对知识点的理解。

View相关知识的理解
在说自定义控件之前让我先来了解一下ViewViewGroup

整个View树的结构图
这里写图片描述

关于View和ViewGroup我们需要注意以下几点:

1. 手机屏幕上的整个界面只有一个根View

如何得到它:activity.getWindow().getDecorview() —>PhoneWindow$decorView
本质类型: FrameLayout
注意: setContentView():执行添加的视图不是整个界面的根View

2.一个View只会有一个父View(ViewGroup),一个ViewGroup可以有多个子View

a.得到父视图: view.getParent(),可以将返回的ViewParent强转为指定的ViewGroup
b.不是所有的View都能添加子view,只有ViewGroup 及其子类才能添加


View是什么

  • View类是所有用来构建用户界面的组件的基类
  • 一个View对象占用屏幕上的一个矩形区域, 它负责界面的绘制和事件处理
  • 手机屏幕上所有看得见摸得着的都是View
  • 常见的View:TextView,EditText,Button,ImageView,ProgressBar…

ViewGroup是什么

  • ViewGroup类是View的一个子类, 是各种布局(五大布局)的基类
  • 一个ViewGroup可以包含多个子View(ViewGroup)
  • 作用: 控制子View的布局,view.layout(left, top, right, bottom)
  • ViewManager及相关方法:
    ① addView():添加子View
    ② removeView():删除子View
    ③ updateViewLayout():更新子View
  • 常见的ViewGroup:LinearLayout,RelativeLayout,FrameLayout,ListView…

View的位置怎么确定

  • Veiw的位置是由它(左上右下)四个顶点确定的。
    top:左上角纵坐标
    bottom:右下角纵坐标
    left:左上角横坐标
    right:右下角横坐标
    它们都是相对坐标,都是相对于View的父容器来说的
    这里写图片描述
    如上图可得View的宽高与坐标的关系:
    width= right - left
    height= bottom - top

  • 那么,How to 得到View的这四个参数?
    View源码中它们对应mLeft,mTop,mRight,mBottom四个成员变量,获取方式:
    left = getLeft();
    top = getTop();
    right = getRight();
    bottom = getBottom();

  • 注:Android 3.0开始额外增加了几个参数: x ,y —是View左上角的坐标
    translationX,translationY—是View左上角相对于父容器的偏移量(默认值为0)
    这几个参数也是相对于父容器的相对坐标。
    它们的换算关系:
    x = left +translationX;
    y = top + translationY;
    需注意,View在平移过程中,top,left表示的是原始坐上角的位置信息,其值不会发生变化。
    此时发生改变的是x,y,translationX,translationY这四个参数。

View(及其子类)的生命周期及其他知识点回顾

1. 创建对象

  • 创建方式(2种):
    new MyView(context)
    加载布局文件,即自定义View必须使用全类名标签
  • 流程方法
    ①构造器
       Xxx(Context context)
      Xxx(Context context, AttributeSet set)
    ②onFinishInflate()
      只有布局的方式才会调用
      重写的目的: 得到子View –>getChildAt(int index):index按照加载顺序排列
      onAttachedToWindow()–>重写: 得到子View

  • 补充 Activity的onResume()执行之后才会进入后面的流程

2.View的工作流程

  • View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制。无图不欢,这里给大家画了一个图:
    这里写图片描述
    通过上面的图形,相信你就可以很明白的知道,各个环节是干嘛的,可以得到什么,以及它的意义所在了。下面再让我们进行具体的分析。

2.1 测量

  • 作用:计算并确定视图的大小(测量的宽/高)

  • 流程方法:
      ①.mesure() :系统在此方法中测量计算出当前视图的宽高,此方法不能重写
      ②.onMesure()
        当mearure()中 计算出的视图的宽高就会调用此方法, 在此方法默认保存的
        视图测量的宽高
        注意:视图测量的宽高不等同于视图的宽高。获取的时机不同
        重写的意义:得到当前视图/子视图测量的宽高;保存我们自己指定的宽高

2.2 布局

  • 作用:确定视图显示的坐标(left, top, right, bottom)以及View最终的宽/高

  • 流程方法:
      ①.layout() :layout(l, t, r, b),不会重写此方法, 只会调用视图对象的此方法, 指
      定其新的显示位置
      ②.onLayout()
        在layout()的过程中 如果①视图的位置change或②强制重新布局就会调
        此方法
        重写它: 可以对子View进行重新布局,调用childView.layout(left, top, right,
        bottom)

  • 强制重新布局: view.requestLayout()

2.3.绘制

  • 作用:画出视图的样子,决定View的显示

  • 流程方法:
    draw(),绘制视图通用的部分,确定绘制的流程,一般不会重写此方法
    onDraw(),重写此方法,绘制自己需要的样子,一些具体的View类(如:TextView,ImageView)都重写了此方法

  • 强制重绘:
    invalidate():只能在主线程执行
    postInvalidate():可以在主线程或分线程执行

注意!

  • 细心的朋友可能注意到了,我这里提到了measure过程中我们得到的是View的测量的宽/高,layout过程我们得到的是View的最终的宽/高。那这测量的宽/高和最终的宽/高它们有什么区别呢?
    我们可以这么理解,由于两者的赋值时机不同,即一个measure过程,一个layout过程,这就导致了一个先后的问题,即测量的宽/高先出生,最终的宽/高后出生。在这里我举一个具体点的例子,可能不是非常的合理,但确实有助于理解:
    这里写图片描述
    如图,在布局中你可能在外部设置了一个ScrollerView,并且设置为match_parent,然后它的内部的内容如虚线内所示,比屏幕的高度要长,那么这时候我们在measure过程中的到的测量高/宽最大也只能为屏幕的高,而在layout过程中得到的实际宽/高,确是比屏幕的高要长的。
    当然大多数情况下,测量的宽/高和最终实际的宽/高是相同的,但是当遇到一些特殊情境时我们还是要多多小心。

  • 这里我们还要注意一点:那就是View的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate,onStart, onResume时某个View就测量完毕了。如果View还没有测量完毕,那么获得的宽/高就是0.

    所以如果我们想在Activity已启动的时候就做一件任务,但是这一件任务需要获取某个View的宽/高。那么这个时候你就不要在天真地告诉我:在onCreate或者onResume里面去获取这个View的宽/高不就行了?真是too young too simple,不可以的哈!

    这里给大家几个解决办法以供参考:
    ①我首推的是ViewTreeObserver,没错视图树。大家可以看下我高仿各大商城引导页面的那篇文章,我在处理下部的小圆点,需要获得它的间距的时候,我就使用了视图树的OnGlobalLayoutListener这个接口,当View树的状态发生改变或者Veiw树内部的View的可见性发生改变是,onGlobalLayout方法就会被调用,因此我在这里面去获取小圆点间的间距就是一个很好的时机,对应部分的代码如下:

 //获取树形视图,每次页面布局完成时会调用,获取点间的距离
        ivWhitePoint.getViewTreeObserver().addOnGlobalLayoutListener(new MyOnGlobalLayoutListener());
 private class MyOnGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {

        @Override
        public void onGlobalLayout() {
            //默认会调用俩次,只需要一次,第一次进入就移除
            ivWhitePoint.getViewTreeObserver().removeGlobalOnLayoutListener(MyOnGlobalLayoutListener.this);
            //间距 = 第1个点距离左边距离 - 第0个点距离左边距离
            leftmax = llPointGroup.getChildAt(1).getLeft() - llPointGroup.getChildAt(0).getLeft();
        }
    }

    private class MyOnPageChangeListener implements ViewPager.OnPageChangeListener {
        /**
         * 当页面滑动回调会调用此方法
         *
         * @param position             当前页面位置
         * @param positionOffset       当前页面滑动百分比
         * @param positionOffsetPixels 滑动的像素数
         */
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            //红点移动的距离 = ViewPager页面的百分比* 间距
            //坐标 = 起始位置 + 红点移动的距离;
            int leftmagin = (int) (position * leftmax + (positionOffset * leftmax));
            ...

        }
...
    }

②当然你也可以使用onWindowFocusChanged这个方法,这个方法的含义是:View已经初始化完毕了,宽/高已经准备好了,这个时候去获取宽/高是没有问题的。
③view.post(runnable),通过psot可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了。


3.事件处理

  • 流程方法:
    dispatchTouchEvent():
        分发事件,从外向里一层一层分发, 分发到事件发生的最里面的视图对象
    boolean onInterceptTouchEvent():
        拦截请求, 只有return true才拦截成功,如果事件被拦截,事件不会再向内层分
        发, 交给当前的视图处理
    boolean onTouchEvent():
        处理事件:消费事件的条件: return true
    requestDisallowInterceptTouchEvent(true)
        反拦截–>view.getParent().requestDisallowInterceptTouchEvent(true)

  • 事件机制:
    ①分发: 将TouchEvent对象从Activity对象开始, 由外向内分发给对应的布局和子View对象(由外向内分发)。

    ②处理: 回调OnTouchListener的boolean onTouch()
             回调View的boolean onTouchEvent()

    ③消费: 回调方法返回true

    ④拦截: onInterceptTouchEvent()执行返回true
             如果返回true, TouchEvent就不会再传入子View对象

    ⑤反拦截: view.getParent().requestDisallowInterceptTouchEvent(true)
               使父View不能再拦截, 事件就会分发到当前View对象
               拦截与反拦截,都是在分发的时候就要决定的。

4.死亡

  • 什么时候死亡:
    Activity死亡之前
    视图对象被移除

  • 流程方法
    onDetachedFromWindow()

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值