setContentView                                                                       

只要你使用过Activity,那么你一定使用过setContentView这个方法。一般都是这样调用该方法:

setContentView(R.layout.main);

然后,在手机或者模拟器上就可以看见自己的布局。

如果,你留意的话,setContentView还有很多过载方法:

public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
} public void setContentView(View view) {
getWindow().setContentView(view);
} public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
}

那么,getWindow()方法是做什么的呢?一探究竟:

public Window getWindow() {
return mWindow;
}

可以看出,该方法返回一个Window实例。但是Window是一个抽象类啊,怎么可以有实例对象???原来,Window类有一个子类PhoneWindow,那么如何得知getWindow返回的是PhoneWindow实例呢?来,看下面这张图:

至此,您应该明白setContentView()方法是调用PhoneWindow类的同名方法。源码如下:

@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
} @Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
} @Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mContentParent.addView(view, params);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}

每个Activity都会实例化一个Window并且只有一个,而View就像是贴在Window上的装饰品。窗户(Window)只有一个,但是窗花(View)可以有很多。

LayoutInflater                                                                        

获得 LayoutInflater 实例

LayoutInflater inflater = getLayoutInflater();

LayoutInflater localinflater =(LayoutInflater)context.getSystemServie
(Context.LAYOUT_INFLATER_SERVICE); LayoutInflater inflater = LayoutInflater.from(context);

对于第一种,主要是调用 Activity 的 getLayoutInflater() 方法。

继续跟踪研究 android 源码,Activity 中的该方法是调用 PhoneWindow 的 getLayoutInflater()方法!

public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}

可以看出它其实是调用 LayoutInflater.from(context), 那么该方法其实是调用第二种方法,看看源码,如下:

/**
* Obtains the LayoutInflater from the given 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;
}

inflate 方法

inflate 原意是充气之类的,在这里主要意思就是,扩张、使之膨胀。换句话说就是将当前视图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); /* R.id.test 是 custom.xml 中根(root)布局 LinearLayout 的 id */
View view = inflater.inflate(R.layout.custom,
(ViewGroup)findViewById(R.id.test)); /* 通过该 view 实例化 EditText对象, 否则报错,因为当前视图不是custom.xml.
即没有 setContentView(R.layout.custom) 或者 addView() */
//EditText editText = (EditText)findViewById(R.id.content);// error
EditText editText = (EditText)view.findViewById(R.id.content);

对于上面代码,指定了第二个参数 ViewGroup root,当然你也可以设置为 null 值。

注意:该方法与 findViewById 方法不同。

inflater 是用来找 layout 下 xml 布局文件,并且实例化!而 findViewById() 是找具体 xml 下的具体 widget 控件(如: Button,TextView 等)。

postInvalidate()   (参考)                                                      

在子线程中控制UI:

@Override
protected void onRestart() {
super.onRestart();
/*onRestart中开启新线程,更新UI*/
Thread thread = new Thread(new Runnable() { @Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
tv.postInvalidate();
btn.postInvalidate();
tv.setText("update UI is success!");
btn.setText("update UI is success!");
}});
thread.start();
}

postInvalidate() 方法,源码:

public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
if (mAttachInfo != null) {
Message msg = Message.obtain();
msg.what = AttachInfo.INVALIDATE_MSG;
msg.obj = this;
mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
}

其实,是调用了 Handler 的处理消息的机制!该方法可以在子线程中直接用来更新UI。但是在 Button 的事件中开启线程,更新 UI就会报错报异常。

Handler 和 invalidate 方法结合多线程更新 UI                              

方法 invalidate 主要用在主线程中(即UI 线程中),不可以用于子线程如果在子线程中需要使用 postInvalidate 方法。

public void invalidate() {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
}
if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
r.set(0, 0, mRight - mLeft, mBottom - mTop);
// Don't call invalidate -- we don't want to internally scroll
// our own bounds
p.invalidateChild(this, r);
}
}
}

invalidate 方法如果你直接在主线程中调用,是看不到任何更新的。需要与Handler结合!

Android 在 onDraw 事件处理绘图,而 invalidate() 函数可以再一次触发 onDraw 事件,然后再一次进行绘图动作。

public class MasterActivity extends Activity {
static int times = 1; /** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView( new View(null){ Paint vPaint = new Paint(); //绘制样式物件
private int i = 0; //弧形角度 @Override
protected void onDraw (Canvas canvas) {
super.onDraw(canvas);
System.out.println("this run " + (times++) +" times!"); // 设定绘图样式
vPaint.setColor( 0xff00ffff ); //画笔颜色
vPaint.setAntiAlias( true ); //反锯齿
vPaint.setStyle( Paint.Style.STROKE ); // 绘制一个弧形
canvas.drawArc(new RectF(60, 120, 260, 320), 0, i, true, vPaint ); // 弧形角度
if( (i+=10) > 360 )
i = 0; // 重绘, 再一次执行onDraw 程序
invalidate();
}
});
}
}

经过测试,发现 times 一直在++,说明 onDraw 被多次调用,并且一直在画图!

注释掉的话:

// 重绘, 再一次执行onDraw 程序
//invalidate();

可以看出,图像只画了一条线,说明onDraw()方法被调用一次。从log上也可以看出来:

D/mark    (  221): this run onDraw() 1 times!

那么,是什么力量促使onDraw()方法被调用呢?

setContentView()View view方法,其实是调用PhoneWindow的setContentView(View view)方法,调用关系如下:

从而可以看出,invalidate()方法是促使onDraw()方法被调用的力量。

那么,修改代码,将内部类MyView的onDraw()方法中的invalidate()注释取消,再看看运行效果:

控制台:

D/mark    (  248): this run onDraw() 5629 times!
D/mark ( 248): this run onDraw() 5630 times!
D/mark ( 248): this run onDraw() 5631 times!
D/mark ( 248): this run onDraw() 5632 times!
D/mark ( 248): this run onDraw() 5633 times!
D/mark ( 248): this run onDraw() 5634 times!
D/mark ( 248): this run onDraw() 5635 times!
D/mark ( 248): this run onDraw() 5636 times!
D/mark ( 248): this run onDraw() 5637 times!
D/mark ( 248): this run onDraw() 5638 times!
D/mark ( 248): this run onDraw() 5639 times!
D/mark ( 248): this run onDraw() 5640 times!
D/mark ( 248): this run onDraw() 5641 times!
D/mark ( 248): this run onDraw() 5642 times!
D/mark ( 248): this run onDraw() 5643 times!
D/mark ( 248): this run onDraw() 5644 times!
D/mark ( 248): this run onDraw() 5645 times!
D/mark ( 248): this run onDraw() 5646 times!

可以看出,invalidate()方法使onDraw()一直被调用,实现重绘的效果。

在invalidate()方法源码中,有这么一段注释:

/**
* Invalidate the whole view. If the view is visible, {@link #onDraw} will
* be called at some point in the future. This must be called from a
* UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
*/

这段话,说明了上面的实现(调用onDraw()方法)。但是在子线程中必须使用postInvalidate()方法。

invalidate()源码分析                                                                

public class ViewDrawTestActivity extends Activity {
// 用于测试
static int times = 1; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyView mView = new MyView(this);
mView.invalidate();
//setContentView(mView);
} /**
* 内部类,继承View
*
* @author mark
*/
class MyView extends View { MyView(Context context) {
super(context);
} Paint vPaint = new Paint(); // 绘制样式物件
int i = 0; // 弧形角度 @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("mark", "this run onDraw() " + (times++) + " times!");
// 设定绘图样式
vPaint.setColor(0xff00ffff); // 画笔颜色
vPaint.setAntiAlias(true); // 反锯齿
vPaint.setStyle(Paint.Style.STROKE);
// 绘制一个弧形
canvas.drawArc(new RectF(60, 120, 260, 320), 0, i, true, vPaint);
// 弧形角度
if ((i += 10) > 360) {
i = 0;
}
// 重绘, 再一次执行onDraw 程序
// invalidate();
}
}
}

子没有多大的变化,只是在onCreate()方法中直接调用invalidate()方法,如:

mView.invalidate();

这样做的目的主要是想看看,自己调用View的invalidate()方法会不会触发onDraw()方法。运行一下:

nDraw()方法并没有执行!那么是不是因为没有调用setContentVIew()方法呢?修改onCreate()方法:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyView mView = new MyView(this);
mView.invalidate();
setContentView(mView);
mView.invalidate();
}

再次运行,效果:

D/mark    (  251): this run onDraw() 1 times!

说明,只有setContentVIew()方法中的invalidate()方法启了作用,自己调用View的invalidate()方法,mView.invalidate()没启任何作用。但是,在MyView的onDraw()方法中调用invalidate()方法可以循环调用onDraw()方法,类似递归。

分析一下,invalidate()方法的源码吧,在这里也许可以找到答案。

/**
* Invalidate the whole view. If the view is visible, {@link #onDraw} will
* be called at some point in the future. This must be called from a
* UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
*/
public void invalidate() {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
} if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
r.set(0, 0, mRight - mLeft, mBottom - mTop);
// Don't call invalidate -- we don't want to internally scroll
// our own bounds
p.invalidateChild(this, r);
}
}
}

这里可以看到p.invalidateChild(this, r)(看源码只看关键部分,不然你会很晕!),其中p是ViewParent实例对象。ViewParent是一个接口,现在我们关心谁实现了这个接口?

通过千辛万苦的search,终于找到ViewParent的实现类ViewRoot:

/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
* detail of {@link WindowManagerImpl}.
*
* {@hide}
*/
@SuppressWarnings({"EmptyCatchBlock"})
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { }

那么,看看该类实现的invalidateChild()方法:

public void invalidateChild(View child, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
mDirty.union(dirty);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}

关键代码在这儿:

if (!mWillDrawSoon) {
scheduleTraversals();
}

这个方法是向Handler发送消息:

public void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
sendEmptyMessage(DO_TRAVERSAL);
}
}

接下来,看看ViewRoot的Handler的handleMessage的实现:

public void handleMessage(Message msg) {
switch (msg.what) {
// 、、、
case DO_TRAVERSAL:
// 、、、
performTraversals();
}
}

performTraversals()方法,调用ViewRoot的私有方法private void draw(boolean fullRedrawNeeded),在该方法中有句代码很关键:

mView.draw(canvas);

其实这句代码,就是调用View的draw()方法 ,关键代码:

if (!dirtyOpaque) onDraw(canvas);

也就是说,满足这个方法,就会回调onDraw()方法。到此为止,您应该明白,当我们自己调用invalidate()方法时,想使onDraw()方法回调,必须满足条件。

调用关系,请看草图!

我是天王盖地虎的分割线                                                               

剧终!参考:http://blog.csdn.net/veryitman/article/details/6695516

Android -- View的更多相关文章

  1. 虾扯蛋:Android View动画 Animation不完全解析

    本文结合一些周知的概念和源码片段,对View动画的工作原理进行挖掘和分析.以下不是对源码一丝不苟的分析过程,只是以搞清楚Animation的执行过程.如何被周期性调用为目标粗略分析下相关方法的执行细节 ...

  2. Android View.setId(int id) 用法

    Android View.setId(int id) 用法 当要在代码中动态的添加View并且为其设置id时,如果直接用一个int值时,Studio会警告. 经过查询,动态设置id的方法有两种; 1. ...

  3. android view 中各函数的执行顺数

    这个就好像是 activity 的生命周期一样,如果我们要使用自定义的 view,那么就很有必要了解一下 view 的那些能够被重写的函数的执行顺序.废话不多讲,以常用的5个函数为例子,见下文: pa ...

  4. Android项目部署时,发生AndroidRuntime:android.view.InflateException: Binary XML file line #168: Error inflating class错误

    这个错误也是让我纠结了一天,当时写的项目在安卓虚拟机上运行都很正常,于是当我部署到安卓手机上时,点击登陆按钮跳转到用户主界面的时候直接结束运行返回登陆界面.    当时,我仔细检查了一下自己的代码,并 ...

  5. Android View 事件分发机制 源码解析 (上)

    一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个My ...

  6. 简单研究Android View绘制三 布局过程

    2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...

  7. java.lang.ClassCastException: android.view.AbsSavedState$1 cannot be cast to android.widget.ProgressBar$SavedState

    java.lang.ClassCastException: android.view.AbsSavedState$1 cannot be cast to android.widget.Progress ...

  8. Android View各种尺寸位置相关的方法探究

    Android View各种尺寸位置相关的方法探究 本来想做一个View间的碰撞检测之类的. 动手做了才发现不是想象的那么简单. 首先,写好了碰撞检测的工具类如下: package com.mengd ...

  9. Activity has leaked window that was originally added -界面退出时未关闭对话框异常 android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? -

    退出Activity时弹出登录框,点击确定finish当前Activity,结果报了这个错,随后查找资料知道 原因: 是因为退出Activity时没有关闭弹出框,出现了这个错误 解决方法: 只需要在a ...

  10. [Android] View.setTag(key,Object) (java.lang.IllegalArgumentException: The key must be an application-specific resource id.)

    转自: http://blog.csdn.net/brokge/article/details/8536906 setTag是android的view类中很有用的一个方法,可以用它来给空间附加一些信息 ...

随机推荐

  1. 数据库连接池问题 Max Pool Size

    摘自: http://blog.csdn.net/chensirbbk/article/details/6225268 Timeout expired 超时时间已到. 达到了最大池大小 错误及Max ...

  2. Android OptionMenu

    1.Java package com.fish.helloworld; import android.app.Activity; import android.content.Context; imp ...

  3. 021ARM处理器工作模式

    1.User模式:usr,普通应用程序运行的模式: 2.FIQ模式:fiq,快速中断模式,当一个程序正在运行时,突然产生一个中断,而且这种中断属于快速中断,那么将进入快速中断模式下运行: 3.IRQ模 ...

  4. 如何防止DDos攻击?

    ---恢复内容开始--- 一.拒绝服务攻击的发展 从拒绝服务攻击诞生到现在已经有了很多的发展,从最初的简单Dos到现在的DDOS.那么什么是Dos和DDOS呢?DoS是一种利用单台计算机的攻击方式.而 ...

  5. minicom/kermit捕捉日志

    1.minicom捕捉日志 ctrl-A Z 命令窗口中有 Capture on/off......L   2.kermit捕捉日志 ctrl-\ C进入kermit命令行模式 log session ...

  6. 将ubuntu12.04中,gcc4.6/g++4.6版本降低到gcc4.4/g++4.4.

    降低Ubuntu中gcc和g++的版本 ubuntu 12.04 中带的gcc/g++都是4.6,将其降到4.4. 操作步骤如下: 一.降低gcc版本 1. $sudo apt-get install ...

  7. HTML5应用之时钟

    利用HTML5的Canvas API可以完成我们以前意想不到的动画效果,以前我们想在网页上放置一个时钟,需要先用flash工具制作一个钟表,并写上复杂的JavaScript代码,然后载入到页面中.而H ...

  8. silverlight 退出系统(关闭当前网页),通过调用JS

    确认后直接退出系统,关闭当前页面 页面部分: <HyperlinkButton x:Name="LinkExit" Style="{StaticResource L ...

  9. 【easyui】—easyui教你编写一个前台的架子

    以前做项目都是在别人搭建好的环境下直接编写单独的页面,也没有处理过怎么搭建一个框架.看到别人的布局都挺好的,自己也想做一个走一下流程. 嘿,刚开始时看着别人写的代码,去找怎么写. 这是我自己的想法,使 ...

  10. WIN10 64位下VS2015 MFC直接添加 halcon 12的CPP文件实现视觉检测

    近段时间开始接触halcon,但是在VS2015里面使用,无论是配置还是生产EXE文件,都不如意. 加上网上的教程很多,经过多次测试,其实有很多地方无需修改,如果修改的太多也失去了直接添加封装的意义. ...