Android UI框架遠遠不止是一個智能的、組織良好的GUI工具箱。當它摘掉眼鏡,甩甩秀髮,可是很有魅力哦!這裡提到的工具當然不夠詳盡。但是,它們可能有助於你開發出豐富多彩的應用。
警告:這裡所探討的一些技術接近於Android的邊緣。因此,比起我們前面章節所討論的類而言,它們還不夠完善:文檔也不夠全面,一些特性還處於「轉型期」,可能還會遇到一些Bug。如果你遇到問題,Google Group「Android Developers」是非常寶貴的資源。關於工具箱的某些功能的問題有時回答者正是這一功能的開發者。
當你因為某些問題搜索Web時,要仔細查看解決方案的日期。有些特徵變化很快。6個月前正常工作的代碼可能現在已經無法工作了。當然,任何分佈廣泛的應用很可能在各個版本上都能夠正常運行。通過使用這些技術,你可能會限制應用的生命週期,以及它支持的設備的數量。
本章後面的部分探討的是一個應用程序,和例8-6所給出的很類似:一些LinearLayout視圖,它們包含單個部件的多個實例,每個實例顯示不同的圖形展示效果。例8-10給出了該部件的關鍵部分,其代碼如前所述,這裡略去。該部件只是繪製一些圖形對象並定義接口,通過該接口,可以對各種圖形進行渲染。
例8-10:Effects widget
public class EffectsWidget extends View { /** The effect to apply to the drawing */ public interface PaintEffect { void setEffect(Paint paint); } // ... // PaintWidget's widget rendering method protected void onDraw(Canvas canvas) { Paint paint = new Paint; paint.setAntiAlias(true); effect.setEffect(paint); paint.setColor(Color.DKGRAY); paint.setStrokeWidth(5); canvas.drawLine(10, 10, 140, 20, paint); paint.setTextSize(26); canvas.drawText("Android", 40, 50, paint); paint = new Paint; paint.setColor(Color.BLACK); canvas.drawText(String.valueOf(id), 2.0F, 12.0F, paint); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(2); canvas.drawRect(canvas.getClipBounds, paint); } }
該應用用到的部件(參見例8-11)看起來也應該很熟悉了。它創建幾個EffectsWidget副本,每個副本分別實現自己的效果。底部右欄的部件很特別。它包含一個動畫背景。
例8-11:Effects應用
private AnimationDrawable buildEfxView(LinearLayout lv, LinearLayout rv) { lv.addView(new EffectsWidget( this, 1, new EffectsWidget.PaintEffect { @Override public void setEffect(Paint paint) { paint.setShadowLayer(1, 3, 4, Color.BLUE); } })); lv.addView(new EffectsWidget( this, 3, new EffectsWidget.PaintEffect { @Override public void setEffect(Paint paint) { paint.setShader( new LinearGradient( 0.0F, 0.0F, 110.0F, 10.0F, new int { Color.BLACK, Color.RED, Color.YELLOW }, new float { 0.0F, 0.5F, 0.95F }, Shader.TileMode.REPEAT)); } })); lv.addView(new EffectsWidget( this, 5, new EffectsWidget.PaintEffect { @Override public void setEffect(Paint paint) { paint.setMaskFilter( new BlurMaskFilter(2, BlurMaskFilter.Blur.NORMAL)); } })); lv.addView(new EffectsWidget( this, 2, new EffectsWidget.PaintEffect { @Override public void setEffect(Paint paint) { paint.setShadowLayer(3, -8, 7, Color.GREEN); } })); rv.addView(new EffectsWidget( this, 4, new EffectsWidget.PaintEffect { @Override public void setEffect(Paint paint) { paint.setShader( new LinearGradient( 0.0F, 40.0F, 15.0F, 40.0F, Color.BLUE, Color.GREEN, Shader.TileMode.MIRROR)); } })); View w = new EffectsWidget( this, 6, new EffectsWidget.PaintEffect { @Override public void setEffect(Paint paint) { } }); rv.addView(w); w.setBackgroundResource(R.drawable.throbber); return (AnimationDrawable) w.getBackground; }
圖8-5給出了以上代碼的運行效果。正如之前提到的,widget 6是動畫模式展示。我們很快會看到,其背景是紅色在閃爍。
圖8-5:圖形效果
在下一節中我們將對每種效果進行深入討論。
陰影、漸變、濾鏡和硬件加速器
PathEffect、MaskFilter、ColorFilter、Shader和ShadowLayer都是Paint的屬性。任何使用Paint繪製的圖形都可以使用一種或多種屬性來完成變換。圖8-5所示的前5個示例給出了其中幾種效果。
widget1和widget2顯示的是陰影效果。陰影通過setShadowLayer方法控制,參數是半徑和x、y位移,控制創建陰影的光源的距離和位置。
第二行的widget顯示的是著色。Android工具箱包含一些預構建的著色。widget3和widget4顯示了其中一個著色方案——LinearGradient shader(線性漸變著色)。漸變是指在所使用的色彩之間逐漸過渡,例如,給頁面背景增添一點生機而不需要使用執行代價很高的位圖資源。LinearGradient通過向量指定,它決定顏色過渡的方向和比率,要過渡的一組色彩及模式。最後一個參數mode(模式)決定當全部漸變無法覆蓋整個繪製的對象時如何處理。例如,在widget4中,漸變轉換的長度只涵蓋15個像素,而繪圖卻超過100個像素。可以使用模式Shader.TileMode.Mirror,使漸變不斷重複,沿著繪製方向交替。在這個例子中,漸變轉換在15個像素內從藍色變成綠色,在下一個15像素內從綠色變成藍色,這樣不斷交替,直到覆蓋滿整個畫布。
隨著Honeycomb的發佈,UI框架重構帶來的影響之一是限制或完全不支持一些很不常見的繪製效果,比如之前提到的drawTextOnPath和drawTextPos方法。setShadowLayer方法還是可以正常工作,但只適合文本類型。如果widget 1和widget 2都使用新的、硬件加速的圖形處理機制,文本會以陰影方式顯示,但其上面的線條不會。
可以強制Honeycomb設備以兼容模式啟動,這樣這些方法還會以Honeycomb之前的方式渲染。強制使用之前的渲染機制(通過軟件,而不是硬件渲染)的最佳方式是在應用的manifest文件中添加一個屬性,如例8-12所示。
例8-12:取消硬件加速
<application ... android:hardwareAccelerated="false">
實際上,這也是默認方式。為了利用Honeycomb之後帶來的硬件加速特性,必須在應用中設置該屬性值為true。否則,毫無疑問,當設備升級成Android v3或更新版本後,成百上千的應用會停止正常工作。一定要記住,除非主動在應用中支持硬件加速,否則這個特性就不會用上。
除了硬件加速以外,還可以對應用有更細粒度的控制。除了在manifest文件的application元素中添加android:hardwareAccelerated屬性(該屬性會影響整個應用),還可以為單個活動應用該屬性。如果一個活動不能使用新的繪圖機制渲染,可以把該屬性設置成false,而把其他活動設置成true。
例8-13給出的兩個代碼片段說明了如何以更細粒度(比如窗口和視圖級別)來控制硬件加速。
例8-13:細粒度級別取消硬件加速
getWindow.setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
然而,要記住的是,你的目標應該是使應用能夠在打開硬件加速的模式下正常工作。除了硬件加速外,沒有其他的優化措施,尤其對於新的設備,新設備往往會充分利用由硬件優化渲染帶來的速度和可用性的提高。
動畫
Android UI工具提供多種動畫工具。漸變動畫(transition animations),Google文檔稱之為漸變動畫(tweened animations),是android.view.animation.Animation的子類:RotateAnimation、TranslateAnimation、ScaleAnimation等。這些動畫用作為視圖之間的過渡。第二種類型的動畫,android.graphics.drawable.AnimationDrawable.AnimationDrawable的子類,有很多不同的效果,可以給任何widget提供背景。最後,在SurfaceView上有完整的動畫類,它可以對自己執行的動畫完全控制。
因為前兩種動畫,漸變和背景類型都是通過View實現的,所以它們幾乎都可以用於所有widget。
漸變動畫
漸變動畫是通過調用View的startAnimation方法執行的,包含Animation的實例(或者你自己的子類)。一旦安裝了漸變動畫,動畫會一直運行直到結束。漸變動畫沒有暫停狀態。
動畫的核心是它的applyTransformation方法。該方法在調用時可以生成動畫的連續幀。例8-14說明了一種轉換的實現。正如你看到的,它不會為動畫真正生成全部的圖形幀。相反,它生成要實現動畫效果的單個圖形的一系列轉換,把這些轉換應用於圖形中。在P225「矩陣轉換」一節中提到,矩陣轉換可以實現對像運動效果。漸變動畫就是依賴於這個特性的。
例8-14:漸變動畫
@Override protected void applyTransformation(float t, Transformation xf) { Matrix xform = xf.getMatrix; float z = ((dir > 0) ? 0.0f : -Z_MAX) - (dir * t * Z_MAX); camera.save; camera.rotateZ(t * 360); camera.translate(0.0F, 0.0F, z); camera.getMatrix(xform); camera.restore; xform.preTranslate(-xCenter, -yCenter); xform.postTranslate(xCenter, yCenter); }
這種特殊的實現使得目標看起來在屏幕上旋轉(調用rotate方法),同時還在遠處漸漸消逝(調用translate方法)。應用於目標圖像的矩陣是通過在調用時傳遞Transformation對像獲得的。
該實現使用了camera,它是工具類Camera的實例。Camera類和手機的攝像頭不同,它是一個工具,它可以記錄渲染的狀態。Camera要把旋轉和漸變轉換組合成單個矩陣,然後把該矩陣儲存為動畫轉換。
applyTransformation方法的第一個參數t,表示的是幀數。該參數的取值範圍在浮點型0.0到1.0之間,也可以理解為動畫完成的百分比。該示例使用t表示執行動畫效果的圖像沿著z軸(和屏幕垂直的一條線)的距離的遞加,以及設置該圖像通過的全部旋轉比例。隨著t的增加,動畫圖像看起來向遠處不斷逆時針旋轉,沿著z軸,變得越來越遠。
為了讓圖像沿著中心變換,preTranslate和postTranslate操作是必要的。默認情況下,矩陣操作是圍繞原點(左上角)變換的。如果我們不執行這些圍繞變換,則目標圖像看起來會沿著左上角旋轉。preTranslate操作有效地把原點轉移到動畫目標的中心,postTranslate操作在轉換完成後恢復默認的原點。
如果考慮漸變動畫可以實現什麼效果,就會發現它實際上可以組成兩個動畫:前一個屏幕向外執行動畫,後一個向內執行動畫。例8-14使用變量dir來表示。變量dir的值是1或-1,控制動畫圖像是向遠處漸漸縮小還是向前不斷變大。我們只需要找到一種方式來構建不斷縮小和不斷增長的動畫。
這是通過我們所熟悉的Listener模式實現的。Animation類定義了一個監聽器,名為Animation.AnimationListener。任何Animation實例都包含一個非空的監聽器,啟動和停止時都會調用一次監聽器,中間每次迭代時也調用一次。監聽器會關注縮小的動畫何時執行完成,完成後產生一個新的不斷變大的動畫,其效果和我們期望的一致。例8-15給出了該動畫後面部分的實現。
例8-15:漸變動畫的構建
public void runAnimation { animateOnce(new AccelerateInterpolator, this); } @Override public void onAnimationEnd(Animation animation) { root.post(new Runnable { public void run { curView.setVisibility(View.GONE); nextView.setVisibility(View.VISIBLE); nextView.requestFocus; new RotationTransitionAnimation(-1, root, nextView, null) .animateOnce(new DecelerateInterpolator, null); } }); } void animateOnce( Interpolator interpolator, Animation.AnimationListener listener) { setDuration(700); setInterpolator(interpolator); setAnimationListener(listener); root.startAnimation(this); }
runAnimation方法啟動漸變操作。onAnimationEnd方法覆蓋了AnimationListener方法,執行後面的操作。當目標圖像看起來在很遠處時,它隱藏了向外顯示動畫的圖像(curView),以新的圖像nextView取代它。然後,它創建新的動畫,沿著相反的方向運行,旋轉和增長新的圖像到前台。
Interpolator類表示對細節的關注。傳遞給applyTransformation的參數t的值,不需要是隨時間推移呈線性分佈。在實現中,動畫看起來在加速回落,然後隨著新的圖像出現又緩慢下來。該效果是通過兩個interpolator實現的,前半個動畫是AccelerateInterpolator,後半個動畫是DecelerateInterpolator。如果沒有interpolator,那麼傳遞給applyTransformation方法的連續的t值是等差的。這使得動畫看起來在勻速運動。AccelerateInterpolator對這些等差t值進行轉換,在開始時差值較小,接近相同,此後不斷增大。這使得動畫看起來是加速的。DecelerateInterpolator的效果剛好相反。Android還提供了CycleInterpolator和LinearInterpolator。
動畫組合實際上是在工具箱內構建的,使用AnimationSet類(該名字可能有點讓人混淆)。AnimationSet類可以很容易指定列表(list),而不是集合(set),它是有序的,可能順序多次指向要播放的動畫。此外,工具箱提供了一些標準的漸變方式:AlphaAnimation、RotateAnimation、ScaleAnimation和TranslateAnimation。當然,正如前面的例子所示,這些漸變動畫不需要對稱。當老的圖像慢慢淡入一個角落時,新的圖像可能就以alpha方式淡出;或者當老的圖像淡出時,新的圖像可能就從底部出來。有無窮多種的顯示方式。
背景動畫
Google文檔中所描述的逐幀動畫(frame-by-frame animation),是非常簡潔明瞭的:一組幀,定期順序播放。這種動畫類型是通過AnimationDrawable子類實現的。
作為Drawable的子類,AnimationDrawable對象可以用於任何能夠使用Drawable對象的場景。但是,Drawable本身不包含動畫機制。為了實現動畫,AnimationDrawable依賴於外部的服務提供者(實現接口Drawable.Callback)來執行動畫。
View類實現了Drawable.Callback接口,它可以用於執行AnimationDrawable動畫。遺憾的是,它只為以Drawable對像為背景的對象提供動畫服務。
但是,好消息,這已經夠用了。背景動畫能夠訪問全部widget畫布。背景動畫所繪製的內容都在View.onDraw方法繪製的內容之後顯示,因此很難使用背景動畫來實現全部效果。然而,只要善於使用DrawableContainer類(它支持同時執行幾個不同的動畫),而且因為背景可以隨時改變,所以有可能不需要自己實現動畫框架就能夠實現很多功能。
視圖背景中的AnimationDrawable完全能夠執行所有操作,比如表示正在執行一些需要長時間處理的操作,可能是一個帶翅膀的包從手機飛向高樓,對此只需要把背景變成按鈕脈衝。
widget6中跳動的按鈕富有說明意義且非常容易實現。例8-16和例8-17給出了所有的代碼。動畫被定義成resource,代碼把它應用到按鈕上。可以使用setBackgroundDrawable或setBackgroundResource把背景設置成Drawable對象。
例8-16:逐幀動畫(resource)
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/throbber_f0" android:duration="160" /> <item android:drawable="@drawable/throbber_f1" android:duration="140" /> <item android:drawable="@drawable/throbber_f2" android:duration="130" /> <item android:drawable="@drawable/throbber_f3" android:duration="100" /> <item android:drawable="@drawable/throbber_f4" android:duration="130" /> <item android:drawable="@drawable/throbber_f5" android:duration="140" /> <item android:drawable="@drawable/throbber_f6" android:duration="160" /> </animation-list>
例8-17給出了運行動畫的代碼。主視圖的onClickListener方法會啟動動畫過渡並切換視圖,因此在下一次點擊時,主視圖會繼續在不同視圖間切換。此外,還會對背景動畫進行切換,這樣當主視圖退出運行時,就不可見了。
注意:在老版本的Android中,Activity類沒有提供onCreate方法來啟動後台動畫,因此為了能夠正常運行,需要某些技巧。這個Bug在API 6(Muffin)中修復了,只需要執行((AnimationBackground)view.getBackground).start,就可以正常工作。
例8-17:逐幀動畫(代碼)
@Override public void onCreate(Bundle savedInstanceState) { // .... code elided // install the animation click listener final View root = findViewById(R.id.main); findViewById(R.id.main).setOnClickListener( new OnClickListener { @Override public void onClick(View v) { new RotationTransitionAnimation(1, root, cur, next) .runAnimation; // exchange views View t = cur; cur = next; next = t; toggleThrobber; } }); } // .... code elided void toggleThrobber { if (null != throbber) { if (efxView.equals(cur)) { throbber.start; } else { throbber.stop; } } }
有必要指出如果你使用過其他的UI框架,尤其是移動UI框架,你可能會習慣於在onDraw方法(或相關的)的前面幾行執行背景繪畫。如果在這裡執行背景繪畫,會覆蓋了動畫。通常來說,使用setBackground方法來控制View的背景是一個良好的習慣,不管是某個純色、漸變、圖像還是動畫。
通過resource指定DrawableAnimation是非常靈活的。可以指定一個drawable resource列表——任何想要的構成動畫的圖像。如果動畫需要動態,AnimationDrawable是創建動態的drawable的很好方法,它可以在View的背景中執行動畫。
平面視圖動畫
全屏動畫需要SurfaceView。SurfaceView在視圖樹中提供了一個節點以及顯示的空間,任何進程可以在該空間上繪製。當展開SurfaceView節點時,它會和其他的widget一樣,接收單擊和更新。但是,它不執行繪製,而只是保存屏幕上的空間,避免其他的widget影響框架內的任何像素。
在SurfaceView上繪製需要實現SurfaceHolder.Callback接口。surfaceCreated方法和surfaceDestroyed方法分別通知實現程序繪製平面可用和不可用。調用這兩個方法所傳遞的參數是類SurfaceHolder的實例。在執行這兩個方法的調用之間,繪製程序可以調用SurfaceView的lockCanvas方法和unlockCanvasAndPost方法來編輯像素。
以上過程看起來確實比較複雜,雖然有些接近於前面給出的動畫。同樣,並發問題還是增加了一些令人討厭且難以查找的Bug。SurfaceView的客戶端必須確保訪問線程間的任何狀態都被正確同步,而且除了在surfaceCreated方法和surfaceDestroyed方法之間的調用,其他地方都沒有使用到SurfaceView、Surface或Canvas。很明顯,工具箱可以得到更全面的SurfaceView動畫的框架支持。
如果考慮使用SurfaceView動畫,那你很可能也考慮使用OpenGL圖形,因為在SurfaceView上存在OpenGL動畫的擴展,雖然這塊知識相對比較隱蔽。
OpenGL圖形
Android平台對OpenGL圖形技術的支持可以說是錦上添花。雖然支持OpenGL圖形技術是Android提供的最令人興奮的技術之一,但它絕對只是Android中比較邊緣的技術。隨著Honeycomb的發佈,OpenGL完全集成到了Android圖形技術中。早期的Android版本只支持OpenGL 1.0和1.1,而根據文檔,Honeycomb不僅支持OpenGL 2.0,而且還把它作為渲染View對象的基礎。OpenGL實質上是一門嵌入在Java中的特定領域的語言。和Java程序員(甚至是Java專家)相比,遊戲UI的開發人員很可能會對開發Android OpenGL程序更得心應手。
在討論OpenGL圖形庫之前,我們應該先花點時間弄清使用OpenGL繪製的像素在屏幕上是如何顯示的。到目前為止,本章已經探討了Android用來對對像進行組織並顯示在屏幕上的複雜的視圖框架。OpenGL是一門語言,這門語言的應用程序的運行環境不僅可能在JVM之外的引擎上運行,而且很有可能運行於其他處理器上(圖形處理單元Graphics Processing Unit,或稱GPU)。協調屏幕上兩個進程之間的視圖是非常棘手的。
前面提到的SurfaceView差不多夠用了。SurfaceView的目的是創建一個平面,除UI圖形之外的線程都可以在這個平面上繪圖。我們將要討論的工具是SurfaceView的擴展,它提供了更多的並發支持和OpenGL支持。Android框架中確實存在該工具。Android SDK分發的所有演示應用所執行的OpenGL動畫都依賴於工具類GLSurfaceView。因為演示應用是由Android的創建者編寫的,它使用了GLSurfaceView這個類,所以在你的應用中也可以考慮使用該類。
GLSurfaceView定義了接口GLSurfaceView.Renderer,它極大地簡化了使用OpenGL和GLSurfaceView的複雜性。GLSurfaceView調用渲染方法getConfigSpec來獲取OpenGL配置信息。GLSurfaceView還調用兩個方法:sizeChanged和surfaceCreated,它們分別用於通知渲染程序其方法發生了改變以及它應該準備繪製。最後,調用接口的核心drawFrame來渲染新的OpenGL幀。
例8-18說明了實現OpenGL渲染器的重要方法
例8-18:使用OpenGL的逐幀動畫
// ... some state set up in the constructor @Override public void surfaceCreated(GL10 gl) { // set up the surface gl.glDisable(GL10.GL_DITHER); gl.glHint( GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); gl.glClearColor(0.4f, 0.2f, 0.2f, 0.5f); gl.glShadeModel(GL10.GL_SMOOTH); gl.glEnable(GL10.GL_DEPTH_TEST); // fetch the checkerboard initImage(gl); } @Override public void drawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity; GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); // apply the checker-board to the shape gl.glActiveTexture(GL10.GL_TEXTURE0); gl.glTexEnvx( GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE); gl.glTexParameterx( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT); gl.glTexParameterx( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT); // animation int t = (int) (SystemClock.uptimeMillis % (10 * 1000L)); gl.glTranslatef(6.0f - (0.0013f * t), 0, 0); // draw gl.glFrontFace(GL10.GL_CCW); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuf); gl.glEnable(GL10.GL_TEXTURE_2D); gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuf); gl.glDrawElements( GL10.GL_TRIANGLE_STRIP, 5, GL10.GL_UNSIGNED_SHORT, indexBuf); } private void initImage(GL10 gl) { int textures = new int[1]; gl.glGenTextures(1, textures, 0); gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); gl.glTexEnvf( GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_REPLACE); InputStream in = context.getResources.openRawResource(R.drawable.cb); Bitmap image; try { image = BitmapFactory.decodeStream(in); } finally { try { in.close; } catch(IOException e) { } } GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, image, 0); image.recycle; }
surfaceCreated方法負責前期準備。它設置widget繪製新的平面時需要初始化的一些OpenGL屬性。此外,它還調用initImage方法,該方法會讀取位圖資源,並把它存儲為二維結構。最後,調用drawFrame方法,繪製的準備工作至此基本完成。把之前保存的二維結構應用到平面上,它的節點是通過構造函數的vertexBuf設置的;選擇動畫的階段;重新繪製場景。圖8-6給出了運行OpenGL的示例。
圖8-6:OpenGL繪製
值得注意的是,你應該一直記著視圖嵌入在活動中,是有生命週期的!如果你使用的是OpenGL,當該活動不可見時,要記得結束長期運行的動畫進程。