2012年4月10日 星期二

[TQC+ Android] 3-9 畫布程式 Use Custom SurfaceView





設計一個全螢幕且背景為黑色的畫布程式 , 支援手指單點觸控 , 於手指按下螢幕時捕捉該觸控點的座標 , 以該觸控點座標為基準點繪製一個10*10的白色實心矩形 , 且每一次繪製時皆會清除前一次遺留的矩形 , 這題會使用自訂的SurfaceView , 並且實作處理手勢變化的 OnGestureListener 及實作一個Runnable 讓 non-UI Thread 幫我們處理Canvas的更新。


package COM.TQC.GDD03;

import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SurfaceHolder;


public class GDD03 extends Activity
{
  public static SView mSurfaceView01;
  public static SurfaceHolder mSurfaceHolder01;
  public GestureDetector detector;  
   

  /** Called when the activity is first created. */
  
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);  
    
    mSurfaceView01 = (SView) this.findViewById(R.id.surfaceView1);
    //從xml檔中取用我們的 CustomView
    
    detector = new GestureDetector(this,mSurfaceView01); 
    //GestureDetector 能幫我們監聽觸控螢幕上的手勢變化
    //它需要一個實作好的 GestureDetector.OnGestureListener interface
    //而這個例子中我們已經傳入 mSurfaceView01 , 因為它實作了 GestureDetector.OnGestureListener
   
  }  
  
  @Override
  public boolean onTouchEvent(MotionEvent event)
  {
 // TODO Auto-generated method stub
   
 //return super.onTouchEvent(event);
 //這是原本method 預設的回傳值 , 在這個例子使用 detector.onTouchEvent(event) 取代
    
 return detector.onTouchEvent(event);
 //GestureDetector 會根據傳入的MotionEvent 判斷手勢的變化 
 //另外它只會判斷Touch events 所以我們將Touch screen傳入的 events 
 //送到 GestureDetector class 的 onTouchEvent(event) , 讓它判斷是哪一種手勢
 //此外GestureDetector 不接受 trackball events  
 
  }
}


在這個class中先宣告Custom SurfaceView : mSurfaceView01 , mSurfaceView01 已經獨立出來存在另一個檔案 SView.java , 另外再使用 GestureDetector 替 mSurfaceView01 註冊監聽事件 , 讓 mSurfaceView01 可以根據手勢變化回應我們想要的動作。


當我們收到 Touch Screen 所傳入的Motion Events , 再將Events 傳入GestureDetector class 中的 onTouchEvent(event) method 去進一步的判斷是哪種手勢 , 再回應相對應的動作。



package COM.TQC.GDD03;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.GestureDetector.OnGestureListener;


public class SView extends SurfaceView implements SurfaceHolder.Callback , OnGestureListener , Runnable 
{ 
 SurfaceHolder mSurfaceHolder01;
 Canvas mCanvas;
 
 //我們已經在xml中定義了自訂的 SurfaceView , 所以系統會選用這個建構子去產生instance
 public SView(Context context, AttributeSet attrs)
 {
  // TODO Auto-generated constructor stub
  
  super(context,attrs);  
  
  mSurfaceHolder01 = this.getHolder();
  //獲取holder , 用來存取及控制 SurfaceView
  
  mSurfaceHolder01.addCallback(this);
  //監聽SurfaceHolder的事件
  
  setFocusable(true);
 } 

 @Override
 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
 {
  // TODO Auto-generated method stub  
 }

 @Override
 public void surfaceCreated(SurfaceHolder arg0)
 {
  // TODO Auto-generated method stub 
  
  new Thread(this).start();
  //當Surface 被建立時 , 產生一個Thread來處理畫布內容的改變
 }

 @Override
 public void surfaceDestroyed(SurfaceHolder arg0)
 {
  // TODO Auto-generated method stub
  
  ThreadFlag = false;
  //終止Thread 的運作  
 }
 
 @Override
 public void onDraw(Canvas canvas)
 {   
  // onDraw() method 已經有 SurfaceHolder.lockCanvas() 的效果了
  // 我們也不用額外的對canvas 執行unlock 的動作
  
  canvas.drawColor(Color.BLACK);
  //把畫布填充為黑色    
  Paint p = new Paint();
  p.setColor(Color.WHITE);
       
  if(DrawFlag) canvas.drawRect(x-5, y-5, x+5, y+5, p);
  // 畫一個矩形  
     
  DrawFlag = false;
  //關閉更新畫布開關 , 直到下一次的Touch 事件發生
 }

 float x , y;
 boolean ThreadFlag = true , DrawFlag = false;

 @Override
 public boolean onDown(MotionEvent arg0)
 {
  // TODO Auto-generated method stub
  
  x = arg0.getRawX();
  y = arg0.getRawY();
  //獲取觸控的座標
  
  DrawFlag = true;
  //開啟更新畫布的開關 
  
  return true;
 }
 
 @Override
 public void run()
 {
  // TODO Auto-generated method stub
  
  while(ThreadFlag)
  {
   if(DrawFlag) this.postInvalidate();
   //由於我們是在non-UI Thread中要求要更新畫布
   //所以使用 postInvalidate() method
  }
 }  

 @Override
 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
   float velocityY)
 {
  // TODO Auto-generated method stub
  return false;
 }
 @Override
 public void onLongPress(MotionEvent e)
 {
  // TODO Auto-generated method stub  
 }
 @Override
 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
   float distanceY)
 {
  // TODO Auto-generated method stub
  return false;
 }
 @Override
 public void onShowPress(MotionEvent e)
 {
  // TODO Auto-generated method stub  
 }
 @Override
 public boolean onSingleTapUp(MotionEvent e)
 {
  // TODO Auto-generated method stub  
  Log.d("Test", "onSingleTapUp");
  return true;
 }
}


至於我們自訂的SurfaceView 它是如何運作的? 我們會在Surface Created 的時候開始run一個non-UI Thread , 這個Thread 專門負責去呼叫 postInvalidate() method 來更新Canvas , 此外postInvalidate() method 會去執行 onDraw() method , 所以我們將畫布要呈現的內容都寫在onDraw() 中等著被執行 , 此外使用一個flag來控制畫布更新的時機 (是在觸控到螢幕時) , 此外當Surface Destroyed 的時候終止Thread 的運作。




接下來是 main.xml的部分 , 請注意我們是使用自訂的SurfaceView
    










P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....







沒有留言:

張貼留言

Google Analytics