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. javaSE第二十二天

    第二十二天    312 1:登录注册IO版本案例(掌握)    312 2:数据操作流(操作基本类型数据的流)(理解)    313 (1)定义:    313 (2)流对象名称    313 (3 ...

  2. 使用select io复用实现超时设置

    在linux的socket编程中,经常会遇到超时设置的问题,例如请求方如果在Ks内不发送数据则服务器要断开连接停止服务.这里我使用select的io复用实现超时5s设置,具体代码片段如下: fd_se ...

  3. Visual Studio 中 Tab 转换为空格的设置

    Visual Studio 中 Tab 转换为空格的设置 在 Visual Studio 中写代码时,按 Tab 键,会自动进行缩进.有时希望实现按 Tab 键,出现多个空格的效果.Visual St ...

  4. java编程的78条黄金法则

    创建和销毁对象 1.考虑用静态工厂方法(返回类的实例的静态方法)代替构造器2.遇到多个构造器参数时要考虑用构造器3.用私有构造器或者枚举类型强化Singleton属性4.通过私有构造器强化不可实例化的 ...

  5. 必须会的SQL语句(二) 创建表、修改表结构、删除表

    1.创建数据库表 --使用哪个数据库,如果不写这一句是默认的数据库,也可以用鼠标选当前数据库 use testDB --创建表    Create Table tablename    (       ...

  6. 在Eclipse中查看JDK类库的源代码

    http://blog.csdn.net/a81895898/article/details/8486802 在Eclipse中查看JDK类库的源代码!!! 设置: 1.点 “window”-> ...

  7. PL/SQL Developer基本用法

    一.新建存储过程

  8. scala学习笔记1

    一.REPL scala解释器读到一个表达式,对它进行求值,将它的打印出来,接着再继续读下一个表达式.这个过程被称作 读取-打印-循环,即REPL. 从技术上讲,scala程序并不是一个解释器.实际发 ...

  9. PHP多例模式

    学习java就知道设计模式中有多例模式: 1.多例类可以有多个实例2.多例类必须能够自我创建并管理自己的实例,并向外界提供自己的实例. 大家都知道PHP单例模式,却很少说PHP多例模式,下面是在wik ...

  10. delphi 基础之四 delphi 组织结构

    delphi 组织结构 在Delphi中,一个正在开发的应用程序可以被称作项目或者工程.一般地,一个项目主要由dpr(项目).pas(单元)和dfm(窗体)三种文件组成,另外还有一些附属文件,如res ...