导航:首页 > 操作系统 > androidview绘制完成监听

androidview绘制完成监听

发布时间:2022-10-03 00:53:08

android自定义View——从零开始实现书籍翻页效果(一)

前言 :本篇是系列博客的第三篇,这次我们要研究 书籍翻页效果 。不知道大家平时有没用过iReader、掌阅这些小说软件,里面的翻页效果感觉十分的酷炫。有心想研究研究如何实现,于是网上找了找,发现这方面的教学资料非常少,所幸能找到 何明桂大大 的 Android 实现书籍翻页效果----原理篇 这样的入门博客(感谢大大 Orz),我们就以这篇博客为切入点从零实现我们自己的翻页效果。由于这次坑比较深,预计会写好几期,感兴趣的小伙伴可以点下关注以便及时收到更新提醒,谢谢大家的支持 ~

本篇只着重于思路和实现步骤,里面用到的一些知识原理不会非常细地拿来讲,如果有不清楚的api或方法可以在网上搜下相应的资料,肯定有大神讲得非常清楚的,我这就不献丑了。本着认真负责的精神我会把相关知识的博文链接也贴出来(其实就是懒不想写那么多哈哈),大家可以自行传送。为了照顾第一次阅读系列博客的小伙伴,本篇会出现一些在之前 系列博客 就讲过的内容,看过的童鞋自行跳过该段即可

国际惯例,先上效果图,本次主要实现了 基本的上下翻页效果 右侧最大翻页距离的限制

在看这篇博客之前,希望大家能先了解一下书籍翻页的实现原理,博客链接我已经贴出来了。通过原理讲解我们知道,整个书籍翻页效果界面分成了三个区域, A 为当前页区域, B 为下一页区域, C 为当前页背面,如图所示

书籍翻页效果的实现就是要以我们 触摸屏幕位置的坐标 为基础绘制出这三个区域,形成模拟翻页的特效。要绘制这三个区域,我们需要通过一组 特定的点 来完成,这些点的坐标需要通过两个已知的点( 触摸点 相对边缘角 )计算得到,下图我将各个特定点的位置和计算公式贴出来,大家对照着原理一起理解(渣画工望体谅 ╮(╯▽╰)╭ ),其中 b 点是由 ae cj 的交点, k 点是由 ah cj 的交点

简单总结一下, a 是触摸点, f 是触摸点相对的边缘角, eh 我们设置为 af 的垂直平分线,则 g af 的中点, ab ak dj 直线 曲线cdb 是起点为 c ,控制点为 e ,终点为 b 二阶贝塞尔曲线 曲线kij 是起点为 k ,控制点为 h ,终点为 j 二阶贝塞尔曲线 ,区域 A B C 就由这些点和线划分开来。我们将这些点称为标识点,下一步就是模拟设定 a f 点的位置,将这组标识点绘制到屏幕上来验证我们的计算公式是否正确,创建 BookPageView

实体类 MyPoint 用来存放我们的标识点坐标

界面布局:

在Activity中进行注册

效果如图

前文我们提到 ab ak dj 直线 曲线cdb 是起点为 c ,控制点为 e ,终点为 b 二阶贝塞尔曲线 曲线kij 是起点为 k ,控制点为 h ,终点为 j 二阶贝塞尔曲线 。通过观察分析得知, 区域A 是由View 左上角 左下角 曲线cdb , 直线 ab ak 曲线kij 右上角 连接而成的区域,修改 BookPageView ,利用 path 绘制处 区域A

效果如图

区域C 理论上应该是由点 a , b , d , i , k 连接而成的闭合区域,但由于 d i 是曲线上的点,我们没办法直接从 d 出发通过 path 绘制路径连接 b 点( i , k 同理),也就不能只用 path 的情况下直接绘制出 区域C ,我们需要用 PorterDuffXfermode 方面的知识“曲线救国”。我们试着先将点 a , b , d , i , k 连接起来,观察闭合区域与 区域A 之间的联系。修改 BookPageView

效果如图

我们将两条曲线也画出来对比观察

观察分析后可以得出结论, 区域C 由直线ab,bd,dj,ik,ak连接而成的区域 减去 与区域A交集部分 后剩余的区域。于是我们设置 区域C 画笔 Xfermode 模式为 DST_ATOP

效果如图

最后是 区域B ,因为 区域B 处于最底层,我们直接将 区域B 画笔 Xfermode 模式设为 DST_ATOP ,在 区域A、C 之后绘制即可,修改 BookPageView

效果如图

翻页可以从右下方翻自然也可以从右上方翻,我们将 f 点设在右上角,由于View上下两部分是呈 镜像 的,所以各标识点的位置也应该是镜像对应的,因为 区域B和C 的绘制与 f 点没有关系,所以我们只需要修改 区域A 的绘制逻辑,新增 getPathAFromTopRight() 方法

效果如图

之前由于测试效果没有对View的大小进行重新测量,在实现触摸翻页之前先把这个结了。重写View的 onMeasure() 方法

我们的需求是,在上半部分翻页时 f 点在右上角,在下半部分翻页时 f 则在右下角,当手指离开屏幕时回到 初始状态 ,根据需求,修改 BookPageView

在Activity中监听View的 onTouch 状态

注意,要设置 android:clickable true ,否则无法监听到 ACTION_MOVE ACTION_UP 状态

效果如图

到这里我们已经实现了基本的翻页效果,但要还原真实的书籍翻页效果,我们还需要设置一些限制条件来完善我们的项目

对于一般的书本来说,最左侧应该是钉起来的,也就是说如果我们从右侧翻页,翻动的距离是 有限制的 ,最下方翻页形成的曲线起点( c 点)的x坐标不能小于0(上方同理),按照这个限定条件,修改我们的 BookPageView

效果如图

至此本篇教程就告一段落了,当然还有许多功能需要继续完善,例如横向翻页、翻页动画、阴影效果等等,这些都会在后面的教程中一一解决。如果大家看了感觉还不错麻烦点个赞,你们的支持是我最大的动力~

❷ 如何构建Android MVVM 应用框架

我们先来看看什么是MVVM,然后再一步一步来设计整个MVVM框架。
MVC、MVP、MVVM
首先,我们先大致了解下Android开发中常见的模式。
MVC
View:XML布局文件。
Model:实体模型(数据的获取、存储、数据状态变化)。
Controllor:对应于Activity,处理数据、业务和UI。
从上面这个结构来看,Android本身的设计还是符合MVC架构的,但是Android中纯粹作为View的XML视图功能太弱,我们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接导致Activity中的代码大爆炸。相信大多数Android开发者都遇到过一个Acitivty数以千行的代码情况吧!所以,更贴切的说法是,这个MVC结构最终其实只是一个Model-View(Activity:View&Controller)的结构。
MVP
View:对应于Activity和XML,负责View的绘制以及与用户的交互。
Model:依然是实体模型。
Presenter:负责完成View与Model间的交互和业务逻辑。
前面我们说,Activity充当了View和Controller两个角色,MVP就能很好地解决这个问题,其核心理念是通过一个抽象的View接口(不是真正的View层)将Presenter与真正的View层进行解耦。Persenter持有该View接口,对该接口进行操作,而不是直接操作View层。这样就可以把视图操作和业务逻辑解耦,从而让Activity成为真正的View层。
但MVP也存在一些弊端:
Presenter(以下简称P)层与View(以下简称V)层是通过接口进行交互的,接口粒度不好控制。粒度太小,就会存在大量接口的情况,使代码太过碎版化;粒度太大,解耦效果不好。同时对于UI的输入和数据的变化,需要手动调用V层或者P层相关的接口,相对来说缺乏自动性、监听性。如果数据的变化能自动响应到UI、UI的输入能自动更新到数据,那该多好!
MVP是以UI为驱动的模型,更新UI都需要保证能获取到控件的引用,同时更新UI的时候要考虑当前是否是UI线程,也要考虑Activity的生命周期(是否已经销毁等)。
MVP是以UI和事件为驱动的传统模型,数据都是被动地通过UI控件做展示,但是由于数据的时变性,我们更希望数据能转被动为主动,希望数据能更有活性,由数据来驱动UI。
V层与P层还是有一定的耦合度。一旦V层某个UI元素更改,那么对应的接口就必须得改,数据如何映射到UI上、事件监听接口这些都需要转变,牵一发而动全身。如果这一层也能解耦就更好了。
复杂的业务同时也可能会导致P层太大,代码臃肿的问题依然不能解决。
MVVM
View:对应于Activity和XML,负责View的绘制以及与用户交互。
Model:实体模型。
ViewModel:负责完成View与Model间的交互,负责业务逻辑。
MVVM的目标和思想与MVP类似,利用数据绑定(Data Binding)、依赖属性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一个更加灵活高效的架构。
数据驱动
在常规的开发模式中,数据变化需要更新UI的时候,需要先获取UI控件的引用,然后再更新UI。获取用户的输入和操作也需要通过UI控件的引用。在MVVM中,这些都是通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,数据成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多。
低耦合度
MVVM模式中,数据是独立于UI的。
数据和业务逻辑处于一个独立的ViewModel中,ViewModel只需要关注数据和业务逻辑,不需要和UI或者控件打交道。UI想怎么处理数据都由UI自己决定,ViewModel不涉及任何和UI相关的事,也不持有UI控件的引用。即便是控件改变了(比如:TextView换成EditText),ViewModel也几乎不需要更改任何代码。它非常完美的解耦了View层和ViewModel,解决了上面我们所说的MVP的痛点。
更新UI
在MVVM中,数据发生变化后,我们在工作线程直接修改(在数据是线程安全的情况下)ViewModel的数据即可,不用再考虑要切到主线程更新UI了,这些事情相关框架都帮我们做了。
团队协作
MVVM的分工是非常明显的,由于View和ViewModel之间是松散耦合的:一个是处理业务和数据、一个是专门的UI处理。所以,完全由两个人分工来做,一个做UI(XML和Activity)一个写ViewModel,效率更高。
可复用性
一个ViewModel可以复用到多个View中。同样的一份数据,可以提供给不同的UI去做展示。对于版本迭代中频繁的UI改动,更新或新增一套View即可。如果想在UI上做A/B Testing,那MVVM是你不二选择。
单元测试
有些同学一看到单元测试,可能脑袋都大。是啊,写成一团浆糊的代码怎么可能做单元测试?如果你们以代码太烂无法写单元测试而逃避,那可真是不好的消息了。这时候,你需要MVVM来拯救。
我们前面说过了,ViewModel层做的事是数据处理和业务逻辑,View层中关注的是UI,两者完全没有依赖。不管是UI的单元测试还是业务逻辑的单元测试,都是低耦合的。在MVVM中数据是直接绑定到UI控件上的(部分数据是可以直接反映出UI上的内容),那么我们就可以直接通过修改绑定的数据源来间接做一些Android UI上的测试。
通过上面的简述以及模式的对比,我们可以发现MVVM的优势还是非常明显的。虽然目前Android开发中可能真正在使用MVVM的很少,但是值得我们去做一些探讨和调研。
如何构建MVVM应用框架
如何分工
构建MVVM框架首先要具体了解各个模块的分工。接下来我们来讲解View、ViewModel、Model它们各自的职责所在。
View
View层做的就是和UI相关的工作,我们只在XML、Activity和Fragment写View层的代码,View层不做和业务相关的事,也就是我们在Activity不写业务逻辑和业务数据相关的代码,更新UI通过数据绑定实现,尽量在ViewModel里面做(更新绑定的数据源即可),Activity要做的事就是初始化一些控件(如控件的颜色,添加RecyclerView的分割线),View层可以提供更新UI的接口(但是我们更倾向所有的UI元素都是通过数据来驱动更改UI),View层可以处理事件(但是我们更希望UI事件通过Command来绑定)。 简单地说:View层不做任何业务逻辑、不涉及操作数据、不处理数据,UI和数据严格的分开。
ViewModel
ViewModel层做的事情刚好和View层相反,ViewModel只做和业务逻辑和业务数据相关的事,不做任何和UI相关的事情,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,做的事情也都只是对数据的操作(这些数据绑定在相应的控件上会自动去更改UI)。同时DataBinding框架已经支持双向绑定,让我们可以通过双向绑定获取View层反馈给ViewModel层的数据,并对这些数据上进行操作。关于对UI控件事件的处理,我们也希望能把这些事件处理绑定到控件上,并把这些事件的处理统一化,为此我们通过BindingAdapter对一些常用的事件做了封装,把一个个事件封装成一个个Command,对于每个事件我们用一个ReplyCommand 去处理就行了,ReplyCommand 会把你可能需要的数据带给你,这使得我们在Vie,具体见 MVVM Light Toolkit 使用指南的 Command 部分 。再强调一遍:ViewModel 不做和UI相关的事。
Model
Model层最大的特点是被赋予了数据获取的职责,与我们平常Model层只定义实体对象的行为截然不同。实例中,数据的获取、存储、数据状态变化都是Model层的任务。Model包括实体模型(Bean)、Retrofit的Service ,获取网络数据接口,本地存储(增删改查)接口,数据变化监听等。Model提供数据获取接口供ViewModel调用,经数据转换和操作并最终映射绑定到View层某个UI元素的属性上。
如何协作
关于协作,我们先来看下面的一张图:

上图反应了MVVM框架中各个模块的联系和数据流的走向,我们从每个模块一一拆分来看。那么我们重点就是下面的三个协作。
ViewModel与View的协作 。
ViewModel与Model的协作 。
ViewModel与ViewModel的协作 。
ViewModel与View的协作

图2中ViewModel和View是通过绑定的方式连接在一起的,绑定分成两种:一种是数据绑定,一种是命令绑定。数据的绑定DataBinding已经提供好了,简单地定义一些ObservableField就能把数据和控件绑定在一起了(如TextView的text属性),但是DataBinding框架提供的不够全面,比如说如何让一个URL绑定到一个ImageView,让这个ImageView能自动去加载url指定的图片,如何把数据源和布局模板绑定到一个ListView,让ListView可以不需要去写Adapter和ViewHolder相关的东西?这些就需要我们做一些工作和简单的封装。MVVM Light Toolkit 已经帮我们做了一部分的工作,关于事件绑定也是一样,MVVM Light Toolkit 做了简单的封装,对于每个事件我们用一个ReplyCommand去处理就行了,ReplyCommand 会把可能需要的数据带给你,这样我们处理事件的时候也只关心处理数据就行了.
由 图 1 中ViewModel的模块中我们可以看出ViewModel类下面一般包含下面5个部分:
Context (上下文)
Model (数据源 java Bean)
Data Field (数据绑定)
Command (命令绑定)
Child ViewModel (子ViewModel)
我们先来看下示例代码,然后再一一讲解5个部分是干嘛用的:
//context
private Activity context;
//model(数据源 Java Bean)
private NewsService.News news;
private TopNewsService.News topNews;
//数据绑定,绑定到UI的字段(data field)
public final ObservableField<String> imageUrl = new ObservableField<>();
public final ObservableField<String> html = new ObservableField<>();
public final ObservableField<String> title = new ObservableField<>();
// 一个变量包含了所有关于View Style 相关的字段
public final ViewStyle viewStyle = new ViewStyle();
//命令绑定(command)
public final ReplyCommand onRefreshCommand = new ReplyCommand<>(() -> {

})
public final ReplyCommand<Integer> onLoadMoreCommand = new ReplyCommand<>((itemCount) -> {

});

//Child ViewModel
public final ObservableList<NewItemViewModel> itemViewModel = new ObservableArrayList<>();

/** * ViewStyle 关于控件的一些属性和业务数据无关的Style 可以做一个包裹,这样代码比较美观,
ViewModel 页面也不会有太多太杂的字段。 **/
public static class ViewStyle {
public final ObservableBoolean isRefreshing = new ObservableBoolean(true);
public final ObservableBoolean progressRefreshing = new ObservableBoolean(true);
}

Context
Context是干嘛用的呢,为什么每个ViewModel都最好需要持了一个Context的引用呢?ViewModel不处理和UI相关的事也不操作控件,更不更新UI,那为什么要有Context呢?原因
Model是什么呢?其实就是数据源,可以简单理解是我们用JSON转过来的Bean。ViewModel要把数据映射到UI中可能需要大量对Model的数据拷贝和操作,拿Model的字段去生成对应的ObservableField然后绑定到UI(我们不会直接拿Model的数据去做绑定展示),这里是有必要在一个ViewModel保留原始的Model引用,这对于我们是非常有用的,因为可能用户的某些操作和输入需要我们去改变数据源,可能我们需要把一个Bean在列表页点击后传给详情页,可能我们需要把这个Model当做表单提交到服务器。这些都需要我们的ViewModel持有相应的Model(数据源)。
Data Field(数据绑定)
Data Field就是需要绑定到控件上的ObservableField字段,这是ViewModel的必需品,这个没有什么好说。但是这边有一个建议:
这些字段是可以稍微做一下分类和包裹的。比如说可能一些字段是绑定到控件的一些Style属性上(如长度、颜色、大小),对于这类针对View Style的的字段可以声明一个ViewStyle类包裹起来,这样整个代码逻辑会更清晰一些,不然ViewModel里面可能字段泛滥,不易管理和阅读性较差。而对于其他一些字段,比如说title、imageUrl、name这些属于数据源类型的字段,这些字段也叫数据字段,是和业务数据和逻辑息息相关的,这些字段可以放在一块。
Command(命令绑定)
Command(命令绑定)简言之就是对事件的处理(下拉刷新、加载更多、点击、滑动等事件处理)。我们之前处理事件是拿到UI控件的引用,然后设置Listener,这些Listener其实就是Command。但是考虑到在一个ViewModel写各种Listener并不美观,可能实现一个Listener就需要实现多个方法,但是我们可能只想要其中一个有用的方法实现就好了。更重要一点是实现一个Listener可能需要写一些UI逻辑才能最终获取我们想要的。简单举个例子,比如你想要监听ListView滑到最底部然后触发加载更多的事件,这时候就要在ViewModel里面写一个OnScrollListener,然后在里面的onScroll方法中做计算,计算什么时候ListView滑动底部了。其实ViewModel的工作并不想去处理这些事件,它专注做的应该是业务逻辑和数据处理,如果有一个东西不需要你自己去计算是否滑到底部,而是在滑动底部自动触发一个Command,同时把当前列表的总共的item数量返回给你,方便你通过 page=itemCount/LIMIT+1去计算出应该请求服务器哪一页的数据那该多好啊。MVVM Light Toolkit 帮你实现了这一点:
public final ReplyCommand<Integer> onLoadMoreCommand = new ReplyCommand<>((itemCount) -> {
int page=itemCount/LIMIT+1;
loadData(page.LIMIT)
});

接着在XML布局文件中通过bind:onLoadMoreCommand绑定上去就行了。
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
bind:onLoadMoreCommand="@{viewModel.loadMoreCommand}"/>
x

当然Command并不是必须的,你完全可以依照自己的习惯和喜好在ViewModel写Listener,不过使用Command可以使ViewModel更简洁易读。你也可以自己定义更多的、其他功能的Command,那么ViewModel的事件处理都是托管ReplyCommand 来处理,这样的代码看起来会比较美观和清晰。Command只是对UI事件的一层隔离UI层的封装,在事件触发时把ViewModel层可能需要的数据传给ViewModel层,对事件的处理做了统一化,是否使用的话,还是看你个人喜好了。
Child ViewModel(子ViewModel)
子ViewModel的概念就是在ViewModel里面嵌套其他的ViewModel,这种场景还是很常见的。比如说你一个Activity里面有两个Fragment,ViewModel是以业务划分的,两个Fragment做的业务不一样,自然是由两个ViewModel来处理,这时候Activity对应的ViewModel里面可能包含了两个Fragment各自的ViewModel,这就是嵌套的子ViewModel。还有另外一种就是对于AdapterView,如ListView RecyclerView、ViewPager等。
//Child ViewModelpublic final
ObservableList<ItemViewModel> itemViewModel = new ObservableArrayList<>();

它们的每个Item其实就对应于一个ViewModel,然后在当前的ViewModel通过ObservableList 持有引用(如上述代码),这也是很常见的嵌套的子ViewModel。我们其实还建议,如果一个页面业务非常复杂,不要把所有逻辑都写在一个ViewModel,可以把页面做业务划分,把不同的业务放到不同的ViewModel,然后整合到一个总的ViewModel,这样做起来可以使我们的代码业务清晰、简短意赅,也方便后人的维护。
总的来说,ViewModel和View之前仅仅只有绑定的关系,View层需要的属性和事件处理都是在XML里面绑定好了,ViewModel层不会去操作UI,只是根据业务要求处理数据,这些数据自动映射到View层控件的属性上。关于ViewModel类中包含哪些模块和字段,这个需要开发者自己去衡量,我们建议ViewModel不要引入太多的成员变量,成员变量最好只有上面的提到的5种(context、model……),能不引入其他类型的变量就尽量不要引进来,太多的成员变量对于整个代码结构破坏很大,后面维护的人要时刻关心成员变量什么时候被初始化、什么时候被清掉、什么时候被赋值或者改变,一个细节不小心可能就出现潜在的Bug。太多不清晰定义的成员变量又没有注释的代码是很难维护的。
另外,我们会把UI控件的属性和事件都通过XML(如bind:text=@{...})绑定。如果一个业务逻辑要弹一个Dialog,但是你又不想在ViewModel里面做弹窗的事(ViewModel不希望做UI相关的事)或者说改变ActionBar上面的图标的颜色,改变ActionBar按钮是否可点击,这些都不是写在XML里面(都是用Java代码初始化的),如何对这些控件的属性做绑定呢?我们先来看下代码:
public class MainViewModel implements ViewModel {
....
//true的时候弹出Dialog,false的时候关掉dialog
public final ObservableBoolean isShowDialog = new ObservableBoolean();
....
.....
}
// 在View层做一个对isShowDialog改变的监听
public class MainActivity extends RxBasePmsActivity {

private MainViewModel mainViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
.....
mainViewModel.isShowDialog.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
if (mainViewModel.isShowDialog.get()) {
dialog.show();
} else {
dialog.dismiss();
}
}
});
}
...
}

简单地说你可以对任意的ObservableField做监听,然后根据数据的变化做相应UI的改变,业务层ViewModel只要根据业务处理数据就行,以数据来驱动UI。

❸ android 怎么在oncreate中获得view的坐标

这个问题大家肯定遇到过不止一次,其实很简单,解决它也很容易,但是咱们追求的毕竟不是解决它,而是找到几种方法去解决,并且这么解决的原理是什么。
这里列出4种解决方案:
Activity/View#onWindowFocusChanged
这个函数的含义是:view已经初始化完毕了,宽/高已经准备好了,这个时候去获取宽高是可以成功获取的。但是需要注意的是onWindowFocusChanged函数会被调用多次,当Activity的窗口得到焦点和失去焦点时均会被调用一次,如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}123456

view.post(runnable)
通过post可以将一个runnable投递到消息队列的尾部,然后等待UI线程Looper调用此runnable的时候,view也已经初始化好了。
v_view1.post(new Runnable() {
@Override
public void run() {
L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});1234567

ViewTreeObserver
使用ViewTreeObserver的众多回调可以完成这个功能,比如使用OnGlobalLayoutListener这个接口,当view树的状态发生改变或者view树内部的view的可见性发生改变时,onGlobalLayout方法将被回调,因此这是获取view的宽高一个很好的时机。需要注意的是,伴随着view树的状态改变等,onGlobalLayout会被调用多次。
v_view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});1234567

再来详细介绍一下ViewTreeObserver这个类,这个类是用来注册当view tree全局状态改变时的回调监听器,这些全局事件包括很多,比如整个view tree视图的布局,视图绘制的开始,点击事件的改变等等。还有千万不要在应用程序中实例化ViewTreeObserver对象,因为该对象仅是由视图提供的。
ViewTreeObserver类提供了几个相关函数用来添加view tree的相关监听器:
public void addOnDrawListener (ViewTreeObserver.OnDrawListener listener)
该函数为api 16版本中添加,作用是注册在该view tree将要绘制时候的回调监听器,注意该函数和相关的remove函数不能在监听器回调的onDraw()中调用。
public void (ViewTreeObserver.OnGlobalFocusChangeListener listener)
该函数用来注册在view tree焦点改变时候的回调监听器。
public void addOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener listener)
该函数用来注册在该view tree中view的全局布局属性改变或者可见性改变时候的回调监听器。
public void addOnPreDrawListener (ViewTreeObserver.OnPreDrawListener listener)
该函数用来注册当view tree将要被绘制时候(view 的 onDraw 函数之前)的回调监听器。
public void addOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener listener)
该函数用来注册当view tree滑动时候的回调监听器,比如用来监听ScrollView的滑动状态。
public void addOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener listener)
该函数用来注册当view tree的touch mode改变时的回调监听器,回调函数onTouchModeChanged (boolean isInTouchMode)中的isInTouchMode为该view tree的touch mode状态。
public void addOnWindowAttachListener (ViewTreeObserver.OnWindowAttachListener listener)
api 18添加,该函数用来注册当view tree被附加到一个window上时的回调监听器。
public void (ViewTreeObserver.OnWindowFocusChangeListener listener)
api 18添加,该函数用来注册当window中该view tree焦点改变时候的回调监听器。
而且对应每一个add方法都会有一个remove方法用来删除相应监听器。

view.measure(int widthMeasureSpec, int heightMeasureSpec)
通过手动对view进行measure来得到view的宽/高,这种情况比较复杂,这里要分情况处理,根据view的layoutparams来分:
match_parent
直接放弃,无法measure出具体的宽/高。原因很简单,根据view的measure过程,构造此种MeasureSpec需要知道parentSize,即父容器的剩余空间,而这个时候我们无法知道parentSize的大小,所以理论上不可能测量处view的大小。
wrap_content
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);123

注意到(1<<30)-1,我们知道MeasureSpec的前2位为mode,后面30位为size,所以说我们使用最大size值去匹配该最大化模式,让view自己去计算需要的大小。
具体的数值(dp/px)
这种模式下,只需要使用具体数值去measure即可,比如宽/高都是100px:
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);123

源码和结果
demo代码如下
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/v_view1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>

<View
android:id="@+id/v_view2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@mipmap/ic_launcher"/>

</LinearLayout>12345678910111213141516171819

activity:
public class MainActivity extends BaseActivity{
private View v_view1;
private View v_view2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

v_view1 = findViewById(R.id.v_view1);
v_view2 = findViewById(R.id.v_view2);

L.i("normal: v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());

v_view1.post(new Runnable() {
@Override
public void run() {
L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});

v_view1.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);
L.i("measure : v_view1.getMeasuredWidth():" + v_view1.getMeasuredWidth()
+ " v_view1.getMeasuredHeight():" + v_view1.getMeasuredHeight());
v_view2.measure(widthMeasureSpec, heightMeasureSpec);
L.i("measure : v_view2.getMeasuredWidth():" + v_view2.getMeasuredWidth()
+ " v_view2.getMeasuredHeight():" + v_view2.getMeasuredHeight());

}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
}
041424344454647484950

log日志:
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:28): normal: v_view1.getWidth():0 v_view1.getHeight():0
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:50): measure : v_view1.getMeasuredWidth():144 v_view1.getMeasuredHeight():144
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:53): measure : v_view2.getMeasuredWidth():16777215 v_view2.getMeasuredHeight():16777215
I/[PID:2659]: [TID:1] 2.onGlobalLayout(line:42): ViewTreeObserver : v_view1.getWidth():144 v_view1.getHeight():144
I/[PID:2659]: [TID:1] 1.run(line:34): post(Runnable) : v_view1.getWidth():144 v_view1.getHeight():144
I/[PID:2659]: [TID:1] MainActivity.onWindowFocusChanged(line:61): onWindowFocusChanged : v_view1.getWidth():144 v_view1.getHeight():144123456

界面:

小的为view_1,大的为view_2,从log日志中就发现有问题了:view_2视图使用measure之后计算出来的宽高是错误的,所以View类的视图使用measure计算出来的结果是不准确的,这点需要特别特别注意。

❹ SurfaceFlinger 原理分析

SurfaceFlinger是Android multimedia的一个部分,在Android 的实现中它是一个service,提供系统范围内的surface composer功能,它能够将各种应用程序的2D、3D surface进行组合。

每个应用程序可能对应着一个或者多个图形界面,而每个界面我们就称之为一个surface ,或者说是window ,在上面的图中我们能看到4 个surface ,一个是home 界面,还有就是红、绿、蓝分别代表的3个surface ,而两个button 实际是home surface 里面的内容。我们需要考虑一下情况:

在实际中对这些Surface 进行merge 可以采用两种方式,一种就是采用软件的形式来merge ,还一种就是采用硬件的方式,软件的方式就是我们的 SurfaceFlinger ,而硬件的方式就是 Overlay

因为硬件merge 内容相对简单,我们首先来看overlay 。以IMX51 为例子,当IPU 向内核申请FB 的时候它会申请3 个FB ,一个是主屏的,还一个是副屏的,还一个就是Overlay 的。 简单地来说,Overlay就是我们将硬件所能接受的格式数据和控制信息送到这个Overlay FrameBuffer,由硬件驱动来负责merge Overlay buffer和主屏buffer中的内容。

一般来说现在的硬件都只支持一个Overlay,主要用在视频播放以及camera preview上,因为视频内容的不断变化用硬件Merge比用软件Merge要有效率得多,下面就是使用Overlay和不使用Overlay的过程:

surfaceFlinger 只是负责 merge Surface 的控制,比如说计算出两个 Surface 重叠的区域,至于 Surface 需要显示的内容,则通过 skia,opengl 和 pixflinger 来计算。
创建过程

SurfaceFlinger 是一个线程类,它继承了 Thread 类。当创建 SurfaceFlinger 这个服务的时候会启动一个 SurfaceFlinger 监听线程,这个线程会一直等待事件的发生,比如说需要进行 sruface flip ,或者说窗口位置大小发生了变化等,一旦产生这些事件,SurfaceComposerClient 就会通过 IBinder 发出信号,这个线程就会结束等待处理这些事件,处理完成以后会继续等待,如此循环。
SurfaceComposerClient 和 SurfaceFlinger 是通过 SurfaceFlingerSynchro 这个类来同步信号的,其实说穿了就是一个条件变量。监听线程等待条件的值一旦变成 OPEN 就结束等待并将条件置成 CLOSE 然后进行事件处理,处理完成以后再继续等待条件的值变成 OPEN ,而 Client 的Surface 一旦改变就通过 IBinder 通知 SurfaceFlinger 将条件变量的值变成 OPEN ,并唤醒等待的线程,这样就通过线程类和条件变量实现了一个动态处理机制。

窗口状态变化的处理是一个很复杂的过程,SurfaceFlinger 只是执行 Windows Manager 的指令,由 Windows manager 来决定什么是偶改变大小、位置、透明度、以及如何调整layer 之间的顺序, SurfaceFlinger 仅仅只是执行它的指令。

普通的Android控件,例如TextView、Button和CheckBox等,它们都是将自己的UI绘制在宿主窗口的绘图表面之上,这意味着它们的UI是在应用程序的主线程中进行绘制的。由于应用程序的主线程除了要绘制UI之外,还需要及时地响应用户输入,否则系统就会认为应用程序没有响应了。而对于一些游戏画面,或者摄像头预览、视频播放来说,它们的UI都比较复杂,而且要求能够进行高效的绘制。这时候就必须要给那些需要复杂而高效UI的视图生成一个独立的绘图表面,以及使用一个独立的线程来绘制这些视图的UI。

SurfaceFlinger服务运行在Android系统的System进程中,它负责管理Android系统的帧缓冲区(Frame Buffer)。Android应用程序为了能够将自己的UI绘制在系统的帧缓冲区上,它们就必须要与SurfaceFlinger服务进行通信。

在APP端执行draw的时候,数据很明显是要绘制到APP的进程空间,但是视图窗口要经过SurfaceFlinger图层混排才会生成最终的帧,而SurfaceFlinger又运行在另一个独立的服务进程,那么View视图的数据是如何在两个进程间传递的呢,普通的Binder通信肯定不行,因为Binder不太适合这种数据量较大的通信,那么View数据的通信采用的是什么IPC手段呢?答案就是共享内存,更精确的说是匿名共享内存。共享内存是Linux自带的一种IPC机制,Android直接使用了该模型,不过做出了自己的改进,进而形成了Android的匿名共享内存(Anonymous Shared Memory-Ashmem)。通过Ashmem,APP进程同SurfaceFlinger共用一块内存,如此,就不需要进行数据拷贝,APP端绘制完毕,通知SurfaceFlinger端合成,再输出到硬件进行显示即可。

在每一个Android应用程序与SurfaceFlinger服务之间的连接上加上一块用来传递UI元数据的匿名共享内存,这个共享内存就是 SharedClient

在每一个SharedClient里面,有至多31个SharedBufferStack。SharedBufferStack就是Android应用程序和SurfaceFlinger 的缓冲区堆栈。用来缓冲 UI 元数据。
一般我们就绘制UI的时候,都会采用一种称为“双缓冲”的技术。双缓冲意味着要使用两个缓冲区,其中一个称为Front Buffer,另外一个称为Back Buffer。UI总是先在Back Buffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。这下就可以理解SharedBufferStack的含义了吧?SurfaceFlinger服务只不过是将传统的“双缓冲”技术升华和抽象为了一个SharedBufferStack。可别小看了这个升华和抽象,有了SharedBufferStack之后,SurfaceFlinger 服务就可以使用N个缓冲区技术来绘制UI了。N值的取值范围为2到16。例如,在Android 2.3中,N的值等于2,而在Android 4.1中,据说就等于3了。

在SurfaceFlinger服务中,每一个SharedBufferStack都对应一个Surface,即一个窗口。这样,我们就可以知道为什么每一个SharedClient里面包含的是一系列SharedBufferStack而不是单个SharedBufferStack: 一个SharedClient对应一个Android应用程序,而一个Android应用程序可能包含有多个窗口 ,即Surface。从这里也可以看出,一个Android应用程序至多可以包含31个Surface。

SharedBufferStack中的 缓冲区只是用来描述UI元数据的 ,这意味着它们不包含真正的UI数据。 真正的UI数据保存在GraphicBuffer中 ,后面我们再描述GaphicBuffer。因此,为了完整地描述一个UI,SharedBufferStack中的每一个已经使用了的缓冲区都对应有一个GraphicBuffer,用来描述真正的UI数据。当SurfaceFlinger服务缓制Buffer-1和Buffer-2的时候,就会找到与它们所对应的GraphicBuffer,这样就可以将对应的UI绘制出来了。

当Android应用程序需要 更新一个Surface 的时候,它就会找到与它所对应的SharedBufferStack,并且从它的空闲缓冲区列表的尾部取出一个空闲的Buffer。我们假设这个取出来的空闲Buffer的编号为index。接下来Android应用程序就请求SurfaceFlinger服务为这个编号为index的 Buffer分配一个图形缓冲区GraphicBuffer

SurfaceFlinger 服务分配好图形缓冲区 GraphicBuffer 之后,会将它的编号设置为 index,然后再将这个图形缓冲区 GraphicBuffer 返回给 Android 应用程序访问。Android应用程序得到了 SurfaceFlinger 服务返回的图形缓冲区 GraphicBuffer 之后,就在里面 写入UI数据 。写完之后,就将与它所对应的缓冲区,即编号为 index 的 Buffer,插入到对应的 SharedBufferStack 的已经使用了的 缓冲区列表的头部 去。这一步完成了之后,Android 应用程序就通知 SurfaceFlinger 服务去绘制那些保存在已经使用了的缓冲区所描述的图形缓冲区GraphicBuffer了。用上面例子来说,SurfaceFlinger服务需要绘制的是编号为1和2的Buffer所对应的图形缓冲区GraphicBuffer。由于SurfaceFlinger服务知道编号为1和2的 Buffer 所对应的图形缓冲区 GraphicBuffer 在哪里,因此,Android 应用程序只需要告诉 SurfaceFlinger 服务要绘制的 Buffer 的编号就OK了。 当一个已经被使用了的Buffer被绘制了之后,它就重新变成一个空闲的 Buffer 了

SharedBufferStack 是在 Android 应用程序和 SurfaceFlinger 服务之间共享的,但是,Android 应用程序和 SurfaceFlinger 服务使用 SharedBufferStack 的方式是不一样的,具体来说,就是 Android 应用程序关心的是它里面的空闲缓冲区列表,而 SurfaceFlinger 服务关心的是它里面的已经使用了的缓冲区列表。从SurfaceFlinger服务的角度来看,保存在 SharedBufferStack中 的已经使用了的缓冲区其实就是在排队等待渲染。

为了方便 SharedBufferStack 在 Android 应用程序和 SurfaceFlinger 服务中的访问,Android 系统分别使用 SharedBufferClient 和 SharedBufferServer 来描述 SharedBufferStack ,其中,SharedBufferClient 用来在Android 应用程序这一侧访问 SharedBufferStack 的空闲缓冲区列表,而 SharedBufferServer 用来在SurfaceFlinger 服务这一侧访问 SharedBufferStack 的排队缓冲区列表。

只要 SharedBufferStack 中的 available 的 buffer 的数量大于0, SharedBufferClient 就会将指针 tail 往前移一步,并且减少 available 的值,以便可以获得一个空闲的 Buffer。当 Android 应用程序往这个空闲的 Buffer 写入好数据之后,它就会通过 SharedBufferClient 来将它添加到 SharedBufferStack 中的排队缓冲区列表的尾部去,即指针 queue_head 的下一个位置上。

当 Android 应用程序通知 SurfaceFlinger 服务更新UI的时候,只要对应的 SharedBufferStack 中的 queued 的缓冲区的数量大于0,SharedBufferServer 就会将指针 head 的下一个Buffer绘制出来,并且将指针 head 向前移一步,以及将 queued 的值减1。

参考:
https://blog.csdn.net/luoshengyang/article/details/7846923

❺ 如何使用ScrollView

填充图案的定义也有两种,一种是按毫米定义的,一种是按英寸定义的,在公制图纸中通常是按毫米定义的。
一、结构
public class ScrollView extends FrameLayout
java.lang.Object
Android.view.View
android.view.ViewGroup
android.widget.FrameLayout
android.widget.ScrollView
二、概述
一种可供用户滚动的层次结构布局容器,允许显示比实际多的内容。ScrollView是一种FrameLayout,意味需要在其上放置有自己滚动内容的子元素。子元素可以是一个复杂的对象的布局管理器。通常用的子元素是垂直方向的LinearLayout,显示在最上层的垂直方向可以让用户滚动的箭头。
TextView类也有自己的滚动功能,所以不需要使用ScrollView,但是只有两个结合使用,才能保证显示较多内容时候的效率。但只有两者结合使用才可以实现在一个较大的容器中一个文本视图效果。
ScrollView只支持垂直方向的滚动。

三、构造函数
public ScrollView (Context context)
创建一个默认属性的ScrollView实例
public ScrollView (Context context, AttributeSet attrs)
创建一个带有attrs属性的ScrollView 实例。
public ScrollView (Context context, AttributeSet attrs, int defStyle)
创建一个带有attrs属性,并且指定其默认样式的ScrollView实例。
四、公共方法
public void addView (View child)
添加子视图。如果事先没有给子视图设置layout参数,会采用当前ViewGroup的默认参数来设置子视图。
参数
child 所添加的子视图
public void addView (View child, int index)
添加子视图。如果事先没有给子视图设置layout参数,会采用当前ViewGroup的默认参数来设置子视图。
参数
child 所添加的子视图
index 添加子视图的位置
public void addView (View child, int index, ViewGroup.LayoutParams params)
根据指定的layout参数添加子视图
参数
child 所添加的子视图
index 添加子视图的位置
params 为子视图设置的layout参数

public void addView (View child, ViewGroup.LayoutParams params)
根据指定的layout参数添加子视图。
参数
child 所添加的子视图
params 为子视图设置的layout参数

public boolean arrowScroll (int direction)
响应点击上下箭头时对滚动条滚动的处理。
参数
direction 按下的箭头所对应的方向
返回值
如果我们处理(消耗)了此事件返回true,否则返回false。

public void computeScroll ()
被父视图调用,用于必要时候对其子视图的值(mScrollX和mScrollY)进行更新。典型的情况如:父视图中某个子视图使用一个Scroller对象来实现滚动操作,会使得此方法被调用。

public boolean dispatchKeyEvent (KeyEvent event)
发送一个key事件给当前焦点路径的下一个视图。此焦点路径从视图树的顶层执行直到当前焦点视图。如果此视图为焦点视图,将为自己发送。否则,会为当前焦点路径的下一个节点发送。此方法也会激起一个key监听器。
参数
event 发送的key事件
返回值
事件被处理返回true,否则返回false。

public void draw (Canvas canvas)
手动绘制视图(及其子视图)到指定的画布(Canvas)。这个视图必须在调用这个函数之前做好了整体布局。当实现一个视图时,不需要继承这个方法;相反,你应该实现onDraw(Canvas)方法。
参数
canvas 绘制视图的画布

public boolean executeKeyEvent (KeyEvent event)
当接收到key事件时,用户可以调用此函数来使滚动视图执行滚动,类似于处理由视图体系发送的事件。
参数
event 需要执行key的事件
返回值
事件被处理返回true,否则返回false。

public void fling (int velocityY)
滚动视图的滑动(fling)手势。(译者注: 如何监听android的屏幕滑动停止事件)
参数
velocityY Y方向的初始速率。正值表示手指/光标向屏幕下方滑动,而内容将向上滚动。

public boolean fullScroll (int direction)
对响应“home/end”短按时响应滚动处理。此方法将视图滚动到顶部或者底部,并且将焦点置于新的可视区域的最顶部/最底部组件。若没有适合的组件做焦点,当前的ScrollView会收回焦点。
参数
direction滚动方向:FOCUS_UP表示视图向上滚动;FOCUS_DOWN表示视图向下滚动
返回值
若key事件被消耗(consumed)返回true,其他情况返回false。

public int getMaxScrollAmount ()
返回值
当前滚动视图响应箭头事件能够滚动的最大数。

public boolean isFillViewport ()
指示当前ScrollView的内容是否被拉伸以填充视图可视范围(译者注:viewport可视范围,参见决定Scrollviewer里面Control的可视范围)。
返回值
内容填充视图返回true,否则返回false。

public boolean isSmoothScrollingEnabled ()
返回值
按箭头方向滚动时,是否显示滚动的平滑效果。

public boolean onInterceptTouchEvent (MotionEvent ev)
实现此方法是为了拦截所有触摸屏幕时的运动事件。可以像处理发送给子视图的事件一样去监视这些事件,并且获取当前手势在任意点的ownership
使用此方法时候需要注意,因为它与View.onTouchEvent(MotionEvent)有相当复杂的交互,并且前提需要正确执行View.onTouchEvent(MotionEvent)。事件将按照如下顺序接收到:
1. 收到down事件
2. Down事件或者由视图组的一个子视图处理,或者被用户自己的onTouchEvent()方法处理;此处理意味你应该执行onTouchEvent()时返回true,这样才能继续看到剩下的手势(取代找一个父视图处理)。如果onTouchEvent()返回true时,你不会收到onInterceptTouchEvent()的任何事件并且所有对触摸的处理必须在onTouchEvent()中发生。
3. 如果此方法返回false,接下来的事件(up to and including the final up)将最先被传递当此,然后是目标的onTouchEvent()。
4. 如果返回true,将不会收到以下任何事件:目标view将收到同样的事件但是会伴随ACTION_CANCEL,并且所有的更进一步的事件将会传递到你自己的onTouchEvent()方法中而不会再在这里出现。
参数
ev 体系向下发送的动作事件
返回值
如果将运动事件从子视图中截获并且通过onTouchEvent()发送到当前ViewGroup ,返回true。当前目标将会收到ACTION_CANCEL事件,并且不再会有其他消息传递到此。
(译者注:onInterceptTouchEvent和onTouchEvent调用时序)

public boolean onTouchEvent (MotionEvent ev)
执行此方法为了处理触摸屏幕的运动事件。
参数
ev 运动事件
返回值
事件被处理返回true,其它返回false。

public boolean pageScroll (int direction)
响应短按“page up/ down”时候对滚动的处理。此方法将向上或者向下滚动一屏,并且将焦点置于新可视区域的最上/最下。如果没有适合的component作为焦点,当前scrollView将收回焦点。
参数
direction 滚动方向:FOCUS_UP表示向上翻一页,FOCUS_DOWN表示向下翻一页。
返回值
此key事件被消耗(cosumed)返回true,其他返回false。

public void requestChildFocus (View child, View focused)
当父视图的一个子视图的要获得焦点时,调用此方法。
参数
child 要获得焦点的父视图的子视图。此视图包含了焦点视图。如果没有特殊徐要求,此视图实际上就是焦点视图。
focused 子视图的子孙视图并且此子孙视图是真正的焦点视图

public boolean requestChildRectangleOnScreen (View child, Rect rectangle, boolean immediate)
当组里的某个子视图需要被定位在屏幕的某个矩形范围时,调用此方法。重载此方法的ViewGroup可确认以下几点:
* 子项目将是组里的直系子项
* 矩形将在子项目的坐标体系中
重载此方法的ViewGroup应该支持以下几点:
* 若矩形已经是可见的,则没有东西会改变
* 为使矩形区域全部可见,视图将可以被滚动显示
参数
child 发出请求的子视图
rectangle 子项目坐标系内的矩形,即此子项目希望在屏幕上的定位
immediate 设为true,则禁止动画和平滑移动滚动条
返回值
进行了滚动操作的这个组(group),是否处理此操作。
public void requestLayout ()
当有改变引起当前视图重新布局时,调用此函数。它将规划一个视图树的layout路径。

public void scrollTo (int x, int y)
设置当前视图滚动到的位置。此函数会引起对onScrollChanged(int, int, int, int)函数的调用并且会让视图更新。
当前版本取消了在子视图中的滚动。
参数
x 滚动到的X位置
y 滚动到的Y位置

public void setFillViewport (boolean fillViewport)
设置当前滚动视图是否将内容高度拉伸以填充视图可视范围(译者注:viewport可视范围,参见决定Scrollviewer里面Control的可视范围)。
参数
fillViewport 设置为true表示拉伸内容高度来适应视口边界;其他设为false。
public void setOverScrollMode (int mode)
为视图设置over-scroll模式。有效的over-scroll模式有OVER_SCROLL_ALWAYS(缺省值),OVER_SCROLL_IF_CONTENT_SCROLLS(只允许当视图内容大过容器时,进行over-scrolling)和OVER_SCROLL_NEVER。只有当视图可以滚动时,此项设置才起作用。
(译者注:这个函数是2.3 r1 中新增的,API Level 9。关于over-scroll这里译为弹性滚动,即,参见帖子:类似iPhone的弹性ListView滚动)
参数
mode The new over-scroll mode for this view.
public void setSmoothScrollingEnabled (boolean smoothScrollingEnabled)
用来设置箭头滚动是否可以引发视图滚动。
参数
smoothScrollingEnabled 设置箭头滚动是否可以引起内容的滚动的bool值
public final void smoothScrollBy (int dx, int dy)
类似于scrollBy(int, int),但是滚动时候是平缓的而不是立即滚动到某处。
参数
dx 在X方向滚动的像素数
dy 在Y方向滚动的像素数
public final void smoothScrollTo (int x, int y)
类似于scrollTo(int, int),但是滚动时候是平缓的而不是立即滚动到某处。
参数
x 要滚动到位置的X坐标
y 要滚动到位置的Y坐标
五、受保护方法
protected int (Rect rect)
计算X方向滚动的总合,以便在屏幕上显示子视图的完整矩形(或者,若矩形宽度超过屏幕宽度,至少要填满第一个屏幕大小)。
参数
rect 矩形
返回值
滚动差值
protected int computeVerticalScrollOffset ()
计算垂直方向滚动条的滑块的偏移。此值用来计算滚动条轨迹的滑块的位置。
范围可以以任意单位表示,但是必须与computeVerticalScrollRange()和computeVerticalScrollExtent()的单位一致。
缺省的偏移是在当前视图滚动的偏移。
返回值
滚动条的滑块垂直方向的偏移。
protected int computeVerticalScrollRange ()
滚动视图的可滚动范围是所有子元素的高度。
返回值
由垂直方向滚动条代表的所有垂直范围,缺省的范围是当前视图的画图高度。
protected float getBottomFadingEdgeStrength ()
返回滚动底部的能见度。能见度的值的范围是0.0(没有消失)到1.0(完全消失)之间。缺省的执行返回值为0.0或者1.0,而不是他们中间的某个值。滚动时子类需要重载这个方法来提供一个平缓的渐隐的实现。
返回值
滚动底部能见度,值的范围在浮点数0.0f到1.0f之间。
protected float getTopFadingEdgeStrength ()
返回滚动顶部的能见度。能见度的值的范围是0.0(没有消失)到1.0(完全消失)之间。缺省的执行返回值为0.0或者1.0,而不是他们中间的某个值。滚动时子类需要重载这个方法来提供一个平缓的渐隐的实现。
返回值
滚动顶部能见度,值的范围在浮点数0.0f到1.0f之间。
protected void measureChild (View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
要求当前视图的一个子视图测量自己,同时兼顾到当前视图的MeasureSpec的要求和它的空白。子视图必须有MarginLayoutParams。比较复杂的工作是在getChildMeasureSpec中完成的。
参数
child 需要自己测量的子视图
parentWidthMeasureSpec 当前视图要求的宽度
parentHeightMeasureSpec 当前视图要求的宽度
protected void measureChildWithMargins (View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
要求当前视图的一个子视图测量自己,同时兼顾到当前视图的MeasureSpec的要求和它的空白和边界。子视图必须有MarginLayoutParams。比较复杂的工作是在getChildMeasureSpec中完成的。
参数
child 需要测量的子视图
parentWidthMeasureSpec 当前视图要求的宽度
widthUsed 水平方向上由父视图使用的空白 (也可能是视图的其他子视图使用的)
parentHeightMeasureSpec 当前视图要求的宽度
heightUsed 垂直方向上由父视图使用的空白 (也可能是视图的其他子视图使用的)
protected void onLayout (boolean changed, int l, int t, int r, int b)
当前视图需要为子视图分配大小和位置时候调用,子类继承必须要重载此方法并调用自己子视图的layout函数。
参数
changed 当前视图的新的大小或者位置
l 相对父视图,左边界位置
t 相对父视图,上边界位置
r 相对父视图,右边界位置
b 相对父视图,下边界位置
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
测量视图以确定其内容宽度和高度。此方法被measure(int, int)调用。需要被子类重写以提供对其内容准确高效的测量。
约定:当重写此方法时,你必须调用setMeasuredDimension(int, int)来保存当前视图view的宽度和高度。不成功调用此方法将会导致一个IllegalStateException异常,是由measure(int, int)抛出。所以调用父类的onMeasure(int, int)方法是必须的。
父类的实现是以背景大小为默认大小,除非MeasureSpec(测量细则)允许更大的背景。子类可以重写onMeasure(int,int)以对其内容提供更佳的尺寸。
如果此方法被重写,那么子类的责任是确认测量高度和测量宽度要大于视图view的最小宽度和最小高度(getSuggestedMinimumHeight() 和 getSuggestedMinimumWidth()),使用这两个方法可以取得最小宽度和最小高度。
参数
widthMeasureSpec 受主窗口支配的水平空间要求。这个需求通过 View.MeasureSpec.进行编码。
heightMeasureSpec 受主窗口支配的垂直空间要求。这个需求通过 View.MeasureSpec.进行编码。
protected void onOverScrolled (int scrollX, int scrollY, boolean clampedX, boolean clampedY)
被overScrollBy(int, int, int, int, int, int, int, int, boolean)调用,来对一个over-scroll操作的结果进行响应。(译者注:这个函数是2.3 r1 中新增的,API Level 9)
参数
scrollX 新的X滚动像素值
scrollY 新的Y滚动像素值
clampedX 当scrollX被over-scroll的边界限制时,值为true
clampedY 当scrollY被over-scroll的边界限制时,值为true
protected boolean onRequestFocusInDescendants (int direction, Rect previouslyFocusedRect)
当在滚动视图的子视图中查找焦点视图时,需要注意不要将焦点设置在滚动出屏幕外的控件上。此方法会比执行缺省的ViewGroup代价高,否则此行为也会设置为缺省
参数
direction 指定下列常量之一:FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT
previouslyFocusedRect 能够给出一个较好的提示的矩形(当前视图的坐标系统)表示焦点从哪里得来。如果没有提示为null。
返回值
是否取得了焦点
protected void onSizeChanged (int w, int h, int oldw, int oldh)
布局期间当视图的大小发生改变时调用。如果只是添加到视图,调用时显示的是旧值0。(译者注:也就是添加到视图时,oldw和oldh返回的是0)。
参数
w 视图当前宽度
h 视图当前高度
oldw 视图改变前的宽度
oldh 视图改变前的高度

❻ android属性动画详解

在 Android 动画中,总共有两种类型的动画View Animation(视图动画)和Property Animator(属性动画);其中
View Animation包括Tween Animation(补间动画)和Frame Animation(逐帧动画);
Property Animator包括ValueAnimator和ObjectAnimation;
首先,直观上,他们有如下三点不同:
1、引入时间不同: View Animation是API Level 1就引入的。Property Animation是API Level 11引入的,即Android 3.0才开始有Property Animation相关的API。
2、所在包名不同: View Animation在包android.view.animation中。而Property Animation API在包 android.animation中。
3、动画类的命名不同: View Animation中动画类取名都叫XXXXAnimation,而在Property Animator中动画类的取名则叫XXXXAnimator大家都知道逐帧动画主要是用来实现动画的,而补间动画才能实现控件的渐入渐出、移动、旋转和缩放的;而Property Animator是在Android 3.0版本才引入的,之前是没有的。
为什么还要引入Property Animator呢?
1、为什么引入Property Animator(属性动画)
我提出一个假设:请问大家,如何利用补间动画来将一个控件的背景色在一分钟内从绿色变为红色?这个效果想必没办法仅仅通过改变控件的渐入渐出、移动、旋转和缩放来实现吧,而这个效果是可以通过Property Animator完美实现的
**这就是第一个原因:Property Animator能实现补间动画无法实现的功能 **大家都知道,补间动画和逐帧动画统称为View Animation,也就是说这两个动画只能对派生自View的控件实例起作用;而Property Animator则不同,从名字中可以看出属性动画,应该是作用于控件属性的!正因为属性动画能够只针对控件的某一个属性来做动画,所以也就造就了他能单独改变控件的某一个属性的值!比如颜色!这就是Property Animator能实现补间动画无法实现的功能的最重要原因。
**我们得到了第二点不同:View Animation仅能对指定的控件做动画,而Property Animator是通过改变控件某一属性值来做动画的。
**假设我们将一个按钮从左上角利用补间动画将其移动到右下角,在移动过程中和移动后,这个按钮都是不会响应点击事件的。这是为什么呢?因为补间动画仅仅转变的是控件的显示位置而已,并没有改变控件本身的值。View Animation的动画实现是通过其Parent View实现的,在View被drawn时Parents View改变它的绘制参数,这样虽然View的大小或旋转角度等改变了,但View的实际属性没变,所以有效区域还是应用动画之前的区域;我们看到的效果仅仅是系统作用在按钮上的显示效果,利用动画把按钮从原来的位置移到了右下角,但按钮内部的任何值是没有变化的,所以按钮所捕捉的点击区域仍是原来的点击区域。(下面会举例来说明这个问题)
这就得到了第三点不同:补间动画虽能对控件做动画,但并没有改变控件内部的属性值。而Property Animator则是恰恰相反,Property Animator是通过改变控件内部的属性值来达到动画效果的

我们前面讲了Property Animator包括ValueAnimator和ObjectAnimator;这篇文章就主要来看看ValueAnimator的使用方法吧。
我觉得谷歌那帮老头是最会起名字的人,单从命名上,就能看出来这个东东的含义。ValueAnimator从名字可以看出,这个Animation是针对值的! ValueAnimator不会对控件做任何操作,我们可以给它设定从哪个值运动到哪个值,通过监听这些值的渐变过程来自己操作控件。 它会自己计算动画的过程,然后我们需要监听它的动画过程来自己操作控件。

这就是ValueAnimator的功能:ValueAnimator对指定值区间做动画运算,我们通过对运算过程做监听来自己操作控件。
总而言之就是两点:
1. ValueAnimator只负责对指定的数字区间进行动画运算
2. 我们需要对运算过程进行监听,然后自己对控件做动画操作

插值器的意义其实就相当于物理公式中的加速度参数,所以这也就是它也叫加速器的原因。 如何自定义插值器:

**input参数与任何我们设定的值没关系,只与时间有关,随着时间的增长,动画的进度也自然的增加,input参数就代表了当前动画的进度。而返回值则表示动画的当前数值进度 **

在getInterpolation函数中,我们将进度反转过来,当传0的时候,我们让它数值进度在完成的位置,当完成的时候,我们让它在开始的位置

ObjectAnimator是派生自ValueAnimator的,所以ValueAnimator中所能使用的方法,在ObjectAnimator中都可以正常使用。ObjectAnimator重写了几个方法,比如ofInt(),ofFloat()等。利用ObjectAnimator重写的ofFloat方法如何实现一个动画:(改变透明度)

前面我们都是定义多个值,即至少两个值之间的变化,那如果我们只定义一个值呢,如下面的方式:(同样以MyPointView为例)

仅且仅当我们只给动画设置一个值时,程序才会调用属性对应的get函数来得到动画初始值。如果动画没有初始值,那么就会使用系统默认值。比如ofInt()中使用的参数类型是int类型的,而系统的Int值的默认值是0,所以动画就会从0运动到100;也就是系统虽然在找到不到属性对应的get函数时,会给出警告,但同时会用系统默认值做为动画初始值。
如果通过给自定义控件MyPointView设置了get函数,那么将会以get函数的返回值做为初始值。

根据 View setBackGroundColor() 方法可以自定义条用属性动画。

❼ android 怎么使用mvvm框架

MVC、MVP、MVVM
首先,我们先大致了解下Android开发中常见的模式。
MVC
View:XML布局文件。
Model:实体模型(数据的获取、存储、数据状态变化)。
Controllor:对应于Activity,处理数据、业务和UI。
从上面这个结构来看,Android本身的设计还是符合MVC架构的,但是Android中纯粹作为View的XML视图功能太弱,我们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接导致Activity中的代码大爆炸。相信大多数Android开发者都遇到过一个Acitivty数以千行的代码情况吧!所以,更贴切的说法是,这个MVC结构最终其实只是一个Model-View(Activity:View&Controller)的结构。
MVP
View:对应于Activity和XML,负责View的绘制以及与用户的交互。
Model:依然是实体模型。
Presenter:负责完成View与Model间的交互和业务逻辑。
前面我们说,Activity充当了View和Controller两个角色,MVP就能很好地解决这个问题,其核心理念是通过一个抽象的View接口(不是真正的View层)将Presenter与真正的View层进行解耦。Persenter持有该View接口,对该接口进行操作,而不是直接操作View层。这样就可以把视图操作和业务逻辑解耦,从而让Activity成为真正的View层。
但MVP也存在一些弊端:
Presenter(以下简称P)层与View(以下简称V)层是通过接口进行交互的,接口粒度不好控制。粒度太小,就会存在大量接口的情况,使代码太过碎版化;粒度太大,解耦效果不好。同时对于UI的输入和数据的变化,需要手动调用V层或者P层相关的接口,相对来说缺乏自动性、监听性。如果数据的变化能自动响应到UI、UI的输入能自动更新到数据,那该多好!
MVP是以UI为驱动的模型,更新UI都需要保证能获取到控件的引用,同时更新UI的时候要考虑当前是否是UI线程,也要考虑Activity的生命周期(是否已经销毁等)。
MVP是以UI和事件为驱动的传统模型,数据都是被动地通过UI控件做展示,但是由于数据的时变性,我们更希望数据能转被动为主动,希望数据能更有活性,由数据来驱动UI。
V层与P层还是有一定的耦合度。一旦V层某个UI元素更改,那么对应的接口就必须得改,数据如何映射到UI上、事件监听接口这些都需要转变,牵一发而动全身。如果这一层也能解耦就更好了。
复杂的业务同时也可能会导致P层太大,代码臃肿的问题依然不能解决。
MVVM
View:对应于Activity和XML,负责View的绘制以及与用户交互。
Model:实体模型。
ViewModel:负责完成View与Model间的交互,负责业务逻辑。
MVVM的目标和思想与MVP类似,利用数据绑定(Data Binding)、依赖属性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一个更加灵活高效的架构。
数据驱动
在常规的开发模式中,数据变化需要更新UI的时候,需要先获取UI控件的引用,然后再更新UI。获取用户的输入和操作也需要通过UI控件的引用。在MVVM中,这些都是通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,数据成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多。
低耦合度
MVVM模式中,数据是独立于UI的。
数据和业务逻辑处于一个独立的ViewModel中,ViewModel只需要关注数据和业务逻辑,不需要和UI或者控件打交道。UI想怎么处理数据都由UI自己决定,ViewModel不涉及任何和UI相关的事,也不持有UI控件的引用。即便是控件改变了(比如:TextView换成EditText),ViewModel也几乎不需要更改任何代码。它非常完美的解耦了View层和ViewModel,解决了上面我们所说的MVP的痛点。
更新UI
在MVVM中,数据发生变化后,我们在工作线程直接修改(在数据是线程安全的情况下)ViewModel的数据即可,不用再考虑要切到主线程更新UI了,这些事情相关框架都帮我们做了。
团队协作
MVVM的分工是非常明显的,由于View和ViewModel之间是松散耦合的:一个是处理业务和数据、一个是专门的UI处理。所以,完全由两个人分工来做,一个做UI(XML和Activity)一个写ViewModel,效率更高。
可复用性
一个ViewModel可以复用到多个View中。同样的一份数据,可以提供给不同的UI去做展示。对于版本迭代中频繁的UI改动,更新或新增一套View即可。如果想在UI上做A/B Testing,那MVVM是你不二选择。
单元测试
有些同学一看到单元测试,可能脑袋都大。是啊,写成一团浆糊的代码怎么可能做单元测试?如果你们以代码太烂无法写单元测试而逃避,那可真是不好的消息了。这时候,你需要MVVM来拯救。
我们前面说过了,ViewModel层做的事是数据处理和业务逻辑,View层中关注的是UI,两者完全没有依赖。不管是UI的单元测试还是业务逻辑的单元测试,都是低耦合的。在MVVM中数据是直接绑定到UI控件上的(部分数据是可以直接反映出UI上的内容),那么我们就可以直接通过修改绑定的数据源来间接做一些Android UI上的测试。
通过上面的简述以及模式的对比,我们可以发现MVVM的优势还是非常明显的。虽然目前Android开发中可能真正在使用MVVM的很少,但是值得我们去做一些探讨和调研。
如何构建MVVM应用框架
如何分工
构建MVVM框架首先要具体了解各个模块的分工。接下来我们来讲解View、ViewModel、Model它们各自的职责所在。
View
View层做的就是和UI相关的工作,我们只在XML、Activity和Fragment写View层的代码,View层不做和业务相关的事,也就是我们在Activity不写业务逻辑和业务数据相关的代码,更新UI通过数据绑定实现,尽量在ViewModel里面做(更新绑定的数据源即可),Activity要做的事就是初始化一些控件(如控件的颜色,添加RecyclerView的分割线),View层可以提供更新UI的接口(但是我们更倾向所有的UI元素都是通过数据来驱动更改UI),View层可以处理事件(但是我们更希望UI事件通过Command来绑定)。 简单地说:View层不做任何业务逻辑、不涉及操作数据、不处理数据,UI和数据严格的分开。
ViewModel
ViewModel层做的事情刚好和View层相反,ViewModel只做和业务逻辑和业务数据相关的事,不做任何和UI相关的事情,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,做的事情也都只是对数据的操作(这些数据绑定在相应的控件上会自动去更改UI)。同时DataBinding框架已经支持双向绑定,让我们可以通过双向绑定获取View层反馈给ViewModel层的数据,并对这些数据上进行操作。关于对UI控件事件的处理,我们也希望能把这些事件处理绑定到控件上,并把这些事件的处理统一化,为此我们通过BindingAdapter对一些常用的事件做了封装,把一个个事件封装成一个个Command,对于每个事件我们用一个ReplyCommand 去处理就行了,ReplyCommand 会把你可能需要的数据带给你,这使得我们在Vie,具体见 MVVM Light Toolkit 使用指南的 Command 部分 。再强调一遍:ViewModel 不做和UI相关的事。

❽ Android 如何判断一个View重绘或加载完成

1、view重绘时回调(即监听函数,当view重绘完成自动动用,需要向view的观察者添加监听器)。格式:

view.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {

@Override

public void onDraw() {

// TODO Auto-generated method stub

}

});

2、view加载完成时回调(当view加载完成自动动用,需要向view的观察者添加监听器)。格式:

view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

// TODO Auto-generated method stub

}

});

(8)androidview绘制完成监听扩展阅读:

两种方式刷新:

1、主线程可以直接调用Invalidate()方法刷新

2、子线程可以直接调用postInvalidate()方法刷新。

API的描述 : Invalidatethe whole view. If the view is visible, onDraw(Canvas) will be called at somepoint in the future. This must be called from a UI thread. To call from anon-UI thread, call postInvalidate().。

API的描述译文:当Invalidate()被调用的时候,View的OnDraw()就会被调用,Invalidate()必须是在UI线程中被调用,如果在新线程中更新视图的就调用postInvalidate()。

❾ Android 重学系列 View的绘制流程(六) 硬件渲染(上)

本文开始聊聊Android中的硬件渲染。如果跟着我的文章顺序,从SF进程到App进程的绘制流程一直阅读,我们到这里已经有了一定的基础,可以试着进行横向比对如Chrome浏览器渲染流程,看看软件渲染,硬件渲染,SF合成都做了什么程度的优化。

先让我们回顾一下负责硬件渲染的主体对象ThreadedRenderer在整个绘制流程中做了哪几个步骤。

在硬件渲染的过程中,有一个很核心的对象RenderNode,作为每一个View绘制的节点对象。

当每一次进行准备进行绘制的时候,都会雷打不动执行如下三个步骤:

如果遇到什么问题欢迎来到 https://www.jianshu.com/p/c84bfa909810 下进行讨论

实际上整个硬件渲染的设计还是比较庞大。因此本文先聊聊ThreadedRender整个体系中主要对象的构造以及相关的原理。

首先来认识下面几个重要的对象有一个大体的印象。

在Java层中面向Framework中,只有这么多,下面是一一映射的简图。

能看到实际上RenderNode也会跟着View 树的构建同时一起构建整个显示层级。也是因此ThreadedRender也能以RenderNode为线索构建出一套和软件渲染一样的渲染流程。

仅仅这样?如果只是这么简单,知道我习惯的都知道,我喜欢把相关总结写在最后。如果把总揽写在正文开头是因为设计比较繁多。因为我们如果以流水线的形式进行剖析容易造成迷失细节的困境。

让我继续介绍一下,在硬件渲染中native层的核心对象。

如下是一个思维导图:

有这么一个大体印象后,就不容易迷失在源码中。我们先来把这些对象的实例化以及上面列举的ThreadedRenderer在ViewRootImpl中执行行为的顺序和大家来聊聊其原理,先来看看ThreadedRenderer的实例化。

当发现mSurfaceHolder为空的时候会调用如下函数:

而这个方法则调用如下的方法对ThreadedRenderer进行创建:

文件:/ frameworks / base / core / java / android / view / ThreadedRenderer.java

能不能创建的了ThreadedRenderer则决定于全局配置。如果ro.kernel.qemu的配置为0,说明支持OpenGL 则可以直接返回true。如果qemu.gles为-1说明不支持OpenGL es返回false,只能使用软件渲染。如果设置了qemu.gles并大于0,才能打开硬件渲染。

我们能看到ThreadedRenderer在初始化,做了三件事情:

关键是看1-3点中ThreadRenderer都做了什么。

文件:/ frameworks / base / core / jni / android_view_ThreadedRenderer.cpp

能看到这里是直接实例化一个RootRenderNode对象,并把指针的地址直接返回。

能看到RootRenderNode继承了RenderNode对象,并且保存一个JavaVM也就是我们所说的Java虚拟机对象,一个java进程全局只有一个。同时通过getForThread方法,获取ThreadLocal中的Looper对象。这里实际上拿的就是UI线程的Looper。

在这个构造函数有一个mDisplayList十分重要,记住之后会频繁出现。接着来看看RenderNode的头文件:
文件:/ frameworks / base / libs / hwui / RenderNode.h

实际上我把几个重要的对象留下来:

文件:/ frameworks / base / core / java / android / view / RenderNode.java

能看到很简单,就是包裹一个native层的RenderNode返回一个Java层对应的对象开放Java层的操作API。

能看到这个过程生成了两个对象:

这个对象实际上让RenderProxy持有一个创建动画上下文的工厂。RenderProxy可以通过ContextFactoryImpl为每一个RenderNode创建一个动画执行对象的上下文AnimationContextBridge。

文件:/ frameworks / base / libs / hwui / renderthread / RenderProxy.cpp

在这里有几个十分重要的对象被实例化,当然这几个对象在聊TextureView有聊过( SurfaceView和TextureView 源码浅析 ):

我们依次看看他们初始化都做了什么。

文件:/ frameworks / base / libs / hwui / renderthread / RenderThread.cpp

能看到其实就是简单的调用RenderThread的构造函数进行实例化,并且返回对象的指针。

RenderThread是一个线程对象。先来看看其头文件继承的对象:
文件:/ frameworks / base / libs / hwui / renderthread / RenderThread.h

其中RenderThread的中进行排队处理的任务队列实际上是来自ThreadBase的WorkQueue对象。

文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h

ThreadBase则是继承于Thread对象。当调用start方法时候其实就是调用Thread的run方法启动线程。

另一个更加关键的对象,就是实例化一个Looper对象到WorkQueue中。而直接实例化Looper实际上就是新建一个Looper。但是这个Looper并没有获取当先线程的Looper,这个Looper做什么的呢?下文就会揭晓。

WorkQueue把一个Looper的方法指针设置到其中,其作用可能是完成了某一件任务后唤醒Looper继续工作。

而start方法会启动Thread的run方法。而run方法最终会走到threadLoop方法中,至于是怎么走进来的,之后有机会会解剖虚拟机的源码线程篇章进行讲解。

在threadloop中关键的步骤有如下四个:

在这个过程中创建了几个核心对象:

另一个核心的方法就是,这个方法为WorkQueue的Looper注册了监听:

能看到在这个Looper中注册了对DisplayEventReceiver的监听,也就是Vsync信号的监听,回调方法为displayEventReceiverCallback。

我们暂时先对RenderThread的方法探索到这里,我们稍后继续看看回调后的逻辑。

文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h

能看到这里的逻辑很简单实际上就是调用Looper的pollOnce方法,阻塞Looper中的循环,直到Vsync的信号到来才会继续往下执行。详细的可以阅读我写的 Handler与相关系统调用的剖析 系列文章。

文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h

实际上调用的是WorkQueue的process方法。

文件:/ frameworks / base / libs / hwui / thread / WorkQueue.h

能看到这个过程中很简单,几乎和Message的loop的逻辑一致。如果Looper的阻塞打开了,则首先找到预计执行时间比当前时刻都大的WorkItem。并且从mWorkQueue移除,最后添加到toProcess中,并且执行每一个WorkItem的work方法。而每一个WorkItem其实就是通过从某一个压入方法添加到mWorkQueue中。

到这里,我们就明白了RenderThread中是如何消费渲染任务的。那么这些渲染任务又是哪里诞生呢?

上文聊到了在RenderThread中的Looper会监听Vsync信号,当信号回调后将会执行下面的回调。

能看到这个方法的核心实际上就是调用drainDisplayEventQueue方法,对ui渲染任务队列进行处理。

能到在这里mVsyncRequested设置为false,且mFrameCallbackTaskPending将会设置为true,并且调用queue的postAt的方法执行ui渲染方法。

还记得queue实际是是指WorkQueue,而WorkQueue的postAt方法实际实现如下:
/ frameworks / base / libs / hwui / thread / WorkQueue.h

情景带入,当一个Vsync信号达到Looper的监听者,此时就会通过WorkQueue的drainDisplayEventQueue 压入一个任务到队列中。

每一个默认的任务都是执行dispatchFrameCallback方法。这里的判断mWorkQueue中是否存在比当前时间更迟的时刻,并返回这个WorkItem。如果这个对象在头部needsWakeup为true,说明可以进行唤醒了。而mWakeFunc这个方法指针就是上面传下来:

把阻塞的Looper唤醒。当唤醒后就继续执行WorkQueue的process方法。也就是执行dispatchFrameCallbacks方法。

在这里执行了两个事情:

先添加到集合中,在上面提到过的threadLoop中,会执行如下逻辑:

如果大小不为0,则的把中的IFrameCallback全部迁移到mFrameCallbacks中。

而这个方法什么时候调用呢?稍后就会介绍。其实这部分的逻辑在TextureView的解析中提到过。

接下来将会初始化一个重要对象:

这个对象名字叫做画布的上下文,具体是什么上下文呢?我们现在就来看看其实例化方法。
文件:/ frameworks / base / libs / hwui / renderthread / CanvasContext.cpp

文件:/ device / generic / goldfish / init.ranchu.rc

在init.rc中默认是opengl,那么我们就来看看下面的逻辑:

首先实例化一个OpenGLPipeline管道,接着OpenGLPipeline作为参数实例化CanvasContext。

文件:/ frameworks / base / libs / hwui / renderthread / OpenGLPipeline.cpp

能看到在OpenGLPipeline中,实际上就是存储了RenderThread对象,以及RenderThread中的mEglManager。透过OpenGLPipeline来控制mEglManager进而进一步操作OpenGL。

做了如下操作:

文件:/ frameworks / base / libs / hwui / renderstate / RenderState.cpp

文件:/ frameworks / base / libs / hwui / renderthread / DrawFrameTask.cpp

实际上就是保存这三对象RenderThread;CanvasContext;RenderNode。

文件:/ frameworks / base / core / jni / android_view_ThreadedRenderer.cpp

能看到实际上就是调用RenderProxy的setName方法给当前硬件渲染对象设置名字。

文件:/ frameworks / base / libs / hwui / renderthread / RenderProxy.cpp

能看到在setName方法中,实际上就是调用RenderThread的WorkQueue,把一个任务队列设置进去,并且调用runSync执行。

能看到这个方法实际上也是调用post执行排队执行任务,不同的是,这里使用了线程的Future方式,阻塞了执行,等待CanvasContext的setName工作完毕。

阅读全文

与androidview绘制完成监听相关的资料

热点内容
python培训知乎 浏览:755
作业文件夹怎么收藏 浏览:529
安卓系统如何授权悬浮窗 浏览:977
压缩包加密可以用汉语吗 浏览:948
ip池源码 浏览:490
缓解压力大放松一下的地方 浏览:928
工厂哪里用压缩空气 浏览:328
centos如何安装php 浏览:110
丹凤服务器是什么 浏览:148
分析html5源码 浏览:994
资治通鉴中华书局pdf 浏览:212
如何搭建服务器和数据库 浏览:537
论文app怎么用 浏览:466
开源文件服务器加密 浏览:588
哈利波特游戏选什么服务器 浏览:424
云服务器怎样格式化 浏览:16
框架柱顶部箍筋加密区规范 浏览:169
pythonjson文件读取 浏览:105
梦幻西游源码架设 浏览:123
抽烟有解压效果吗 浏览:827