Android -- View
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
出处:http://yydcdut.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。