Transcript Document

Chapter 12
Android動畫程式設計
本投影片(下稱教用資源)僅授權給採用教用資源相關之旗標書籍為教科書之授課老師(下稱老師)專用,老師為教學使用之目的,得摘錄、編輯、重製教用資
源(但使用量不得超過各該教用資源內容之80%)以製作為輔助教學之教學投影片,並於授課時搭配旗標書籍公開播放,但不得為網際網路公開傳輸之遠距教學、
網路教學等之使用;除此之外,老師不得再授權予任何第三人使用,並不得將依此授權所製作之教學投影片之相關著作物移作他用。
著作權所有 © 旗標出版股份有限公司
前言
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):
public void onDraw(Canvas canvas)
{
/* 取得寬度 */
int px = getMeasuredWidth();
int py = getMeasuredWidth();
/* 設定線畫筆顏色 */
linePaint.setColor(Color.WHITE);
/* 設置為true可消除邊緣效果 */
linePaint.setAntiAlias(true);
/* 呼叫save方法儲存目前狀態 */
canvas.save();
........
/* 呼叫restore方法 */
canvas.restore();
/* 於畫布繪製方塊 */
canvas.drawRect(px, py, 300, 300,
rectPaint);
/* 設定背景畫筆顏色 */
backgroundPaint.setColor(Color.BLUE);
backgroundPaint.setAntiAlias(true);
/* 設定方塊畫筆顏色 */
rectPaint.setColor(Color.RED);
rectPaint.setAntiAlias(true);
/* 於畫布繪製圖形 */
canvas.drawRect(0, 0, px, py,
backgroundPaint);
由於程式碼過多,完整程式碼請參考光
碟中CanvasEX.java、DrawAction.java
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):
List<Point> points = new ArrayList<Point>();
Paint paint = new Paint();
public DrawAction(Context context)
{
super(context);
/* 設定可取得焦點 */
setFocusable(true);
/* 設定在觸控模式可取得焦點 */
setFocusableInTouchMode(true);
程式碼-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();
/* 設定監聽器 */
this.setOnTouchListener(this);
/* 新增點至Point物件 */
points.add(point);
/* 設定畫筆顏色 */
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
/* 用於更新View */
invalidate();
}
return true;
}
Paint
了解上述Canvas與Paint結合的範例後,接著
解說關於在Android中如何顯示字體,因為
字體在所有應用中是最常被使用到的。字體
一般擁有的屬性有大小、顏色、對齊方式、
粗體、斜體、下劃線等。
Paint
在Android中使用Typeface類別來定義字體。
Typeface可以指定字體和字體風格,並可用
Paint繪製字型,Typeface就類似Paint中的其
它屬性textSize,textSkewX,textScaleX...等
等。下表為字體常數的定義:
字體(Typeface)
DEFAULT
DEFAULT_BO
LD
MONOSPACE
SANS_SERIF
SERIF
Paint
這些字體常數,在應用程式中是可以直接使
用的,例如說:Typeface. SERIF。
另外Typeface也包含了一些用來處理字體的
方法,例如:建立字體Create(),取得字體
屬性getStyle()、isBold()、isItalic()…等等。
Typeface類別不僅定義了字體,還包括粗體
(Bold)、斜體(Italic)。
Paint
其它對顯示String有影響的因素,都可以在
Paint類別中找到它們的方法,如下頁表所示:
功能
字體
Paint中的相關操作
setTypeface(Typeface typeface)
getTypeface()
對齊方 setTextAlign(Paint.Align align)
式
Paint.Align getTextAlign()
字體大 getTextSize()
小
setTextSize(float textSize)
顏色
setColor(int color)
getColor()
下劃線 isUnderlineText()
setUnderlineText(boolean underlineText)
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;
/* 繪製預設字體 */
paint.setTypeface(null);
canvas.drawText("Default Font", 10, 100, paint);
/* 繪製自定義字體 */
paint.setTypeface(typeFace);
canvas.drawText("Custom Font", 10, 200, paint);
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);
}
動畫效果
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):
private OnClickListener listener = new OnClickListener()
{
public void onClick(View v)
{
/* 判斷點擊到哪個按鈕 */
switch(v.getId())
{
/* 點擊到上一張按鈕就呼叫showPrevious方法 */
case R.id.btnPrevious:
viewFlipper.showPrevious();
break;
public View addTextByText(String text)
{
/* 設定TextView屬性並回傳 */
TextView textView = new TextView(this);
textView.setText(text);
textView.setGravity(1);
return textView;
/* 點擊到下一張按鈕就呼叫showNext方法 */
case R.id.btnNext:
viewFlipper.showNext();
break;
}
}
public View addImageById(int id)
{
/* 設定ImageView屬性並回傳 */
ImageView imageView = new ImageView(this);
imageView.setImageResource(id);
return imageView;
}
}
};
完整程式碼請參考光碟中
ViewFlipperEX.java
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):
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();
}
}
完整程式碼請參考光碟中
ViewSwitcherEX.java
OpenGL
前言
在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 ClearGLSurfaceView extends GLSurfaceView
{
private ClearRenderer mRenderer;
class ClearRenderer implements GLSurfaceView.Renderer
{
private float mRed;
private float mGreen;
private float mBlue;
public ClearGLSurfaceView(Context context)
{
super(context);
mRenderer = new ClearRenderer();
setRenderer(mRenderer);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
}
public void onSurfaceChanged(GL10 gl, int w, int h)
{
/* 決定視窗上的繪圖區域 */
gl.glViewport(0, 0, w, h);
}
public boolean onTouchEvent(final MotionEvent event)
{
/* 啟動執行緒,並傳入座標設定顏色 */
queueEvent(new Runnable()
{
public void run()
{
mRenderer.setColor(event.getX() / getWidth(),
event.getY() / getHeight(), 1.0f);
}});
return true;
}
}
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;
}
}
}
SurfaceView
依照傳統的作法,要快速更新畫面或做遊戲
之類的應用,通常會新增執行緒處理工作後,
使用handler傳送訊息給View顯示畫面或是
在非使用者執行緒呼叫View。
SurfaceView
SurfaceView通常用於遊戲中或是需快速更
新畫面之應用程式,藉此來加快程式畫面運
行速度。SurfaceView也是View的一種,但
有獨立buffer,稱為surface。
由於此buffer不需透過 Android framework做
畫面更新,可直接對應到畫面上的區塊,提
供更好的效能。
SurfaceView
但實際上還是間接更新到畫面上,但少
了 Android framework 這一層,而且可透過
硬體加速(加速的功能視平台而定),範例程
式如圖所示。
SurfaceView
程式碼-1(SurfaceViewEX.java):
SurfaceHolder holder;
public AnimView(Context context)
{
super(context);
程式碼-2(SurfaceViewEX.java):
public void run()
{
while( running )
{
try
{
/* 鎖定畫布 */
canvas = holder.lockCanvas(null);
/* 取得Holder */
holder = this.getHolder(); //holder
/* 移動畫布 */
canvas.translate(dx, dy);
paint = new Paint();
paint.setColor(Color.RED);
/* 新增Callback */
holder.addCallback(this);
}
/* 繪製畫布 */
canvas.drawColor(Color.BLACK);
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int
height)
{
/* 啟動執行緒繪圖 */
new Thread(new AnimThread(holder)).start();
}
/* 於畫布繪製圖形 */
canvas.drawRect(new RectF( pLeft,pTop,pRight,pBottom ), paint);
dx++;
}
catch(Exception ex) {}
finally
{
/* 解鎖畫布 */
holder.unlockCanvasAndPost(canvas);
}
完整程式碼請參考光碟中
SurfaceViewEX.java
/* 移動邊界限制 */
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…等等方
法可以應用。
Matrix
布局文件(res/layout/main.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:id="@+id/btnSmall"
android:layout_width="90px"
android:layout_height="60px"
android:text="縮小"
android:textSize="18sp"
/>
<Button
android:id="@+id/btnBig"
android:layout_width="90px"
android:layout_height="60px"
android:text="放大"
android:textSize="18sp"
/>
<ImageView
android:id="@+id/imageView"
android:src="@drawable/icon"
android:layout_width="90px"
android:layout_height="60px"
/>
</LinearLayout>
Matrix
程式碼(MatrixEX.java):
/* 取得bitmap寬高 */
int bmpWidth = bitmap.getWidth();
int bmpHeight = bitmap.getHeight();
/* 設定縮小比例 */
double scale = 0.7;
id++;
ImageView imageView = new ImageView(MatrixEX.this);
imageView.setId(id);
imageView.setImageBitmap(resizeBmp);
linearLayout.addView(imageView);
setContentView(linearLayout);
scaleWidth = (float)(scaleWidth * scale);
scaleHeight = (float)(scaleHeight * scale);
Matrix matrix = new Matrix();
/* 縮放圖片 */
matrix.postScale(scaleWidth, scaleHeight);
/* 重新縮放Bitmap */
Bitmap resizeBmp = Bitmap.createBitmap(bitmap, 0, 0, bmpWidth,
bmpHeight, matrix, true);
if(id == 0)
{
/* 移除View */
linearLayout.removeView(imageView);
}
else
{
linearLayout.removeView((ImageView)findViewById(id));
}
完整程式碼請參考光碟中
MatrixEX.java