android 7.0 system UI之快速启动栏的分析(一)

作者:jason.chen


  从状态栏往下拉的时候,就会出现“快速设置”界面,如下图所示。快速启动界面可以让用户快速设置和操作。对于急需或频繁使用的控件和操作,保留“快速设置”图块,且不应将其用作启动应用的快捷方式。android7.0添加了新的API,让开发者可以为自己的应用添加快速设置图标。这篇文件将要分析快速启动的设计。当然,只是个人见解。
android 7.0 system UI之快速启动栏的分析(一)_第1张图片     android 7.0 system UI之快速启动栏的分析(一)_第2张图片
  快速设置图标有两种类型,一种是system UI预置的(QSTile),另一种是第三方应用添加的(CustiomTile)。system UI使用Tile来代表一个快速设置。本文只介绍system UI预置的QSTile,关于第三方应用的Tile后续再分析。
  

一、Tile数据加载

1、主要类介绍

  android 7.0 system UI之快速启动栏的分析(一)_第3张图片
  QSTile:快速设置Tile的基类,快速设置都继承自这个类。它使用Host提供的Looper, 管理着快速设置(以下文章都用Tile来指代快速设置)的状态(State)变化。Tile通过重载handleUpdateState方法来更新状态。如果监听到状态变化,或者点击事件需要更新状态,使用refreshState来更新State。
  QSTileHost: Host的实现,管理Tile 状态的变化。它包含各种Tile的Control类,用来设置和监听Tile。
  State: Tile的状态,包含图标,名称等信息。
  H:继承自Handler, 通过looper跑在QSTileHost的线程里。用来处理Tile的各种事件。
  Tunable: 接口,配置变化的回调。QSTileHost实现这个接口。
  TunerSercice:用来监听各种配置变化。

2、Tile加载流程

这里从创建View开始跟踪(之前的流程可以参考http://blog.csdn.net/zhudaozhuan/article/details/50817180),过程如下图所示:
android 7.0 system UI之快速启动栏的分析(一)_第4张图片
在TunerService的addTunable的方法,会监听ContentProvider中的Tile的变化(一般是增加或减少Tile),监听的函数是registerContentObserver。Tile是存储在Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser),相关代码如下:

    private void addTunable(Tunable tunable, String key) {
        if (!mTunableLookup.containsKey(key)) {
            mTunableLookup.put(key, new ArraySet());
        }
        mTunableLookup.get(key).add(tunable);
        Uri uri = Settings.Secure.getUriFor(key);
        if (!mListeningUris.containsKey(uri)) {
            mListeningUris.put(uri, key);
            //listening for the tile change
            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
        }
        // Send the first state.
        String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
        tunable.onTuningChanged(key, value);
    }

  当Tile发生变化或第一次添加的时候,会执行QSTileHost的onTuningChanged的方法,在这个方法里会创建所有需要显示的Tile。部分代码如下:
  

       for (String tileSpec : tileSpecs) {
            QSTile tile = mTiles.get(tileSpec);
            if (tile != null && (!(tile instanceof CustomTile)
                    || ((CustomTile) tile).getUser() == currentUser)) {
                if (DEBUG) Log.d(TAG, "Adding " + tile);
                tile.removeCallbacks();
                newTiles.put(tileSpec, tile);
            } else {
                if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
                try {
                    tile = createTile(tileSpec);
                    if (tile != null && tile.isAvailable()) {
                        tile.setTileSpec(tileSpec);
                        newTiles.put(tileSpec, tile);
                    }
                } catch (Throwable t) {
                    Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
                }
            }
        }
        mCurrentUser = currentUser;
        mTileSpecs.clear();
        mTileSpecs.addAll(tileSpecs);
        mTiles.clear();
        mTiles.putAll(newTiles);
        for (int i = 0; i < mCallbacks.size(); i++) {
            mCallbacks.get(i).onTilesChanged();
        }

  createTile方法,根据tileSpec创建响应的Tile,使用的是工厂方法。最终会把创建的Tile都存储在QSTileHost的mTiles,如果有注册QSTile.Host.Callback的监听,也会回调响应的onTilesChanged方法。
  自此,QSTileHost已经获取到了Tile的数据。

二、未展开的界面显示

  显示Tile的地方有两个,请看前面的两张图,一个是未展开的状态,只显示前面几个Tile;另一个是全部展开的状态,可以显示所有的Tile,还有其他的设置功能。这里先介绍未展开的Tile界面。
  android 7.0 system UI之快速启动栏的分析(一)_第5张图片

1.主要类介绍

android 7.0 system UI之快速启动栏的分析(一)_第6张图片
  QSPanel: 用来显示快速设置的界面。
  QuickQSPanel: QSPanel的子类,里面包含一个HeaderTileLayout,用来显示少数几个Tile的界面。在快速设置界面的顶部。
  HeaderTileLayout: QuickQSPanel创建的View,用来显示少数几个Tile的界面。
  TileRecord: 包含Tile数据,Tile界面(QSTileBaseView)等数据。
  QSTileBaseView: 继承自LinearLayout,用来显示Tile。不显示标题。
  QSTileView: 继承自QSTileBaseView, 用来显示Tile,会显示标题。HeaderTileLayout不使用这个。
  

2. 界面显示流程

    未展开的Tile显示在QuickQSPanel的HeaderTileLayout里。可以查看QuickStatusBarHeader的xml文件:quick_status_bar_expanded_header.xml。在QuickQSPanel的构造函数中,创建并加入HeaderTileLayout:

    public QuickQSPanel(Context context, AttributeSet attrs) {
        super(context, attrs);
        if (mTileLayout != null) {
            for (int i = 0; i < mRecords.size(); i++) {
                mTileLayout.removeTile(mRecords.get(i));
            }
            removeView((View) mTileLayout);
        }
        mTileLayout = new HeaderTileLayout(context);
        mTileLayout.setListening(mListening);
        addView((View) mTileLayout, 1 /* Between brightness and footer */);
    }

  它的父类QSPanel的构造函数会加入一个Brightness View和Footer View, 这里并不需要显示。加载Tile的时序图如下所示:android 7.0 system UI之快速启动栏的分析(一)_第7张图片
  在QSPanel的addTile中,会去创建TileRecord。TileRecord包含Tile的界面和tile的数据,同时还包含一个callback,这在处理事件的时候,会用到。然后在HeaderTileLayout的add Tile方法,会把这个QSTileBaseView添加进去。相关代码代码如下:

    protected void addTile(final QSTile tile, boolean collapsedView) {
        final TileRecord r = new TileRecord();
        r.tile = tile;
        r.tileView = createTileView(tile, collapsedView);
        final QSTile.Callback callback = new QSTile.Callback() {
            @Override
            public void onStateChanged(QSTile.State state) {
                drawTile(r, state);
            }

            @Override
            public void onShowDetail(boolean show) {
                // Both the collapsed and full QS panels get this callback, this check determines
                // which one should handle showing the detail.
                if (shouldShowDetail()) {
                    QSPanel.this.showDetail(show, r);
                }
            }

            @Override
            public void onToggleStateChanged(boolean state) {
                if (mDetailRecord == r) {
                    fireToggleStateChanged(state);
                }
            }

            @Override
            public void onScanStateChanged(boolean state) {
                r.scanState = state;
                if (mDetailRecord == r) {
                    fireScanStateChanged(r.scanState);
                }
            }

            @Override
            public void onAnnouncementRequested(CharSequence announcement) {
                announceForAccessibility(announcement);
            }
        };
        r.tile.addCallback(callback);
        r.callback = callback;
        final View.OnClickListener click = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onTileClick(r.tile);
            }
        };
        final View.OnLongClickListener longClick = new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                r.tile.longClick();
                return true;
            }
        };
        r.tileView.init(click, longClick);
        r.tile.refreshState();
        mRecords.add(r);

        if (mTileLayout != null) {
            mTileLayout.addTile(r);
        }
    }

三、未展开界面事件处理

主要有点击事件,长按事件等。

1.点击事件

  onClick的事件,参考前面addTile的代码,里面有创建一个Click类,用来监听onClick事件。时序图如下:
android 7.0 system UI之快速启动栏的分析(一)_第8张图片
  1)QuickQSPanel有重载了QSPanle的onTileClick事件,会执行handleSecondaryClick。从上图可以看出,它默认的行为也是执行QSTile的handleClick事件。每个QSTile的子类都可以重载handleSecondaryClick方法,来区别未展开和展开的Tile点击事件。所以,点击QuickQSPanel的Tile界面,执行的是handleSecondaryClick
  2)QSPanel的点击事件流程。触发点击事件后,执行QSTile的handleClick方法。这是一个虚方法,各个QSTile的子类需要重载这个方法。有些Tile会在这里执行刷新状态refreshState, 有些Tile会执行showDetail。
  3)refreshState的流程,QSTile的子类,要在这里更新Tile的State
  4)showDetail的流程。这里有一个要注意的地方是,在QSTile.Callback的onShowDetail方法中,会判断shouldShowDetail的方法。这个方法在未展开的panel里,返回的是!mExpanded, 而在展开的Panel里,返回的是
  QuickQSPanle是QSPanel的子类,可能会重载QSPanel的方法。跟踪代码的时候,需要注意是否有重载。像onTileClick就有重载。
  

2.longClick事件

  长按事件也是在QSPanl的addTile方法里创建的,最后会执行到handleLongClick的方法。在这里会从getLongClickIntent里面,获取一个Intent, 启动该Intent的activity。getLongClickIntent是一个虚方法,每个QSTile的子类, 需要重载该方法。
  

你可能感兴趣的:(android 7.0 system UI之快速启动栏的分析(一))