2010年11月23日 星期二

[Android] 如何使用Google Map 2.0 & 3.0



接續之前的文章 [Android Tips] 如何取得手機的Cell ID 和 Location Area Code , 在我們獲得Cell ID 和 Location Area Code 之後 , 轉譯成我們要的地理座標呢? 這時候需要連上Google Map API接收他轉譯好的經緯度資訊 , 網址為: http://www.google.com/glm/mmap 點進去看到錯誤訊息不要以為他壞掉了 , 其實關於如何夠過上面的網址把Cell ID 和 Location Area Code轉譯成經緯度的資訊  Wei-Meng Lee poor man 的文章說得很清處 , 流程大概就是連接上網頁POST丟入參數然後解析網頁回傳的資訊。


在成功的獲得經緯度資訊後 , 就可以拿他們去呼叫Google Map定位了 , 在Google Map 3.0推出之後 , 官網呼籲開發者把2.0的版本改成3.0的 , 3.0 載入速度更快 , 尤其是在行動裝置的瀏覽器上(但是我用模擬器測試反應倒是有點慢....)


其實這次3.0的作法是希望把API放在伺服器上(有別於2.0的原生API) , 這樣開發者就不必因為Map版本得更心要手動去更新Code , 所以Google想要開發者直接把Google Map的網頁嵌入在程式內 , 所以!!! 到了3.0的版本 , 裝載Google Map的容器換成是WebView了(有別於2.0使用MapView) , 而他們的官方網頁有簡單的教學 。


接下來是2.0的部分 , 其實也不用介紹太多 , 很多網站和書籍都有範例 , 像Google 的Andorid 開發者網站就有這兩篇文章Google Map View 和 Location and Maps , Google Map API 2.0 是需要申請金鑰的 , 關於這個步驟這個網站說明的很詳細




相關文章:

2010年11月22日 星期一

[Android] 如何取得手機的Cell ID 和 Location Area Code



甚麼是Cell ID 和 Location Area Code , 他們的用途是甚麼? 這篇文章說得很清楚了, 那在Android手機上該如何取得呢?其實步驟蠻簡單的 , 只要透過TelephonyManager即可 , 不過TelephonyManager要取得Instance , 一定要透過Context.getSystemService(Context.TELEPHONY_SERVICE).以下是簡單的代碼


if(TM.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM)
        {
         GCLocation = (GsmCellLocation) TM.getCellLocation();
         if (GCLocation != null)
         {
          int CellID = GCLocation.getCid();
                        int LAC = GCLocation.getLac();
                        Log.d("CellID",String.valueOf(CellID));
                        Log.d("LAC",String.valueOf(LAC));
                }
         else Log.d("無法獲得 GsmCellLocation 物件","");
        }
        else Log.d("這不是GSM手機","");


另外在AndoridManifest.xml中要加入一些權限


    
        
            
                
                
            
        
        
    
    
 


不過在模擬器上測試的時候回傳的 GCLocation 是 NULL , 畢竟是模擬器....
而這個opencillid網站提供了不少免費的資料 , 有了這些資料之後就算沒有GPS也可以定位了!!!



相關文章:
[Android Tips] 如何使用Google Map 2.0 & 3.0
[Android] 如何藉由Cell ID 和 Locatin Area Code 取得經緯度座標 (by Geolocation API)

2010年11月20日 星期六

[Android] 如何開啟Camera拍照 (Camera Capture)



這陣子在研究Camera錄影 , 但是網路上很多文章說模擬器似乎不能成功錄影 , 錄音倒是可以 , 所以退而求其次使用Camera來拍照 , 模擬器真的是一個模擬器....不能完全模擬出實機的情況 , 如果把code轉到實機上去跑 , 應該有很多要Debug的東西....下面是Layout佈局 , 沒啥特別的



   
   






如果要開啟Camera鏡頭的話 , 需要在AndroidManifest.xml中註冊屬性

    
        
            
                
                
            
        
    
  
 
  
 


接下來是程式碼的部分
package com.android;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.widget.Button;

public class Camera_Capture extends Activity implements Button.OnClickListener , SurfaceHolder.Callback , Camera.ShutterCallback
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);       
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        //這行程式碼也可以隱藏Title Bar但是位置要放對
        setContentView(R.layout.main);
        
        this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        //強制讓螢幕的內容呈現橫向
        
        SurfaceView SV = (SurfaceView) findViewById(R.id.SurfaceView);
        surfaceHolder = SV.getHolder();
        surfaceHolder.addCallback(this);
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        
        captureButton = (Button) findViewById(R.id.captureButton);
        captureButton.setOnClickListener(this);
        
        TempFile = this.getFilesDir();
    }
    
    Button captureButton;    
    SurfaceHolder surfaceHolder;    
    
 Camera mCamera;
 Parameters mParameters;
 
 File TempFile;
 
 Camera.PictureCallback TakeJpeg = new Camera.PictureCallback()
 {
  @Override
  public void onPictureTaken(byte[] data, Camera camera)
  {
   // TODO Auto-generated method stub
   
            try {              
                 Bitmap mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);  
           File temp = File.createTempFile("PhotoTemp", ".jpg", TempFile);
           //在模擬器中開一個暫存檔 路徑為 //data/data/YourPackageName/files/
           //在DDMS視窗中可以看到
           
           BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(temp));  
                 mBitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos);  
                 //採用壓縮轉檔方法
                 //format 只有JPEG , PNG 兩種
                 //quality Hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning compress for max quality. Some formats, like PNG which is lossless, will ignore the quality setting
                 //stream The outputstream to write the compressed data.
                                
                 bos.flush();
                 bos.close();
   } catch (IOException e)
   {    
    e.printStackTrace();
   }   
   mCamera.startPreview();
   //由於拍照完後 , PreView會停止
   //故重新啟動PreView
   
  }  
 };
 
 Camera.PictureCallback TakeRAW = new Camera.PictureCallback()
 {
  @Override
  public void onPictureTaken(byte[] data, Camera camera)
  {
   //raw: the callback for raw (uncompressed) image data, or null
   Log.d("onTakeRAW",""+data);
  }  
 };
 
 Camera.PictureCallback PostView = new Camera.PictureCallback()
 {
  @Override
  public void onPictureTaken(byte[] data, Camera camera)
  {
   //postview: callback with postview image data, may be null
   Log.d("onPostView",""+data);
  }  
 };
 
 @Override
 public void onShutter()
 {
  //Called as near as possible to the moment when a photo is captured from the sensor. 
  //This is a good opportunity to play a shutter sound or give other feedback of camera operation. 
  //This may be some time after the photo was triggered, but some time before the actual data is available.
     Log.d("onShutter()","onShutter()");
 }

 @Override
 public void onClick(View v)
 {  
  switch(v.getId())
  {
    case R.id.captureButton:
    mCamera.takePicture(this, TakeRAW, PostView ,TakeJpeg);  
    break;
  }
 }
 
 @Override
 public void surfaceCreated(SurfaceHolder holder)
 {  
  mCamera = Camera.open();
  
  try {            
            mParameters = mCamera.getParameters();
            mParameters.setPictureFormat(PixelFormat.JPEG);
            //mParameters.setPreviewSize(320, 480);
            //設定螢幕Preview大小 , 對模擬器拍出的圖片沒影響
            //mParameters.setPictureSize(320, 480);
            //設定圖片解析度大小
            mCamera.setParameters(mParameters);
            mCamera.setPreviewDisplay(holder);

        } catch (IOException exception)
        {
            mCamera.release();
            mCamera = null;            
        }
        mCamera.startPreview();
        
 }

 @Override
 public void surfaceChanged(SurfaceHolder holder, int format, int width,
   int height)
 {
  // TODO Auto-generated method stub
 } 

 @Override
 public void surfaceDestroyed(SurfaceHolder holder)
 {  
  mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
 }
}


關於takePicture的函式說明 , 有三個PictureCallback的物件當作參數 , 然後針對這三個物件實作程式內容 , 不知道是否在同一個PictureCallback就能統一處理


以下是畫面截圖





然後不管你怎麼拍照 , 結果永遠是下面那張圖....







2010年11月16日 星期二

[Android] 關於螢幕方向翻轉 screenOrientation



通常在螢幕方向改變的時候 , 我們的Layout佈局應該也要跟著改變 , 才能讓使用者有良好的使用體驗和環境
所以通常我們會built兩個不同的Layout檔案因應螢幕方向的改變 , 而這篇文章要提的就是當使用者更改螢幕方向的時候時(橫轉直 , 直轉橫) , 該怎麼載入適當的Layout佈局?




Android 很貼心的幫開發者設計一個方法 , 在原本的res/資料夾下再建立兩個資料夾 , 名稱分別是layout-land , layout-port , 這原理和drawable-hdpi , drawable-ldpi....是一樣的 , 而layout-land裡面放置橫螢幕的Layout檔 , layout-port裡面放置直螢幕的Layout檔 , 當我們進入模擬器的時候 , 他就會根據螢幕的方向自動載入合適的Layout檔案 , 另外在模擬器中翻轉螢幕的方法是按下ctrl + F12 , 另外這邊有一個方法可以設定螢幕的方向






關於android:screenOrientation的屬性Android開發網站這邊有提到
但如果我們想根據螢幕的方向不同 , 而在onCreate的時候執行不同的程式碼該怎麼判斷呢? 這時候我們需要知道螢幕到底是直的還是橫的 , 這邊的一個小技巧是使用 this.getResources().getDisplayMetrics(); 來取得DisplayMetrics物件 , 比較螢幕長和寬來判斷螢幕方向 , 避免混淆 , 分別先把兩個layout資料夾移除 , 在原來的res/layout資料夾下 , 建立 landscape_main , portrait_main檔案 , 而程式碼如下

import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;

public class ScreenOrientationTest extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        
        dm = getResources().getDisplayMetrics();
        //取得螢幕顯示的資料
        ScreenWidth = dm.widthPixels;  
        ScreenHeight = dm.heightPixels; 
        //螢幕寬和高的Pixels
        
        if(ScreenHeight > ScreenWidth) setContentView(R.layout.portrait_main);
       
        else setContentView(R.layout.landscape_main);
    }
    
    DisplayMetrics dm;   
    int ScreenWidth , ScreenHeight;
}

這時候就可以根據螢幕方向的情況來執行不同的程式碼了




另外 , 根據Activity的life cycle的資料 , 當螢幕的方向改變和開關實體鍵盤時 , 都會重新執行onCreate()的部分 , 所以當onCreate()裡的程式碼需要initial許多物件的時候 , 翻轉螢幕的動作就會變得相當的耗費資源和拖累執行速度。

為了不讓每次的螢幕翻轉都重新跑一次onCreate() , 我們必須在AndroidManifest.xml裡activity屬性中做宣告 , 完整檔案如下


    
          
            
                
                
            
        
    
 

這麼一來當螢幕的方向改變或是開關實體鍵盤的時候 , activity就不會再執行onCreate()了 , 取而代之的是執行 public void onConfigurationChanged(Configuration newConfig) , 所以我們必須覆寫裡面的內容


import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.DisplayMetrics;

public class ScreenOrientationTest extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        
        dm = getResources().getDisplayMetrics();
        //取得螢幕顯示的資料
        ScreenWidth = dm.widthPixels;  
        ScreenHeight = dm.heightPixels; 
        //螢幕寬和高的Pixels
        
        if(ScreenHeight > ScreenWidth) setContentView(R.layout.portrait_main);
       
        else setContentView(R.layout.landscape_main);
    }
    
    DisplayMetrics dm;   
    int ScreenWidth , ScreenHeight;
    int oldOrientation = -1;
    //Configuration.ORIENTATION_LANDSCAPE Constant Value: 2
    //Configuration.ORIENTATION_PORTRAIT Constant Value: 1 
    
    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
         
     super.onConfigurationChanged(newConfig);
     
     //橫轉直會多跑一次 , 所以加上判斷過濾
     
     if(oldOrientation != newConfig.orientation)
     {
      if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
      {
       setContentView(R.layout.landscape_main);
      }             
         else
         {
          setContentView(R.layout.portrait_main);
         }
      oldOrientation = newConfig.orientation;
     }  
    }
}


接著就可以在判斷式內加上自己要的程式碼了!

[Android] 如何達到物件Overlay的效果並使用TouchMode拖移半透明的Button



如何讓兩個View達到Overlay的效果呢? 這篇要做的是讓一個Button overlay VideoView , 並且可以使用滑鼠移動Button(實機上是使用手指觸控移動) , 至於VideoView我們根據之前文章提到的方式來建構 , 

[Android] 如何使用VideoView播放影片 , 接下來我們要使用FrameLayout來達成物件Overlay的效果 , Layout如下












Layout檔案裡面使用了FrameLayout來使裡面的物件達到Overlay的效果 , 所以放了ViedoView和Button在裡面(擺放的順序是有關係的) , 但是只有如此的話會發現當我們移動Button走出一定範圍之後 , Button會消失 , 所以又放了一個LinearLayout , 再把Button擺了進去 , 想當然的LinearLayout必須是透明的 , 不然根本看不到VideoView的影像 , 所以在LinearLayout的屬性宣告上加了這行android:background="@+drawable/transparent_background , 所以我們必須事先宣告@+drawable/transparent_background , 他擺放的位置是在res/values/colors.xml 內容如下


    #e0000000
    
    #00000000
    


至於要移動Button , 要先實作OnTouchListener , 而且為了不要讓Button超出螢幕 , 我們還要取得顯示螢幕的長和寬的資料 , 程式碼如下

import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.MediaController;

public class CustomVideoViewTest extends Activity implements OnTouchListener
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
     super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        //隱藏 notification bar
        
        dm = getResources().getDisplayMetrics();
        //取得螢幕顯示的資料
        ScreenWidth = dm.widthPixels;  
        ScreenHeight = dm.heightPixels; 
        //螢幕寬和高的Pixels當作界線
        
        MoveButton = (Button) findViewById(R.id.Button);
        MoveButton.setOnTouchListener(this);
        
        CVView = (CustomVideoView)findViewById(R.id.VideoView);        
        MediaController controller = new MediaController(this); 
        CVView.setMediaController(controller); 
        //CVView.setVideoPath("android.resource://com.android/"+R.raw.video);
        //播放Local端的影音資源 , SD Card or res/raw 裡的影音檔
        CVView.setVideoURI(Uri.parse("http://www.dubblogs.cc:8751/Android/Test/Media/mp4/test.mp4"));
        //播放遠端的影音資源 , 影音串流
        CVView.requestFocus();
        CVView.start();
    }
    
    CustomVideoView CVView;
    DisplayMetrics dm;
    int lastX , lastY;
    int ScreenWidth , ScreenHeight;
    Button MoveButton;
    
    @Override
 public boolean onTouch(View view, MotionEvent event)
 {
  // TODO Auto-generated method stub
  
     int events = event.getAction();
     
     switch(events)
        {
         case MotionEvent.ACTION_DOWN:           
         
         lastX = (int)event.getRawX();
         lastY = (int)event.getRawY();
         
         break;
        
         //layout(Left,Top,Right,Bottom)
         //     Left position, relative to parent 
         //      Top position, relative to parent 
         //    Right position, relative to parent 
         //  Bottom position, relative to parent  
         
         case MotionEvent.ACTION_MOVE:
         int Movex=(int)event.getRawX()-lastX;
         int Movey=(int)event.getRawY()-lastY;           
         
         int Left=view.getLeft()+ Movex; 
         int Bottom=view.getBottom()+ Movey;
         int Right=view.getRight()+ Movex;
         int Top=view.getTop()+ Movey;
         
         if(Left<0)
         {
          Left=0;    
          Right=Left+view.getWidth();
         }
         
         if(Top<0)
         {
          Top=0;
          Bottom=Top+view.getHeight();
         }
         
         if(Right > ScreenWidth)
         {
         Right = ScreenWidth;
          Left=Right-view.getWidth();
         }
        
         if(Bottom > ScreenHeight-50)
         {
         Bottom = ScreenHeight-50;
            Top = Bottom - view.getHeight();
         }
         view.layout(Left, Top, Right, Bottom);
         lastX =(int)event.getRawX();
         lastY =(int)event.getRawY();
   view.postInvalidate();
                
         break; 
 
        }
     
     return false;
 }    
}

執行的效果如下




Button還可以加上背景圖 , 更棒的是Button的形狀會依照圖片做變更 , 只要稍作修改就可以






如果想對圖片的透明度的改變 , 只要加入下面那段程式碼


MoveButton = (Button) findViewById(R.id.Button);        
MoveButton.getBackground().setAlpha(100);

先讓他回傳一個Drawable物件 , 接著對他的透明度進行改變 setAlpha 的參數越小透明化的效果越明顯



public Drawable getBackground ()

Since: API Level 1
Gets the background drawable
Returns
  • The drawable used as the background for this view, if any.

public abstract void setAlpha (int alpha)

Since: API Level 1
Specify an alpha value for the drawable. 0 means fully transparent, and 255 means fully opaque.

Google Analytics