Social Icons

2010年12月27日 星期一

如何得知程式是跑在"Debug"模式下

有時候,會需要知道程式現在是跑在debug模式還是release模式下,
像是MapView的Key在Debug和Release的模式下,是需要傳不同的key值才可以的。
下面就是使用方法:

通常如果是debug模式,我們會在AndroidManifest.xml檔案中加入這樣的設定:
...

...

而下面這段程式碼,就可以使用在程式中,來決定目前是不是在debug模式下了。
而我通常會將它寫成static function,方便在各個view中來使用。
public static boolean isDebugMode(Context ctx) {
  PackageInfo packageInfo;
  try {
   packageInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), PackageManager.GET_SIGNATURES);
   int flags = packageInfo.applicationInfo.flags; 
   if ((flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
       return true;
   }
  } catch (NameNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return false;
 }

2010年11月20日 星期六

將View的內容截取出來

因為程式中會需要截取MapView的內容,並上傳到facebook,所以將截取View內容的方式紀錄下來。

Bitmap bitmap = Bitmap.createBitmap(mapView.getWidth(), mapView.getHeight(), Bitmap.Config.ARGB_8888); 
Canvas canvas = new Canvas(bitmap); 
mapView.draw(canvas);

重點其實只有:
(一)以View的size來建立一個Bitmap。
(二)以這個bitmap來建立canvas。
(三)將mapView的內容畫在canvas上。

然後,就可以將這個Bitmap轉化成PNG的格式,可以儲存下來,或者上傳到網路上,夠簡單了吧。

2010年11月3日 星期三

使用facebook api

在我的程式中有使用facebook api,雖然不是很難,不過也把我使用的方法紀錄和分享下來。

  • login和授權:
facebook api已經將登入和授權整個UI都包好了,使用時也是非常方便。
mFacebook = new Facebook;
...
        if (mFacebook.isSessionValid() == false) {
         mFacebook.authorize(ctx, APP_ID, FB_PERMISSIONS, new DialogListener() {
    @Override
    public void onCancel() {
    }
    @Override
    public void onComplete(Bundle values) {
     post();
    }
    @Override
    public void onError(DialogError e) {
     sendNotify(false);
    }
    @Override
    public void onFacebookError(FacebookError e) {
     sendNotify(false);
    }
         });
        } else {
         post();
        }
當mFacebook.isSessionValid()為true時代表授權還沒有過期,是不需要在要求使用者登錄的,可以繼續我們上傳的程序。

  • Post message:
下面是post message的範例。
  final String GRAPH_PATH = "me/feed";
  final String POST_MESSAGE = "message";
  Bundle params = new Bundle();
  String msgVal = null;
  if (this.msg != null) {
   msgVal = this.title+"\n"+this.msg;
  } else {
   msgVal = this.title;
  }
  params.putString(POST_MESSAGE, msgVal);
  
  try {
   String resp = mFacebook.request(GRAPH_PATH, params, "POST");
   JSONObject json = Util.parseJson(resp);
   postId = json.getString("id");

  • Post photo:
重點在於如何將photo變成binary傳送到facebook;另外縮圖也很重要,facebook上傳照片的時間很長,如果能先縮圖可以減少上傳時間。
  final String GRAPH_PATH = "me/photos";
  final String POST_MESSAGE = "message";
  Bundle params = new Bundle();
  
  String msgVal = null;
  if (this.msg != null) {
   msgVal = this.title+"\n"+this.msg;
  } else {
   msgVal = this.title;
  }
  params.putString(POST_MESSAGE, msgVal);
  ByteArrayOutputStream out = new ByteArrayOutputStream();
  travelMap.compress(Bitmap.CompressFormat.PNG, 100, out);
  params.putByteArray("picture", out.toByteArray());   
        
  try {
   String resp = mFacebook.request(GRAPH_PATH, params, "POST");
   JSONObject json = Util.parseJson(resp);
   postId = json.getString("id");

  • Thread:
因為facebook api會花一些時間等待,所以最好把整個程序放在背景執行會比較好。
    private void post() {
     Thread thread = new Thread()
     {
         @Override
         public void run() {
          boolean bRet = false;
      switch(hi.getType()) {
      case HistoryDB.HISTORY_PHOTO_TYPE:
       bRet = postPhoto();
       break;
           
      case HistoryDB.HISTORY_POI_TYPE:
       bRet = postFeed();
       break;
           
      default:
       break;
      }
          
          Message msg = new Message();
          if (bRet == true)
           msg.arg1 = 1;
          else
           msg.arg1 = 0;
          handler.sendMessage(msg);
         }
     };
     thread.start();
    }
    
    Handler handler=new Handler() {
        @Override
        public void handleMessage(Message msg) {
         if (msg.arg1 == 1) {
          sendNotify(true);
         } else {
          sendNotify(false);
         }
        }
    };

2010年10月27日 星期三

程式中呼叫相簿來顯示照片

上回提到了在程式中如何呼叫相簿來選取照片,那如果我們已經知道照片的位置,如何利用系統的相簿來顯示呢?
請看下列範例:

Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
File file = new File(photo.getLocalPath());
intent.setDataAndType(Uri.fromFile(file), "image/*");
context.startActivity(intent);

真的很簡單!

2010年10月21日 星期四

如何在程式中呼叫相本來選取相片

上一篇文章中提到了如何在程式中呼叫照相機來拍照,這次也介紹一下如何從程式中呼叫相本來選取相片,並回傳。

呼叫相本選取照片,記得還是需要自行定義回傳碼 SELECT_IMAGE_ACTIVITY_REQUEST_CODE
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setType("image/*");
startActivityForResult(intent, SELECT_IMAGE_ACTIVITY_REQUEST_CODE);


處理回傳的相片,在onActivityResult中,先判斷回傳碼正確,再如下處理:
if (resultCode == RESULT_OK) {
    Cursor cursor = null;
    try {
     cursor = getContentResolver().query(data.getData(), null, null, null, null);
     if (cursor.moveToFirst()) {
      int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
      String absoluteFilePath = cursor.getString(idx);
      if (absoluteFilePath != null)
       photoPath = absoluteFilePath;
     }
   } finally {
    if (cursor != null) {
     cursor.close();
    }
   }
}

2010年10月19日 星期二

如何從程式中呼叫系統的照相機,並得到相片的資訊

在Android中內建了很多有用的程式,和content provider。所以有很多功能是不需要自己重新再寫的,善用content provider,可以讓程式簡潔很多。我的程式裡頭常常會用到照相機,並紀錄相片的資料(存儲位置。。。),我的做法是呼叫照相機的provider,照完相後再讀取剛才的照片來處理。

這在android中要怎麽做呢?查了一下分成兩個部分:
第一個部分是呼叫照相機
public static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 1234567;
...
mBtnTakePicture.setOnClickListener(new Button.OnClickListener() {
    @Override
    public void onClick(View v) {
        //define the file-name to save photo taken by Camera activity
 String fileName = "new-photo-name.jpg";
    
 //create parameters for Intent with filename
 ContentValues values = new ContentValues();
 values.put(MediaStore.Images.Media.TITLE, fileName);
 values.put(MediaStore.Images.Media.DESCRIPTION,"Image capture by camera");
    
 //imageUri is the current activity attribute, define and save it for later usage (also in onSaveInstanceState)
 imageUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    
 //create new Intent
 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
 intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
 startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
    }

其中重要的是要定義一個辨識碼(CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE),在呼叫startActivityForResult中,將辨識碼傳入,這是為了返回時用來辨識的。

第二步當照相機結束返回時
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
         if (resultCode == RESULT_OK) {
             //use imageUri here to access the image
      Cursor cursor = null;
      try {
          String [] proj={ MediaStore.Images.Media.DATA }; //, MediaStore.Images.Media._ID, MediaStore.Images.ImageColumns.ORIENTATION };
          cursor = RecordView.this.managedQuery(imageUri,
            proj, // Which columns to return
           null,       // WHERE clause; which rows to return (all rows)
           null,       // WHERE clause selection arguments (none)
           null); // Order-by clause (ascending by name)
          int file_ColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
          //int orientation_ColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION);
          if (cursor.moveToFirst()) {
               String imagePath = cursor.getString(file_ColumnIndex);
....
          } finally {
           if (cursor != null) {
            cursor.close();
           }
          }

先判斷返回碼是正確的,再確認結果是正確的(表示使用者有拍照並存儲),然後利用managedQuery讀出cursor,就可以取出照片在存儲媒體中的位置了。

2010年10月16日 星期六

如何有效率的讀取圖片

我們知道現在手機的畫數動則幾百萬,可是手機對於記憶的管制非常嚴,直接讀取常常會失敗,哪有什麽好方法嗎?
其實是有的,簡單的步驟分為三。

1. 先讀取照片的長寬。
2. 利用要顯示的長寬來找出讀取後的倍率。
3. 依照倍率來讀取適當的照片。

下面是簡單的範例程式:
BitmapFactory.Options option1s = new BitmapFactory.Options();
option1s.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mediaPath, option1s);
int oriheight = option1s.outHeight;
int oriwidth = option1s.outWidth;
double widthScale = (double)oriwidth / (double)(drawRect.right-drawRect.left+1);
double heightScale = (double)oriheight / (double)(drawRect.bottom - drawRect.top+1);
int scale = 1;
if (widthScale > heightScale)
scale = (int)heightScale;
else
scale = (int)widthScale;
if (scale < 1)
    scale = 1;
BitmapFactory.Options options = new BitmapFactory.Options(); 
options.inSampleSize = scale; 
Bitmap bmp = BitmapFactory.decodeFile(mediaPath, options);

上面的讀法實際讀出來的照片的大小會比顯示區域大一些,使用縮小倍率最小的邊來縮圖, 所以顯示的時候要注意。如果要將照片和現實位置的中心點對齊的話,在draw的時候就要做一些處理。
int newWidth = (drawRect.right-drawRect.left);
int newHeight = (drawRect.bottom-drawRect.top);
if (bmp.getWidth() < newWidth)
    newWidth = bmp.getWidth();
if (bmp.getHeight() < newHeight)
    newHeight = bmp.getHeight();
Bitmap resizeBmp = Bitmap.createBitmap(bmp, (bmp.getWidth()/2)-(newWidth/2), (bmp.getHeight()/2)-(newHeight/2), newWidth, newHeight);

另外,如果希望縮小的倍率是2的倍數,可以參考下面的演算法:
if (widthScale > heightScale) {
    scale = 2 ^ ((int) Math.ceil(Math.log((drawRect.bottom-drawRect.top+1) / (double) oriheight) / Math.log(0.5)) -2);
} else {
    scale = 2 ^ ((int) Math.ceil(Math.log((drawRect.right-drawRect.left+1) / (double) oriwidth) / Math.log(0.5)) -2);
}

如何對應不同解析度的螢幕

在我的程式中為了要顯示先現在為止位置的方向,需要得到compass或GPS的bearing,然後在利用matrix中rotate的功能將方向的圖示圖示做對應的選zhau旋轉。可是有時候程式會無法得到正確bearing的值,如果將NaN的值塞到rotate中會造成程式crash。Google了一下原來在Java中要判斷float的NaN,還是要用到一些技巧的。


也還蠻簡單的,不過要利用Float object來判斷,程式碼如下:
           Float dbear = new Float(this.myloc.bearingTo(oxlocate));
            if (dbear.isNaN() == false) {

View Pager 使用教學

最近一個計劃中需要用到ViewPager,參考了一些資料,所以將所得寫下來供日後參考。 如果還不知道什麼是view pager,可以想想看在home launcher上,透過手指滑動螢幕,而切換不同的頁面,這個UI就是view pager。以前如果要達到這個功能要自己寫,現在Google已經提供了,只要套用就好了。 首先我們需要先設定環境 
A: 下載Android compatibility。叫起SDK manager然後下載Compatiblity package. 


B. 將 android-sdk-windows/android-compatibility/v4/下的android-support-v4.jar這個檔案,拷貝到你專案下的libs目錄(如果沒有請自行建立),建立好之後,如果你的開發環境是Eclipse的話,請在你的專案下按右鍵選擇"refresh"之後,你就可以看到這個目錄和這個檔案了。

C: 最後我們只要將剛才複製的這個檔案加入build path就可以開始使用了。(在Eclipse下選擇這個檔案,然後按滑鼠右鍵,選擇"Build Path...",然後選擇"Add to build path"就可以了) 

接下來就是在程式中使用View pager了,首先我們看看layout file,只要加上 android.support.v4.view.ViewPager就可以了。
<?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"
    android:background="#a4c639">
<android.support.v4.view.ViewPager
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:id="@+id/awesomepager"/>
</LinearLayout>
程式中要使用ViewPager來顯示資訊必須自己繼承PagerAdapter,來完成手指滑動切換視窗的動作。
package com.geekyouup.paug.awesomepager;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.widget.TextView;

public class AwesomePagerActivity extends Activity {
    
 private ViewPager awesomePager;
 private static int NUM_AWESOME_VIEWS = 20;
 private Context cxt;
 private AwesomePagerAdapter awesomeAdapter;
 
 /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        cxt = this;
        
        awesomeAdapter = new AwesomePagerAdapter();
        awesomePager = (ViewPager) findViewById(R.id.awesomepager);
        awesomePager.setAdapter(awesomeAdapter);
    }
    
    private class AwesomePagerAdapter extends PagerAdapter{

  
  @Override
  public int getCount() {
   return NUM_AWESOME_VIEWS;
  }

     /**
      * Create the page for the given position.  The adapter is responsible
      * for adding the view to the container given here, although it only
      * must ensure this is done by the time it returns from
      * {@link #finishUpdate()}.
      *
      * @param container The containing View in which the page will be shown.
      * @param position The page position to be instantiated.
      * @return Returns an Object representing the new page.  This does not
      * need to be a View, but can be some other container of the page.
      */
  @Override
  public Object instantiateItem(View collection, int position) {
   TextView tv = new TextView(cxt);
   tv.setText("Bonjour PAUG " + position);
   tv.setTextColor(Color.WHITE);
   tv.setTextSize(30);
   
   ((ViewPager) collection).addView(tv,0);
   
   return tv;
  }

     /**
      * Remove a page for the given position.  The adapter is responsible
      * for removing the view from its container, although it only must ensure
      * this is done by the time it returns from {@link #finishUpdate()}.
      *
      * @param container The containing View from which the page will be removed.
      * @param position The page position to be removed.
      * @param object The same object that was returned by
      * {@link #instantiateItem(View, int)}.
      */
  @Override
  public void destroyItem(View collection, int position, Object view) {
   ((ViewPager) collection).removeView((TextView) view);
  }

  
  
  @Override
  public boolean isViewFromObject(View view, Object object) {
   return view==((TextView)object);
  }

  
     /**
      * Called when the a change in the shown pages has been completed.  At this
      * point you must ensure that all of the pages have actually been added or
      * removed from the container as appropriate.
      * @param container The containing View which is displaying this adapter's
      * page views.
      */
  @Override
  public void finishUpdate(View arg0) {}
  

  @Override
  public void restoreState(Parcelable arg0, ClassLoader arg1) {}

  @Override
  public Parcelable saveState() {
   return null;
  }

  @Override
  public void startUpdate(View arg0) {}
     
    }
}
在範例中,可以看到需要告訴程式有多少頁面可以滑動(NUM_AWESOME_VIEWS),然後當滑動到該頁面是要如何顯示(instantiateItem)。其實這還蠻簡單的,請自行參考上述的範例。 到這裡可能會覺得好像還少了一點東西!對,想Google+上面不是還會顯示目前頁面的indicator嗎?該如何達成?賣個關子,下次說給你聽。呵呵! PS, 以上的範例是參考AwesomePager,可以點擊到該項目的網頁下載程式與說明。

2010年10月15日 星期五

將Bitmap儲存到Sqlite的欄位

在寫程式的過程中,遇到需要將Bitmap儲存在sqlite的binary欄位中。
Google了一下發現下面的方法:

1. 首先先生成一個ByteArrayOutput
2. 然後將Bitmap壓縮成PNG(or其他格式),並輸出到ByteArrayOutput裡
3. 最後在放到binary的value中。

部分的程式片段如下:
ByteArrayOutputStream out = new ByteArrayOutputStream();
thumb.compress(Bitmap.CompressFormat.PNG, 100, out);
values.put(FIELD_THUMB, out.toByteArray());

取出Bitmap並重新建立的方法如下:
byte[] buf;
buf = cur.getBlob(FIELD_THUMB_IDX);
Bitmap bmp = BitmapFactory.decodeByteArray(buf, 0, buf.length);

查了很久,不過知道了之後還覺得蠻簡單的說。