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