Android自定义View:View(二)

栏目: IOS · Android · 发布时间: 6年前

内容简介:什么?你说你掌握了自定义View?来来来,试着回答如下问题:什么?你说这些问题太抽象?来来来,继续回答如下问题:其实,说了这么多,到底怎样才能学好自定义View?其实只需掌握三个问题,就可以轻松搞定它:

什么?你说你掌握了自定义View?来来来,试着回答如下问题:

  • Google提出View这个概念的目的是什么?
  • View这个概念与Activtiy、Fragment以及Drawable之间是一种什么样的关系?
  • View能够感知Activity的生命周期事件吗?为什么?

什么?你说这些问题太抽象?来来来,继续回答如下问题:

  • View的生命周期是什么?
  • 当View所在的Activity进入stop状态后,View去哪了?如果我在一个后台线程中持有一个View的引用,我此时能够改变它的状态吗?为什么?
  • View能够与其他的View交叉重叠吗?重叠区域发生的点击事件交给谁去处理呢?可不可以重叠的两个View都处理?
  • View控制一个Drawable的方法途径有哪些?Drawable能不能与View通信?如果能如何通信?
  • 假如View所在的ViewGroup中的子View减少了,View因此获得了更大的空间,View如何及时有效地利用这些空间,改变自己的绘制?
  • 假如我要在View中动态地注册与解除广播接收器,应该在哪里完成呢?
  • 假如我的手机带键盘(自带或者外接),你的自定义View应该如何响应键盘事件。
  • AnimationDrawable作为View的背景,会自动进行动画,View在其中扮演了怎样的角色?

其实,说了这么多,到底怎样才能学好自定义View?其实只需掌握三个问题,就可以轻松搞定它:

  • 问题一:从Android系统设计者的角度,View这个概念究竟是做什么的?
  • 问题二:Android系统中那个View类,它有哪些默认功能和行为,能干什么,不能干什么?(知己知彼,才好自定义!)
  • 问题三:我要改变这个View的行为,外观,肯定是覆写View类中的方法,但是怎么覆写,覆写哪些方法能够改变哪些行为?

从Android系统设计者的角度,View这个概念究竟是做什么的?

关于这个问题,最权威的当然是官方文档,如下:

This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling.

可见这句话,包含三层含义:

  • View是用户接口组件的基本构建块 。通俗讲,在Android中,一个用户与一个应用的交互,其实就是与这个应用中的许许多多的View的交互,这些View既可以是简单的View,也可以是若干View组合而成的一个ViewGroup。由此我们可以明白,所谓View是基本构件块,原因就在于它是复合View(就是ViewGroup)的基本组成单元。这层含义,就是告诉你,View就是用来与用户交互的,那么很自然地,我们要问,我们用户在哪里与View交互,以及怎样与View交互呢?

  • View在屏幕上占据一个矩形区域 。这是说,既然View是用户与应用交互的基本构建块,而用户使用Android设备时,主要是通过一个触摸屏来交互的,相应的,Andorid的设计者们,就让一个View就在屏幕上占据一个矩形区域,用户在这个区域中发生的交互动作(点击、滑动、拖动等),就是与这个View的交互。什么?为什么不让View占据一个圆形区域或者五角星区域呢?当然是为了简单。这就解决了在哪里与View交互的问题。很自然地,我们又想问,View在屏幕上占据一个矩形区域,这个区域的大小、位置怎么确定,它们会不会变化,谁来决定这个变化呢?如果这个变化不是由View自己来决定的,而是其他外界因素决定的,View又要怎样响应这种变化呢?不要急,后面都会有答案。

  • View通过绘制自己与事件处理两种方式与用户交互 。这是解决了如何交互的问题。简单讲,View与用户交互就两个办法,一个是改变自己的模样,也就是通过绘制自己与用户交互,比如,当用户点击自己时,就改变自己的背景颜色,以此来告诉用户:“本View已经响应你的点击了!”第二个方式就是事件处理,比如,当用户点击View时,就完成一定的任务,然后弹出一个Toast,告诉用户该View完成了什么任务,这样,用户也就知道这次交互结果如何。

现在我们明白了,设计View,主要是为了让应用能够与用户交互,要想完成交互,这个View就要在屏幕上占据一个矩形区域,然后利用这块屏幕区域与用户交互,交互的方式就两种,绘制自己与事件处理。

Android系统中的View类,它有哪些默认功能和行为?能干什么,不能干什么?

解决了第一个问题,我们很可能有更多的疑问,我们想知道:

  • View是怎样被显示到屏幕上的?
  • View在屏幕上的位置是怎样决定的?
  • View所占据的矩形大小是怎样决定的?
  • 屏幕上肯定不止一个View,View之间互相知道对方吗?它们之间能协作吗?
  • View完成与用户的交互后,能够自动隐藏,在需要交互的时候重新显示在屏幕上吗?

首先,一个用户界面,上面有许多View,既有基本View,也有复合View,把它们组织起来还让它们很好地协作确实是一个难题,Google的解决方案是:首先,一套完整的用户界面用一个Window来表示,Window这个概念和我们在计算机上所说的Window很相似。Window负责管理所有的View们,怎么管理?很简单,借鉴复合View的思路,Window首先加载一个超级复合View,用它来包含所有的其他View,这个超级复合View就叫做DecorView。但是这个DecorView除了包含我们的用户界面上那些View,还包含了作为一个Window特有的View,叫做titlebar,这个我们就不细说了。

这样,在Window中的View们被组织起来了,形成一个巨大的ViewGroup,下面又有若干ViewGroup和若干View,每个ViewGroup下面又有若干ViewGroup和若干View,很像数据结构中的树,叶子节点就是基本View。

好了,这些View已经被组织起来了,DecorView已经能够完全控制它们了,同时,DecorView掌握着能够分配给这些View的屏幕区域,包括区域的大小和位置。

我们知道,屏幕的大小是有限的,一个Window的DecorView能够控制的屏幕区域更加有限,AndroidN中引入多Window机制后,DecorView能掌控的屏幕区域更加小了,因为屏幕上有多个Window将成为常态。这些有限的区域还要被Window特有的View(titlebar)占去一小部分,剩下的才是留给用户界面上的View们分的,如果你是DecorView,你肯定为难了,如何将这些有限的屏幕区域分给这些View们?分给他们后还得为每个View排好在屏幕上的位置,难上加难。

停一停,想一想,如果是你,你怎么解决这个问题?

首先,不同的View是为了完成特定的交互任务的,比如,Button就是用来点击的,TextView就是用来显示字符的,等等。

DecorView知道,不同的View为了完成自己的交互任务所需要的屏幕区域大小是不同的,所以DecorView在确定给每个View分配的屏幕区域大小时,是允许View参与进来,与它一起商量的。但是每个View在屏幕区域中的位置就不能让View自己来决定了,而是由DecorView一手操办,这个比较简单,我们就先来看看DecorView是怎样决定每个View的位置的吧。

确定每个View的位置

我们在Activity中,调用了setContentView(View),实际上就是将用户界面的所有的View交给了DecorView中的一个FrameLayout,这个FrameLayou代表着可以分配给用户界面使用的屏幕区域。而用户界面View既可以是一个简单的View,也可以是一个ViewGroup,如果是一个简单的View,比如就是一个TextView,那么这个TextView就会占据整个FrameLayout的屏幕区域,也就是说,此时用户在FrameLayout的屏幕区域内的所有交互都是与这个TextView交互。但是更常见的情况时,我们的用户界面是一个ViewGroup(想想常用的布局五大金刚),里面包含着其他的ViewGroup和View。这个时候,首先这个ViewGroup就会占据FrameLayout所代表的屏幕区域,剩下的任务,就是这个ViewGroup给它内部的小弟们(各种ViewGroup和各种View)分配区域了。至于怎么分,不同的ViewGroup有不同的分法,总体来看,可说是有总有分。所谓总,举例来讲,像vertical的LinearLayout,它按照 自己的小弟数量,把自己竖向裁成不同的区域,如下图所示:

Android自定义View:View(二)

虽然View无法决定自己在ViewGroup中的位置,但是开发者在使用View时,可以向ViewGroup表达自己所用的View要放在哪里,以vertical LinearLayout为例,开发者书写布局文件时,子View在LinearLayout中的出现顺序将决定它们在屏幕上的上下顺序,同时还可以借助layout_margin ,layout_gravity等配置进一步调整子View在分给自己的矩形区域中的位置。

我们可以理解,layout_*之类的配置虽然在书写上与View的属性在一起,但它们并不是View的属性,它们只是使用该View的使用者用来细化调整该View在ViewGroup中的位置的,同时,这些值在Inflate时,是由ViewGroup读取,然后生成一个ViewGroup特定的LayoutParams对象,再把这个对象存入子View中的,这样,ViewGroup在为该子View安排位置时,就可以参考这个LayoutParams中的信息了。进一步思考,我们发现,调用inflate时,除了输入布局文件的id外,一般要求传入parent ViewGroup,传入这个参数的目的,就是为了读取布局文件中的layout配置信息,如果没有传入,这些信息将会丢失。

不同的ViewGroup拥有不同的LayoutParams内部类,这是因为,它们允许子View调整自己的位置的方式是不一样的 ,具体讲就是配置子View时,允许使用的layout_*是不一样的,比如,RelativeLayout就允许layout_toRightOf等配置,其他的ViewGroup没有这些配置。

确定View位置的过程,是被包装在View 的layout方法中 ,这样也很容易理解,对于基本View而言,这个方法是没有用的,所以都是空的,你可以查看下ImageView、TextView等的源代码,验证下这一点。对于ViewGroup而言,它们会用该方法为自己的子View安排位置。

确定View大小

要确定View的大小,这是一个 开发者ViewViewGroup 三方相互商量的过程。

  • 第一步 ,开发者在书写布局文件时,会为一个View写上 android:layout_width="\*\*\*" android:layout_height="\*\*\*" 两个配置,这是开发者向ViewGroup表达的,我这个View需要的大小是多少。星号的取值有三种:

    具体值
    match_parent
    wrap_parent
    
  • 第二步 ,ViewGroup收到了开发者对View大小的说明,然后ViewGroup会综合考虑自己的空间大小以及开发者的请求,然后生成两个 MeasureSpec 对象(width与height)传给View,这两个对象是ViewGroup向子View提出的要求,就相当于告诉子View:“ 我已经与你的使用者(开发者)商量过了,现在把我们商量确定的结果告诉你,你的宽度不能违反width MeasureSpec对象的要求,你的高度不能违反height MeasureSpec对象的要求,现在,你赶紧根据这个要求确定下自己要多大空间,只许少,不许多哦。

然后,这两个 MeasureSpec 对象将会传到 子Viewprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法中。子View能怎么办呢?它肯定是要先看看ViewGroup的要求是什么吧,于是,它从传入的两个对象中解译出如下信息:

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize =  MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
复制代码

Mode与Size一起,准确表达出了ViewGroup的要求。下面我们举例说明,假设Size是100dp, Mode的取值有三种,它们代表了ViewGroup的总体态度:

  • EXACTLY 表示,ViewGroup对View说,你只能用100dp,原因是多样的,可能是你的使用者说要你完全占据我的空间,而我只有100dp。也可能这是你的使用者的要求,他需要你占这么大的空间,而我恰好也有这么多的空间,你的使用者让你占这么大的空间,肯定有他自己的考虑,你不能不理不顾,不然你达不到他的要求,他可能就不用你了。
  • AT_MOST 表示,你最多只能用100dp,这是因为你的使用者说让你占据wrap_content的大小,让我跟你商量,我又不知道你到底要占多大区域,但是我告诉你,我只有100dp,你最多也只能用这么多哈。(这里,可以看出,当使用者在布局文件中要求一个View是 wrap_content 时,此时,View的大小决定权就交给View自己了,默认的View类中的实现,比较粗暴,就是将此时ViewGroup提供的空间全占据,完全没有真正根据自己的内容来确定大小,为什么这么粗暴?因为View是一个基类,所有的组件都是它的子类,每个子类的content都各不相同,View怎么可能知道content的大小呢,所以,它把wrap_content情况下,自己尺寸大小的决定权下放给了不同的子组件,让它们自己根据自己的内容去决定自己的大小,同样,我们自定义View时,也要考虑这一点)
  • UNSPECIFIED 表示,你自己看着办,把你最理想的大小告诉我,我考虑考虑。
  • 第三步 ,好了,子View已经清楚地理解了ViewGroup和它的使用者对它的大小的期望和要求了。下步就要在该要求下来确定自己的大小并告诉ViewGroup了。(废话,不告诉ViewGroup大小,它怎么给你安排位置(layout),无法给你layout,你也就占据不了一块屏幕区域,占不了屏幕区域,你就无法与用户交互,无法与用户交互,要你何用啊!)

关于子View怎么确定自己的大小,不同的View有不同的态度,但是有几点基本的规矩是要遵守的:

  • 规矩一就是 ,不要违反ViewGroup的规定,最后设置的尺寸一定要在ViewGroup要求的范围内(不论是宽度还是高度),但是你说,假如我就是想要更大的空间,难道就没有办法了吗,我能不能遵守要求的情况下,同时告诉ViewGroup,虽然我告诉你的我要求的尺寸是遵照你的旨意来的,但实际上我是委屈求全的,我真实想要的大小不是这样的,你能不能再考虑一下。答案是:有。那就是如下调用:
resolveSizeAndState((int)(wantedWidth), widthMeasureSpec, 0);

resolveSizeAndState((int) (wantedHeight), heightMeasureSpec, 0);
复制代码

View可以把自己想要的宽和高进行一个 resolveSizeAndState 处理, 就可以达到上述目的。即如果想要的大小没超过要求,一切都Ok,如果超过了,在该方法内部,就会把尺寸调整成符合ViewGroup要求的,但是也会在尺寸中设置一个标记,告诉ViewGroup,这个大小是子View委屈求全的结果。至于ViewGroup会不会理会这一标记,要看不同的ViewGroup了。如果你实现自己的ViewGroup,最好还是关注下这个标记,毕竟作为大哥的你,最主要的职责就是把自己的小弟(子View)安排好,让它们都满意嘛。(这一点,我没有看到任何一篇讲解自定义View的文章提到过!) 什么?好奇的你想看看究竟是怎样设置标记的?来来来,满足你:

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {  

     final int specMode = MeasureSpec.getMode(measureSpec);  
     final int specSize = MeasureSpec.getSize(measureSpec);  
     final int result;  
    
     switch (specMode) {     
        case MeasureSpec.AT_MOST:         
            if (specSize < size) {            
                 result = specSize | MEASURED_STATE_TOO_SMALL;
    
            } else {            
                 result = size;      
            }         
            break;   
            
        case MeasureSpec.EXACTLY:          
             result = specSize;      
             break;   
             
        case MeasureSpec.UNSPECIFIED:   
        
        default:        
             result = size;   
      }   
      return result | (childMeasuredState & MEASURED_STATE_MASK);
}
复制代码

上面的代码中的 MEASURED_STATE_TOO_SMALL 就是在子View想要的空间太大时设置的标记了。

  • 规矩二 就是要在该方法中调整自己的绘制参数,这一点很好理解,毕竟ViewGroup提出了尺寸要求,要及时根据这一要求调整自己的绘制,比如,如果自己的背景图片太大,那就算算要缩放多少才合适,并且设置一个合理的缩放值。

  • 规矩三 就是一定要设置自己考虑后的尺寸,如果不设置就相当于没有告诉ViewGroup自己想要的大小,这会导致ViewGroup无法正常工作,设置的办法就是在onMeasure方法的最后,调用 setMeasuredDimension 方法。为什么调用这个方法就可以了呢?这只是一个约定,没有必要深究了。

View的绘制

关于View的绘制,非常简单,就是一个方法onDraw。

以上,View的三个基本知识点,我们都了解了,即View 的位置如何确定,大小如何确定以及如何绘制自己。这都是默认的View类中为我们准备好的。

我要改变这个View的外观和行为,肯定是覆写View类中的方法,但是怎么覆写,覆写哪些方法能够改变哪些行为?

好了,View的位置和大小怎么确定我们都清楚了,现在,是时候开始自定义View了。

首先,关于View所要具备的一般功能,View类中都有了基本的实现,比如确定位置,它有layout方法,当然,这个只适用于ViewGroup,实现自己的ViewGroup时,才需要修改该方法。确定大小,它有onMeasure方法,如果你不满意默认的确认大小的方法,也可以自己定义。改变默认的绘制,就覆写onDraw方法。下面,我们通过一张图,来看看,自定义View时,我们最可能需要修改的方法是哪些:

Android自定义View:View(二)

把这些方法都搞明白了,你也就理解了View的生命周期了。

比如View被inflated出来后,系统会回调该View的 onFinishInflate 方法,你的View可以在这个方法中,做一些准备工作。

如果你的View所属的Window可见性发生了变化,系统会回调该View的 onWindowVisibilityChanged 方法,你也可以根据需要,在该方法中完成一定的工作,比如,当Window显示时,注册一个监听器,根据监听到的广播事件改变自己的绘制,当Window不可见时,解除注册,因为此时改变自己的绘制已经没有意义了,自己也要跟着Window变成不可见了。

当ViewGroup中的子View数量增加或者减少,导致ViewGroup给自己分配的屏幕区域大小发生变化时,系统会回调View的 onSizeChanged 方法,该方法中,View可以获取自己最新的尺寸,然后根据这个尺寸相应调整自己的绘制。

当用户在View所占据的屏幕区域发生了触摸交互,系统会将用户的交互动作分解成如DOWN、MOVE、UP等一系列的MotionEvent,并且把这些事件传递给View的 onTouchEvent 方法,View可以在这个方法中进行与用户的交互处理。当然这个是基本的流程,实际的流程会稍复杂些。

除了这些方法,View还实现了三个接口,如下:

  • Drawable.Callback:是用来让View中的Drawable能够与View通信的,尤其是AnimationDrawable,更是必须依赖该回调才能实现动画效果。
  • KeyEvent.Callback:是用来处理键盘事件的,这与onTouchEvent用来处理触摸事件是相对的。
  • AccessibilityEventSource

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Algorithm Design Manual

The Algorithm Design Manual

Steven S Skiena / Springer / 2011-11-14 / GBP 55.07

....The most comprehensive guide to designing practical and efficient algorithms.... Written by a well-known algorithms researcher who received the IEEE Computer Science and Engineering Teaching Aw......一起来看看 《The Algorithm Design Manual》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具