Android之对TabActivity的见解,个人觉得不错
Android实现通用的ActivityGroup(效果类似Android微博客户端主界面),强烈建议不要再使用TabActivity
ActivityGroup在实际的开发中是十分常见的,在我使用过的Android应用中,十个应用里面有九个应用的主界面都是使用ActivityGroup的。说起ActivityGroup,在国内好像直接使用它开发的并不多,基本都是使用TabActivity,它是ActivityGroup唯一的一个子类。Android端新浪微博的主界面就是用TabActivity来实现的,还有其它的一些应用也几乎都用TabActivity来实现。在我眼里,TabActivity是Google提供的一个非常失败的API(至少我现在这么认为,下文我会说它失败在哪里),但中国几乎所有的应用都使用TabActivity,我不禁在思考这是巧合还是复制粘贴的产物。使用ActivityGroup(或者说TabActivity)开发出来的主界面效果图如下(涉及版权问题,我这里就不粘微博的主界面了,我粘我自己的,虽然比较难看,有兴趣可以去参考新浪微博,微信等Android客户端):

可以说ActivityGroup是Google提供的一个非常优秀的API,但它需要做稍微复杂的重写才能用起来比较方便,本文拟将实现这个稍微复杂的重写。TabActivity作为ActivityGroup唯一的子类却让人大失所望。
首先来说ActivityGroup的优秀之处以及它的必要性,它为开发者提供了一种可能,这种可能不将Activity作为屏幕的顶级元素(Context)呈现,而是嵌入到ActivityGroup当中。这是一种极大的飞跃,它将场景(Context)细分化了,ActivityGroup是一个主场景,而用户可以通过导航按钮来切换想要的子场景。如使用微博功能,它是一个相当宏大的场景,具有看最新的广播信息、自己发微博、修改资料等子场景,用户可以通过按钮来切换到想要的子场景,而这个子场景仍活动于主场景之中。让一个主场景能拥有多个逻辑处理模块,主场景不再负责子场景逻辑,主场景只负责切换场景的逻辑,即每一个Activity(子场景)拥有一个逻辑处理模块,一个ActivityGroup有多个Activity,却不干预Activity的逻辑,这无疑细分化和模块化了逻辑代码。ActivityGroup和它将要内嵌的Activity所要实现的功能完全可以用只一个Activity来完成,你可以试想,当你把一个ActivityGroup和它所拥有的Activity的逻辑代码放在一个Activity中时,那这个Activity会拥有多少行代码,为维护带来非常的不便。
再来说说TabActivity的不足之处,首先,TabActivity自己独有的视图几乎没人使用(也就是难看的标签页按钮形式),国内开发者用到的特性几乎都是从ActivityGroup继承下来的。还有就是TabActivity的强制依赖关系,它的布局文件必须将TabHost作根标签,并且id必须为"@android:id/tabhost",必须有TabWidget标签,且它的id必须是"@android:id/tabs",还有加载Activity的View容器,id必须为@android:id/tabcontent。光是强制依赖关系,我就觉得不是很舒服。不仅仅是TabActivity,在一些特殊的Activity中,如ListActivity都存在这种强制依赖关系,ListActivity必须有id为xxx(想不起来了)的ListView,我想这些弊端应该获得Google开发者的重视。
那么我下面我就将自己实现ActivityGroup,告别强制依赖关系,并随心所欲的建立视图。下面这个类是一个抽象类,开发者只需对这个抽象类稍做修改,并加以实现自己的视图就能告别TabActivity。

package com.chenjun.demo.abstracttabactivity; import android.app.Activity;
import android.app.ActivityGroup;
import android.app.LocalActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.RadioButton; /**
* 自己实现的一个通用ActivityGroup。
* 可以通过简单的重写它来制作有导航按钮和用导航按钮控制动态加载Activity的ActivityGroup。
* 开发者需要在实现类中实现三个方法:
* 1.指定动态加载Activity的容器的对象,getContainer()方法。
* 2.初始化所有的导航按钮,initRadioBtns()方法,开发者要遍历所有的导航按钮并执行initRadioBtn(int id)方法。
* 3.实现导航按钮动作监听器的具体方法,onCheckedChanged(...)方法。这个方法将实现某个导航按钮与要启动对应的Activity的关联关系,可以调用setContainerView(...)方法。
* @author zet
*
*/
public abstract class AbstractMyActivityGroup extends ActivityGroup implements
CompoundButton.OnCheckedChangeListener{ @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initRadioBtns();
} //加载Activity的View容器,容器应该是ViewGroup的子类
private ViewGroup container; private LocalActivityManager localActivityManager; /**
* 加载Activity的View容器的id并不是固定的,将命名规则交给开发者
* 开发者可以在布局文件中自定义其id,通过重写这个方法获得这个View容器的对象
* @return
*/
abstract protected ViewGroup getContainer(); /**
* 供实现类调用,根据导航按钮id初始化按钮
* @param id
*/
protected void initRadioBtn(int id){
RadioButton btn = (RadioButton) findViewById(id);
btn.setOnCheckedChangeListener(this);
} /**
* 开发者必须重写这个方法,来遍历并初始化所有的导航按钮
*/
abstract protected void initRadioBtns(); /**
* 为启动Activity初始化Intent信息
* @param cls
* @return
*/
private Intent initIntent(Class<?> cls){
return new Intent(this, cls).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
} /**
* 供开发者在实现类中调用,能将Activity容器内的Activity移除,再将指定的某个Activity加入
* @param activityName 加载的Activity在localActivityManager中的名字
* @param activityClassTye 要加载Activity的类型
*/
protected void setContainerView(String activityName, Class<?> activityClassTye){
if(null == localActivityManager){
localActivityManager = getLocalActivityManager();
} if(null == container){
container = getContainer();
} //移除内容部分全部的View
container.removeAllViews(); Activity contentActivity = localActivityManager.getActivity(activityName);
if (null == contentActivity) {
localActivityManager.startActivity(activityName, initIntent(activityClassTye));
} //加载Activity
container.addView(
localActivityManager.getActivity(activityName)
.getWindow().getDecorView(),
new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
} }

需要重写的方法以及为什么需要重写我都已在原代码中标明。下面我们来具体的实现这个类,来达到我们想要的预期。

package com.chenjun.demo.abstracttabactivity; import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.RadioButton; public class TestMyActivityGroup extends AbstractMyActivityGroup{
//加载的Activity的名字,LocalActivityManager就是通过这些名字来查找对应的Activity的。
private static final String CONTENT_ACTIVITY_NAME_0 = "contentActivity0";
private static final String CONTENT_ACTIVITY_NAME_1 = "contentActivity1";
private static final String CONTENT_ACTIVITY_NAME_2 = "contentActivity2";
private static final String CONTENT_ACTIVITY_NAME_3 = "contentActivity3";
private static final String CONTENT_ACTIVITY_NAME_4 = "contentActivity4"; @Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.my_activity_group);
super.onCreate(savedInstanceState); ((RadioButton)findViewById(R.id.radio_button0)).setChecked(true);
} /**
* 找到自定义id的加载Activity的View
*/
@Override
protected ViewGroup getContainer() {
return (ViewGroup) findViewById(R.id.container);
} /**
* 初始化按钮
*/
@Override
protected void initRadioBtns() {
initRadioBtn(R.id.radio_button0);
initRadioBtn(R.id.radio_button1);
initRadioBtn(R.id.radio_button2);
initRadioBtn(R.id.radio_button3);
initRadioBtn(R.id.radio_button4);
} /**
* 导航按钮被点击时,具体发生的变化
*/
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
switch (buttonView.getId()) { case R.id.radio_button0:
setContainerView(CONTENT_ACTIVITY_NAME_0, ContentActivity0.class);
break; case R.id.radio_button1:
setContainerView(CONTENT_ACTIVITY_NAME_1, ContentActivity1.class);
break; case R.id.radio_button2:
setContainerView(CONTENT_ACTIVITY_NAME_2, ContentActivity2.class);
break; case R.id.radio_button3:
setContainerView(CONTENT_ACTIVITY_NAME_3, ContentActivity3.class);
break; case R.id.radio_button4:
setContainerView(CONTENT_ACTIVITY_NAME_4, ContentActivity4.class);
break; default:
break;
}
}
} }

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginTop="0.0px"
xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:id="@+id/container" android:layout_width="fill_parent" android:layout_height="0.0dip" android:layout_weight="1.0" /> <RadioGroup android:gravity="center_vertical" android:layout_gravity="bottom" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content">
<RadioButton android:id="@+id/radio_button0" android:layout_marginTop="2.0dip" android:text="按钮1" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_call" />
<RadioButton android:id="@+id/radio_button1" android:layout_marginTop="2.0dip" android:text="按钮2" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_camera" />
<RadioButton android:id="@+id/radio_button2" android:layout_marginTop="2.0dip" android:text="按钮3" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_agenda" />
<RadioButton android:id="@+id/radio_button3" android:layout_marginTop="2.0dip" android:text="按钮4" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_delete" />
<RadioButton android:id="@+id/radio_button4" android:layout_marginTop="2.0dip" android:text="按钮5" style="@style/tab_radio" android:drawableTop="@android:drawable/ic_menu_help" />
</RadioGroup> </LinearLayout> </LinearLayout>

具体的实现效果(这里Activity基本没有内容的,就加了一行字):

具体的代码演示就差不多了,这里要做一些说明的:
1.开发者在自己的实现类中的onCreate方法中,必须先设置视图,再调用super.oncreate(...)方法。具体为什么看了抽象类的源代码我相信读者应该会明白。
2.关于导航按钮使用RadioButton。Android没有特意为我们定制适合我们在这种场合下使用的按钮,也就是上面可以设置简笔画,下面有文字说明。解决方案:1)使用ImageButton,将简笔画和文字说明P在一张图片里面,但这样有一个非常明显的弊端,文字说明的文字字体是固定的,是P在图片里的,那么不和系统的文字一样。如果用户使用一些比较花哨的系统文字,而导航按钮却是宋体,在上面的内容部分是他的系统文字,那么我很难想象他下一次是否还会打开您所开发的应用。2)自己去实现一个View,去代替RadioButton,出于学习目的这是好的。最佳的解决方案我还是认为是用RadioButton,只需对它稍做修改即可,具体可以参照新浪微博的资源文件。
缺陷反思:这些代码都是我从重构得来的,当时开发的时候并没有设计好开发流程(我是先有那个实现类,才有了那个抽象类的)。自己写的ActivityGroup与TabActivity相比,优点显而易见,缺点就是可能不稳定,但暂时没有发现Bug,动态加载的Activity的逻辑代码都能正确执行。
Android之对TabActivity的见解,个人觉得不错的更多相关文章
- Android自己定义TabActivity(实现仿新浪微博底部菜单更新UI)
现在Android上非常多应用都採用底部菜单控制更新的UI这样的框架,比如新浪微博 点击底部菜单的选项能够更新界面.底部菜单能够使用TabHost来实现,只是用过TabHost的人都知道自己定义Tab ...
- Android : Activity 和 TabActivity 共用一个OptionMenu
在虚拟机上我可以用menu键打开OptionMenu,但是到了真机上没有menu键了.OptionMenu没有办法打开,所以只能采用google推荐到ActionBar.用右上角到三个点代替实体到me ...
- 【转】Android动态改变对 onCreateDialog话框值 -- 不错不错!!!
原文网址:http://www.111cn.net/sj/android/46484.htm 使用方法是这样的,Activity.showDialog()激发Activity.onCreateDial ...
- 【转】Android 源码编译make的错误处理--不错
原文网址:http://blog.csdn.net/ithomer/article/details/6977386 Android源码下载:官方下载 或参考android源码下载方式 Android编 ...
- 【转】android蓝牙开发---与蓝牙模块进行通信--不错
原文网址:http://www.cnblogs.com/wenjiang/p/3200138.html 近半个月来一直在搞android蓝牙这方面,主要是项目需要与蓝牙模块进行通信.开头的进展很顺利, ...
- 【转】如何定制android源码的编译选项 & 后期安装? ---- 不错
原文网址:http://blog.sina.com.cn/s/blog_3e3fcadd0100z3o9.html Android编译过程比较长,配置起来也很麻烦.现仅就工作遇到的问题做个总结.所用硬 ...
- 如何自学Android
看到很多人提问非科班该如何学习编程,其实科班也基本靠自学.有句话叫"师傅领进门修行靠个人",再厉害的老师能教你的东西都是很有限的,真正的修行还是要靠自己.博主本科是数学专业,虽研究 ...
- 转自:如何自学Android(强烈推荐)
转自: http://gityuan.com/2016/04/24/how-to-study-android/ 看到很多人提问非科班该如何学习编程,其实科班也基本靠自学.有句话叫“师傅领进门修行靠个人 ...
- Android 中文 API (101) —— AsyncTask
一.结构 public abstract class AsyncTask extends Object java.lang.Object android.os.AsyncTask<Params, ...
随机推荐
- Oracle JDK vs OpenJDK
OpenJDK是Sun在2006年末把Java开源而形成的项目,这里的“开源”是通常意义上的源码开放形式,即源码是可被复用的,例如IcedTea.UltraViolet都是从OpenJDK源码衍生出的 ...
- CSS3中border-image属性详解
border-images可以说也是CSS3中的重量级属性,如同圆角.边框颜色属性border-color.块阴影属性一样,也是属于边框属性中的一员. 从其字面意思上看,我们可以理解为“边框-图片”, ...
- smali 语言语法
Androidkiller 可以反编译Android的apk,生成一种.smali代码.(这理解好像不对) 网上找了一篇smali的语法手册,可以方便查找,文章名<Smali文件语法参考> ...
- SpringAOP实战应用
Springboot中使用AOP特性非常简单,使用@AspectJ注解,然后再配置中开启AspectJ即可.在日常的应用,有时可以将日志记录和异常处理在一个拦截器中统一处理,但有时在项目中无法通过一个 ...
- 干货: 可视化项目实战经验分享,轻松玩转 Bokeh (建议收藏)
作者 | Will Koehrsen 翻译 | Lemon 译文出品 | Python数据之道 (ID:PyDataRoad) 本文通过一个项目案例,详细的介绍了如何从 Bokeh 基础到构建 Bok ...
- 3D游戏开发之UE4中的集合:TSet容器
好久没有更新了,最近一直在老家过年,网络不通的,今天才有时间更新一集. 一.TSet<T>是什么 UE4中,除了TArray动态数组外,还提供了各种各样的模板容器.这一节,我们就介绍集合容 ...
- View初探
View初探 学习自 <Android开发艺术探索> View漫谈 Activity构成了我们的界面但是知识一个空壳子,Activity与View相结合才构成了我们丰富多彩的界面,并且为了 ...
- 23.python中的类属性和实例属性
在上篇的时候,我们知道了:属性就是属于一个对象的数据或者函数,我们可以通过句点(.)来访问属性,同时 python 还支持在运作中添加和修改属性. 而数据变量,类似于: name = 'scolia' ...
- 【BZOJ 4816】 4816: [Sdoi2017]数字表格 (莫比乌斯)
4816: [Sdoi2017]数字表格 Time Limit: 50 Sec Memory Limit: 128 MBSubmit: 666 Solved: 312 Description Do ...
- BZOJ.2134.[国家集训队]单选错位(概率 递推)
题目链接 如题目中的公式,我们只要把做对每个题的概率加起来就可以了(乘个1就是期望). 做对第i道题的概率 \[P_i=\frac{1}{max(a_{i-1},a_i)}\] 原式是 \(P_i=\ ...