当前位置:首页 » 安卓系统 » android添加view

android添加view

发布时间: 2023-09-12 01:55:16

⑴ Android 自定义View之Layout过程

系列文章:

在上篇文章: Android 自定义View之Measure过程 ,我们分析了Measure过程,本次将会掀开承上启下的Layout过程神秘面纱,
通过本篇文章,你将了解到:

在上篇文章的比喻里,我们说过:

该ViewGroup 重写了onMeasure(xx)和onLayout(xx)方法:

同时,当layout 执行结束,清除PFLAG_FORCE_LAYOUT标记,该标记会影响Measure过程是否需要执行onMeasure。

该View 重写了onMeasure(xx)和onLayout(xx)方法:

MyViewGroup里添加了MyView、Button两个控件,最终运行的效果如下:

可以看出,MyViewGroup 里子布局的是横向摆放的。我们重点关注Layout过程。实际上,MyViewGroup里我们只重写了onLayout(xx)方法,MyView也是重写了onLayout(xx)方法。
接下来,分析View Layout过程。

与Measure过程类似,连接ViewGroup onLayout(xx)和View onLayout(xx)之间的桥梁是View layout(xx)。

可以看出,最终都调用了setFrame(xx)方法。

对于Measure过程在onMeasure(xx)里记录了尺寸的值,而对于Layout过程则在layout(xx)里记录了坐标值,具体来说是在setFrame(xx)里,该方法两个重点地方:

View.onLayout(xx)是空实现
从layout(xx)和onLayout(xx)声明可知,这两个方法都是可以被重写的,接下来看看ViewGroup是否重写了它们。

ViewGroup.layout(xx)虽然重写了layout(xx),但是仅仅做了简单判断,最后还是调用了View.layout(xx)。

这重写后将onLayout变为抽象方法,也就是说继承自ViewGroup的类必须重写onLayout(xx)方法。
我们以FrameLayout为例,分析其onLayout(xx)做了什么。

FrameLayout.onLayout(xx)为子布局Layout的时候,起始坐标都是以FrameLayout为基准,并没有记录上一个子布局占了哪块位置,因此子布局的摆放位置可能会重叠,这也是FrameLayout布局特性的由来。而我们之前的Demo在水平方向上记录了上一个子布局的摆放位置,下一个摆放时只能在它之后,因此就形成了水平摆放的功能。
由此类推,我们常说的某个子布局在父布局里的哪个位置,决定这个位置的即是ViewGroup.onLayout(xx)。

上边我们分析了View.layout(xx)、View.onLayout(xx)、ViewGroup.layout(xx)、ViewGroup.onLayout(xx),这四者什么关系呢?
View.layout(xx)

View.onLayout(xx)

ViewGroup.layout(xx)

ViewGroup.onLayout(xx)

View/ViewGroup 子类需要重写哪些方法:

用图表示:

通过上述的描述,我们发现Measure过程和Layout过程里定义的方法比较类似:

它俩的套路比较类似:measure(xx)、layout(xx)一般不需要我们重写,measure(xx)里调用onMeasure(xx),layout(xx)为调用者设置坐标值。
若是ViewGroup:onMeasure(xx)里遍历子布局,并测量每个子布局,最后将结果汇总,设置自己测量的尺寸;onLayout(xx)里遍历子布局,并设置每个子布局的坐标。
若是View:onMeasure(xx)则测量自身,并存储测量尺寸;onLayout(xx)不需要做什么。

Measure过程虽然比Layout过程复杂,但仔细分析后就会发现其本质就是为了设置两个成员变量:

而Layout过程虽然比较简单,其本质是为了设置坐标值

将Measure设置的变量和Layout设置的变量联系起来:

此外,Measure过程通过设置PFLAG_LAYOUT_REQUIRED 标记来告诉需要进行onLayout,而Layout过程通过清除 PFLAG_FORCE_LAYOUT来告诉Measure过程不需要执行onMeasure了。
这就是Layout的承上作用

我们知道View的绘制需要依靠Canvas绘制,而Canvas是有作用区域限制的。例如我们使用:

Cavas绘制的起点是哪呢?
对于硬件绘制加速来说:正是通过Layout过程中设置的RenderNode坐标。
而对于软件绘制来说:

关于硬件绘制加速/软件绘制 后续文章会分析。

这就是Layout的启下作用
以上即是Measure、Layout、Draw三者的内在联系。
当然Layout的"承上"还需要考虑margin、gravity等参数的影响。具体用法参见最开始的Demo。

getMeasuredWidth()/getMeasuredHeight 与 getWidth/getHeight区别
我们以获取width为例,分别来看看其方法:

getMeasuredWidth():获取测量的宽,属于"临时值"
getWidth():获取View真实的宽
在Layout过程之前,getWidth() 默认为0
何时可以获取真实的宽、高

下篇将分析Draw()过程,我们将分析"一切都是draw出来的"道理

本篇基于 Android 10.0

⑵ Android:窗口、自定义view、bitmap

1、ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对棚弯象和 DecorView 建立关联

2、 自定义View-绘制流程概述

4、 Android Handler
6、 Android Bitmap

2、MeasureSpec:

3、一般来说,使用多进程会造成以下几个方面的问题:

5、Window 概念与分类:
Window 是一个抽象类,它的具体实现是 PhoneWindow。WindowManager 是外界访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。Android 中所有的视图都是通过 Window 来呈现,因此 Window 实际是 View 的直接管理者。

6、window的三大操作:addView、upView、removeView
7、 Bitmap 中有两个内部枚举类:

保存图片资源:

图片压缩

基本使用:

8、Context 本身是一个抽象类,是对一系列系统服务接口的封装,包括:内部伏丛资源、包、类加载、I/O操作、权限、主线程、IPC 和组件启动等操作的管理。ContextImpl, Activity, Service, Application 这些都是 Context 的直接或间接子类

9、SharedPreferences 采用key-value(键值对)形式, 主要用于轻量级的数据存储, 尤其适合保存应用的配置参数, 但不建议使用 SharedPreferences 来存储大规模的数据, 可能会降低性能

10、SharedPreferences源码有用synchronize进行加锁同步

11、Handler 有两个主要用途:
(1)安排 Message 和 runnables 在将来的某个时刻执行;
(2)将要在不同链厅闷于自己的线程上执行的操作排入队列。(在多个线程并发更新UI的同时保证线程安全。)
只有主线程能对UI进行操作,所以在对UI进行跟改之前,ViewRootImpl 对UI操作做了验证,这个验证工作是由 ViewRootImpl的 checkThread 方法完成:

12、ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,其他线程则无法获取。Looper、ActivityThread 以及 AMS 中都用到了 ThreadLocal。当不同线程访问同一个ThreadLocal 的 get方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLcoal 的索引去查找对应的value值:

13、Android 提供了几种途径来从其他线程访问 UI 线程:

Android单线程模式必须遵守的规则:

14、HandlerThread 集成了 Thread,却和普通的 Thread 有显着的不同。普通的 Thread 主要用于在 run 方法中执行一个耗时任务,而 HandlerThread 在内部创建了消息队列,外界需要通过 Handler 的消息方式通知 HanderThread 执行一个具体的任务。

15、IntentService 可用于执行后台耗时的任务,当任务执行后会自动停止,由于其是 Service 的原因,它的优先级比单纯的线程要高,所以 IntentService 适合执行一些高优先级的后台任务。在实现上,IntentService 封装了 HandlerThread 和 Handler。IntentService 第一次启动时,会在 onCreatea 方法中创建一个 HandlerThread,然后使用的 Looper 来构造一个 Handler 对象 mServiceHandler,这样通过 mServiceHandler 发送的消息最终都会在 HandlerThread 中执行。每次启动 IntentService,它的 onStartCommand 方法就会调用一次,onStartCommand 中处理每个后台任务的 Intent,onStartCommand 调用了 onStart 方法。可以看出,IntentService 仅仅是通过 mServiceHandler 发送了一个消息,这个消息会在 HandlerThread 中被处理。mServiceHandler 收到消息后,会将 Intent 对象传递给 onHandlerIntent 方法中处理,执行结束后,通过 stopSelf(int startId) 来尝试停止服务。(stopSelf() 会立即停止服务,而 stopSelf(int startId) 则会等待所有的消息都处理完毕后才终止服务)。

16、RecyclerView 优化

⑶ Android - View 绘制流程

我们知道,在 Android 中,View 绘制主要包含 3 大流程:

Android 中,主要有两种视图: View 和 ViewGroup ,其中:

虽然 ViewGroup 继承于 View ,但是在 View 绘制三大流程中,某些流程需要区分 View 和 ViewGroup ,它们之间的操作并不完全相同,比如:

对 View 进行测量,主要包含两个步骤:

对于第一个步骤,即求取 View 的 MeasureSpec ,首先我们来看下 MeasureSpec 的源码定义:

MeasureSpec 是 View 的一个公有静态内部类,它是一个 32 位的 int 值,高 2 位表示 SpecMode(测量模式),低 30 位表示 SpecSize(测量尺寸/测量大小)。
MeasureSpec 将两个数据打包到一个 int 值上,可以减少对象内存分配,并且其提供了相应的工具方法可以很方便地让我们从一个 int 值中抽取出 View 的 SpecMode 和 SpecSize。

一个 MeasureSpec 表达的是:该 View 在该种测量模式(SpecMode)下对应的测量尺寸(SpecSize)。其中,SpecMode 有三种类型:

对 View 进行测量,最关键的一步就是计算得到 View 的 MeasureSpec ,子View 在创建时,可以指定不同的 LayoutParams (布局参数), LayoutParams 的源码主要内容如下所示:

其中:

LayoutParams 会受到父容器的 MeasureSpec 的影响,测量过程会依据两者之间的相互约束最终生成子View 的 MeasureSpec ,完成 View 的测量规格。

简而言之,View 的 MeasureSpec 受自身的 LayoutParams 和父容器的 MeasureSpec 共同决定( DecorView 的 MeasureSpec 是由自身的 LayoutParams 和屏幕尺寸共同决定,参考后文)。也因此,如果要求取子View 的 MeasureSpec ,那么首先就需要知道父容器的 MeasureSpec ,层层逆推而上,即最终就是需要知道顶层View(即 DecorView )的 MeasureSpec ,这样才能一层层传递下来,这整个过程需要结合 Activity 的启动过程进行分析。

我们知道,在 Android 中, Activity 是作为视图组件存在,主要就是在手机上显示视图界面,可以供用户操作, Activity 就是 Andorid 中与用户直接交互最多的系统组件。

Activity 的基本视图层次结构如下所示:

Activity 中,实际承载视图的组件是 Window (更具体来说为 PhoneWindow ),顶层View 是 DecorView ,它是一个 FrameLayout , DecorView 内部是一个 LinearLayout ,该 LinearLayout 由两部分组成(不同 Android 版本或主题稍有差异): TitleView 和 ContentView ,其中, TitleView 就是标题栏,也就是我们常说的 TitleBar 或 ActionBar , ContentView 就是内容栏,它也是一个 FrameLayout ,主要用于承载我们的自定义根布局,即当我们调用 setContentView(...) 时,其实就是把我们自定义的布局设置到该 ContentView 中。

当 Activity 启动完成后,最终就会渲染出上述层次结构的视图。

因此,如果我们要求取得到子View 的 MeasureSpec ,那么第一步就是求取得到顶层View(即 DecorView )的 MeasureSpec 。大致过程如下所示:

经过上述步骤求取得到 View 的 MeasureSpec 后,接下来就可以真正对 View 进行测量,求取 View 的最终测量宽/高:

Android 内部对视图进行测量的过程是由 View#measure(int, int) 方法负责的,但是对于 View 和 ViewGroup ,其具体测量过程有所差异。

因此,对于测量过程,我们分别对 View 和 ViewGroup 进行分析:

综上,无论是对 View 的测量还是 ViewGroup 的测量,都是由 View#measure(int widthMeasureSpec, int heightMeasureSpec) 方法负责,然后真正执行 View 测量的是 View 的 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法。

具体来说, View 直接在 onMeasure(...) 中测量并设置自己的最终测量宽/高。在默认测量情况下, View 的测量宽/高由其父容器的 MeasureSpec 和自身的 LayoutParams 共同决定,当 View 自身的测量模式为 LayoutParams.UNSPECIFIED 时,其测量宽/高为 android:minWidth / android:minHeight 和其背景宽/高之间的较大值,其余情况皆为自身 MeasureSpec 指定的测量尺寸。

而对于 ViewGroup 来说,由于布局特性的丰富性,只能自己手动覆写 onMeasure(...) 方法,实现自定义测量过程,但是总的思想都是先测量 子View 大小,最终才能确定自己的测量大小。

当确定了 View 的测量大小后,接下来就可以来确定 View 的布局位置了,也即将 View 放置到屏幕具体哪个位置。

View 的布局过程由 View#layout(...) 负责,其源码如下:

View#layout(...) 主要就做了两件事:

ViewGroup 的布局流程由 ViewGroup#layout(...) 负责,其源码如下:

可以看到, ViewGroup#layout(...) 最终也是通过 View#layout(...) 完成自身的布局过程,一个注意的点是, ViewGroup#layout(...) 是一个 final 方法,因此子类无法覆写该方法,主要是 ViewGroup#layout(...) 方法内部对子视图动画效果进行了相关设置。

由于 ViewGroup#layout(...) 内部最终调用的还是 View#layout(...) ,因此, ViewGroup#onLayout(...) 就会得到回调,用于处理 子View 的布局放置,其源码如下:

由于不同的 ViewGroup ,其布局特性不同,因此 ViewGroup#onLayout(...) 是一个抽象方法,交由 ViewGroup 子类依据自己的布局特性,摆放其 子View 的位置。

当 View 的测量大小,布局位置都确定后,就可以最终将该 View 绘制到屏幕上了。

View 的绘制过程由 View#draw(...) 方法负责,其源码如下:

其实注释已经写的很清楚了, View#draw(...) 主要做了以下 6 件事:

我们知道,在 Activity 启动过程中,会调用到 ActivityThread.handleResumeActivity(...) ,该方法就是 View 视图绘制的起始之处:

可以看到, ActivityThread.handleResumeActivity(...) 主要就是获取到当前 Activity 绑定的 ViewManager ,最后调用 ViewManager.addView(...) 方法将 DecorView 设置到 PhoneWindow 上,也即设置到当前 Activity 上。 ViewManager 是一个接口, WindowManager 继承 ViewManager ,而 WindowManagerImpl 实现了接口 WindowManager ,此处的 ViewManager.addView(...) 实际上调用的是 WindowManagerImpl.addView(...) ,源码如下所示:

WindowManagerImpl.addView(...) 内部转发到 WindowManagerGlobal.addView(...) :

在 WindowManagerGlobal.addView(...) 内部,会创建一个 ViewRootImpl 实例,然后调用 ViewRootImpl.setView(...) 将 ViewRootImpl 与 DecorView 关联到一起:

ViewRootImpl.setView(...) 内部首先关联了传递过来的 DecorView (通过属性 mView 指向 DecorView 即可建立关联),然后最终调用 requestLayout() ,而 requestLayout() 内部又会调用方法 scheleTraversals() :

ViewRootImpl.scheleTraversals() 内部主要做了两件事:

Choreographer.postCallback(...) 会申请一次 VSYNC 中断信号,当 VSYNC 信号到达时,便会回调 Choreographer.doFrame(...) 方法,内部会触发已经添加的回调任务, Choreographer 的回调任务有以下四种类型:

因此, ViewRootImpl.scheleTraversals(...) 内部通过 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null) 发送的异步视图渲染消息就会得到回调,即回调 mTra

热点内容
扫行程码为什么需要支付密码 发布:2025-01-31 22:47:08 浏览:738
什么样的配置能玩地平线4 发布:2025-01-31 22:44:05 浏览:241
python正则表达式符号 发布:2025-01-31 22:43:50 浏览:391
androidmime 发布:2025-01-31 22:34:44 浏览:782
ftp和http的中文含义是 发布:2025-01-31 22:33:48 浏览:402
sqlite3存储图片 发布:2025-01-31 22:27:14 浏览:162
sqlserverphp 发布:2025-01-31 22:22:55 浏览:877
曲马多存储 发布:2025-01-31 22:22:52 浏览:538
缓存儿歌 发布:2025-01-31 22:21:26 浏览:528
学java有发展吗 发布:2025-01-31 21:44:45 浏览:569