520 likes | 788 Views
Chapter 12. Android 動畫程式設計. 前言. Android 在動畫程式設計上有非常多成熟的工具可以使用 。 例如說: Canvas 畫布、 ViewFlipper 動畫等,另外也可以使用 OpenGL 去設計 2D/3D 圖形或動畫,使手機多媒體或是操作介面有更多爆炸性的發展。. 畫布 / 畫筆. Canvas.
E N D
Chapter 12 Android動畫程式設計
前言 Android在動畫程式設計上有非常多成熟的工具可以使用。 例如說:Canvas畫布、ViewFlipper動畫等,另外也可以使用OpenGL去設計2D/3D圖形或動畫,使手機多媒體或是操作介面有更多爆炸性的發展。
Canvas • Canvas物件在Java應用程式上已經非常成熟,用於Android上也是開發動畫或UI的一個好工具。Canvas就像手機中的畫布,可以在Canvas上繪製圖形或者圖片,一般我們可以使用以下四個組成部分,在Android上繪製圖形: • 點陣圖(包含像素) • Canvas畫布(包含繪畫內容,寫入點陣圖 ) • 初始圖形(例如Rect、Bitmap、text…等等 ) • Paint(用來描述上面初始圖形的顏色和類型…等等)
Canvas • View類別的onDraw方法會傳入一個Canvas物件,用來繪製元件界面的畫布。在實作onDraw方法時,經常會看到呼叫到save和restore方法,下面就解釋這兩個方法的作用: • save:保存Canvas的狀態。save之後,可以呼叫Canvas中的位移、縮放、旋轉、裁切…等等操作。 • restore:回復Canvas之前保存的狀態。防止save後對Canvas執行的操作對後續繪製有所影響。
Canvas save和restore要同時使用,如果restore呼叫次數比save多,會引發Error。 下面將有一簡易範例解說Canvas中較重要的部分,如圖所示。
Canvas 程式碼(CanvasEX.java): package ncu.bnlab.CanvasExample; import android.app.Activity; import android.os.Bundle; public class CanvasEX extends Activity { DrawAction drawAction; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); drawAction = new DrawAction(this); setContentView(drawAction); /* requestFocus方法用於設置新焦點 */ drawAction.requestFocus(); } }
Canvas 程式碼(DrawAction.java): 由於程式碼過多,完整程式碼請參考光碟中CanvasEX.java、DrawAction.java public void onDraw(Canvas canvas) { /* 取得寬度 */ int px = getMeasuredWidth(); int py = getMeasuredWidth(); /* 設定線畫筆顏色 */ linePaint.setColor(Color.WHITE); /* 設置為true可消除邊緣效果 */ linePaint.setAntiAlias(true); /* 設定背景畫筆顏色 */ backgroundPaint.setColor(Color.BLUE); backgroundPaint.setAntiAlias(true); /* 設定方塊畫筆顏色 */ rectPaint.setColor(Color.RED); rectPaint.setAntiAlias(true); /* 於畫布繪製圖形 */ canvas.drawRect(0, 0, px, py, backgroundPaint); /* 呼叫save方法儲存目前狀態 */ canvas.save(); ........ /* 呼叫restore方法 */ canvas.restore(); /* 於畫布繪製方塊 */ canvas.drawRect(px, py, 300, 300, rectPaint);
Canvas 從範例可看出,其中紅色的方塊位置有明顯差異。 造成這樣的原因是因為將Canvas中save與restore方法註解,所有的圖都是在旋轉90°後的畫布上繪製。當執行完onDraw方法,系統自動將畫布恢復回來。
Paint Paint類別擁有樣式與顏色資訊,主要是有關於如何繪製幾何圖形、文字及點陣圖的方法,範例如圖所示。
Paint 布局文件(res/layout/main.xml): <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" > <Button android:id="@+id/btnPrevious" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Previous" /> <Button android:id="@+id/btnNext" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Next" /> </LinearLayout> <ViewFlipper android:id="@+id/viewFlipper" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" /> </LinearLayout>
Paint 程式碼(PaintEX.java): package ncu.bnlab.PaintExample; import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; @SuppressWarnings("unused") public class PaintEX extends Activity { DrawAction drawAction; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); */ /* requestWindowFeature(Window.FEATURE_NO_TITLE); */ drawAction = new DrawAction(this); setContentView(drawAction); /* requestFocus方法用於設置新焦點 */ drawAction.requestFocus(); } }
Paint 程式碼-1(DrawAction.java): 程式碼-2(DrawAction.java): @Override public void onDraw(Canvas canvas) { for (Point point : points) { /*於畫布上繪製圖形*/ canvas.drawCircle(point.x, point.y, 5, paint); } } public boolean onTouch(View view, MotionEvent event) { Point point = new Point(); /* 取得目前觸碰螢幕之x,y值 */ point.x = event.getX(); point.y = event.getY(); /* 新增點至Point物件 */ points.add(point); /* 用於更新View */ invalidate(); return true; } List<Point> points = new ArrayList<Point>(); Paint paint = new Paint(); public DrawAction(Context context) { super(context); /* 設定可取得焦點 */ setFocusable(true); /* 設定在觸控模式可取得焦點 */ setFocusableInTouchMode(true); /* 設定監聽器 */ this.setOnTouchListener(this); /* 設定畫筆顏色 */ paint.setColor(Color.WHITE); paint.setAntiAlias(true); }
Paint 了解上述Canvas與Paint結合的範例後,接著解說關於在Android中如何顯示字體,因為字體在所有應用中是最常被使用到的。字體一般擁有的屬性有大小、顏色、對齊方式、粗體、斜體、下劃線等。
Paint 在Android中使用Typeface類別來定義字體。 Typeface可以指定字體和字體風格,並可用Paint繪製字型,Typeface就類似Paint中的其它屬性textSize,textSkewX,textScaleX...等等。下表為字體常數的定義:
Paint 這些字體常數,在應用程式中是可以直接使用的,例如說:Typeface. SERIF。 另外Typeface也包含了一些用來處理字體的方法,例如:建立字體Create(),取得字體屬性getStyle()、isBold()、isItalic()…等等。 Typeface類別不僅定義了字體,還包括粗體(Bold)、斜體(Italic)。
Paint 其它對顯示String有影響的因素,都可以在Paint類別中找到它們的方法,如下頁表所示:
Paint 至此讀者應對Paint有基本的瞭解,實際上在Paint中還有其他一些功能,例如用Alpha來處理透明度、用Dither來處理混色...等等,詳細內容可參考Android SDK。
Paint 接著就以一個簡單的範例來演練Typeface與Paint結合的做法,程式如圖所示。
Paint 首先需將ttf字型檔加入專案中的fonts資料夾(於asset下新建),如圖所示。
Paint 程式碼(TypefaceEX.java): private static class FontView extends View { /* Paint.ANTI_ALIAS_FLAG為消除鋸齒 */ private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private Typeface typeFace; public FontView(Context context) { super(context); /* 自定義字體 */ typeFace = Typeface.createFromAsset(getContext().getAssets(), "fonts/verdanaz.ttf"); // typeFace = Typeface.createFromFile("/sdcard/verdanaz.ttf"); /* 設定字體大小 */ paint.setTextSize(32); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); /* 繪製預設字體 */ paint.setTypeface(null); canvas.drawText("Default Font", 10, 100, paint); /* 繪製自定義字體 */ paint.setTypeface(typeFace); canvas.drawText("Custom Font", 10, 200, paint); } }
ViewFlipper ViewFlipper可以包含多個View) 且View之間的切換有動畫效果,例如說漸變效果。它也可以根據時間週期切換顯示項目,像是一個幻燈片播放的效果,範例如圖所示。
ViewFlipper • ViewAnimator的作用是為FrameLayout裡面的View切換提供動畫效果。 • ViewAnimator類別有幾個和動畫相關的方法: • setInAnimation:設定View進入螢幕時候使用的動畫。 • setOutAnimation: 設定View退出螢幕時候使用的動畫。 • showNext:呼叫此方法顯示Layout裡面的下一個View。 • showPrevious:呼叫此方法顯示Layout裡面的上一個View。
ViewFlipper • 一般不直接使用ViewAnimator而是使用它的兩個子類別ViewFlipper和ViewSwitcher。 • ViewFlipper可以用來指定Layout內多個View之間的切換效果,可以一次指定也可以每次切換的時候都指定單獨的效果。此類別額外提供了如下幾個方法: • isFlipping:用來判斷View切換是否正在進行。 • setFilpInterval:設定View之間切換的時間間隔。 • startFlipping:使用上面設定的時間間隔來切換所有View,會以幻燈片方式進行。 • stopFlipping: 停止View切換。
ViewFlipper 布局文件(res/layout/main.xml): <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" > <Button android:id="@+id/btnPrevious" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Previous" /> <Button android:id="@+id/btnNext" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Next" /> </LinearLayout> <ViewFlipper android:id="@+id/viewFlipper" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" /> </LinearLayout>
ViewFlipper 程式碼-1(ViewFlipperEX.java): public class ViewFlipperEX extends Activity { public final static int VIEW_TEXT = 0; public final static int VIEW_IMAGE = 1; private Button btnPrevious; private Button btnNext; private ViewFlipper viewFlipper; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initView(); /* 新增圖檔及文字於viewFlipper此View中 */ viewFlipper.addView( addTextByText("Android 1.6") ); viewFlipper.addView( addImageById(R.drawable.play) ); viewFlipper.addView( addTextByText("Android 2.0") ); viewFlipper.addView( addImageById(R.drawable.normal) ); viewFlipper.addView( addTextByText("HTC Hero") ); }
ViewFlipper 程式碼-2(ViewFlipperEX.java): 程式碼-3(ViewFlipperEX.java): public View addTextByText(String text) { /* 設定TextView屬性並回傳 */ TextView textView = new TextView(this); textView.setText(text); textView.setGravity(1); return textView; } public View addImageById(int id) { /* 設定ImageView屬性並回傳 */ ImageView imageView = new ImageView(this); imageView.setImageResource(id); return imageView; } 完整程式碼請參考光碟中ViewFlipperEX.java private OnClickListener listener = new OnClickListener() { public void onClick(View v) { /* 判斷點擊到哪個按鈕 */ switch(v.getId()) { /* 點擊到上一張按鈕就呼叫showPrevious方法 */ case R.id.btnPrevious: viewFlipper.showPrevious(); break; /* 點擊到下一張按鈕就呼叫showNext方法 */ case R.id.btnNext: viewFlipper.showNext(); break; } } };
ViewSwitcher ViewSwitcher簡單來說就是Switcher在兩個View之間切換,可以透過ViewSwitcher指定一個ViewSwitcher.ViewFactory 來建立兩個View。 此類別具有兩個子類別ImageSwitcher、TextSwitcher分別用於圖片和文字切換,範例如下頁圖所示。
ViewSwitcher 程式範例圖:
ViewSwitcher 一開始要在res資料夾底下新增兩個XML檔案,接著將一開始新增的兩個XML檔,作為兩個View,並使用ViewSwitcher的方法去做兩個View之間的切換。 當按下讀取更多的按鈕時,ViewSwitcher就會切換到另外一個View,當背景作業處理完成後,才會切換回原本的View。
ViewSwitcher 布局文件(res/layout/button.xml): <?xml version="1.0" encoding="utf-8"?> <!--使用 ViewSwitcher 切換的第一個View--> <Button xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/btn_loadmorecontacts" android:text="Load More Items" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:minHeight="?android:attr/listPreferredItemHeight" android:textColor="#FFFFFF" android:background="@android:drawable/list_selector_background" android:clickable="true" android:onClick="onClick" />
ViewSwitcher 布局文件(res/layout/progress.xml): <?xml version="1.0" encoding="utf-8"?> <!--使用 ViewSwitcher 切換的第二個View--> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" android:minHeight="?android:attr/listPreferredItemHeight"> <ProgressBar android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" /> <TextView android:text="Loading…" android:textAppearance="?android:attr/textAppearanceLarge" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_toRightOf="@+id/progressBar" android:layout_centerVertical="true" android:gravity="center" android:padding="10dip" android:textColor="#FFFFFF" /> </RelativeLayout>
ViewSwitcher 程式碼-1(ViewSwitcherEX.java): public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); switcher = new ViewSwitcher(this); button = (Button)View.inflate(this, R.layout.button, null); progress = View.inflate(this, R.layout.progress, null); /* 將button與progressbar加入switcher中 */ switcher.addView(button); switcher.addView(progress); /* 取得ListView並將switcher加入 */ getListView().addFooterView(switcher); /* 設定ListAdapter,其中第二個參數可選擇樣式 */ setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, ITEMS)); }
ViewSwitcher 程式碼-2(ViewSwitcherEX.java): 完整程式碼請參考光碟中ViewSwitcherEX.java private class getMoreItems extends AsyncTask { @Override protected Object doInBackground(Object... params) { /* 此處可撰寫新增項目之程式碼 */ try { /* 執行緒會呼叫sleep方法 */ Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Object result) { switcher.showPrevious(); } }
前言 在Android中支援高效能的3D圖形,主要是透過OpenGL/ES API。 所謂的ES API就是OpenGL所規範給嵌入式設備使用的。
GLSurfaceView GLSurfaceView是一個新API,開始使用是從Android1.5版。 GLSurfaceView可以使得創造一個OpenGL ES應用程式更為容易,通常用於遊戲或快速更新畫面的應用程式上。
GLSurfaceView • GLSurfaceView具有下列優點: • 提供了glue code來連接OpenGL ES的視圖系統。 • 提供了glue code使得OpenGL ES與活動生命週期一起工作。 • 可以更簡單的選擇適合的Frame buffer像素格式。 • 建立與管理一個獨立的繪圖執行緒,使得動畫效果更為流暢。 • 提供簡易的除錯工具。
GLSurfaceView 下面將以一個簡易的範例使讀者更了解GLSurfaceView的使用方式,範例執行結果如圖所示。
GLSurfaceView 程式碼-1(GLSurfaceViewEX.java): 程式碼-2(GLSurfaceViewEX.java): class ClearRenderer implements GLSurfaceView.Renderer { private float mRed; private float mGreen; private float mBlue; public void onSurfaceCreated(GL10 gl, EGLConfig config) { } public void onSurfaceChanged(GL10 gl, int w, int h) { /* 決定視窗上的繪圖區域 */ gl.glViewport(0, 0, w, h); } public void onDrawFrame(GL10 gl) { /* 清除顏色buffer */ gl.glClearColor(mRed, mGreen, mBlue, 1.0f); gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); } public void setColor(float r, float g, float b) { mRed = r; mGreen = g; mBlue = b; } } class ClearGLSurfaceView extends GLSurfaceView { private ClearRenderer mRenderer; public ClearGLSurfaceView(Context context) { super(context); mRenderer = new ClearRenderer(); setRenderer(mRenderer); } public boolean onTouchEvent(final MotionEvent event) { /* 啟動執行緒,並傳入座標設定顏色 */ queueEvent(new Runnable() { public void run() { mRenderer.setColor(event.getX() / getWidth(), event.getY() / getHeight(), 1.0f); }}); return true; } } }
SurfaceView 依照傳統的作法,要快速更新畫面或做遊戲之類的應用,通常會新增執行緒處理工作後,使用handler傳送訊息給View顯示畫面或是在非使用者執行緒呼叫View。
SurfaceView SurfaceView通常用於遊戲中或是需快速更新畫面之應用程式,藉此來加快程式畫面運行速度。SurfaceView也是View的一種,但有獨立buffer,稱為surface。 由於此buffer不需透過 Android framework做畫面更新,可直接對應到畫面上的區塊,提供更好的效能。
SurfaceView 但實際上還是間接更新到畫面上,但少了 Android framework 這一層,而且可透過硬體加速(加速的功能視平台而定),範例程式如圖所示。
SurfaceView 程式碼-1(SurfaceViewEX.java): 程式碼-2(SurfaceViewEX.java): SurfaceHolder holder; public AnimView(Context context) { super(context); /* 取得Holder */ holder = this.getHolder(); //holder /* 新增Callback */ holder.addCallback(this); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { /* 啟動執行緒繪圖 */ new Thread(new AnimThread(holder)).start(); } 完整程式碼請參考光碟中SurfaceViewEX.java public void run() { while( running ) { try { /* 鎖定畫布 */ canvas = holder.lockCanvas(null); /* 移動畫布 */ canvas.translate(dx, dy); paint = new Paint(); paint.setColor(Color.RED); /* 繪製畫布 */ canvas.drawColor(Color.BLACK); /* 於畫布繪製圖形 */ canvas.drawRect(new RectF( pLeft,pTop,pRight,pBottom ), paint); dx++; } catch(Exception ex) {} finally { /* 解鎖畫布 */ holder.unlockCanvasAndPost(canvas); } /* 移動邊界限制 */ if(width - 100 <= dx) running = false; } }
SurfaceView 在這個範例當中,需覆寫surfaceChanged方法,在此方法中啟動執行緒,使用translate方法,並設定條件,達成方塊移動的動畫效果。 藉此範例可以了解到有關SurfaceView中的標準用法,流程將於後面敘述說明。
SurfaceView流程 當程式需要更好的效能時,繼承SurfaceView也是一種方式。透過 SurfaceView,canvas不需透過onDraw()取得。 可以透過呼叫SurfaceView.getHolder()取得surface holder,而SurfaceHolder.lockCanvas()會傳回SkCanvas。 Native code 可以在這個 SkCanvas上作畫,然後呼叫 SurfaceHolder.unlockCanvasAndPost(),將內容更新到畫面上。
SurfaceView注意事項 每次更新畫面前,都要透過 SurfaceHolder.lockCanvas() 取得一個新的SkCanvas,在這個canvas上繪圖。 繪圖完成後都需呼叫SurfaceHolder.unlockCanvasAndPost() 進行更新,才能正確的更新畫面。
Matrix Android的2D繪圖功能其實是非常強大的,尤其是matrix。透過matrix,可以非常容易的控制Android繪圖座標的位移、旋轉、縮放…等等功能。範例程式執行結果如下頁圖所示。
Matrix 點選縮小按鈕可將旁邊圖示縮小;選放大按鈕則可將圖示放大,其它還有像setRotate、setSinCos、setScale、setTranslate…等等方法可以應用。