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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章