TabActivity+Tabhost+ActivityGroup 创建多tab的分页APP

来源:互联网 发布:linux中echo命令 编辑:程序博客网 时间:2024/05/07 22:27
一: 首先展示简易效果图
类似:
二: 程序结构如下,和效果图不是同一套.但是代码讲述以代码结构图为准.

这套代码有三个module: app, app-ai2, app-multibt(注:结构图中该名写错)
代码主要功能实现三个界面,一个帮助页面另外两个分别是原来两个独立的APP合进来后的功能,即一个是AI,一个是Multibt. 因为之前是独立的APP,所以AI和Multibt下必然有自己的Activity和Menu内容.

下面安装代码结构逐级解释:
PEMainActivity:完成分页功能,即在此类中完成多个Tab的创建.
package com.xunhai.portableequipment;


import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.app.TabActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.media.AudioManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.widget.TextView;


import com.nwu.hci.multibt.SystemController;
import com.nwu.hci.multibt.ui.AnalysisActivity;
import com.nwu.hci.multibt.ui.LogoActivity;
import com.nwu.hci.multibt.ui.MultibtActivityGroup;
import com.xxx.ai.antenna.AiActivityGroup;
import com.xxx.ai.antenna.AiLogoActivity;
import com.xxx.xxx.AppApplication;
import com.xxx.xxx.UserUseItem;
import com.xxx.xxx.bussiness.invoke.ThirdInvoke;


public class PEMainActivity extends TabActivity {
    private static final String Tab1 = "Tab1";
    private static final String Tab2 = "Tab2";
    private static final String Tab3 = "Tab3";
    private static final String Tab4 = "Tab4";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_pemain);
        requestWindowFeature( Window.FEATURE_NO_TITLE);
            setContentView(R.layout.activity_pemain);
        //1得到TabHost对象,正对TabActivity的操作通常都有这个对象完成
        final TabHost tabHost = this.getTabHost();
        final TabWidget tabWidget = tabHost.getTabWidget();




        //2生成一个Intent对象,该对象指向一个Activity,当然现在例子比较简单就没有绑定其他的Activity故而不用
        //3生成一个TabSpec对象,这个对象代表了一个Tab页
        TabHost.TabSpec tabSpec = tabHost.newTabSpec(Tab1);
        //设置该页的indicator(指示器)设置该Tab页的名字和图标,以及布局
        tabSpec.setIndicator(composeLayout("帮助", R.drawable.icon)) .setContent(new Intent(PEMainActivity.this, HelpActivity.class));
        //4将设置好的TabSpec对象添加到TabHost当中
        tabHost.addTab(tabSpec);


        //第二个Tab,跳转到小被动程序
        tabHost.addTab(tabHost.newTabSpec(Tab2).setIndicator(composeLayout("Multibt", R.drawable.icon))
                .setContent(new Intent(PEMainActivity.this,MultibtActivityGroup.class)));


        //第三个Tab,跳转到智能天线程序
        tabHost.addTab(tabHost.newTabSpec(Tab3).setIndicator(composeLayout("Ai", R.drawable.icon))
                .setContent(new Intent(PEMainActivity.this,AiActivityGroup.class)));


        //设置当前要显示的tab
        tabHost.setCurrentTab(0);
        /**这是对Tab标签本身的初始设置*/
        int width =45;
        int height =120;
        for(int i = 0; i < tabWidget.getChildCount(); i++)
        {
            //设置高度、宽度,不过宽度由于设置为fill_parent,在此对它没效果
            tabWidget.getChildAt(i).getLayoutParams().height = height;
            tabWidget.getChildAt(i).getLayoutParams().width = width;
            /**
             * 下面是设置Tab的背景,可以是颜色,背景图片等
             */
            View v = tabWidget.getChildAt(i);
            if (tabHost.getCurrentTab() == i) {
                v.setBackgroundColor(Color.BLUE);
                //在这里最好自己设置一个图片作为背景更好
                //v.setBackgroundDrawable(getResources().getDrawable(R.drawable.icon32x32));
            } else {
                v.setBackgroundColor(Color.WHITE);
            }
        }


        /**设置Tab变换时的监听事件*/
        tabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {


            @Override
            public void onTabChanged(String tabId) {
                // TODO Auto-generated method stub
                //结束除当前tab外的其他activity
                System.out.println("***### tabHost.getCurrentTab()="+tabHost.getCurrentTab());
                //当点击tab选项卡的时候,更改当前的背景
                for (int i = 0; i < tabWidget.getChildCount(); i++) {
                    View v = tabWidget.getChildAt(i);
                    if (tabHost.getCurrentTab() == i) {
                        v.setBackgroundColor(Color.BLUE);
                    } else {
                        //这里最后需要和上面的设置保持一致,也可以用图片作为背景最好
                        v.setBackgroundColor(Color.WHITE);
                    }
                }


            }
        });


    }
    @Override
    protected void onResume() {
        super.onResume();
     }
    /**
     * 这是设置TabWidget的布局
     * 这个设置Tab标签本身的布局,需要TextView和ImageView不能重合
     * s:是文本显示的内容
     * i:是ImageView的图片位置
     * 将它设置到setIndicator(composeLayout("开心", R.drawable.coke))中
     */
    public View composeLayout(String s, int i){
        Log.e("Error", "composeLayout");
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);


        TextView tv = new TextView(this);
        tv.setGravity(Gravity.CENTER);
        tv.setSingleLine(true);
        tv.setText(s);
        tv.setTextColor(Color.RED);
        layout.addView(tv,
                new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));


        ImageView iv = new ImageView(this);
        iv.setImageResource(i);
        layout.addView(iv,
                new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
        return layout;
    }


}

AiActivityGroup:存放何如的独立APP AI功能,因为AI有多个之前就已经创建并使用的Activity,所以此处需要ActivityGroup来存放. 否则后面的Activity就放不到指定View中,而是覆盖了Tab
MultibtActivityGroup分支和AI分之类似,所以就只说明AI.

package com.xxx.ai.antenna;


import android.app.ActivityGroup;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;


/**
 * Created by Administrator on 2015/11/30.
 */
public class AiActivityGroup extends ActivityGroup {
    public static ActivityGroup group;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        group = this;
        //要跳转的界面
        //Intent intent = new Intent(this, AiLogoActivity.class). addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        Intent intent = new Intent(this, AiLogoActivity.class);
        //把一个Activity转换成一个View
        Window w = group.getLocalActivityManager().startActivity("AiLogoActivity",intent);
        View view = w.getDecorView();
        //把View添加大ActivityGroup中
        group.setContentView(view);
    }
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        System.out.println("***### AiActivityGroup:onKeyDown");
        //把后退事件交给子Activity处理
        group.getLocalActivityManager() .getCurrentActivity().onKeyDown(keyCode, event);
        return true;
    }
}

AiLogoActivity:这个类就是个登录界面,这个比较简单和基本的实现一样.
基本代码就是这些,代码可以慢慢看而且可以直接使用. 但是在完成过程中发现的几个问题再次重点记录;
三:主要问题
问题1:ActivityGroup中怎么实现各Activity间的切换



ActivityGroup中各Activity间的切换和普通的不同,因为在ActivityGroup中要保证切换到的Activity不能超出指定View覆盖Tab. 此处要求AI和Multibt两个功能要同时使用.
先上代码:
在ActivityGroup类中
public class AiActivityGroup extends ActivityGroup {
public static ActivityGroup group;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
group = this;
//要跳转的界面
//Intent intent = new Intent(this, AiLogoActivity.class). addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Intent intent = new Intent(this, AiLogoActivity.class);
//把一个Activity转换成一个View
Window w = group.getLocalActivityManager().startActivity("AiLogoActivity",intent);
View view = w.getDecorView();
//把View添加大ActivityGroup中
group.setContentView(view);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
System.out.println("***### AiActivityGroup:onKeyDown");
//把后退事件交给子Activity处理
group.getLocalActivityManager() .getCurrentActivity().onKeyDown(keyCode, event);
return true;
}
}
在非ActivityGroup类中
//要跳转的界面
//Intent intent = new Intent(AiLogoActivity.this, WorkspaceActivity.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Intent intent = new Intent(AiLogoActivity.this, WorkspaceActivity.class);
//把一个Activity转换成一个View
Window w = AiActivityGroup.group.getLocalActivityManager().startActivity("WorkspaceActivity", intent);
View view = w.getDecorView();
//把View添加大ActivityGroup中
AiActivityGroup.group.setContentView(view);
// AiLogoActivity.this.finish();
综上所述,跳来跳去就是上面这一种方法. Intent.FLAG_ACTIVITY_CLEAR_TOP 这个参数可根据自己需要设置.
public static final int FLAG_ACTIVITY_CLEAR_TOP
Added in API level 1
If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent.
也就是说 如果设置这个属性,是当要启动的Activity已经存在当前Task中,才会在启动的时候销毁其他的Activity。

举例说明:当 B - A - B 跳转的时候,使用Intent的FLAG_ACTIVITY_CLEAR_TOP会让第一个B和第二个A,destory掉; 但是当B - A - C跳转的时候不会调用B和A的destory
如果设置了FLAG_ACTIVITY_CLEAR_TOP,那么有可能下次切回来就是NEW一个新的,所以根据需要设置.

问题2:ActivityGroup下的Activity中创建弹出框提示错误
LayoutInflater factory = LayoutInflater.from(this.getParent().getParent());
final View textEntryView = factory.inflate(R.layout.record, null);
ListView targetListView = (ListView)textEntryView.findViewById(R.id.ListViewRecord);
final RecordViewItemAdapter listTargetAdapter = new RecordViewItemAdapter(AnalysisActivity.this, AppApplication.RECPRD);
targetListView.setAdapter(listTargetAdapter);
targetListView.setOnItemClickListener(new OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {

for (RecordItem i : AppApplication.RECPRD) {
i.setSelected(false);
}
AppApplication.RECPRD.get(arg2).setSelected(true);
listTargetAdapter.notifyDataSetChanged();
}
});
Dialog dlg = new AlertDialog.Builder(AnalysisActivity.this.getParent().getParent()) //zhangkai 20151202:增加".getParent().getParent()"
.setView(textEntryView)
.create();
原因分析:
因为new对话框的时候,参数content 指定成了this,即指向当前子Activity的content。但子Activity是动态创建的是ActivityGroup类型。其父TabHost的content是稳定存在的. 所以如果是ActivityGroup就要用到父类,如果是ActivityGroup的子类那就需要用到父类的父类了.这就是要使用 this.getParent().getParent() 的原因. 

问题2:ActivityGroup下的Activity中怎么响应onKeydown
本例中因为在Activity下有响应物理菜单键和回退键, 但是在对应的Activity中写的 onKeyDown 完全没有执行到. 

以下是我查到的资料,很有用.

键盘事件只会发送到当前获得焦点的View,这个KeyEvent只能被最上层获得焦点窗口的activity和view得到。一般来说这些事件会从上到下去寻找合适的接受组件,ViewGroup的一个childView的onKeyDown()方法return true,那么表示该方法消费了此次事件,此时不会再传递到ViewGroup的onKeyDown()方法,如果onKeyDown()方法return false,那么表示该方法并未处理完全,该事件仍然需要以某种方式传递下去继续等待处理,这时传递到ActivityGroup的onKeyDown()方法。但是KeyEvent.KEYCODE_MENU不会传递到ActivityGroup去。


那么在ActivityGroup中需要设置

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
System.out.println("***### AiActivityGroup:onKeyDown");
//把后退事件交给子Activity处理
group.getLocalActivityManager() .getCurrentActivity().onKeyDown(keyCode, event);
return true;
}
这样ActivityGroup子Activity就可以获取到对应的onKeyDown消息了. 好了可以处理对应的消息了,这会我们来实现我们的需求:

需求1: 在ActivityGroup的某个Activity下要响应菜单键弹出Menu供选择

需求2: 在ActivityGroup的某个Activity下不能响应返回键

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return true; //返回true代表该activity接收到这个消息自己处理,不需要父类处理了. 如果此处返回super.onKeydown 那么有可能一块都退出了.
}
if(KeyEvent.KEYCODE_MENU==keyCode){ //20151202 zhangkai 新增:实现菜单响应
System.out.println("***### SystemConfigActvity:KEYCODE_MENU");
super.openOptionsMenu(); //弹出菜单,并返回true
return true;
}
return super.onKeyDown(keyCode, event);
}



ActivityGroup中各Activity间的切换和普通的不同,因为在ActivityGroup中要保证切换到的Activity不能超出指定View覆盖Tab. 此处要求AI和Multibt两个功能要同时使用.
先上代码:
在ActivityGroup类中
public class AiActivityGroup extends ActivityGroup {
public static ActivityGroup group;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
group = this;
//要跳转的界面
//Intent intent = new Intent(this, AiLogoActivity.class). addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Intent intent = new Intent(this, AiLogoActivity.class);
//把一个Activity转换成一个View
Window w = group.getLocalActivityManager().startActivity("AiLogoActivity",intent);
View view = w.getDecorView();
//把View添加大ActivityGroup中
group.setContentView(view);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
System.out.println("***### AiActivityGroup:onKeyDown");
//把后退事件交给子Activity处理
group.getLocalActivityManager() .getCurrentActivity().onKeyDown(keyCode, event);
return true;
}
}
在非ActivityGroup类中
//要跳转的界面
//Intent intent = new Intent(AiLogoActivity.this, WorkspaceActivity.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Intent intent = new Intent(AiLogoActivity.this, WorkspaceActivity.class);
//把一个Activity转换成一个View
Window w = AiActivityGroup.group.getLocalActivityManager().startActivity("WorkspaceActivity", intent);
View view = w.getDecorView();
//把View添加大ActivityGroup中
AiActivityGroup.group.setContentView(view);
// AiLogoActivity.this.finish();
综上所述,跳来跳去就是上面这一种方法. Intent.FLAG_ACTIVITY_CLEAR_TOP 这个参数可根据自己需要设置.
public static final int FLAG_ACTIVITY_CLEAR_TOP
Added in API level 1
If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent.
也就是说 如果设置这个属性,是当要启动的Activity已经存在当前Task中,才会在启动的时候销毁其他的Activity。

举例说明:当 B - A - B 跳转的时候,使用Intent的FLAG_ACTIVITY_CLEAR_TOP会让第一个B和第二个A,destory掉; 但是当B - A - C跳转的时候不会调用B和A的destory
如果设置了FLAG_ACTIVITY_CLEAR_TOP,那么有可能下次切回来就是NEW一个新的,所以根据需要设置.

问题2:ActivityGroup下的Activity中创建弹出框提示错误
LayoutInflater factory = LayoutInflater.from(this.getParent().getParent());
final View textEntryView = factory.inflate(R.layout.record, null);
ListView targetListView = (ListView)textEntryView.findViewById(R.id.ListViewRecord);
final RecordViewItemAdapter listTargetAdapter = new RecordViewItemAdapter(AnalysisActivity.this, AppApplication.RECPRD);
targetListView.setAdapter(listTargetAdapter);
targetListView.setOnItemClickListener(new OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {

for (RecordItem i : AppApplication.RECPRD) {
i.setSelected(false);
}
AppApplication.RECPRD.get(arg2).setSelected(true);
listTargetAdapter.notifyDataSetChanged();
}
});
Dialog dlg = new AlertDialog.Builder(AnalysisActivity.this.getParent().getParent()) //zhangkai 20151202:增加".getParent().getParent()"
.setView(textEntryView)
.create();
原因分析:
因为new对话框的时候,参数content 指定成了this,即指向当前子Activity的content。但子Activity是动态创建的是ActivityGroup类型。其父TabHost的content是稳定存在的. 所以如果是ActivityGroup就要用到父类,如果是ActivityGroup的子类那就需要用到父类的父类了.这就是要使用 this.getParent().getParent() 的原因. 

问题2:ActivityGroup下的Activity中怎么响应onKeydown
本例中因为在Activity下有响应物理菜单键和回退键, 但是在对应的Activity中写的 onKeyDown 完全没有执行到. 

以下是我查到的资料,很有用.

键盘事件只会发送到当前获得焦点的View,这个KeyEvent只能被最上层获得焦点窗口的activity和view得到。一般来说这些事件会从上到下去寻找合适的接受组件,ViewGroup的一个childView的onKeyDown()方法return true,那么表示该方法消费了此次事件,此时不会再传递到ViewGroup的onKeyDown()方法,如果onKeyDown()方法return false,那么表示该方法并未处理完全,该事件仍然需要以某种方式传递下去继续等待处理,这时传递到ActivityGroup的onKeyDown()方法。但是KeyEvent.KEYCODE_MENU不会传递到ActivityGroup去。


那么在ActivityGroup中需要设置

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
System.out.println("***### AiActivityGroup:onKeyDown");
//把后退事件交给子Activity处理
group.getLocalActivityManager() .getCurrentActivity().onKeyDown(keyCode, event);
return true;
}
这样ActivityGroup子Activity就可以获取到对应的onKeyDown消息了. 好了可以处理对应的消息了,这会我们来实现我们的需求:

需求1: 在ActivityGroup的某个Activity下要响应菜单键弹出Menu供选择

需求2: 在ActivityGroup的某个Activity下不能响应返回键

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return true; //返回true代表该activity接收到这个消息自己处理,不需要父类处理了. 如果此处返回super.onKeydown 那么有可能一块都退出了.
}
if(KeyEvent.KEYCODE_MENU==keyCode){ //20151202 zhangkai 新增:实现菜单响应
System.out.println("***### SystemConfigActvity:KEYCODE_MENU");
super.openOptionsMenu(); //弹出菜单,并返回true
return true;
}
return super.onKeyDown(keyCode, event);
}


0 0