天天看点

Launcher3 OverviewMode和mInsets

OverviewMode

Launcher3 OverviewMode和mInsets

Launcher3里,在Workspace界面长按空白处,桌面进去OverviewMode,查看源码:Launcher类实现了View.OnLongClickListener接口,可以看到在onLongClick方法中有这么一句:

mWorkspace.enterOverviewMode();
           

这句代码在onLongClick方法中出现了两次:

  • 在长按的view为Workspace且当前不在OverviewMode模式
  • 长按的地方为Hotseat图标外的空白地方时,且当前不在拖动状态和OverviewMode下

退出OverviewMode的方法是exitOverviewMode,通过查看这两个方法,它们都调用了enableOverviewMode方法。

看一下enableOverviewMode方法,这一句比较重要:

Animator workspaceAnim = getChangeStateAnimation(finalState, animated, , snapPage);
           

如同对象名workspaceAnim,这句代码是取得Launcher3在各种状态之间变化时的动画,Workspace中有一个enum State,代表桌面的各种状态,OverviewMode时State为State.OVERVIEW,显示Workspace时状态为State.NORMAL,getChangeStateAnimation方法第一个参数就是桌面即将进入的状态。

getChangeStateAnimation方法比较重要,在桌面在各种状态之间变化时都会调用,这里看一下进入OverviewMode的动画:一开始经过新老状态boolean的判断,决定背景透明度(finalBackgroundAlpha)、Hotseat和PageIndicator透明度(finalHotseatAndPageIndicatorAlpha)等相关属性的设定和当前进行的状态改变。然后取得桌面上各种“拼图”:SearchBar,HotSeat,PageIndicator等对象并进行各种动画效果,比如scale就是Workspace对象的缩放动画。

LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
scale.scaleX(mNewScale)
     .scaleY(mNewScale)
     .translationY(finalWorkspaceTranslationY)
     .setDuration(duration)
     .setInterpolator(mZoomInInterpolator);
anim.play(scale);
...
           

mInsets

在计算动画时各对象的属性变化中,关于透明度,缩放的值的计算比较简单,但是位置的计算通常要从DeviceProfile中取得信息计算而来,比如Workspace缩放后的TranslationY在OverviewMode下由Workspace的getOverviewModeTranslationY方法得来。

看这个方法使用到了mInsets对象,看源代码时一直想不明白这个值的意义,上网搜索后才恍然大悟,可以参考这里:LauncherRootView和DragLayer的布局过程。

这个网址打开较慢,摘录一下相关的:

在主布局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是statusbar的高度),Hotseat刚好在navigationbar的上方(gravity为bottom,bottomMargin为navigation bar的高度)。

可以看一下InsettableFrameLayout的setFrameLayoutChildInsets方法

public void setFrameLayoutChildInsets(View child, Rect newInsets, Rect oldInsets) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        if (child instanceof Insettable) {
            ((Insettable) child).setInsets(newInsets);
        } else if (!lp.ignoreInsets) {
            lp.topMargin += (newInsets.top - oldInsets.top);
            lp.leftMargin += (newInsets.left - oldInsets.left);
            lp.rightMargin += (newInsets.right - oldInsets.right);
            lp.bottomMargin += (newInsets.bottom - oldInsets.bottom);
        }
        child.setLayoutParams(lp);
    }
           

child是当前类的子类,如果其不是Insettable的子类,fitSystemWindow方法中传进来的Rect就会当成Margin值加到LayoutParams中, 由于InsettableFrameLayout继承了FrameLayout,加了这些Margin的View可以保证自己至少不会被状态栏和导航栏遮挡。

如果child没有继承Insettable,如Workspace。Workspace重写了setInsets方法,将fitSystemWindow方法传递进来的mInsets给自己的mInsets,这样在计算布局的时候(例如上面提到的Workspace的getOverviewModeTranslationY方法),就可以考虑到状态栏和导航栏的影响了。