[置顶] 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的程序里面,具体 效果如下图所示

                                               [置顶] Android源码之DeskClock (二)_第1张图片


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

你可能感兴趣的:(源码,android,DeskClock,安卓时钟,原生程序)