深入理解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

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-