Android源码之DeskClock (二)

一.概述

在DeskClock(一)中介绍了该程序源码的迁出,现在开始分析该应用的源码,DeskClock主要有四个功能,闹钟,时钟,定时,和秒表,在这篇博客中主要分析DeskClock的入口和主UI上的逻辑结构,在后续的系列中会把这四个功能都串起来.

二.源码分析

1.activity-alias 多入口配置

以前装应用的时候有些应用会在桌面上生成两个图标,这两个图标有些是同一个Activity的入口,有些是另外一个Activity的入口,这样的效果是怎么实现的呢?在看Android原生DeskClock程序的时候看到了这个功能的实现.使用的是activity-alias:

1).语法格式

<activity-alias android:enabled=["true" | "false"]
                android:exported=["true" | "false"]
                android:icon="drawable resource"
                android:label="string resource"
                android:name="string"
                android:permission="string"
                android:targetActivity="string" >
    . . .
</activity-alias>

2).DeskClock中应用

从下面的配置可以看出这是同一个activity(DeskClock)的两个入口,并且这两个入口的名字图标都一样,这样做有什么意义呢?可以看到activity-alias中标记了一个名为android.intent.category.DESK_DOCK的category,这个是在android设备插上桌面Dock底 座的时候才会触发alias入口.

<activity android:name="DeskClock"
         android:label="@string/app_label"
         android:theme="@style/DeskClock"
         android:icon="@mipmap/ic_launcher_alarmclock"
         android:launchMode="singleTask"
         >

     <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.DEFAULT" />
         <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>
</activity>

<activity-alias android:name="DockClock"
         android:targetActivity="DeskClock"
         android:label="@string/app_label"
         android:theme="@style/DeskClock"
         android:icon="@mipmap/ic_launcher_alarmclock"
         android:launchMode="singleTask"
         android:enabled="@bool/config_dockAppEnabled"
         >
     <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.DEFAULT" />
         <category android:name="android.intent.category.DESK_DOCK" />
     </intent-filter>
</activity-alias>

activity-alias通过指定targetActivity来决定入口相连接的activity,给该程序更改一个不同的label(ClockAlias)和icon(菊花)并且替换掉Dock底座的category,如下部代码配置所示.

<activity-alias android:name="DockClock"
         android:targetActivity="DeskClock"
         android:label="@string/app_second_label"
         android:theme="@style/DeskClock"
         android:icon="@mipmap/entrance"
         android:launchMode="singleTask"
         >
     <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.DEFAULT" />
         <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>
</activity-alias>

这样修改完成配置之后就可以实现在android设备上双入口图标了,点击两个图标都可以进入到DeskClock的程序里面,具体 效果如下图所示

2.主页的主要结构

主页主要是由Action bar ,ViewPager 和FragmentPagerAdapter 三部分组合而成的,通过四个Action bar TAB和抽象过的Fragment由自定义的FragmentPagerAdapter适配器来完成DeskClock中四个主要功能的切换.

在DeskClock被创建的时候会先取得一个ActionBar被选中显示的标识位置,再去初始化Views.这里需要注意的是该ViewPager通过setOffscreenPageLimit方法来设置预加载的Fragment,这里是四个主要的fragment,DeskClock显示一个fragment,再预加载另外3个fragment来提高UI显示的流畅度.

        if (mTabsAdapter == null) {
            mViewPager = new ViewPager(this);
            mViewPager.setId(R.id.desk_clock_pager);
            // Keep all four tabs to minimize jank.
            mViewPager.setOffscreenPageLimit(3);
            mTabsAdapter = new TabsAdapter(this, mViewPager);
            createTabs(mSelectedTab);
        }
        setContentView(mViewPager);
        mActionBar.setSelectedNavigationItem(mSelectedTab);

主页中主要的页面切换等大部分逻辑都在自定义的FragmentPagerAdapter中,这里主要分析下适配器。TabsAdapter在构造的时候可以获取到DeskClock的context,actionbar,viewpager并绑定该适配器,绑定页面变化的监听.

        public TabsAdapter(Activity activity, ViewPager pager) {
            super(activity.getFragmentManager());
            mContext = activity;
            mMainActionBar = activity.getActionBar();
            mPager = pager;
            mPager.setAdapter(this);
            mPager.setOnPageChangeListener(this);
        }

其中定义了一个TabInfo的内部类,用来标记每个ItemView的属性和特征,在填充适配器item的时候来构造TabInfo,并将TabInfo绑定到相对应的ActionBar的 tab上.

        final class TabInfo {
            private final Class<?> clss;
            private final Bundle args;

            TabInfo(Class<?> _class, int position) {
                clss = _class;
                args = new Bundle();
                args.putInt(KEY_TAB_POSITION, position);
            }

            public int getPosition() {
                return args.getInt(KEY_TAB_POSITION, 0);
            }
        }
        public void addTab(ActionBar.Tab tab, Class<?> clss, int position) {
            TabInfo info = new TabInfo(clss, position);
            tab.setTag(info);
            tab.setTabListener(this);
            mTabs.add(info);
            mMainActionBar.addTab(tab);
            notifyDataSetChanged();
        }

当tab被选中时,由于之前已经把fragment的详细信息绑定到tab上了,这里就可以直接通过tab获取到TabInfo的所有信息(主要是position),之后就可以根据位置信息来转换fragment了.但是这个getRtlPosion是什么鬼?RTL就是right to left,地球上大部分国家的阅读习惯都是LTR的,但是也是有少部分地区有RTL,google当然也要兼容这些用户了,就提供了RTL支持。getRtlPosion方法是在本地不支持RTL的时候,先去判断在配置文件里面有没有设置RTL,如果设置了就自己通过switch
case调转page item的position的方式来提供RTL支持,虽然这个技术在国内一般是用不到的,但也可以简单了解一下.

当page变化时,直接通过position通知ActionBar同步变化就行了.

        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            TabInfo info = (TabInfo)tab.getTag();
            int position = info.getPosition();
            mPager.setCurrentItem(getRtlPosition(position));
        }
        public void onPageSelected(int position) {
            // Set the page before doing the menu so that onCreateOptionsMenu knows what page it is.
            mMainActionBar.setSelectedNavigationItem(getRtlPosition(position));
            notifyPageChanged(position);

            // Only show the overflow menu for alarm and world clock.
            if (mMenu != null) {
                // Make sure the menu's been initialized.
                if (position == ALARM_TAB_INDEX || position == CLOCK_TAB_INDEX) {
                    mMenu.setGroupVisible(R.id.menu_items, true);
                    onCreateOptionsMenu(mMenu);
                } else {
                    mMenu.setGroupVisible(R.id.menu_items, false);
                }
            }
        }
        private boolean isRtl() {
            return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
                    View.LAYOUT_DIRECTION_RTL;
        }

        private int getRtlPosition(int position) {
            if (isRtl()) {
                switch (position) {
                    case TIMER_TAB_INDEX:
                        return RTL_TIMER_TAB_INDEX;
                    case CLOCK_TAB_INDEX:
                        return RTL_CLOCK_TAB_INDEX;
                    case STOPWATCH_TAB_INDEX:
                        return RTL_STOPWATCH_TAB_INDEX;
                    case ALARM_TAB_INDEX:
                        return RTL_ALARM_TAB_INDEX;
                    default:
                        break;
                }
            }
            return position;
        }

TabsAdapter在加载不同的fragment的时候也是同理的,通过positon取得TagInfo的数据,再根据Fragment的instantiate方法以ClassLoader的方式实例跟positon相对应的Fragment.

        public Fragment getItem(int position) {
            TabInfo info = mTabs.get(getRtlPosition(position));
            DeskClockFragment f = (DeskClockFragment) Fragment.instantiate(
                    mContext, info.clss.getName(), info.args);
            return f;
        }

TabsAdapter中还有一个页面变化监听的注册和注销,当有页面变化TabsAdapter会把这个变化通知到注册监听的Fragment中,在这个程序里面是有两个功能Fragment(定时器和秒表)需要根据页面变化的状态来做不同的业务逻辑处理的,这个后续讲到这两个功能的时候会细分析的.

        public void registerPageChangedListener(DeskClockFragment frag) {
            String tag = frag.getTag();
            if (mFragmentTags.contains(tag)) {
                Log.wtf(LOG_TAG, "Trying to add an existing fragment " + tag);
            } else {
                mFragmentTags.add(frag.getTag());
            }
            // Since registering a listener by the fragment is done sometimes after the page
            // was already changed, make sure the fragment gets the current page
            frag.onPageChanged(mMainActionBar.getSelectedNavigationIndex());
        }

        public void unregisterPageChangedListener(DeskClockFragment frag) {
            mFragmentTags.remove(frag.getTag());
        }
        private void notifyPageChanged(int newPage) {
            for (String tag : mFragmentTags) {
                final FragmentManager fm = getFragmentManager();
                DeskClockFragment f = (DeskClockFragment) fm.findFragmentByTag(tag);
                if (f != null) {
                    f.onPageChanged(newPage);
                }
            }
        }

三.总结

这一篇博客主要介绍了DeskClock程序中主UI部分的逻辑,主要分析了两点(多入口配置和ViewPager,ActionBar,适配器之间切换和数据绑定)现在四大功能部分还没有涉及,后续的系列就要开始介绍四大功能.

转载请注明出处:http://blog.csdn.net/l2show/article/details/46722999

时间: 2024-08-04 01:15:59

Android源码之DeskClock (二)的相关文章

Cordova Android源码分析系列二(CordovaWebView相关类分析)

本篇文章是Cordova Android源码分析系列文章的第二篇,主要分析CordovaWebView和CordovaWebViewClient类,通过分析代码可以知道Web网页加载的过程,错误出来,多线程处理等. CordovaWebView类分析 CordovaWebView类继承了Android WebView类,这是一个很自然的实现,共1000多行代码.包含了PluginManager pluginManager,BroadcastReceiver receiver,CordovaInt

Android源码浅析(二)——Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境

Android源码浅析(二)--Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境 接着上篇,上片主要是介绍了一些安装工具的小知识点Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置,其实Ubuntu Kylin 16.04 LTS也只是为了体验,我们为了追求稳定,还是使用了Ubuntu14.04 这里提供一个国内镜像的下载链接,可以用迅雷,下载下来之后后缀

Android源码之DeskClock (一)

一.概述 一直有read the fucking source code的计划,但是实行起来都是断断续续的.到现在也没有真正得读过多少Android的源码(主要是懒的).现在回想起来实在是很惭愧,再加上好久没有写博客了,经过几天的琢磨准备在CSDN博客开两个长时间更新的系列博客(Android 源码和Android设计模式),每周更新最少一篇.以此来督促自己,并且跟其他小伙伴一起分享这个过程. 之前是直接读的framework层的源码,读起来比较枯燥和生涩,碰巧上周写了一篇MVP在Android

android源码解析(二十五)--&gt;onLowMemory执行流程

上篇文章中我们分析了Activity的onSaveInstanceState方法执行时机,知道了Activity在一般情况下,若只是执行onPause方法则不会执行onSaveInstanceState方法,而一旦执行了onStop方法就会执行onSaveInstanceState方法,具体的信息,可以参见onSaveInstanceState方法执行时机:http://blog.csdn.net/qq_23547831/article/details/51464535 这篇文章中同样的我们分析

android源码解析(二十二)--&gt;Toast加载绘制流程

前面我们分析了Activity.Dialog.PopupWindow的加载绘制流程,相信大家对整个Android系统中的窗口绘制流程已经有了一个比较清晰的认识了,这里最后再给大家介绍一下Toast的加载绘制流程. 其实Toast窗口和Activity.Dialog.PopupWindow有一个不太一样的地方,就是Toast窗口是属于系统级别的窗口,他和输入框等类似的,不属于某一个应用,即不属于某一个进程,所以自然而然的,一旦涉及到Toast的加载绘制流程就会涉及到进程间通讯,看过前面系列文章的同

android源码解析(二十四)--&gt;onSaveInstanceState执行时机

我们已经分析过Activity的启动流程,从中也分析了Activity的生命周期.而其中有一个生命周期方法:onSaveInstanceState方法,今天我们主要讲解一下onSaveInstanceState方法的执行时机. 可能部分同学对Activity的onSaveInstanceState方法不是特别熟悉,这里我们简单介绍一下.onSaveInstanceState方法是Activity的成员方法,主要用于在Activity销毁时保存Activity相关的对象信息,而其执行的时机不是我们

Android 源码系列之&lt;二&gt;从安全的角度深入理解BroadcastReceiver(上)

提起BroadcastReceiver大家都很熟悉,它和Activity,Service以及ContentProvider并称为Android的四大组件(四大金刚),可见BroadcastReceiver的重要性,今天我们主要从安全的角度来讲解称为四大组件之一的BroadcastReceiver.可能有的童靴看到这里会有疑问,BroadcastReceiver有啥好讲的,不就是先定义自己的广播接收器然后在manifest.xml文件中注册,在需要发送广播的地方调用Context的sendBroa

android源码解析(二十一)--&gt;PopupWindow加载绘制流程

在前面的几篇文章中我们分析了Activity与Dialog的加载绘制流程,取消绘制流程,相信大家对Android系统的窗口绘制机制有了一个感性的认识了,这篇文章我们将继续分析一下PopupWindow加载绘制流程. 在分析PopupWindow之前,我们将首先说一下什么是PopupWindow?理解一个类最好的方式就是看一下这个类的定义,这里我们摘要了一下Android系统中PopupWindow的类的说明: A popup window that can be used to display

android源码解析(二十六)--&gt;截屏事件流程

今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程.用过android系统手机的同学应该都知道,一般的android手机按下音量减少键和电源按键就会触发截屏事件(国内定制机做个修改的这里就不做考虑了).那么这里的截屏事件是如何触发的呢?触发之后android系统是如何实现截屏操作的呢?带着这两个问题,开始我们的源码阅读流程. 我们知道这里的截屏事件是通过我们的按键操作触发的,所以这里就需要我们从android系统的按键触发模块开始看起,由于我们在不同的App页面,操作音量减少键和电