Launcher3 中 StateManager 状态机

概述

StateManager path:com.android.launcher3.statemanager.StateManager

核心状态控制类 ,基于不同状态为 StatefulActivity 管理不同状态之间转换的类

那状态机是怎么工作的?

首先我们要弄清楚一个点状态机是用来管理状态的,他要做的就是把每一种状态改变通知到相关方,并维护自己的状态值,即完成了一次状态管理;具体来说就是相关方在每一种状态下都有哪些组件有哪些变化。

StateHandler 接口所有实现

在进入正题之前,先看下StateHandler,即状态处理器,他是状态机和各个组件通信高度抽象。这里列出了所有的具体实现,后面分析会用到。

举例说明状态机原理

这里以在桌面拖动一个图标为例,拖动过程中状态是SpringLoaded, 即状态会从Normal 变成 SpringLoaded.

1. Workspace 会收到拖动事件

com.android.launcher3.Workspace#onDragStart

@Override
public void onDragStart(DragObject dragObject, DragOptions options) {
    ...
    // Always enter the spring loaded mode
    mLauncher.getStateManager().goToState(SPRING_LOADED);
    ...
}
  • 通过 mLauncher.getStateManager() 获取到StateManager ,此对象是在Launcher的Activity中创建

  • 然后调用 goToState 切换到 SPRING_LOADED

2. StateManager#goToState() 控制切换

com.android.launcher3.statemanager.StateManager#goToState(STATE_TYPE, boolean, long, android.animation.Animator.AnimatorListener)

StateManager 根据不同情况重载了多个 goToState() ,最终都会调用到参数最长的这个

private void goToState(
        STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
    animated &= areAnimatorsEnabled(); // 注释0

    // 注释1 
    if (mActivity.isInState(state)) {
        if (mConfig.currentAnimation == null) {
            // Run any queued runnable
            if (listener != null) {
                listener.onAnimationEnd(null);
            }
            return;
        } else if (!mConfig.userControlled && animated && mConfig.targetState == state) {
            // We are running the same animation as requested
            if (listener != null) {
                mConfig.currentAnimation.addListener(listener);
            }
            return;
        }
    }

    // 注释2
    // Cancel the current animation. This will reset mState to mCurrentStableState, so store it.
    STATE_TYPE fromState = mState;
    cancelAnimation();

    // 注释3
    if (!animated) {
        mAtomicAnimationFactory.cancelAllStateElementAnimation();
        onStateTransitionStart(state); // 通知状态转换开始
        // 通知所有观察者
        for (StateHandler handler : getStateHandlers()) {
            handler.setState(state);
        }

        onStateTransitionEnd(state); // 通知状态转换完毕

        // Run any queued runnable
        if (listener != null) {
            listener.onAnimationEnd(null);
        }
        return;
    }

    // 注释4 
    if (delay > 0) {
        // Create the animation after the delay as some properties can change between preparing
        // the animation and running the animation.
        int startChangeId = mConfig.changeId;
        mUiHandler.postDelayed(() -> {
            if (mConfig.changeId == startChangeId) {
                goToStateAnimated(state, fromState, listener);
            }
        }, delay);
    } else {
        // 注释5
        goToStateAnimated(state, fromState, listener);
    }
}
  • 注释0:areAnimatorsEnabled() 实际上判断系统是否被禁用动画,默认true

  • 注释1 :判断要切换的目标状态,和当前activity的状态是否一致,实际上判断的是 mState == state

如果已经是这个状态,返回即可

  • 注释2,重置状态并取消动画

  • 注释3,如果不需要动画,会通过 handler.setState(state)来通知状态变化; 并更新状态

  • 注释4和注释5 实际区别就是是否有延迟,通过mUiHandler来执行延迟动作,最终都会执行需要动画的切换状态方法,即 goToStateAnimated()

那么我们注意到 注释3 会通过遍历所有 StateHandler进而来通知所有的观察者,那么都有哪些观察者呢?什么时候注册的?

3. StateManager#getStateHandlers() 获取观察者

getStateHandlers

com.android.launcher3.statemanager.StateManager#getStateHandlers

public StateHandler[] getStateHandlers() {
    if (mStateHandlers == null) {
        ArrayList<StateHandler> handlers = new ArrayList<>();
        mActivity.collectStateHandlers(handlers);
        mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
    }
    return mStateHandlers;
}
  • 只获取一次,如果不为空直接就返回了

  • 为空的时候,通过调用activity的collectStateHandlers方法,并存如list集合中,最后转成数组返回

Launcher#collectStateHandlers

com.android.launcher3.Launcher#collectStateHandlers

@Override
protected void collectStateHandlers(List<StateHandler> out) {
    out.add(getAllAppsController());
    out.add(getWorkspace());
}
  • 添加观察者 AllAppsController

  • 添加观察者 Workspace

BaseQuickstepLauncher#collectStateHandlers

com.android.launcher3.BaseQuickstepLauncher#collectStateHandlers

@Override
protected void collectStateHandlers(List<StateHandler> out) {
    super.collectStateHandlers(out);
    out.add(getDepthController());
    out.add(new RecentsViewStateController(this));
    out.add(new BackButtonAlphaHandler(this));
    out.add(getTaskbarStateHandler());
}
  • 添加观察者 DepthController

  • 添加观察者 RecentsViewStateController

  • 添加观察者 BackButtonAlphaHandler

  • 添加观察者 TaskbarStateHandler

小结

观察者:launcher主activity会注册6个观察者,并且都实现接口 StateHanlder

被观察者:状态机对象本身,即StateManager

通知更新:根据是否需要动画调用 setStatesetStateWithAnimation 完成通知

4. StateManager#goToStateAnimated()

com.android.launcher3.statemanager.StateManager#goToStateAnimated

private void goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState,
        AnimatorListener listener) {
    // 由于状态 mBaseState 可以从多个状态到达,
    //因此只需假设转换反向播放并使用与前一个状态相同的持续时间。
    mConfig.duration = state == mBaseState
            ? fromState.getTransitionDuration(mActivity)
            : state.getTransitionDuration(mActivity);
    // 注释1
    prepareForAtomicAnimation(fromState, state, mConfig);
    // 注释2
    AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
    if (listener != null) {
        animation.addListener(listener);
    }
    // 注释3
    mUiHandler.post(new StartAnimRunnable(animation));
}
  • 注释1:prepareForAtomicAnimation 准备从 fromState 到 toState 的非用户控制动画。准备工作包括:

    • 为状态转换中包含的各种动画设置插值器。

    • 为隐藏但即将显示的视图设置一些起始值(例如比例)。

  • 注释2:创建具体的动画并设置监听,来下文中 [createAnimationToNewWorkspaceInternal]

  • 注释3:使用 mUiHandler 执行动画集

5. StateManager#createAnimationToNewWorkspaceInternal

private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
    PendingAnimation builder = new PendingAnimation(mConfig.duration);
    if (!mConfig.hasAnimationFlag(SKIP_ALL_ANIMATIONS)) {
        for (StateHandler handler : getStateHandlers()) {
            handler.setStateWithAnimation(state, mConfig, builder);
        }
    }
    builder.addListener(createStateAnimationListener(state));
    mConfig.setAnimation(builder.buildAnim(), state);
    return builder;
}
  • 同样的通过 StateHandler#setStateWithAnimation来通知各个观察者完成动画的属性设置

  • 通过builder模式来聚合不同配置,进而构建不同的动画集合

  • 如果想看具体每个观察者都怎么设置的动画,就要去看各个实现了,这里不再讲述

总结

  1. 当需要切换状态时,会调用StateManager#goToState,并传入要到达的状态

  2. StateManager会收集所有的观察者StateHandler, 并通过setState或者setStateWithAnimation通知到所有的观察者做响应的变化

  3. 然后根据是否有动画来构造动画集,并调用UiHandler执行

  4. 最后StateManager同时会维护自己的本对象的几个状态值,便于管理切换和回退,以及通过StateListener来通知需要观察状态切换过开始和结束的对象

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容