一.概述
在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