子墨庖丁Android的ActionBar源码分析 (一)实例化

如果你从事过Android客户端开发,相信你对ActionBar这套框架并不陌生,或者说你并不了解它,但是你应该时不时的要跟它打交道。抛开ActionBar的实现不说,ActionBar实际上是对Android的TitleBar行为的抽象,这种框架可以适用于这种模式的应用,是对需要的行为视图的抽象。当然或许你也和我一样,对ActionBar的实现效率并不满意,因为你打开它的视图,你会发现它的实现非常的ugly。不过我们庆幸的看到的是,ActionBar在设计的时候就并不是以一个强类型的姿态存在,我们发现它并不是以一个View的方式存在,而跟Fragment一样是一个很单纯的工具类。这种设计正好屏蔽了内部的实现,从而可以让我们对它的实现进行改造。当然ActionBar的改造对我来说并不是文章的重点,如果你对自定义控件已经熟门熟路了,那么相信你阅读完这个系列的文章以后,能更有助于你改造ActionBar。对本章我将从ActionBar生成入口开始讲述。

我们知道我们再定义一个Activity的时候,跟WMS直接挂钩的客户端代理是Window类,当然,我这么说本身不准确。因为Window是间接持有这种代理类,不过这不影响我们对ActionBar的整体理解。对于Window类来说,它跟我们直接打交道是PhoneWindow。我们将调用setContentView的方式来注册我们需要的内部视图,为什么说是内部视图,因为除了我们的视图之外,Window里面还注册有多个的视图装饰。其实这也是装饰模式的一种,甚至很像模板方法。

@Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mContentParent.addView(view, params);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

我们发现实际上我们的视图是包含在一个叫做mContentParent的ViewGroup中。而这个对象的生成是定义在Window类中的protected ViewGroup generateLayout(DecorView decor)方法中。

我们知道对于一个Window视图的影响除了Window.LayoutParams外还有Feature。Feature对视图的影响并不直接跟WMS打交道。即使跟WMS打交道也是通过WIndow.LayoutParams类控制,也就是说Feature本身就是一种可有可无的小甜点。在生成mContentParent的时候你会经常看到一些属性匹配代码:

int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(Injector.getFloatingWindowWidth(getContext()), WRAP_CONTENT); // Miui Hook
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }

我们知道我们可以制定一个视图的主题集合,而这种主题集合中可以定制各种的外观参数,还有特征属性。其中一部分要转化成为Window.LayoutParams来跟WMS打交道。比如:

if (!hasSoftInputMode()) {
            params.softInputMode = a.getInt(
                    com.android.internal.R.styleable.Window_windowSoftInputMode,
                    params.softInputMode);
        }

我们可以看出,实际上对于一个窗口的输入法管理,是需要WMS的介入,而这种介入你在客户端配置文件中定义的时候,需要转化成为Window.layoutparams参数的属性,让它来传递给WMS来触发管理。

if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = com.android.internal.R.layout.screen_simple;
        }

我们看出,如果你已经定义了ActionBar主题项,那么它将使用screen_simple或者overlay这两种模式的我们只考虑simple方式,我们来看下simple的布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <com.android.internal.widget.ActionBarContainer
        android:id="@+id/action_bar_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/actionBarStyle">
        <com.android.internal.widget.ActionBarView
            android:id="@+id/action_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="?android:attr/actionBarStyle" />
        <com.android.internal.widget.ActionBarContextView
            android:id="@+id/action_context_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone"
            style="?android:attr/actionModeStyle" />
    </com.android.internal.widget.ActionBarContainer>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
    <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="?android:attr/actionBarSplitStyle"
                  android:visibility="gone"
                  android:gravity="center"/>
</LinearLayout>

我们可以直观的看到这跟我们所熟知的ActionBar布局一致。纵向,ActionBar其实就是ActionBarContainer。对于ActionBar的解析和布局我们放到后面再讲。我们看到当我们决定使用哪种布局之后,通过调用:

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

方法获取mContentParent,也就是说我们的视图就是包含在这个ActionBar容器中的Content容器中。

我们回到Activity的setConentView方法。我们在设置完我们的视图以后,它会初始化initActionBar();

我前面已经说过了,ActionBar和Fragment本身不属于AndroidUI系统的一部分,因此需要对它进行初始化。ActionBar的实现类是com.android.internal.app.ActionBarImpl,由于ActionBarImpl针对的是Window,因此不论你是Activity或者是Dialog或者是PopupWindow理论上都可以使用ActionBar。这种理论实际上也可以说明一点,就是在同一个界面中出现两个ActionBar是合理的。甚至你在同一个Window里面不同的Fragment中实现自己的一套ActionBar也是可行的。因为它并不纳入在WMS的管理中,好吧,有点扯远了,我们继续前文。

public ActionBarImpl(Activity activity) {
        mActivity = activity;
        Window window = activity.getWindow();
        View decor = window.getDecorView();
        init(decor);
        if (!mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY)) {
            mContentView = decor.findViewById(android.R.id.content);
        }
    }

我们看到ActionBarImpl的初始化主要通过init方法实现。

private void init(View decor) {
        mContext = decor.getContext();
        mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
                com.android.internal.R.id.action_bar_overlay_layout);
        if (mOverlayLayout != null) {
            mOverlayLayout.setActionBar(this);
        }
        mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
        mContextView = (ActionBarContextView) decor.findViewById(
                com.android.internal.R.id.action_context_bar);
        mContainerView = (ActionBarContainer) decor.findViewById(
                com.android.internal.R.id.action_bar_container);
        mTopVisibilityView = (ViewGroup)decor.findViewById(
                com.android.internal.R.id.top_action_bar);
        if (mTopVisibilityView == null) {
            mTopVisibilityView = mContainerView;
        }
        mSplitView = (ActionBarContainer) decor.findViewById(
                com.android.internal.R.id.split_action_bar);

        if (mActionView == null || mContextView == null || mContainerView == null) {
            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
                    "with a compatible window decor layout");
        }

        mActionView.setContextView(mContextView);
        mContextDisplayMode = mActionView.isSplitActionBar() ?
                CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;

        // This was initially read from the action bar style
        final int current = mActionView.getDisplayOptions();
        final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0;
        if (homeAsUp) {
            mDisplayHomeAsUpSet = true;
        }

        ActionBarPolicy abp = ActionBarPolicy.get(mContext);
        setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
        setHasEmbeddedTabs(abp.hasEmbeddedTabs());
    }

主要为了初始化一些视图参数,还有往ActionBarOverlayLayout对象注入一个ActionBar控制回调,当然也就是它本身。但我们也能从代码看出,ActionBar本身每个内部对象之间耦合度相对较高,互相引用,不过面向ActionBar接口来编程能有效的屏蔽掉这种低效率。到这里,实际上,对于ActionBar的实例化已经完成。下一章我们将开始ActionBar视图布局的的讨论。

非子墨:

QQ:1025250620

SINA:http://weibo.com/1752090185/profile?rightmod=1&wvr=5&mod=personinfo

子墨庖丁Android的ActionBar源码分析 (一)实例化

时间: 2024-10-12 13:23:47

子墨庖丁Android的ActionBar源码分析 (一)实例化的相关文章

Android 开源项目源码分析第一期正式发布

由 Trinea 发起.几十名 Android 开发者参与的Android 开源项目源码分析第一期正式发布. 从简介.总体设计.流程图.详细设计全方面分析开源库源码,第一期包括 10 个著名开源库及 5 个公共技术点的全面介绍. 分析文档 作者 Volley 源码解析 grumoon Universal Image Loader 源码分析 huxian99 Dagger 源码解析 扔物线 EventBus 源码解析 Trinea xUtils 源码解析 Caij ViewPagerindicat

android 网络框架 源码分析

android 网络框架 源码分析 导语: 最近想开发一个协议分析工具,来监控android app 所有的网络操作行为, 由于android 开发分为Java层,和Native层, 对于Native层我们只要对linux下所有网络I/O接口进行拦截即可,对于java 层,笔者对android 网络框架不是很了解,所以这个工具开发之前,笔者需要对android 的网络框架进行一个简单的分析. 分析结论: 1. android 的网络框架都是基于Socket类实现的 2. java 层Socket

Android -- 消息处理机制源码分析(Looper,Handler,Message)

android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因此我没将其作为核心类.下面一一介绍: Looper Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程.所谓Looper线程就是循环工作的线程.在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Lo

Android网络框架源码分析一---Volley

转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium=mobile_author_hots&utm_source=recommendation 公司最近新起了一个项目,对喜欢尝鲜的我们来说,好处就是我们可以在真实的项目中想尝试一些新技术,验证想法.新项目对网络框架的选取,我们存在三种方案: 1.和我们之前的项目一样,使用Loader + HttpCli

Android分包MultiDex源码分析

概述 Android开发者应该都遇到了64K最大方法数限制的问题,针对这个问题,google也推出了multidex分包机制,在生成apk的时候,把整个应用拆成n个dex包(classes.dex.classes2.dex.classes3.dex),每个dex不超过64k个方法.使用multidex,在5.0以前的系统,应用安装时只安装main dex(包含了应用启动需要的必要class),在应用启动之后,需在Application的attachBaseContext中调用MultiDex.i

[Android]简略的Android消息机制源码分析

相关源码 framework/base/core/java/andorid/os/Handler.java framework/base/core/java/andorid/os/Looper.java framework/base/core/java/andorid/os/Message.java framework/base/core/java/andorid/os/MessageQueue.java libcore/luni/src/main/java/java/lang/ThreadLo

Android消息机制源码分析

本篇主要介绍Android中的消息机制,即Looper.Handler是如何协同工作的: Looper:主要用来管理当前线程的消息队列,每个线程只能有一个Looper Handler:用来将消息(Message)插入到当前线程的消息队列,并负责分发Looper中的消息,将消息发送到当前线程执行 具体关系图如下所示: 接下来我们来分析一下Looper和Handler的源码,了解一下其中的奥妙. 首先我们从一个程序运行的入口来分析,源码如下: public static void main(Stri

Android init进程——源码分析

概述 Android本质上是一个基于Linux内核的开源操作系统,与我现在用的Ubuntu系统类似,但是所有的Android设备都是运行在ARM处理器(ARM源自进阶精简指令集机器,源自ARM架构)上,而像Ubuntu操作系统是x86(x86是一系列的基于intel 8086 CPU计算机微处理器指令集架构)系统.不过既然Android也是基于Linux内核的系统,那么基本的启动过程也应该符合Linux的规则.下图基本描述了当你按下电源开关后Android设备的执行步骤: 一个完整的Linux系

Android Settings(系统设置)源码分析(一)

由于工作需求,经常要用到系统设置中的一些功能,如调用里面的一些屏保.屏幕缩放.分辨率.音频输出模式等等接口和数据,于是决定对Settings(版本:Android4.4.4)源码做一个大体的分析. 先上一张平时经常接触到的界面,即 系统设置(Settings)界面: 上面是4.2版本的,4.4的和它差不多 Settings源码位于android系统packages/apps目录下,由系统编译. 找到Settings的AndroidManifest.xml文件,找到程序入口,如下图: 发现Sett