想要制作出拥有多个Tab相互切换的界面,需要用到几个主要的组件:
1. android.app.TabActivity;
2. android.widget.TabHost;
3. android.view.LayoutInflater;
4. android.widget.TabWidget
5. android.widget.FrameLayout
本文描述了制作Tabs界面的三种常用方法,参考来自于Android官方文档及ApiDemo。
在此需要特别说明:android.app.TabActivity;在api13已经被视为废弃组件,官方推荐使用android.app.Fragment,但是如果为了兼容低版本设备,则可以使用v4 support library提供的fragment api。
即便如此,预先学习一下旧式的TabActivity,也能更好地学习Fragment。
效果图和示例代码:
1.主界面
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <ListView android:id="@+id/lstv_demos" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
MainActivity.java
package com.panny.tabsdemo; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { private ListView lstvDemos; private String[] contents = new String[]{ "Content By Id", "Content By Factory", "Content By Intent", "Scrollable"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_main); this.lstvDemos = (ListView) findViewById(R.id.lstv_demos); ArrayAdapter<String> adapter = new ArrayAdapter<String>( getCurrentContext(), android.R.layout.simple_list_item_1, contents); this.lstvDemos.setAdapter(adapter); this.lstvDemos.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { switch (position) { case 0: startActivity(new Intent(getCurrentContext(), ContentByIdActivity.class)); break; case 1: startActivity(new Intent(getCurrentContext(), TabByContentFactoryActivity.class)); break; case 2: startActivity(new Intent(getCurrentContext(), TabByIntentActivity.class)); break; case 3: startActivity(new Intent(getCurrentContext(), ScrollableActivity.class)); break; default: break; } } }); } private MainActivity getCurrentContext() { return MainActivity.this; } }
2.Content By Id
ContentByIdActivity.java
package com.panny.tabsdemo; import android.os.Bundle; import android.app.TabActivity; import android.content.Context; import android.view.LayoutInflater; import android.widget.TabHost; public class ContentByIdActivity extends TabActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* * TabHost作为标签窗体试图的容器。 * 这个对象包含两个元素,第一个是一个包含了若干个标签名称的集合,用户通过点击它来切换标签视图; * 第二个是一个帧布局对象,用于显示被选中标签的内容。 * 实际上它们是相互映射,并且相互独立的,但是它们的内容均由TabHost容器控制,而非它们自身。 */ TabHost tabHost = getTabHost(); // 如果用户没有自定义id为@android:id/tabhost的<TabHost/>,那么将自动从系统获取。 /* * 首先通过系统服务获取已经在当前上下文挂载并且与设备配置好的LayoutInflater实例 * 这一点必须讲究一下,因为某些文档会直接使用:LayoutInflater.from(this).inflate(...) * 虽然这并不影响运行效果,但是可能会有部分性能消耗,即使不是很大,但我们也应该注意。 */ LayoutInflater inflter = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); /* * LayoutInflater的作用是将layout布局文件转换成为View实例,正如第一个参数所为; * 第二个参数的作用是把tabHost.getTabContentView()得到的FrameLayout实例作为root; * 第三个参数的作用是将第一个参数的View挂载在root下。 */ inflter.from(getCurrentContext()).inflate(R.layout.activity_content_by_id, tabHost.getTabContentView(), true); /* * TabHost实例负责控制tab增减 * (1)创建一个标签空间,名称为tab01,但它不用于显示, 但它有自己的用处,可参看TabByContentFactoryActivity * (2)设置标签显示的名称,或许也可以添加一个背景图 * (需要注意的是:在添加背景图使,笔者在emulator-api10 和 emulator-api17上的运行效果不同,emulator-api10可以允许同时添加label和drawable, * 而emulator-api17需要如此.setIndicator("", getResources().getDrawable(R.drawable.ic_launcher))) * (3)设置标签对应的内容 */ tabHost.addTab( tabHost.newTabSpec("tab01") // (1) .setIndicator("one", getResources().getDrawable(R.drawable.ic_launcher))// (2) .setContent(R.id.tv_view01)); // (3) tabHost.addTab(tabHost.newTabSpec("tab02").setIndicator("two").setContent(R.id.tv_view02)); tabHost.addTab(tabHost.newTabSpec("tab03").setIndicator("three").setContent(R.id.tv_view03)); } private ContentByIdActivity getCurrentContext() { return ContentByIdActivity.this; } }
activity_content_by_id.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/tv_view01" android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="This is the view01" /> <TextView android:id="@+id/tv_view02" android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="This is the view02" /> <TextView android:id="@+id/tv_view03" android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="This is the view03" /> </FrameLayout>
3.Content By Factory
TabByContentFactoryActivity.java
package com.panny.tabsdemo; import android.app.TabActivity; import android.os.Bundle; import android.view.View; import android.widget.TabHost; import android.widget.TabHost.TabContentFactory; import android.widget.TextView; public class TabByContentFactoryActivity extends TabActivity implements TabContentFactory { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TabHost tabHost = getTabHost(); tabHost.addTab( tabHost.newTabSpec("tab01") .setIndicator("one") .setContent(this)); tabHost.addTab( tabHost.newTabSpec("tab02") .setIndicator("two") .setContent(this)); tabHost.addTab( tabHost.newTabSpec("tab03") .setIndicator("three") .setContent(this)); } @Override public View createTabContent(String tag) { TextView tv = new TextView(this); tv.setText("This is the " + tag); // 这个参数来自于:tabHost.newTabSpec("tab01") return tv; } }
4.Content By Intent
TabByIntentActivity.java
package com.panny.tabsdemo; import android.app.TabActivity; import android.content.Intent; import android.os.Bundle; import android.widget.TabHost; public class TabByIntentActivity extends TabActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TabHost tabHost = getTabHost(); tabHost.addTab( tabHost.newTabSpec("list") .setIndicator("list") .setContent(new Intent(this, Cities.class))); tabHost.addTab( tabHost.newTabSpec("photos") .setIndicator("photos") .setContent(new Intent(this, Phones.class))); } }
Cities.java
package com.panny.tabsdemo; import android.app.ListActivity; import android.os.Bundle; import android.widget.ArrayAdapter; public class Cities extends ListActivity { private String[] cities = new String[]{ "ShangHai", "BeiJin", "GuangZhou", "ChongQing", "WuHan", "FuJian", "XiAn", "HeiLongJiang", "QingDao" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, cities); getListView().setAdapter(adapter); } }
Phones.java
package com.panny.tabsdemo; import java.util.ArrayList; import android.app.ListActivity; import android.content.Context; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; public class Phones extends ListActivity { private Button btnAdd; private Button btnClear; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.photos_list); this.btnAdd = (Button) findViewById(R.id.btn_add_photo); this.btnClear = (Button) findViewById(R.id.btn_clear_photos); // 设置当listview为空时,应该显示什么视图,在此显示预先定义的TextView getListView().setEmptyView(findViewById(R.id.tv_empty)); final PhotoAdapter adapter = new PhotoAdapter(this); setListAdapter(adapter); this.btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { adapter.add(); } }); this.btnClear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { adapter.clear(); } }); } private class PhotoAdapter extends BaseAdapter { private Context mContext; private int[] photosPool = new int[]{ R.drawable.sample_1, R.drawable.sample_2, R.drawable.sample_3, R.drawable.sample_4, R.drawable.sample_5, R.drawable.sample_6, R.drawable.sample_7 }; // 为了动态添加图片,它才是adapter真正的内容提供者 private ArrayList<Integer> photos = new ArrayList<Integer>(); public PhotoAdapter(Context mContext) { this.mContext = mContext; } @Override public int getCount() { return photos.size(); } @Override public Object getItem(int position) { return photos.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ImageView i = new ImageView(mContext); i.setImageResource(photos.get(position)); i.setAdjustViewBounds(true); i.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); i.setBackgroundResource(R.drawable.picture_frame); return i; } private void add() { int wicthPic = (int) Math.round(Math.random() * (photosPool.length - 1)); photos.add(photosPool[wicthPic]); notifyDataSetChanged(); } private void clear() { photos.clear(); notifyDataSetChanged(); } } }
R.layout.photos_list.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/btn_add_photo" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:text="Add Photo"/> <Button android:id="@+id/btn_clear_photos" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:text="Clear Photos"/> </LinearLayout> <!-- The frame layout is here since we will do showing either the list view or the text view when the list view is empty. --> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Here is the list. Since we are using a ListActivity, we have to identity it as "@android:id/list" so ListActivity will find it --> <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" android:drawSelectorOnTop="false"/> <!-- Here is the view to show if the list is emtpy --> <TextView android:id="@+id/tv_empty" android:layout_width="match_parent" android:layout_height="match_parent" android:text="No photos" /> </FrameLayout> </LinearLayout>
4.Scrollable
ScrollableActivity.java
package com.panny.tabsdemo; import android.app.TabActivity; import android.os.Bundle; import android.view.View; import android.widget.TabHost; import android.widget.TabHost.TabContentFactory; import android.widget.TextView; public class ScrollableActivity extends TabActivity implements TabContentFactory { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tab_scroll); // 不要忘记加载自定义的布局文件 TabHost tabHost = getTabHost(); for(int i = 0; i < 30; i ++) { tabHost.addTab( tabHost.newTabSpec("tab" + i) .setIndicator("tab_" + i) .setContent(this)); } } @Override public View createTabContent(String tag) { TextView tv = new TextView(this); tv.setText("This is " + tag); return tv; } }
R.layout.tab_scroll.xml
<?xml version="1.0" encoding="utf-8"?> <!-- Here is the root and it must be identified as @android:id/tabhost so that it can be found by getTabHost()--> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@android:id/tabhost" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="5dip"> <HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none"> <!-- Here is the tab label container and it must be identified as @android:id/tabs so that it can be found by tabhost when addTab() --> <TabWidget android:id="@android:id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal|center_vertical" /> </HorizontalScrollView> <!-- Here is the tab content container and it must be indentified as @android:id/tabcontent so that it could be found by tabhost when setContent() --> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="5dip" /> </LinearLayout> </TabHost>
Best Regards,