Android中invalidate() 函数详解(结合Android 4.0.4 最新源码)

invalidate()函数的主要作用是请求View树进行重绘,该函数可以由应用程序调用,或者由系统函数间接调用,例如setEnable(), setSelected(), setVisiblity()都会间接调用到invalidate()来请求View树重绘,更新View树的显示。

注:requestLayout()和requestFocus()函数也会引起视图重绘

下面我们通过源码来了解invalidate()函数的工作原理,首先我们来看View类中invalidate()的实现过程:

  1. /**
  2.      * Invalidate the whole view. If the view is visible,
  3.      * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
  4.      * the future. This must be called from a UI thread. To call from a non-UI thread,
  5.      * call {@link #postInvalidate()}.
  6.      */
  7.     public void invalidate() {
  8.         invalidate(true);
  9.     }

invalidate()函数会转而调用invalidate(true),继续往下看:

  1. /**
  2.      * This is where the invalidate() work actually happens. A full invalidate()
  3.      * causes the drawing cache to be invalidated, but this function can be called with
  4.      * invalidateCache set to false to skip that invalidation step for cases that do not
  5.      * need it (for example, a component that remains at the same dimensions with the same
  6.      * content).
  7.      *
  8.      * @param invalidateCache Whether the drawing cache for this view should be invalidated as
  9.      * well. This is usually true for a full invalidate, but may be set to false if the
  10.      * View's contents or dimensions have not changed.
  11.      */
  12.     void invalidate(boolean invalidateCache) {
  13.         if (ViewDebug.TRACE_HIERARCHY) {
  14.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
  15.         }
  16.         if (skipInvalidate()) {
  17.             return;
  18.         }
  19.         if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
  20.                 (invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) ||
  21.                 (mPrivateFlags & INVALIDATED) != INVALIDATED || isOpaque() != mLastIsOpaque) {
  22.             mLastIsOpaque = isOpaque();
  23.             mPrivateFlags &= ~DRAWN;
  24.             mPrivateFlags |= DIRTY;
  25.             if (invalidateCache) {
  26.                 mPrivateFlags |= INVALIDATED;
  27.                 mPrivateFlags &= ~DRAWING_CACHE_VALID;
  28.             }
  29.             final AttachInfo ai = mAttachInfo;
  30.             final ViewParent p = mParent;
  31.             //noinspection PointlessBooleanExpression,ConstantConditions
  32.             if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
  33.                 if (p != null && ai != null && ai.mHardwareAccelerated) {
  34.                     // fast-track for GL-enabled applications; just invalidate the whole hierarchy
  35.                     // with a null dirty rect, which tells the ViewAncestor to redraw everything
  36.                     p.invalidateChild(this, null);
  37.                     return;
  38.                 }
  39.             }
  40.             if (p != null && ai != null) {
  41.                 final Rect r = ai.mTmpInvalRect;
  42.                 r.set(0, 0, mRight - mLeft, mBottom - mTop);
  43.                 // Don't call invalidate -- we don't want to internally scroll
  44.                 // our own bounds
  45.                 p.invalidateChild(this, r);
  46.             }
  47.         }
  48.     }

下面我们来具体进行分析invalidate(true)函数的执行流程:

1、首先调用skipInvalidate(),该函数主要判断该View是否不需要重绘,如果不许要重绘则直接返回,不需要重绘的条件是该View不可见并且未进行动画

2、接下来的if语句是来进一步判断View是否需要绘制,其中表达式 (mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)的意思指的是如果View需要重绘并且其大小不为0,其余几个本人也未完全理解,还望高手指点~~如果需要重绘,则处理相关标志位

3、对于开启硬件加速的应用程序,则调用父视图的invalidateChild函数绘制整个区域,否则只绘制dirty区域(r变量所指的区域),这是一个向上回溯的过程,每一层的父View都将自己的显示区域与传入的刷新Rect做交集。

接下来看invalidateChild()的 实现过程:

  1. public final void invalidateChild(View child, final Rect dirty) {
  2.     if (ViewDebug.TRACE_HIERARCHY) {
  3.         ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD);
  4.     }
  5.     ViewParent parent = this;
  6.     final AttachInfo attachInfo = mAttachInfo;
  7.     if (attachInfo != null) {
  8.         // If the child is drawing an animation, we want to copy this flag onto
  9.         // ourselves and the parent to make sure the invalidate request goes
  10.         // through
  11.         final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;
  12.         if (dirty == null) {
  13.             if (child.mLayerType != LAYER_TYPE_NONE) {
  14.                 mPrivateFlags |= INVALIDATED;
  15.                 mPrivateFlags &= ~DRAWING_CACHE_VALID;
  16.                 child.mLocalDirtyRect.setEmpty();
  17.             }
  18.             do {
  19.                 View view = null;
  20.                 if (parent instanceof View) {
  21.                     view = (View) parent;
  22.                     if (view.mLayerType != LAYER_TYPE_NONE) {
  23.                         view.mLocalDirtyRect.setEmpty();
  24.                         if (view.getParent() instanceof View) {
  25.                             final View grandParent = (View) view.getParent();
  26.                             grandParent.mPrivateFlags |= INVALIDATED;
  27.                             grandParent.mPrivateFlags &= ~DRAWING_CACHE_VALID;
  28.                         }
  29.                     }
  30.                     if ((view.mPrivateFlags & DIRTY_MASK) != 0) {
  31.                         // already marked dirty - we're done
  32.                         break;
  33.                     }
  34.                 }
  35.                 if (drawAnimation) {
  36.                     if (view != null) {
  37.                         view.mPrivateFlags |= DRAW_ANIMATION;
  38.                     } else if (parent instanceof ViewRootImpl) {
  39.                         ((ViewRootImpl) parent).mIsAnimating = true;
  40.                     }
  41.                 }
  42.                 if (parent instanceof ViewRootImpl) {
  43.                     ((ViewRootImpl) parent).invalidate();
  44.                     parent = null;
  45.                 } else if (view != null) {
  46.                     if ((view.mPrivateFlags & DRAWN) == DRAWN ||
  47.                             (view.mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
  48.                         view.mPrivateFlags &= ~DRAWING_CACHE_VALID;
  49.                         view.mPrivateFlags |= DIRTY;
  50.                         parent = view.mParent;
  51.                     } else {
  52.                         parent = null;
  53.                     }
  54.                 }
  55.             } while (parent != null);
  56.         } else {
  57.             // Check whether the child that requests the invalidate is fully opaque
  58.             final boolean isOpaque = child.isOpaque() && !drawAnimation &&
  59.                     child.getAnimation() == null;
  60.             // Mark the child as dirty, using the appropriate flag
  61.             // Make sure we do not set both flags at the same time
  62.             int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY;
  63.             if (child.mLayerType != LAYER_TYPE_NONE) {
  64.                 mPrivateFlags |= INVALIDATED;
  65.                 mPrivateFlags &= ~DRAWING_CACHE_VALID;
  66.                 child.mLocalDirtyRect.union(dirty);
  67.             }
  68.             final int[] location = attachInfo.mInvalidateChildLocation;
  69.             location[CHILD_LEFT_INDEX] = child.mLeft;
  70.             location[CHILD_TOP_INDEX] = child.mTop;
  71.             Matrix childMatrix = child.getMatrix();
  72.             if (!childMatrix.isIdentity()) {
  73.                 RectF boundingRect = attachInfo.mTmpTransformRect;
  74.                 boundingRect.set(dirty);
  75.                 //boundingRect.inset(-0.5f, -0.5f);
  76.                 childMatrix.mapRect(boundingRect);
  77.                 dirty.set((int) (boundingRect.left - 0.5f),
  78.                         (int) (boundingRect.top - 0.5f),
  79.                         (int) (boundingRect.right + 0.5f),
  80.                         (int) (boundingRect.bottom + 0.5f));
  81.             }
  82.             do {
  83.                 View view = null;
  84.                 if (parent instanceof View) {
  85.                     view = (View) parent;
  86.                     if (view.mLayerType != LAYER_TYPE_NONE &&
  87.                             view.getParent() instanceof View) {
  88.                         final View grandParent = (View) view.getParent();
  89.                         grandParent.mPrivateFlags |= INVALIDATED;
  90.                         grandParent.mPrivateFlags &= ~DRAWING_CACHE_VALID;
  91.                     }
  92.                 }
  93.                 if (drawAnimation) {
  94.                     if (view != null) {
  95.                         view.mPrivateFlags |= DRAW_ANIMATION;
  96.                     } else if (parent instanceof ViewRootImpl) {
  97.                         ((ViewRootImpl) parent).mIsAnimating = true;
  98.                     }
  99.                 }
  100.                 // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
  101.                 // flag coming from the child that initiated the invalidate
  102.                 if (view != null) {
  103.                     if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
  104.                             view.getSolidColor() == 0) {
  105.                         opaqueFlag = DIRTY;
  106.                     }
  107.                     if ((view.mPrivateFlags & DIRTY_MASK) != DIRTY) {
  108.                         view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag;
  109.                     }
  110.                 }
  111.                 parent = parent.invalidateChildInParent(location, dirty);
  112.                 if (view != null) {
  113.                     // Account for transform on current parent
  114.                     Matrix m = view.getMatrix();
  115.                     if (!m.isIdentity()) {
  116.                         RectF boundingRect = attachInfo.mTmpTransformRect;
  117.                         boundingRect.set(dirty);
  118.                         m.mapRect(boundingRect);
  119.                         dirty.set((int) boundingRect.left, (int) boundingRect.top,
  120.                                 (int) (boundingRect.right + 0.5f),
  121.                                 (int) (boundingRect.bottom + 0.5f));
  122.                     }
  123.                 }
  124.             } while (parent != null);
  125.         }
  126.     }
  127. }

 

大概流程如下,我们主要关注dirty区域不是null(非硬件加速)的情况:

1、判断子视图是否是不透明的(不透明的条件是isOpaque()返回true,视图未进行动画以及child.getAnimation() == null),并将判断结果保存到变量isOpaque中,如果不透明则将变量opaqueFlag设置为DIRTY_OPAQUE,否则设置为DIRTY。

2、定义location保存子视图的左上角坐标

3、如果子视图正在动画,那么父视图也要添加动画标志,如果父视图是ViewGroup,那么给mPrivateFlags添加DRAW_ANIMATION标识,如果父视图是ViewRoot,则给其内部变量mIsAnimating赋值为true

4、设置dirty标识,如果子视图是不透明的,则父视图设置为DIRTY_OPAQUE,否则设置为DIRTY

5、调用parent.invalidateChildInparent(),这里的parent有可能是ViewGroup,也有可能是ViewRoot(最后一次while循环),首先来看ViewGroup, ViewGroup中该函数的主要作用是对dirty区域进行计算

以上过程的主体是一个do{}while{}循环,不断的将子视图的dirty区域与父视图做运算来确定最终要重绘的dirty区域,最终循环到ViewRoot(ViewRoot的parent为null)为止,并将dirty区域保存到ViewRoot的mDirty变量中

  1. /**
  2.      * Don't call or override this method. It is used for the implementation of
  3.      * the view hierarchy.
  4.      *
  5.      * This implementation returns null if this ViewGroup does not have a parent,
  6.      * if this ViewGroup is already fully invalidated or if the dirty rectangle
  7.      * does not intersect with this ViewGroup's bounds.
  8.      */
  9.     public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
  10.         if (ViewDebug.TRACE_HIERARCHY) {
  11.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD_IN_PARENT);
  12.         }
  13.         if ((mPrivateFlags & DRAWN) == DRAWN ||
  14.                 (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
  15.             if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
  16.                         FLAG_OPTIMIZE_INVALIDATE) {
  17.                 dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
  18.                         location[CHILD_TOP_INDEX] - mScrollY);
  19.                 final int left = mLeft;
  20.                 final int top = mTop;
  21.                 if ((mGroupFlags & FLAG_CLIP_CHILDREN) != FLAG_CLIP_CHILDREN ||
  22.                         dirty.intersect(0, 0, mRight - left, mBottom - top) ||
  23.                         (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
  24.                     mPrivateFlags &= ~DRAWING_CACHE_VALID;
  25.                     location[CHILD_LEFT_INDEX] = left;
  26.                     location[CHILD_TOP_INDEX] = top;
  27.                     if (mLayerType != LAYER_TYPE_NONE) {
  28.                         mLocalDirtyRect.union(dirty);
  29.                     }
  30.                     return mParent;
  31.                 }
  32.             } else {
  33.                 mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
  34.                 location[CHILD_LEFT_INDEX] = mLeft;
  35.                 location[CHILD_TOP_INDEX] = mTop;
  36.                 if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
  37.                     dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
  38.                 } else {
  39.                     // in case the dirty rect extends outside the bounds of this container
  40.                     dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
  41.                 }
  42.                 if (mLayerType != LAYER_TYPE_NONE) {
  43.                     mLocalDirtyRect.union(dirty);
  44.                 }
  45.                 return mParent;
  46.             }
  47.         }
  48.         return null;
  49.     }

该函数首先调用offset将子视图的坐标位置转换为在父视图(当前视图)的显示位置,这里主要考虑scroll后导致子视图在父视图中的显示区域会发生变化,接着调用union函数求得当前视图与子视图的交集,求得的交集必定是小于dirty的范围,因为子视图的dirty区域有可能超出其父视图(当前视图)的范围,最后返回当前视图的父视图。

再来看ViewRoot中invalidateChildInparent的执行过程:

  1. public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
  2.         invalidateChild(null, dirty);
  3.         return null;
  4.     }

该函数仅仅调用了ViewRoot的invalidateChild,下面继续看invalidateChild的源码:

  1. public void invalidateChild(View child, Rect dirty) {
  2.         checkThread();
  3.         if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
  4.         if (dirty == null) {
  5.             // Fast invalidation for GL-enabled applications; GL must redraw everything
  6.             invalidate();
  7.             return;
  8.         }
  9.         if (mCurScrollY != 0 || mTranslator != null) {
  10.             mTempRect.set(dirty);
  11.             dirty = mTempRect;
  12.             if (mCurScrollY != 0) {
  13.                dirty.offset(0, -mCurScrollY);
  14.             }
  15.             if (mTranslator != null) {
  16.                 mTranslator.translateRectInAppWindowToScreen(dirty);
  17.             }
  18.             if (mAttachInfo.mScalingRequired) {
  19.                 dirty.inset(-1, -1);
  20.             }
  21.         }
  22.         if (!mDirty.isEmpty() && !mDirty.contains(dirty)) {
  23.             mAttachInfo.mSetIgnoreDirtyState = true;
  24.             mAttachInfo.mIgnoreDirtyState = true;
  25.         }
  26.         mDirty.union(dirty);
  27.         if (!mWillDrawSoon) {
  28.             scheduleTraversals();
  29.         }
  30.     }

具体分析如下:

1、判断此次调用是否在UI线程中进行

2、将dirty的坐标位置转换为ViewRoot的屏幕显示区域

3、更新mDirty变量,并调用scheduleTraversals发起重绘请求

至此一次invalidate()就结束了

总结:invalidate主要给需要重绘的视图添加DIRTY标记,并通过和父视图的矩形运算求得真正需要绘制的区域,并保存在ViewRoot中的mDirty变量中,最后调用scheduleTraversals发起重绘请求,scheduleTraversals会发送一个异步消息,最终调用performTraversals()执行重绘,performTraversals()的具体过程以后再分析。

以上所有代码基于Android 4.0.4,并结合《Android内核剖析》分析总结而成,源码中涉及到的部分细节本人也未完全理解,还望高手指点~~

Android LayoutInflater详解

在实际开发中LayoutInflater这个类还是非常有用的,它的作用类似于findViewById()。不同点是LayoutInflater是用来找res/layout/下的xml布局文件,并且实例化;而findViewById()是找xml布局文件下的具体widget控件(如Button、TextView等)。
具体作用:
1、对于一个没有被载入或者想要动态载入的界面,都需要使用LayoutInflater.inflate()来载入;
2、对于一个已经载入的界面,就可以使用Activiyt.findViewById()方法来获得其中的界面元素。
LayoutInflater 是一个抽象类,在文档中如下声明:
public abstract class LayoutInflater extends Object
获得 LayoutInflater 实例的三种方式
1. LayoutInflater inflater = getLayoutInflater();//调用Activity的getLayoutInflater()
2. LayoutInflater inflater = LayoutInflater.from(context);
3. LayoutInflater inflater = (LayoutInflater)context.getSystemService
                              (Context.LAYOUT_INFLATER_SERVICE);
其实,这三种方式本质是相同的,从源码中可以看出:
getLayoutInflater():
Activity 的 getLayoutInflater() 方法是调用 PhoneWindow 的getLayoutInflater()方法,看一下该源代码:
public PhoneWindow(Context context)
{
 super(context);
    mLayoutInflater = LayoutInflater.from(context);
}
可以看出它其实是调用 LayoutInflater.from(context)。
LayoutInflater.from(context):
public static LayoutInflater from(Context context)
{
 LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService
         (Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null)
    {
     throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}
可以看出它其实调用 context.getSystemService()。
结论:所以这三种方式最终本质是都是调用的Context.getSystemService()。
另外getSystemService()是Android很重要的一个API,它是Activity的一个方法,根据传入的NAME来取得对应的Object,然后转换成相应的服务对象。以下介绍系统相应的服务。
传入的Name 返回的对象 说明
WINDOW_SERVICE WindowManager 管理打开的窗口程序
LAYOUT_INFLATER_SERVICE LayoutInflater 取得xml里定义的view
ACTIVITY_SERVICE ActivityManager 管理应用程序的系统状态
POWER_SERVICE PowerManger 电源的服务
ALARM_SERVICE AlarmManager 闹钟的服务
NOTIFICATION_SERVICE NotificationManager 状态栏的服务
KEYGUARD_SERVICE KeyguardManager 键盘锁的服务
LOCATION_SERVICE LocationManager 位置的服务,如GPS
SEARCH_SERVICE SearchManager 搜索的服务
VEBRATOR_SERVICE Vebrator 手机震动的服务
CONNECTIVITY_SERVICE Connectivity 网络连接的服务
WIFI_SERVICE WifiManager Wi-Fi服务
TELEPHONY_SERVICE TeleponyManager 电话服务
inflate 方法
通过 sdk 的 api 文档,可以知道该方法有以下几种过载形式,返回值均是 View 对象,如下:
public View inflate (int resource, ViewGroup root)
public View inflate (XmlPullParser parser, ViewGroup root)
public View inflate (XmlPullParser parser, ViewGroup root, boolean attachToRoot)
public View inflate (int resource, ViewGroup root, boolean attachToRoot)
示意代码:
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.custom, (ViewGroup)findViewById(R.id.test));
//EditText editText = (EditText)findViewById(R.id.content);// error
EditText editText = (EditText)view.findViewById(R.id.content);
对于上面代码,指定了第二个参数 ViewGroup root,当然你也可以设置为 null 值。
注意:
·inflate 方法与 findViewById 方法不同;
·inflater 是用来找 res/layout 下的 xml 布局文件,并且实例化;
·findViewById() 是找具体 xml 布局文件中的具体 widget 控件(如:Button、TextView 等)。

Android 中 getApplicationContext()、this、getApplication()之间的区别

1. getApplicationContext():生命周期是整个应用,应用摧毁,它才摧毁。
2. this:代表当前,在Activity当中就是代表当前的Activity,换句话说就是Activity.this在Activity当中可以缩写为this.
3. getApplication():andorid 开发中共享全局数据;

我们在平时的开发中,有时候可能会需要一些全局数据,来让应用中得所有Activity和View都能访问到,大家在遇到这种情况时,可能首先会想到自己定义一个类,然后创建很多静态成员,不过andorid已经为我们提供了这种情况的解决方案:在Android中,有一个名为Application的类,我们可以在Activity中使用getApplication(),方法来获得,它是代表我们的应用程序的类,使用它可以获得当前应用的主题,资源文件中的内容等,这个类更灵活的一个特性就是可以被我们继承,来添加我们自己的全局属性。

Android之TabHost布局

1.概念
盛放Tab的容器就是TabHost。TabHost的实现有两种方式:
第一种继承TabActivity,从TabActivity中用getTabHost()方法获取TabHost。各个Tab中的内容在布局文件中定义就行了。
第二种方式,不继承TabActivity,在布局文件中定义TabHost即可,但是TabWidget的id必须是@android:id/tabs,FrameLayout的id必须是@android:id/tabcontent。
2.案例
1)继承TabActivity
res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- 定义TabHost组件 -->
<TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent">
<!-- 定义第一个标签页的内容 -->
<LinearLayout android:id="@+id/tab01" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
<!-- 定义两个TextView用于显示标签页中的内容 -->
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="孙悟空-2011/07/12"/>
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="猪八戒-2011/07/10"/>
</LinearLayout>
<!-- 定义第二个标签页的内容 -->
<LinearLayout android:id="@+id/tab02" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="萨僧-2011/07/11"/>
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="唐僧-2011/07/10"/>
</LinearLayout>
<!-- 定义第三个标签页的内容 -->
<LinearLayout android:id="@+id/tab03" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="孙悟空-2011/07/12"/>
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="萨僧-2011/07/08"/>
</LinearLayout>
</TabHost>

HelloTabHost.java

public class HelloTabHost extends TabActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//调用TabActivity的getTabHost()方法获取TabHost对象
TabHost tabHost = getTabHost();

//设置使用TabHost布局
LayoutInflater.from(this).inflate(R.layout.main, tabHost.getTabContentView(),true);

//添加第一个标签页
tabHost.addTab(tabHost.newTabSpec("tab01").setIndicator("已接电话").setContent(R.id.tab01));

//添加第二个标签页,并在其标签上添加一个图片
tabHost.addTab(tabHost.newTabSpec("tab02").setIndicator("未接电话",getResources().getDrawable(R.drawable.icon)).setContent(R.id.tab02));

//添加第三个标签页
tabHost.addTab(tabHost.newTabSpec("tab03").setIndicator("已拨电话").setContent(R.id.tab03));
}
}

 

2)不继承TabActivity
继承普通Activity,<TabWidget>标签id必须为tabs、<FrameLayout>标签id必须为tabcontent.这个方式在通过findViewById获得TabHost之后,必须要调用setup方法。
main.xml代码

<?xml version="1.0" encoding="utf-8"?>
<!-- TabHost必须包含一个 TabWidget和一个FrameLayout-->
<TabHost android:id="@+id/tabhost" android:layout_width="fill_parent" android:layout_height="wrap_content">
<LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
<!-- TabWidget的id属性必须为 @android:id/tabs-->
<TabWidget android:id="@android:id/tabs" android:orientation="horizontal" android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<!-- FrameLayout的id属性必须为 @android:id/tabcontent-->
<FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="fill_parent">
<TextView android:id="@+id/view1" android:layout_width="fill_parent" android:layout_height="fill_parent"/>
<TextView android:id="@+id/view2" android:layout_width="fill_parent" android:layout_height="fill_parent"/>
<TextView android:id="@+id/view3" android:layout_width="fill_parent" android:layout_height="fill_parent"/>
</FrameLayout>
</LinearLayout>
</TabHost>
</LinearLayout>

Java代码

public class TabHostTest extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 获取TabHost对象
TabHost tabHost = (TabHost) findViewById(R.id.tabhost);
// 如果没有继承TabActivity时,通过该种方法加载启动tabHost
tabHost.setup();
tabHost.addTab(tabHost.newTabSpec("tab1").setIndicator("第一个标签",
getResources().getDrawable(R.drawable.icon)).setContent(
R.id.view1));

tabHost.addTab(tabHost.newTabSpec("tab3").setIndicator("第三个标签")
.setContent(R.id.view3));

tabHost.addTab(tabHost.newTabSpec("tab2").setIndicator("第二个标签")
.setContent(R.id.view2));
}
}

自建TabHost,为什么R.java中无tabhost的id

问题出在你的xml布局定义中的这一行:
android:id="@android:id/tabhost"

如果是想不用android的tabhost的id,那么这行应该改为:

android:id="@+id/tabhost"

如果确实要用android的定义,又要找到这个窗口,那么应该改代码中的这行:

TabHost mTabHost = (TabHost)findViewById(R.id.tabhost);

应该改成

TabHost mTabHost = (TabHost)findViewById(android.R.id.tabhost);