LauncherRootView和DragLayer的布局过程

(系列文章点这里)

话说讲解Launcher3的布局过程真是要耗费一番功夫了,因为他的过程很难理解, 一般来说我们普通应用的布局过程其实都是按照系统自带的ViewGroup来的,如果自定义了一些布局方法,开发者在考虑xml标记的布局文件的时候,还要考虑运行时的一些动态变化。

在接着往下读之前,你最好先去了解一下View的绘制流程。

在主布局launcher.xml中, 最外层是LauncherRootView。

我们发现它只是重写了一个方法, View.fitSystemWindow(rect), 那这个方法是干什么的呢?查了官方文档,还是看不懂他到底是做什么的,但是倒是会用了。如果你的View在status bar或者navigation bar之下,那么这个方法会传递进去一个参数,说明这个View的四周有多少是被status bar或者navigation bar挡住了的。这个方法的会调用父类的setInsets()方法,我们来看看父类InsettableFrameLayout。

InsettableFrameLayout继承了FrameLayout, 并且实现了父类的一些方法,总的来说,他的目的就是给子View都设定一个Margin值,防止被status bar或者navigation bar挡住。如果一个View已经是Insettable的了,那么就忽略他。在Launcher3中,Workspace, DragLayer和AppsCustomizeTabHost都实现了这个接口,那么他们自身就不会被加Margin, 而搜索栏和Hotseat都没有实现这个接口,那么说明他们在被加到父亲上得时候会被设一个Margin,最后的到得效果就是搜索栏刚好在status bar之下(gravity为top,topmargin是status bar的高度),Hotseat刚好在navigation bar的上方(gravity为bottom,bottomMargin为navigation bar的高度)。

LauncherRootView的实现已经很明了了,他的孩子只有DragLayer,DragLayer实现了Insettable接口, 所以他不会加上margin。DragLayer其实也是InsettableFrameLayout的子类, DragLayer的孩子有Workspace,Hotseat,AppsCustomizeTabHost 和 SearchDropTargetBar等等 。

DragLayer的布局过程稍微复杂一点,不过还好。DragLayer自己定义了一个LayoutParams, 在FrameLayout原有的LayoutParams基础之上,添加了几个参数,x,y和customPosition。在DragLayer中添加孩子的时候,都会自来于提供的LayoutParams来计算View的布局位置。

我们来看下DragLayer重写的onLayout方法[如果还不清楚这个方法是干啥的话,赶快搜一搜]:

2015-05-20_17-47-41

刚开始先按照FrameLayout的方式进行布局,然后,遍历所有的孩子的LayoutParams, 如果这个孩子的LayoutParams参数中含有的customPosition参数为true,那么就会使用参数里执行的x坐标和y坐标。

为什么需要这样的功能呢?比如说我们在拖拽一个图标的时候,这个图标其实是放在了DragLayer里的[drag layer,拖拽层嘛],如果给拖拽的图标分配了一个自定义位置的布局参数,那么布局完之后就可以显示在指定位置了。拖拽一个AppWidget和显示文件夹的时候也是同样道理。

理解了Launcher最外层的两个ViewGroup[LauncherRootView,DragLayer]的布局方式之后,相信你了解的更深入了一些,同学还学了自定义ViewGroup的时候如何自定义LayoutParams布局参数。再往Launcher的底层看得话就是Workspace,CellLayout了,他们会更为复杂,接下来的博文会介绍到。

继续阅读 →

IconCache原理

(系列文章点这里)

在Launcher3中,IconCache从名字上来看就是用来做应用图标缓存的,以前没有在意过这个类,最近刚好需要研究下,就做个笔记。

IconCache不仅会保存图标,还会保存应用的title, 我们可以看到缓存都是保存在mCache中的,他是个HashMap,键是ComponentName和UserHandleCompat组成的对象, 值是一个对象CacheEntry, CacheEntry的三个成员是图标,标题和内容描述[其实我也搞不懂]。

在Launcher启动的时候, 会在LauncherApplication中初始化LauncherAppState,而他会初始化IconCache对象, 刚开运行的时候IconCache肯定是空的, 只有在Launcher.java 这个Activity启动之后才会有缓存。在Launcher.onCreate()方法中,执行了IconCache的flushInvalidIcons, 会清空现有的缓存,如果Activity的方向发生了改变[即图标的大小发生了变化],原来的缓存可能都没有用了。那什么时候IconCache会被完全清空呢, 也就是图标和文字无效的时候,在用户的语言发生了变化之后会被清空,因为获取的图标和title很可能已经不同了。

下面来看图片是怎么加进去的。添加图标最关键的方法是IconCache.cacheLocked(), 这个方法会获取某个Component[Activity]的图标和标题,并存入mCache中, 我们看到这个方法名最后是“Locked”表明,方法调用的时候已经保证是线程安全的,可以看到IconCache.cacheLocked()每个调用的方法都有关键字synchronized。

最后IconCache还会生成一个系统默认图标,如果一个Activity由于某种原因我们没有找到对应的图标, 那么IconCache会返回一个默认图标, 这个图标是从系统取到的资源, 资源id是android.R.mipmap.sym_def_app_icon, 不同的系统这个资源对应的图标是不一样的, 比如4.4 和 5.1的系统默认图标就是不一样的。

好了IconCache需要说的就这么多。

继续阅读 →

如何给Launcher3添加左屏

(系列文章点这里)

如果你用过谷歌即时桌面(Google Now)的话,你一定知道滑到桌面最左屏会是一个搜索界面,而且会呈现一些卡片比如天气,行程,新闻等等。这个东西也是我们可以自定义的,谷歌即时桌面其实也是基于Launcher3的。想必AOSP中一定有源码可以控制最左屏。

2014-10-23_14-14-48

 

如果你已经导入了Eclipse项目,可以到Launcher类中寻找Launcher.hasCustomContentToLeft()方法,从字面上理解就是左边是否有自定义内容。之后跟自定义内容相关的方法或者变量的名字基本都跟“custom content”相关。这个方法默认是返回false的,也就是最左屏不存在。不妨替换成true试试,运行后我们可以发现的确可以滑到左屏的,但是什么东西都没有,只留一个搜索框。

下一步是向自定义内容中添加View了,添加的方法是Workspace.addToCustomContentPage(…),你可以传递一个View和与左屏相关的回调接口。然后就OK了。你可以在需要的时候调用这个方法,什么时候调呢?Google已经帮你想好了。就是Launcher.addCustomContentToLeft()方法,这个方法会在Launcher.onFinishBindingItems()中调用到,也就是桌面加载结束的时候加载自定义内容。着实帮你省去了很多开发时间。

所以说,如果你想要在左屏添加天气呀,系统设置呀等是完全OK的,毕竟传递个View进去就行,你也可以弄个Fragment专门控制左屏的内容和逻辑。这样的设计还是挺符合用户的需求的,毕竟Google的东西我们在大陆没办法正常使用,你如果能设计一个类似的,国内用户可以正常使用的包含左屏信息页的Launcher,我觉得还是挺有价值的。

再说说技术实现吧,既然是在Workspace上添加一个View,那么他应该是放在CellLayout里面的,但是为什么他和其他的CellLayout大小不一,而且占据全屏呢。我们找到桌面加载每一屏的代码,在Launcher.bindScreens()里面,会在加载完正常的图标屏之后再加载自定义的左屏,通过Workspace.createCustomContentPage()。然后会发现他给新生成的CellLayout的LayoutParams新添加了属性值,isFullScreenPage=true,一旦有了这个属性,在PagedView(Workspace的父类)的onMeasure和onLayout就会对其做特殊处理,保证其占据全屏。

 

继续阅读 →

Launcher3分析之拖动图标的流程——放下

(系列文章点这里)

我们来看下放下的过程,对应的触摸事件是ACTION_UP,我们直接跳到DragController的onTouchEvent。在处理ACTION_UP的时候多调用了一次handleMoveEvent,可能是因为ACTION_UP相对于上一次的时间也会有位置的变化。

up松手的时候会判断是是不是在“扔”图标(通过isFlingingToDelete),如果你用的不仔细的话,可能发现不了这个扔的过程:在说面拖动某个图标,然后快速甩向屏幕上方,就会触发删除的事件。这里检查你是否在扔是通过VelocityTracker来计算的,他会记录每次事件,然后计算出速度,包括X方向和Y方向的。如果不是在“扔”,就会调用放下图标最重要的一个方法,drop方法。
(更多…)

继续阅读 →

Launcher3分析之拖动图标的流程——移动

(系列文章点这里)

移动的逻辑还是比较清楚的,因为他抽象出来的模型十分易于理解的。

DropTarget是一个可放置(drop)区域的抽象,也就是我们松开手的时候想要把图标放到某个东西上,这个东西就是DropTarget,实现他的都是View,比如说文件夹,Workspace,删除区等等,你可以通过“ Open Type Hierarchy”来查看哪些类继承了DropTarget接口。下图是他比较重要的几个接口:

droptarget
(更多…)

继续阅读 →

Launcher3分析之拖动图标的流程——按住

(系列文章点这里)

拖动图标是Launcher的特色,非常形象地解释了如何改变桌面的布局,让小白用户也可以轻松上手,这背后所做的工作也是把复杂的东西简单化,简单到按下-移动-松开。

今天说一说这个按下的过程是如何产生的。这里说的按是长按,长按就要找到他的长按监听器。在Workspace中addInScreen方法最后,给图标设置的监听器是Launcher对象,他实现了onLongClick方法。

拖动的流程-按下
(更多…)

继续阅读 →

Android 4.4 Launcher3桌面源码分析

由于工作的原因,对Android的桌面有了一些了解。之前一段时间对桌面的构成十分模糊,最近看代码该代码发现渐渐有了感觉,所以准备写一系列分析安卓桌面的文章。

市面上有很多桌面产品,比如91桌面,go桌面,小米桌面等等,虽说千差万别,但都完成了桌面的基本任务,比如说组织图标,拖动图标,滑动页面,显示小工具等等功能。桌面跟其他Android App很大的不同是,他只有一个Activity,但是这个Activity却十分复杂,用几个自定义View把Android的触摸事件的处理玩儿的眼花缭乱,没有长时间的折腾是很难熟悉的。
(更多…)

继续阅读 →