好久没有写博客了!最近做了Android 设置的列表菜单风格改为Iphone的tab菜单风格的尝试!我知道,有许多朋友有自己的方式已经实现了这个界面风格的开发,今天大家来看看我的做法吧!
做这个开发前,首先要看看Android默认的设置列表菜单风格的实现!由 AndroidManifest.xml可以知道Settings这个Activity是我们关注的焦点!所以我们来到Settings.java来一探究竟,从中我们可以清除的知道他其实是一个PreferenceActivity,而PreferenceActivity又继承了ListActivity,我们知道ListActivity其实就是专门用于显示ListView的activity.这样一来,我们就清楚了,为什么显示的是列表菜单。实际上在Settings这个Activity里面利用这个函数loadHeadersFromResource来加载不同的xml配置文件,就可以加载对应的菜单了!在这个xml文件里面每一项菜单项几乎都定义了自己的fragment,这样一来,只要你点击对应的菜单就能进入对应的fragment了!
如果,你多关注一下Settings这个activity,你就会发现这个activity比你想象的要复杂的多!比如,在手机里面很多需要对手机设置的地方都可能调用Settings这个activity,想要通过改动这个activity来实现Iphone的tab菜单风格,我觉得要冒一定的风险!所以,在做Iphone的tab菜单风格的时候我提出了两个规则:
(1)尽量不影响Settings这个activity,包括这个activity的定义、已经被何时、何地被调用。
(2)尽量要求效率!
一般来说,做tab菜单风格无非有三种方式:(1)TabHost + ViewPager ;(2)ActionBar + ViewPager;(3)TabWidget + ViewPager ;从中可以知道,都要用到ViewPager . 而ViewPager 是通过Adapter加载不同的view来实现不同的tab的菜单显示!这样的话就需要你写一个activity,通过加载包含ViewPager 的layout来实现!这样一来的话,要实现这个效果,你就需要从写一个另外一直方式的Settings的activity,这显然是有风险的!然后来看看ActionBar能否实现Iphone的tab折中底部tab按钮的效果!答案是否定的!(网上有各种办法,我实验的结果是无法实现这种效果的)。
ok,来说说我的实现办法把!
首先,我选择TabActivity来取代原来Settings的Activity地位!定义自己的TabActivity:TabSetting,在AndroidManifest.xml里面改为:
<activity android:name="TabSetting" android:label="@string/settings_label_launcher" android:taskAffinity="com.android.settings" android:theme="@style/Theme.Settings.Light" android:configChanges="keyboardHidden|screenSize|mcc|mnc" android:launchMode="singleTask"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <action android:name="android.settings.SETTINGS" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.APP_SETTINGS" /> </intent-filter> </activity>
原来的settings配置如下:
<activity android:name="Settings" android:label="@string/settings_label_launcher" android:taskAffinity="com.android.settings" android:theme="@style/Theme.Settings.Light" android:configChanges="keyboardHidden|screenSize|mcc|mnc" android:launchMode="singleTask"> </activity>
然后来看看TabSetting这个Activity的布局文件的定义:
<?xml version="1.0" encoding="utf-8"?> <TabHost android:id="@+android:id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <FrameLayout android:id="@+android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" /> <TabWidget android:orientation="horizontal" android:id="@+android:id/tabs" android:layout_width="fill_parent" android:layout_height="57.0dip" android:divider="@null" style="?android:attr/tabWidgetStyle" /> </Linear
从上面的布局,我们就这里采用的是TabWidget.
接下来的重点就是TabHost的配置了!我们直接看代码:
private void createTabs(int selectedIndex) { mTabHost = getTabHost(); LinearLayout tab_view; final LayoutInflater inflater = LayoutInflater.from(this); if (mTabHost != null) { Intent start_activity_intent = TabSettings_content.getIntent_for_tab(this); start_activity_intent.putExtra(INIT_SELECTED_TAB, selectedIndex); tab_view =(LinearLayout) inflater.inflate(tab_view_resource_id, mTabHost.getTabWidget(), false); ((TextView)tab_view.findViewById(R.id.tab_hint)).setText(R.string.meng_tab_network); ((ImageView)tab_view.findViewById(R.id.tab_icon)).setImageResource(R.drawable.xunhu_tab_network); start_activity_intent.putExtra(SELECT_TAB_INTENT_EXTRA, NETWORK_TAB_INDEX); mTabHost.addTab(mTabHost.newTabSpec("network") .setIndicator(tab_view) .set_SameContent(start_activity_intent)); tab_view =(LinearLayout) inflater.inflate(tab_view_resource_id, mTabHost.getTabWidget(), false); ((TextView)tab_view.findViewById(R.id.tab_hint)).setText(R.string.meng_tab_device); ((ImageView)tab_view.findViewById(R.id.tab_icon)).setImageResource(R.drawable.xunhu_tab_device); start_activity_intent =(Intent) start_activity_intent.clone(); start_activity_intent.putExtra(SELECT_TAB_INTENT_EXTRA, DEVICE_TAB_INDEX); mTabHost.addTab(mTabHost.newTabSpec("device") .setIndicator(tab_view) .set_SameContent(start_activity_intent)); tab_view =(LinearLayout) inflater.inflate(tab_view_resource_id, mTabHost.getTabWidget(), false); ((TextView)tab_view.findViewById(R.id.tab_hint)).setText(R.string.meng_tab_personal); ((ImageView)tab_view.findViewById(R.id.tab_icon)).setImageResource(R.drawable.xunhu_tab_personal); start_activity_intent =(Intent) start_activity_intent.clone(); start_activity_intent.putExtra(SELECT_TAB_INTENT_EXTRA, PERSONAL_TAB_INDEX); mTabHost.addTab(mTabHost.newTabSpec("personal") .setIndicator(tab_view) .set_SameContent(start_activity_intent)); tab_view =(LinearLayout) inflater.inflate(tab_view_resource_id, mTabHost.getTabWidget(), false); ((TextView)tab_view.findViewById(R.id.tab_hint)).setText(R.string.meng_tab_system); ((ImageView)tab_view.findViewById(R.id.tab_icon)).setImageResource(R.drawable.xunhu_tab_system);// start_activity_intent =(Intent) start_activity_intent.clone(); start_activity_intent.putExtra(SELECT_TAB_INTENT_EXTRA, SYSTEM_TAB_INDEX); mTabHost.addTab(mTabHost.newTabSpec("system") .setIndicator(tab_view) .set_SameContent(start_activity_intent)); mTabHost.setOnTabChangedListener(new OnTabChangeListener() { public void onTabChanged(String tabId) { if (tabId.equals("network")) { mSelectedTab = NETWORK_TAB_INDEX; } else if(tabId.equals("device")) { mSelectedTab = DEVICE_TAB_INDEX; } else if(tabId.equals("personal")) { mSelectedTab = PERSONAL_TAB_INDEX; } else if(tabId.equals("system")) { mSelectedTab = SYSTEM_TAB_INDEX; } //mTabHost.setCurrentTab(mSelectedTab); TabSetting.this.updateTabStyle(TabSetting.this.mTabHost); } }); } }
这里请关注一下红色的代码!你会发现不同tab都配置了相同的intent。这是为什么呢!实际上这个start_activity_intent就是用于启动我们shettings的avtivity的!所以,我的目的其实就是不同tab菜单的切换其实都是用一个activity,而且这个activity就是系统原本有的shettings的activity。只是在不同的tab,loadHeadersFromResource加载不同的xml来实现显示不同的菜单!这里我们来看看这个我们启动的activity(TabSettings_content)的定义:
public class TabSettings_content extends Settings{ private static final boolean DEBUG = true; private static int mCurr_SelectedTab = TabSetting.NETWORK_TAB_INDEX; private static int mNext_SelectedTab = TabSetting.NETWORK_TAB_INDEX; private int SettingMenuStyle_index = -1; .... .... .... }
从上面的代码可以知道 TabSettings_content其实就是一个Settings的封装。
那在TabSettings_content里面是怎么知道在不同的tab界面加载不同的xlm文件呢!这个时候activity的onNewIntent这个方法就有用处了!如下:
@Override public void onNewIntent(Intent newIntent) { super.onNewIntent(newIntent); // update our intent so that we can consult it to determine whether or // not the most recent launch was via the event setIntent(newIntent); //Get the saved selected tap_index, int tab = newIntent.getIntExtra(TabSetting.SELECT_TAB_INTENT_EXTRA, mCurr_SelectedTab); if (!getResources().getBoolean(R.bool.TabSettingPage_Change_Animal)){ if(tab != mCurr_SelectedTab){ mCurr_SelectedTab = tab; mNext_SelectedTab = mCurr_SelectedTab; invalidateHeaders(); } }else if (tab != -1 && tab != mCurr_SelectedTab) { <span style="BACKGROUND-COLOR: #ff0000">Switch_tab_anima(tab); </span> } }
从上面的代码,你可能有点晕,因为你不太知道Switch_tab_anima(tab);这个语句在做什么。通过字面的意思,可以知道这应该就是tab切换并且还播放动画了! 其实这里播放的动画其实就是左右两个tab往左边移出、右边tab从右边移入的这样的一个动画效果!下面代码来看看这个方法Switch_tab_anima的实现吧:
private void Switch_tab_anima(int the_tap_index){ if(false == Init_Animation){ Init_Animation_Play(); } if (the_tap_index != mCurr_SelectedTab && the_tap_index != -1) { if((the_tap_index > mCurr_SelectedTab && !(the_tap_index == 3 && mCurr_SelectedTab == 0)) || (the_tap_index == 0 && mCurr_SelectedTab == 3)){ TabSettings_ListView.startAnimation(slide_left_out); <span style="BACKGROUND-COLOR: #ff0000">MainHandler.sendEmptyMessageDelayed(222,195); </span> } else{ TabSettings_ListView.startAnimation(slide_right_out); <span style="BACKGROUND-COLOR: #ff0000">MainHandler.sendEmptyMessageDelayed(000,195); </span> } mNext_SelectedTab = the_tap_index; } }
一样的,上面有两行我标记红色的代码,这个地方MainHandler就是一个我定义的Handler,主要目的就是延时,其目的是:原来的tab移除以后,新的tab才开始移入。这里的延时就是等原来的tab移除。
最后,你会发现,我实现的这个tab菜单风格,当用手指左右滑动菜单时候,并不会切换到下一个tab(或者上一个tab),这怎么办呢!下面通过2步来实现:
(1):首先要能获取手指触摸屏的事件:这里你不得不选择dispatchTouchEvent:
@Override public boolean dispatchTouchEvent(MotionEvent m) { // Elog.d(TAG, "dispatchTouchEvent()"); Log.d("TabSetting", "TabSetting dispatchTouchEvent()"); if (this.detector != null) { this.detector.onTouchEvent(m); } boolean flag = super.dispatchTouchEvent(m); Log.d("TabSetting", "TabSetting dispatchTouchEvent() flag="+flag); return flag; }
(2):我们刚刚定义的TabActivity:TabSetting 需要实现OnGestureListener这个手势接口!该接口的onFling和onScroll来实现识别手指滑动的操作,操作成功则切换不同tab:下面代码是方法onFling的实现(和方法onScroll实现一样的):
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int tab_index; if(TouchedDown_Done == true){ return false; } if (e1.getX() - e2.getX() > <span style="BACKGROUND-COLOR: #ff0000">move_switch_len</span>) { tab_index = mSelectedTab == 3 ? 0:mSelectedTab+1; mTabHost.setCurrentTab(tab_index); mSelectedTab = tab_index; TouchedDown_Done = true; return true; } else if(e2.getX() - e1.getX() > move_switch_len){ tab_index = mSelectedTab == 0 ? 3:mSelectedTab-1; mTabHost.setCurrentTab(tab_index); mSelectedTab = tab_index; TouchedDown_Done = true; return true; } else{ return false; } }
关注一下move_switch_len这个量吧。这个值不能太大也不能太小!你知道原因吗?
我想该说的已经说完了!我这里实现tab风格其实还是调用原来的Settings这个activity,只是外面用一个TabActivity包装了一下!这就是所有的!你应该了解了吧。