深入理解Android View《一》—IT蓝豹分享

2023-10-19 15:40

本文主要是介绍深入理解Android View《一》—IT蓝豹分享,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

做android其实也有一段时间了,我们每个人都会碰到一些这样或那样的问题,碰到问题了就拼命百度,可是发现,我们解决问题的能力并没有提升很多,所以我才有想总结一下我项目中所用过的相关知识,并了解一下Android源代码中是如何定义这些属性的,如何去实现的。以后再碰到类似的问题,我该如何实现。本人也不常写博客,希望各位博友能指点,分享,并提出博客中不正确的地方,共勉! 
   首先我发一份我做的关于Android View深入实现的的XMind的思维导图,可以帮助我一起整理思路,若是博友有什么想到的地方可以帮我一起补充。 

24142922_qEJ2.png 

    今天首先来介绍下View,View字面意思理解就是视图,什么是视图,说白了UI,你在屏幕上看到的,呈现在你眼前的东西都是属于视图。但是有个问题:我们在做刷新视图的时候,要用Handler机制来对UI组件进行相应的刷新,为什么不能直接在UI线程中做相关的刷新呢?若是在做一些比较费时的操作,比如读取数据库,网络访问,这个时候我们基本都会在UI线程中开一个子线程,然后利用Handler机制去子线程中取相应的数据,利用消息通知机制来UI主线程中刷新数据。我的理解是:其实UI,View,这些可以看得到的组件在运行的过程中,首先他要创建相关的UI的属性,比如要显示的图形的坐标点,大小,长宽,然后有一个画笔和画布,画上去的。就是说,电脑屏幕上你所有看到的东西都是画上去的。但是你发现,一旦断电,屏幕上的东西立马就不见了,是的!屏幕其实一直在不断的重绘,主要是利用了人眼的视觉暂留效果,两次重绘在0.05秒-0.2秒的时候,人不会发现发现屏幕在闪烁!计算机里有一个叫UI线程,其实他只做绘画,只管不停地在屏幕上不断画画,而且要再那么短的时间不断画,他是一个死循环,他很繁忙,还有一点,比如你操作数据库的数据,通过网络访问数据这些都是比较慢的操作,内存取数据>文件取数据>网络访问请求取数据。这个时候若是在UI主线程中去做非时操作,UI就阻塞了。那这个时候需要开辟一个子线程,他来做数据相关的操作,Handler的作用就好像是UI线程和子线程之间的信使,UI主线程这个时候发出一个通知:郑打理朝政繁忙,哪个子民可以为我平息边疆的战争,这个时候,主线程不是直接把这个话通知到下面的所有臣子,而是用了个中间的对象,就好比皇帝身边的人,先告诉他:最近要平息边疆战争,你给我告诉传达到每一个下面的臣子,务必传达到!好了,于是Handler就忙起来了,他忙着通知,接受大臣的反馈,大臣接受到这个间接的通知之后,带兵大战,驰骋沙场。那老皇帝等的着急吧,战争胜利了还是失败了呢?还是通过Handler把子线程运行的结果告诉Handler,Handler把结果拿给UI线程,一做刷新就完美呈现了!这个过程有什么好处呢,就是保证每个事物,每个人都分工很明确,各干各的事情。UI线程只做UI组件的创建,渲染,重绘,而Handler只管理UI线程和子线程之间的交互和反馈,子线程只管做自己擅长的具体事情就好了,他唯一要做的是把运行的结果告诉Handler,Handler再拿着结果反馈给UI线程,UI再拿着这个结果去刷新屏幕。 
      其实不光光是UI绘制这个过程是这样的,比如:我们在点击一个Button的时候,会给他注册一个setOnClickListener(),但是UI组件自己是不知道自己干什么的,UI是不知道自己是被点击了,还是被滑动了,他只管自己绘画,而真正的处理这个事情的过程是由另一个接口来处理的,相应的接口做相应的处理,处理的结果再由回调来响应。 
      说了那么多,都只是刚开始,其实这是一种很好的思想,叫做委托,比如你因为各种原因,没时间,没有资源,我委托一个中间的代理来帮我找能帮我做相应事情的人,然后这个代理再把找到的结果反馈给你。你要租房子,但是找不到很多的房源,那就要通过第三方比如中介来做这个事情了。 
    在说View之前,我提出了很多问题:我的学习方法是带着问题从源代码中去找解决方式。这样才能知其然而知其所以然,然后用理论再在结合实际的一些小例子 
   1)首先一个View要绘制之前,肯定要定义相应的属性和方法,比如坐标点,长,宽,高,绘制的方式,绘制的风格,这些是哪里定义的呢? 
(2)View是如何被创建出来的呢,绘图绘制的机制是如何的?是如何把相应的属性结合起来呈现视图效果的? 
(3)一个View他要占地方,他的位置如何确定的。他的大小如何确定的? 
(4)一个View画出来之后,其实就像水氢原子+氧原子+电产生的,可是产生了之后用什么来存放呢? 
(5)就一个图形放在那里,我们不知道他如何和外部交互啊?UI叫做UserInterface,用户接口,既然是给用户用的,那我怎么和计算机交互呢?怎么样赋予它生命呢,和外界感知? 
(6)如何用这些知识在项目中实战 
总的来说,分成三个方面,(1)UI的外部形态?(2)谁来产生UI,谁来去创造它?(3)事件机制,能被外界感知到 
  先来说第一个问题,在说道UI的外部形态的时候,有三个决定他外部形态的函数 
-> onDraw()方法 
-> onMeasure()方法 
-> onLayout()方法 
从字面上来 onDraw()和onLayout()很好理解 一个是用来绘制的,一个是用来确定他的位置的,但是这个onMeasure()我查了下英文字典,measure是测量,尺寸的意思,就是测量UI组件的大小的。 
还是先来看onDraw()的源代码 
咋一看 
  <pre name="code" class="java">  /** 
     * Implement this to do your drawing. 
     * 
     * @param canvas the canvas on which the background will be drawn 
     */ 
    protected void onDraw(Canvas canvas) { 

    }</pre> 
其中什么代码也没有?是不是很奇怪,这个时候就应该想是哪个地方调用这个函数的。可以在AndroidStudio下按下ctrl+g 
你可以看到有一个方法里调用了onDraw()方法,这个方法就是draw()方法。 
首先来看一下英文的解释:注意其中的2点,draw()是通过Canvas来实现的(2)当这个方法draw()方法被调用的时候,这个View其实已经生成了一个完整的布局了,然后后面说的,在编程的时候如果你要在一个子类中需要调用重写该方法,要使用onDraw()方法来替代这个draw()方法 
这里要注意2点,第一,View的相关绘制追踪到再底层是由Canvas绘制的,Canvas是画布,可以在画布上绘制各种雏形,然后可以通过跟踪代码的方式按F3,其实在绘制View的时候会首先绘制一个矩形这样的雏形。第二,我有问我网友,我为什么不能重写draw(),若是重写父类的draw()方法我要重写这个onDraw()方法呢,他给我的解释是这样的: 
draw()方法是系统的方法,是一个framework层的方法,onDraw()方法是用户的方法,是给用户间接调用实现特定用户需求的方法的 
如果要重写draw()方法的话就变成了 
<pre name="code" class="java"> public void draw(){ 
   //framework code 
   //custom code 
   //framework code 
}</pre> 
但是若是采用重写onDraw()方法的话就变成了 
<pre name="code" class="java">public void draw(){ 
   //framework code 
   onDraw(); 
   //framework code</pre> 
   其实后面要说到的layout()方法,官方中也是这么解释的,要你使用onLayout()方法来重写,而不是调用layout()方法。这个就是所谓的钩子函数!不知道说的准不准确,欢迎博友一起纠正!继续往下读代码,你可以知道要绘制一个View 需要6个步骤:我简要总结一下, 
1。绘制背景。 
2.绘制View或者该View存在的子View。 
3.还要对View加上相应的滚动条,比如有些View ScrollView就是带有滚动条的,还有有些列表也是带有的。 

     那倒这里我又有一个疑惑了,为什么有些View是带有滚动条的呢,而有些View就是没有呢,比如我在项目中用一个ListView,若是ListView中有很多很多条数据,加入数据超过了相应的显示长度,这个时候我们是不是要在ListView里加一个滚动条组件,这样就可以允许用户通过滚动条的滑动来查看下面的数据,但是:有些组件,比如我要上传自己的一个照片到服务器端,但是照片太大了 ,我要进行相应的裁剪,这个时候View其实是做了一个裁剪的操作,就是说:每个不同的View其实根据实际的应用需求来对View做相应的处理。那具体是根据什么方式来划分的呢?别着急,这些源代码里都有下面有一个方法确定View大小的方法 onMeasure()里就有涉及到这方面的处理! 
     当然draw()方法还有一个重载的方法 ,这个英文的描述: 
这个方法是被ViewGroup.drawChild()方法来调用的,用来绘制依次在ViewGroup里的childView。这样一说其实也就明白了,原来Viewgroup就是一个View的容器,他是用来装View的地方的。那可以联想到我们常用的LinearLayout,RelativeLayout布局容器,他们其实也不就是ViewGroup嘛,在写XML的时候他们是作为根节点的,他们下面有很多子节点作为父容器的孩子节点。 
再来看一下确定View位置的相关方法Layout()方法,为什么把这2个方法放在一起说呢,就是因为刚才说到一点,layout()方法和draw()方法一样,若是子类要重写父类这个方法的时候,也是要调用onLayout()方法来重写,所以他的代码结构其实和draw()方法是一样的。 
<pre name="code" class="java">public void layout(int l, int t, int r, int b) { 
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { 
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); 
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; 
        } 

        int oldL = mLeft; 
        int oldT = mTop; 
        int oldB = mBottom; 
        int oldR = mRight; 

        boolean changed = isLayoutModeOptical(mParent) ? 
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); 

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { 
            onLayout(changed, l, t, r, b); 
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; 

            ListenerInfo li = mListenerInfo; 
            if (li != null && li.mOnLayoutChangeListeners != null) { 
                ArrayList<OnLayoutChangeListener> listenersCopy = 
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); 
                int numListeners = listenersCopy.size(); 
                for (int i = 0; i < numListeners; ++i) { 
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); 
                } 
            } 
        } 

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; 
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; 
    } 
在这个方法里要分三段来看,(1)setFrame() 要设置改组件距离左边,上边,底部和右边的距离。这几个值构成的矩形的区域就是该View显示的位置,不过这里的具体位置都是相对父容器的(2)此处有一个判断位置是否改变的返回boolean值的函数,若是位置改变,则进行相应的重绘。然后回调onLayout函数,最后回调所有注册过的listener的onLayoutChange函数。对于View来说,onLayout只是一个空实现,一般情况下我们也不需要重载该函数: 
然后在看代码的时候我发现一个细节要特别注意,setFrame()有一个这个方法中notifySubtreeAccessibilityStateChangedIfNeeded() 
该方法我单从字面意思上去理解就是说,这个方法是父容器通知子容器的有效重绘的通知,为了不必要的系统开销而建立的通知机制。 
然后按F3跟踪进去 
** 
     * Notifies that the accessibility state of this view changed. The change 
     * is *not* local to this view and does represent structural changes such 
     * as children and parent. For example, the view size changed. The 
     * notification is at at most once every 
     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()} 
     * to avoid unnecessary load to the system. Also once a view has a pending 
     * notification this method is a NOP until the notification has been sent. 
     * 
     * @hide  
     */ 
    public void notifySubtreeAccessibilityStateChangedIfNeeded() { 
        if (!AccessibilityManager.getInstance(mContext).isEnabled()) { 
            return; 
        } 
        if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { 
            mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; 
            if (mParent != null) { 
                try { 
                    mParent.notifySubtreeAccessibilityStateChanged( 
                            this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); 
                } catch (AbstractMethodError e) { 
                    Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + 
                            " does not fully implement ViewParent", e); 
                } 
            } 
        } 
    }</pre> 
然后,我这里还会提出一个问题: 
你在创建对象的时候,都会用该对象的构造方法。我们每次都是通过构造方法来实现对该对象的内存分配,属性赋值等相关操作,那View的构造方法上有什么可以值得推敲的地方呢,这个时候促使我想起,View是怎么来的,如何被创建的。 
先小看一下View的三个构造方法: 
1.public void CustomView(Context context) {} 
2.public void CustomView(Context context, AttributeSet attrs) {} 
3.public void CustomView(Context context, AttributeSet attrs, int defStyle) {} 
那这个时候我们需要好好的问问自己:为什么要定义三个构造方法呢,2个不够用嘛?然后再猜想一下这个AttributeSet和 defStyle是用来干嘛的?从名字看是关于View的设计风格的文件的,那他又是如何加载这些设计风格的文件呢?程序员去编程的时候如何使用这些用户接口呢?将再下一块涉及到这些问题。 

 

文章来自IT蓝豹:http://www.itlanbao.com  请转发时明标注出处,谢谢。


转载于:https://my.oschina.net/u/2447098/blog/510653

这篇关于深入理解Android View《一》—IT蓝豹分享的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/240747

相关文章

Android 12解决push framework.jar无法开机的方法小结

《Android12解决pushframework.jar无法开机的方法小结》:本文主要介绍在Android12中解决pushframework.jar无法开机的方法,包括编译指令、框架层和s... 目录1. android 编译指令1.1 framework层的编译指令1.2 替换framework.ja

Android开发环境配置避坑指南

《Android开发环境配置避坑指南》本文主要介绍了Android开发环境配置过程中遇到的问题及解决方案,包括VPN注意事项、工具版本统一、Gerrit邮箱配置、Git拉取和提交代码、MergevsR... 目录网络环境:VPN 注意事项工具版本统一:android Studio & JDKGerrit的邮

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

MySQL数据库约束深入详解

《MySQL数据库约束深入详解》:本文主要介绍MySQL数据库约束,在MySQL数据库中,约束是用来限制进入表中的数据类型的一种技术,通过使用约束,可以确保数据的准确性、完整性和可靠性,需要的朋友... 目录一、数据库约束的概念二、约束类型三、NOT NULL 非空约束四、DEFAULT 默认值约束五、UN

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多

Android使用ImageView.ScaleType实现图片的缩放与裁剪功能

《Android使用ImageView.ScaleType实现图片的缩放与裁剪功能》ImageView是最常用的控件之一,它用于展示各种类型的图片,为了能够根据需求调整图片的显示效果,Android提... 目录什么是 ImageView.ScaleType?FIT_XYFIT_STARTFIT_CENTE

SpringBoot请求参数接收控制指南分享

《SpringBoot请求参数接收控制指南分享》:本文主要介绍SpringBoot请求参数接收控制指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring Boot 请求参数接收控制指南1. 概述2. 有注解时参数接收方式对比3. 无注解时接收参数默认位置

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

Android实现两台手机屏幕共享和远程控制功能

《Android实现两台手机屏幕共享和远程控制功能》在远程协助、在线教学、技术支持等多种场景下,实时获得另一部移动设备的屏幕画面,并对其进行操作,具有极高的应用价值,本项目旨在实现两台Android手... 目录一、项目概述二、相关知识2.1 MediaProjection API2.2 Socket 网络

Android实现悬浮按钮功能

《Android实现悬浮按钮功能》在很多场景中,我们希望在应用或系统任意界面上都能看到一个小的“悬浮按钮”(FloatingButton),用来快速启动工具、展示未读信息或快捷操作,所以本文给大家介绍... 目录一、项目概述二、相关技术知识三、实现思路四、整合代码4.1 Java 代码(MainActivi