Android 是一個多功多執行的作業系統,雖然可以多執行緒
但是遵循著mvc模式 ,所以當非UI的執行序,嘗試去改變ui介面的程式就會出現錯誤而關閉程式
例如
寫了一個遊戲,遊戲的核心是一個執行緒,定期需要計算遊戲的內容,且需要定時的更新畫面,然而跟跟新畫面,需要呼叫 invalidate 這個方法,偏偏這個方法使會變更介面,所以android只好把你強迫關閉程式。告訴你Only the original thread that created a view hierarchy can touch its views
最簡單的方法就是不要使用 invalidate 改用 postInvalidate();
但只是剛好invalidate 有 postInvalidate(); 可以幫助更新畫面,但是如果要更變tTextBox內容,或是 setContentView(view); 那可就沒那麼幸運了。
事實上這樣龜毛的規則並不是Android訂製的,c#的開發使用者也是有著相同的困腦,
但在c# 會使用委派來將 要改變介面的事情 透過委派 交給 UI的執行序去執行。
然而在Android 使用了比較值覺得寫法
runOnUiThread(new Runnable(){
public void run() {
//要改變介面的程式寫在這裡
}});
這樣android就會裡面的程式碼,改由ui的執行序來執行,而不會出錯了,
所以類推本來寫postInvalidate(); 可以改寫成
runOnUiThread(new Runnable(){
public void run() {
invalidate
}});
通常錯誤如下:
06-23 15:47:40.123: ERROR/AndroidRuntime(393): Uncaught handler: thread Thread-10 exiting due to uncaught exception
06-23 15:47:40.135: ERROR/AndroidRuntime(393): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
2010年6月23日 星期三
2010年6月19日 星期六
Android 多點觸控
Android 的多點觸控以現在的api來看 最多3點觸控
於程式中加入
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
即可抓取觸控的座標以及事件
x[0] = (int) event.getX(0); //單點觸控的座標
y[0] = (int) event.getY(0);
x[1] = (int) event.getX(1); //第二點觸控的座標
y[1] = (int) event.getY(1);
x[2] = (int) event.getX(2);//第三點觸控的座標
y[2] = (int) event.getY(2);
可惜的是我拿得是 DESIRE 最多兩點觸控,第三點是沒有作用的,且有交叉誤判的現象
抓取事件
if (event.getAction() == MotionEvent.ACTION_MOVE )
{
這個事件在模擬與實機有很大的出入,模擬器只會移動一下觸發一次,而實機摸下去之後不管有沒有移動 都會瘋狂的觸發。
}
if (event.getAction() == MotionEvent.ACTION_DOWN )
{
指尖按下
}
if (event.getAction() == MotionEvent.ACTION_OUTSIDE )
if (event.getAction() == MotionEvent.ACTION_POINTER_1_DOWN )
基本上 如果使用了 MotionEvent.ACTION_DOWN 這個事件永遠不會觸發
if (event.getAction() == MotionEvent.ACTION_POINTER_2_DOWN )
第二點按下
if (event.getAction() == MotionEvent.ACTION_POINTER_3_DOWN )
{
第三點按下
}
if (event.getAction() == MotionEvent.ACTION_POINTER_3_UP )
{
第三點放開 兩點觸控的 也不會產生這個事件
}
好了看完很多疑問,為什麼point 1 23 的 move
兩點原因
1.不管是哪個點移動 都是觸發 move 旗標
2.按下去之後就瘋狂觸發所以乾脆三點都檢查
move up 1 2
要由一系列動作解釋
按下第一鍵
觸發 down
按下第二鍵
觸發 point 2 down
放開第二鍵 觸發
觸發 point 2 up
放開第一鍵 觸發 up (注意不是point 1 up)
再次實驗
按下第一鍵
觸發 down
按下第二鍵
觸發 point 2 down
放開第一鍵 觸發
觸發 point 1 up
這時候 本來的point 2 變成point 1 座標 也變成由point 1取得雖然他應該是第二鍵
放開第一鍵 觸發 up (注意不是point 2 up)
於程式中加入
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
即可抓取觸控的座標以及事件
x[0] = (int) event.getX(0); //單點觸控的座標
y[0] = (int) event.getY(0);
x[1] = (int) event.getX(1); //第二點觸控的座標
y[1] = (int) event.getY(1);
x[2] = (int) event.getX(2);//第三點觸控的座標
y[2] = (int) event.getY(2);
可惜的是我拿得是 DESIRE 最多兩點觸控,第三點是沒有作用的,且有交叉誤判的現象
抓取事件
if (event.getAction() == MotionEvent.ACTION_MOVE )
{
這個事件在模擬與實機有很大的出入,模擬器只會移動一下觸發一次,而實機摸下去之後不管有沒有移動 都會瘋狂的觸發。
}
if (event.getAction() == MotionEvent.ACTION_DOWN )
{
指尖按下
}
if (event.getAction() == MotionEvent.ACTION_OUTSIDE )
if (event.getAction() == MotionEvent.ACTION_POINTER_1_DOWN )
基本上 如果使用了 MotionEvent.ACTION_DOWN 這個事件永遠不會觸發
if (event.getAction() == MotionEvent.ACTION_POINTER_2_DOWN )
第二點按下
if (event.getAction() == MotionEvent.ACTION_POINTER_3_DOWN )
{
第三點按下
}
if (event.getAction() == MotionEvent.ACTION_POINTER_3_UP )
{
第三點放開 兩點觸控的 也不會產生這個事件
}
好了看完很多疑問,為什麼point 1 23 的 move
兩點原因
1.不管是哪個點移動 都是觸發 move 旗標
2.按下去之後就瘋狂觸發所以乾脆三點都檢查
move up 1 2
要由一系列動作解釋
按下第一鍵
觸發 down
按下第二鍵
觸發 point 2 down
放開第二鍵 觸發
觸發 point 2 up
放開第一鍵 觸發 up (注意不是point 1 up)
再次實驗
按下第一鍵
觸發 down
按下第二鍵
觸發 point 2 down
放開第一鍵 觸發
觸發 point 1 up
這時候 本來的point 2 變成point 1 座標 也變成由point 1取得雖然他應該是第二鍵
放開第一鍵 觸發 up (注意不是point 2 up)
Android 常用小技巧
//全螢幕
貼再 Activity 的onCreate
requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN , WindowManager.LayoutParams.FLAG_FULLSCREEN);
//強迫橫銀幕
在AndroidManifest.xml 中的 的 activity 屬性宣告
軟貓玩具
http://www.blogger.com/
//開啟使用網路權限
在AndroidManifest.xml 中的加入
//不讓手機自動進入休眠,持續背光
1.先在
在AndroidManifest.xml 中的加入
2.在程式中宣告
private PowerManager.WakeLock mWakeLock = mPowerManager.newWakeLock(mPowerManager.SCREEN_BRIGHT_WAKE_LOCK, "BackLight");
啟動持續背光模式
mWakeLock.acquire(); 通常加在 protected void onResume() 要不然
離開程式返回的時候效果就消失了
關閉持續背光
mWakeLock.release(); 通常加在 protected void onPause()
否則程式離開程式之後手機一直耗電
//使用GPS
在AndroidManifest.xml 中的加入
程式中宣告
LocationManager locationManger = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
註冊gps 觸發事件為 LocationListener
locationManger.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, LocationListener); //1000ms 跟新一次 時間越短越耗電
private final LocationListener LocationListener = new LocationListener()
{
@Override
public void onLocationChanged(Location location)
//GPS移動的時候
@Override
public void onProviderDisabled(String provider)
@Override
public void onProviderEnabled(String provider)
@Override
public void onStatusChanged(String provider, int status, Bundle extras)
//gps定位完成沒偵測
};
貼再 Activity 的onCreate
requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN , WindowManager.LayoutParams.FLAG_FULLSCREEN);
//強迫橫銀幕
在AndroidManifest.xml 中的
軟貓玩具
http://www.blogger.com/
//開啟使用網路權限
在AndroidManifest.xml 中的
//不讓手機自動進入休眠,持續背光
1.先在
在AndroidManifest.xml 中的
2.在程式中宣告
private PowerManager.WakeLock mWakeLock = mPowerManager.newWakeLock(mPowerManager.SCREEN_BRIGHT_WAKE_LOCK, "BackLight");
啟動持續背光模式
mWakeLock.acquire(); 通常加在 protected void onResume() 要不然
離開程式返回的時候效果就消失了
關閉持續背光
mWakeLock.release(); 通常加在 protected void onPause()
否則程式離開程式之後手機一直耗電
//使用GPS
在AndroidManifest.xml 中的
程式中宣告
LocationManager locationManger = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
註冊gps 觸發事件為 LocationListener
locationManger.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, LocationListener); //1000ms 跟新一次 時間越短越耗電
private final LocationListener LocationListener = new LocationListener()
{
@Override
public void onLocationChanged(Location location)
//GPS移動的時候
@Override
public void onProviderDisabled(String provider)
@Override
public void onProviderEnabled(String provider)
@Override
public void onStatusChanged(String provider, int status, Bundle extras)
//gps定位完成沒偵測
};
2010年3月29日 星期一
Android 貼圖教學,透明貼圖教學
遊戲設計最重要的一門課題,貼圖
觀看了月球登入的sample遊戲之後,發現月球登入使用了兩種方式的貼圖
貼圖之前必須要先將圖形放到資源檔裡,專案資料夾下方的res裡的drawable
到了2.0之後的版本,android會自動分出三個資料夾 drawable-h m L dpi 分別是讓開法者存放
不同解析度的圖形,因為現在手機的解析度已經亂的可以,低階手機到高階手機解析度都不一
該怎麼用我也還沒學到,實驗之後發現不管放在哪個資料夾都會自動更新到R.java裡面
關於該放什麼圖檔,還有透明的問題,Android 的貼圖看Sample裡並沒有特別寫到透明的程式碼
如果是Directx 的貼圖還需設定圖形透通顏色色定,不過在Android裡看來是不用寫任何程式碼,只要存放png圖檔,png圖檔本身就支援透明色,設定的透明色於android中的貼圖就自動的透明貼圖了,使用上還蠻方便的,不過這樣反而有個疑問是如果這張圖形是動態產生的,且想要透明怎麼辦
阿栽....... 有需要再來說。
先使用 jetboy裡的太空船,

把他copy到drawable-hdpi
第一種貼圖 宣告成
private Drawable PIC1;
取得資源檔圖形放入Drawable中
PIC1 = this.getResources().getDrawable(R.drawable.ship);
貼圖的方式
PIC1.setBounds(左x, 上y, 右x, 下y); //設定貼出範圍, 自動縮放圖形寬與高
PIC1.draw(canvas); //將圖形貼於canvas 畫布上
第二種貼圖方式
宣告成Bitmap 格式
private Bitmap PICBitmap;
取得資源檔圖形,轉換成Bitmap
PICBitmap = BitmapFactory.decodeResource(this.getResources() ,
R.drawable.ship);
//於x,y 貼出 沒有使用縮放圖形,原圖貼出
canvas.drawBitmap(PICBitmap, x, y, null);
完整程式碼
package com.DrawTest;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
public class DrawTest extends Activity {
/** Called when the activity is first created. */
private Drawable PIC1;
private Bitmap PICBitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyView myview = new MyView(this);
setContentView(myview);
PIC1 = this.getResources().getDrawable(R.drawable.ship);
PICBitmap = BitmapFactory.decodeResource(this.getResources() ,
R.drawable.ship);
}
private class MyView extends View
{
public MyView(Context context)
{
super(context);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Style.FILL);
canvas.drawCircle(120, 40, 30, paint);
canvas.drawRect(90,170,150,200, paint);
PIC1.setBounds(10, 10, 100, 100);
PIC1.draw(canvas);
PIC1.setBounds(120, 120, 200, 200);
PIC1.draw(canvas);
canvas.drawBitmap(PICBitmap, 0, 200, null);
}
}
}
附上小技巧 去掉標題bar的寫法圖片 與飛行船的貼圖測試
2010年3月25日 星期四
Android 繪圖
package Com.DrawTest;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.os.Bundle;
import android.view.View;
public class DrawTest extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyView myview = new MyView(this);
setContentView(myview);
}
private class MyView extends View
{
public MyView(Context context)
{
super(context);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Style.FILL);
canvas.drawCircle(120, 40, 30, paint);
canvas.drawRect(90,170,150,200, paint);
//canvas.drawBitmap(bitmap, matrix, paint)
}
}
}
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.os.Bundle;
import android.view.View;
public class DrawTest extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyView myview = new MyView(this);
setContentView(myview);
}
private class MyView extends View
{
public MyView(Context context)
{
super(context);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Style.FILL);
canvas.drawCircle(120, 40, 30, paint);
canvas.drawRect(90,170,150,200, paint);
//canvas.drawBitmap(bitmap, matrix, paint)
}
}
}
2010年3月24日 星期三
Android Socket教學
Android是一隻網路功能強大的手機作業系統,網路的撰寫不算困難也很好玩
特別是連線對戰的遊戲,一群朋友一起拿著自己的手機和對方廝殺,這個畫面一定很有趣
今天先來解說一寫我在學習撰寫網路連線的時候遇到的一些困難和解決方法
介紹伺服端ServerSocket 物件
網路的連線一定要有一端當作主機(Server),一端當作客戶 (Client),Android裡將這兩種
連線方式分成兩種物件,分別是ServerSocket 和 Socket , 如果要開發主機端就使用ServerSocket物件, 開發客戶端就用Socket.
通常伺服端的撰寫流程都是 宣告ServerSocket物件->實體化並且傳入通訊Port->Accept等待連線->產生新的執行續,將Socket傳給新的執行續-> 回到Accept等待
ServerSocket主要功能就是聆聽Client的要求,並且產生新的socket和Clinet連線, ServerSocket的工作就告一段落,重新再度回到等待,至於和Clinet之前的傳送接收則是由新的新的執行續,和client去溝通。
宣告物件實體化如下
import java.net.ServerSocket;
public static final int SERVERPORT = 7777;
ServerSocket serverSocket = new ServerSocket(SERVERPORT);
會發這樣寫,於執行階段會產生一個權限問題,
Permission denied (maybe missing INTERNET permission)
只要在AndroidManifest.xml 裡設定新增 uses-permission 名字為 android.permission.INTERNET
或是於xml裡直接輸入
即可。
接下來
Accept等待連線->產生新的執行續,將Socket傳給新的執行續
Socket NewSocket = serverSocket.accept();
serverSocket.accept();會將程式阻塞,程式會停止在這裡不動,所以最好在使用serverSocket.accept()是一個不同於main的執行序,程式會一直等到有使用者者嘗試連線
程式才會繼續往下跑,並且產生一個NewSocket物件與 使用者進行互動連線
這時候如果還有其他使用者來要求連線,程式沒有辦法接受,要接受使用者要求連線必須讓程式
停在serverSocket.accept(); , 所以不建議accept之後就開始接收資料與回應資料,應立即產生新的執行序 並將NewSocket 交付出去,讓新的執行序執行傳送接收工作,並立刻回到serverSocket.accept();繼續等待連線,才可以服務多數使用者。
弄到這裡我們應該可以連線到android ,如果是實體手機是可以的,但如果是模擬器
需要如下動作
1.先啟動模擬器
2.再開啟 windows的CMD 執行 telnet 127.0.0.1 5554
5554代表模擬器的port
3.輸入
redir add tcp:7777:7777
打完之後 顯示ok表示成功。

這麼做的意思是,這台電腦接收到7777 port的資料請轉給 模擬器7777 裡面
例如再ie瀏覽器上 http://127.0.0.1:7777 使用debug就可以看到資訊已經送到模擬器裡
關於傳送接收,下回繼續。
特別是連線對戰的遊戲,一群朋友一起拿著自己的手機和對方廝殺,這個畫面一定很有趣
今天先來解說一寫我在學習撰寫網路連線的時候遇到的一些困難和解決方法
介紹伺服端ServerSocket 物件
網路的連線一定要有一端當作主機(Server),一端當作客戶 (Client),Android裡將這兩種
連線方式分成兩種物件,分別是ServerSocket 和 Socket , 如果要開發主機端就使用ServerSocket物件, 開發客戶端就用Socket.
通常伺服端的撰寫流程都是 宣告ServerSocket物件->實體化並且傳入通訊Port->Accept等待連線->產生新的執行續,將Socket傳給新的執行續-> 回到Accept等待
ServerSocket主要功能就是聆聽Client的要求,並且產生新的socket和Clinet連線, ServerSocket的工作就告一段落,重新再度回到等待,至於和Clinet之前的傳送接收則是由新的新的執行續,和client去溝通。
宣告物件實體化如下
import java.net.ServerSocket;
public static final int SERVERPORT = 7777;
ServerSocket serverSocket = new ServerSocket(SERVERPORT);
會發這樣寫,於執行階段會產生一個權限問題,
Permission denied (maybe missing INTERNET permission)
只要在AndroidManifest.xml 裡設定新增 uses-permission 名字為 android.permission.INTERNET
或是於xml裡直接輸入
即可。
接下來
Accept等待連線->產生新的執行續,將Socket傳給新的執行續
Socket NewSocket = serverSocket.accept();
serverSocket.accept();會將程式阻塞,程式會停止在這裡不動,所以最好在使用serverSocket.accept()是一個不同於main的執行序,程式會一直等到有使用者者嘗試連線
程式才會繼續往下跑,並且產生一個NewSocket物件與 使用者進行互動連線
這時候如果還有其他使用者來要求連線,程式沒有辦法接受,要接受使用者要求連線必須讓程式
停在serverSocket.accept(); , 所以不建議accept之後就開始接收資料與回應資料,應立即產生新的執行序 並將NewSocket 交付出去,讓新的執行序執行傳送接收工作,並立刻回到serverSocket.accept();繼續等待連線,才可以服務多數使用者。
弄到這裡我們應該可以連線到android ,如果是實體手機是可以的,但如果是模擬器
需要如下動作
1.先啟動模擬器
2.再開啟 windows的CMD 執行 telnet 127.0.0.1 5554
5554代表模擬器的port
3.輸入
redir add tcp:7777:7777
打完之後 顯示ok表示成功。
這麼做的意思是,這台電腦接收到7777 port的資料請轉給 模擬器7777 裡面
例如再ie瀏覽器上 http://127.0.0.1:7777 使用debug就可以看到資訊已經送到模擬器裡
關於傳送接收,下回繼續。
2010年3月17日 星期三
MAC電腦視窗畫面截取,MAC畫面拍照
windows下想要截取視窗(拍照)只要按下print screen鍵就可以
但是在MAC下要拍螢幕上的畫面使用 Command + Shift + 4 三個按鈕
這時候滑鼠就會變成坐標的方式可以圈選所要拍照的範圍
如果要像windows那樣可以對單一視窗拍照,只要按下空白鍵
就可以對單一視窗拍照。
只是問題來了,畫面拍完了,照片在哪裡阿?
MAC的小畫家在哪裡呢?
這時候需要一套繪圖軟體,推薦GIMP
他是一套和photoshop可批敵的軟體,重點是他是免費且跨平台的繪圖軟體
不過下載的過程當中呢喃一下
windows xp 跳過vista 失敗的其中一項原因就是軟體支援度的問題
很多軟體在vista下沒有辦法執行,
但是在蘋果這樣的問題似乎是不被重視的,apple喜歡自斷筋骨
我改了作業系統,之前的程式幾乎都不能相容,強迫所有軟體廠商重新撰寫
其實這是很糟糕的現象.......
所以GIMP也要特地為 10.6 出一個版本,還好有出,要不然我也用不到這套軟體
抓以前的版本是沒有辦法相容的。
安裝完成GIMP就可以將拍照的話面貼到gimp中,然後存檔。
訂閱:
文章 (Atom)
