2013年5月25日 星期六

[Win32] 開關螢幕及鎖定(Screen On/Off and Lock)

開啟及關閉螢幕(Screen On/Off)
我們利用SendMessage來廣播WM_SYSCOMMAND訊息給所有視窗(windows),
然後代入SC_MONITORPOWER這個參數來設定顯示狀態,設定值如下面描述。

SC_MONITORPOWER
  • -1 (開啟螢幕)
  • 1  (讓螢幕處於低耗電量)
  • 2  (關閉螢幕)

下面是程式碼片段,注意最後一個參數的設定,代表不同功能。
void screenOff()
{
    SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER,(LPARAM)2);
}

void screenOn()
{
    SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER,(LPARAM)-1);
}

void screenLowPower()
{
    SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER,(LPARAM)1);
}

鎖定(Lock)
鎖定的功能就和按下 Ctrl+Alt+Del,然後在選擇"鎖定"一樣
void lock()
{
    LockWorkStation();
}

[Win32] 關機及重開機(shutdown and restart)

關機和重開機(shutdown and restart)首先要先調高你的程序(process)到可以關機及重開機的權限,
然後在利用這個InitiateSystemShutdown API來達到關機和重開的功能。

我們來介紹一下InitiateSystemShutdown這個API,依照MSDN的描述,

BOOL WINAPI InitiateSystemShutdown(
_In_opt_ LPTSTR lpMachineName,
_In_opt_ LPTSTR lpMessage,
_In_ DWORD dwTimeout,
_In_ BOOL bForceAppsClosed,
_In_ BOOL bRebootAfterShutdown
);

lpMachineName [in, optional]: 如果您想要關閉另一台電腦,在這裡則要輸入那台電腦的網路名稱;反之如果設成NULL,則表示關閉當下這台電腦。
lpMessage [in, optional]: 這裡是用來設定關機對話框的內容描述。
dwTimeout [in]: 搭配第二個參數,用來設定對話框的停留的時間,如果設定成零的話,就不會顯示對話框,然後直接關機。
bForceAppsClosed [in]:如果為TRUE,應用程式不會被存檔,即會強迫馬上關機,所以資料有可能會遺失。
如果為FALSE,系統會顯示一個對話框,來引導使用者去關閉其它已開啟的應用程式。
bRebootAfterShutdown [in]:如果為TRUE為重開的意思,如果為FALSE則是關機。

程式碼如下:
void shutdown()
{
 HANDLE hToken; 
 TOKEN_PRIVILEGES tkp; 
 BOOL fResult; 

 // Get the current process token handle so we can get shutdown privilege.
 if (!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
  printf("OpenProcessToken failed.");

 // Get the LUID for shutdown privilege.
 LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid);
 tkp.PrivilegeCount = 1; // one privilege to set
 tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

 // Get shutdown privilege for this process.
 AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0);

 if (GetLastError() != ERROR_SUCCESS)
  printf("AdjustTokenPrivileges enable failed.");

 fResult = InitiateSystemShutdown( NULL, NULL, 0, FALSE, FALSE); //最後一個參數,TRUE表示重開機,FASLE表示關機
 if (!fResult)
 {
  printf("InitiateSystemShutdown failed.");
 }

 // Disable shutdown privilege.
 tkp.Privileges[0].Attributes = 0;
 AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,(PTOKEN_PRIVILEGES) NULL, 0);

 if (GetLastError() != ERROR_SUCCESS)
 {
  printf("AdjustTokenPrivileges disable failed.");
 } 
}

[Win32] 休眠及睡眠(hibernate and suspend)

剛好用到幾個電源管理的APIs,先簡單介紹一下休眠及睡眠。
休眠和睡眠有什麼不同呢?

睡眠(suspend)會讓電腦處於一個低耗電量的模式,但當電源被用光時,
電腦還是會關機的,所以沒有被儲存的資料還是會遺失。

休眠(hibernate)則會將電腦當下的狀態存到硬碟裡,然後power off;
當喚醒時(resume),則會回存power off前狀態到記憶體裡(RAM)。
舉個例子,當你開啟了office word和excel,然後按下休眠,再喚醒時,依然會看到word和excel;
反之,如果是重開機,就看不到啦。

實作上會用到一個API,SetSuspendState
BOOLEAN WINAPI SetSuspendState(
_In_ BOOLEAN Hibernate,
_In_ BOOLEAN ForceCritical,
_In_ BOOLEAN DisableWakeEvent
);

Hibernate [in] : 如果是TRUE,系統將進入hibernates;反之則是suspended。
ForceCritical [in] : 如果是TRUE,系統則立刻suspend;
反之系統會先廣播一個PBT_APMQUERYSUSPEND事件給每一個應用程式,請求要suspend的權限。
DisableWakeEvent [in] : 如果是TRUE,系統將關掉所有wake event,反之wake events得是開啟的。

程式碼如下:
void suspend()
{
   //If the first parameter is TRUE, the system hibernates. 
  //If the parameter is FALSE, the system is suspended.
   SetSuspendState(FALSE, TRUE, FALSE); 
}

void hibernation()
{
   SetSuspendState(TRUE, TRUE, FALSE); 
}

2013年5月19日 星期日

[Android] 計時器(Chronometer)

1.在使用計時器時,有一個地方需要注意,
就是setBase,這個方法是用來設定一個基準點,也就是我從這個基準點開始計時。

假設我們的程式是這樣寫,就會發生問題,
當我們呼叫start()後,等到過了25秒,按下停止,
再過了15秒後,我們再按下開始讓它繼續計時,
就會發現不是從25秒往下算,而是從40秒開始算。
這是因為計時器從第一次按下開始的時間做為基準點,所以是40秒(25+15)
  btnStart.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    chronometer.start();
   }
  });

  btnStop.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    chronometer.stop();
   }
  });


2.為了解決上述的問題,我們需要額外利用一個參數(escapeTime)及setBase方法
在按下停止時先記錄已經跑過的時間,
然後待下一次按開始繼續計時的時候,在加上去
MainActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Chronometer;

public class MainActivity extends Activity {
 long escapeTime = 0;

 /* Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  final Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer1);
  final Button btnStart = (Button) findViewById(R.id.button1);
  final Button btnStop = (Button) findViewById(R.id.button2);
  final Button btnReset = (Button) findViewById(R.id.button3);

  btnStart.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {

    // SystemClock.elapsedRealtime()方法會回傳從開機到現在的時間
    // 把這個時間做為一個Base,從這個Base開始計時
    chronometer.setBase(SystemClock.elapsedRealtime() + escapeTime);
    chronometer.start();
   }
  });

  btnStop.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    //getBase是用來取得上一次setBase()的時間"chronometer.setBase(SystemClock.elapsedRealtime() + escapeTime);"
    escapeTime = chronometer.getBase() - SystemClock.elapsedRealtime();
    chronometer.stop();
   }
  });

  btnReset.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    chronometer.setBase(SystemClock.elapsedRealtime());
    chronometer.stop();
    escapeTime = 0;
   }
  });

 }

}


3.layout即拉入一個計時器,三個按鈕。
res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Chronometer
        android:id="@+id/chronometer1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="14dp"
        android:text="Chronometer" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="開始(start)" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="停止(stop)" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="18dp"
        android:text="重置(reset)" />

</LinearLayout>
結果:

2013年5月17日 星期五

[Android] 鬧鐘(AlarmMamager and PendingIntent) 下篇

上篇鬧鐘的片段
Intent intent = new Intent(MainActivity.this,
      PlayReceiver.class);
    intent.putExtra("msg", "play_voice");
    intent.addCategory(String.valueOf(SystemClock.elapsedRealtime()));

    long elapsed = SystemClock.elapsedRealtime() + 60 * 1000; // millis

    PendingIntent pi = PendingIntent.getBroadcast(
      MainActivity.this, 1, intent,
      PendingIntent.FLAG_UPDATE_CURRENT);

    AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
    am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapsed, pi);

當我們設定鬧鐘後,如果想取消的話呢?
很簡單,只要呼叫cancel function即可
am.cancel(pi);

那如何確認鬧鐘是否真的被取消呢?
主要是利用PendingIntent.FLAG_NO_CREATE這個參數,
當被包起來的Inent不存在,得回傳null
 
  boolean alarmUp = (PendingIntent.getBroadcast(MainActivity.this, 1,
    intent, PendingIntent.FLAG_NO_CREATE) != null);

  if (alarmUp) 
      Log.d("myTag", "Alarm is already active");
  else
      Log.d("myTag", "Alarm is not already active");

2013年5月16日 星期四

[Android] 鬧鐘(AlarmMamager and PendingIntent) 上篇

我們利用AlarmManager實作一個鬧鐘,
讓他在開機後1分鐘後,發出鈴響。
只要利用PendingIntent及AlarmManager就可以做到這樣的功能。

1. MainActivity.java
import android.os.Bundle;
import android.os.SystemClock;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
 Button mButton;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  mButton = (Button) findViewById(R.id.button1);
  mButton.setOnClickListener(new Button.OnClickListener() {

   @Override
   public void onClick(View v) {
    
    Intent intent = new Intent(MainActivity.this, PlayReceiver.class);
    intent.putExtra("msg", "play_voice");
    intent.addCategory(String.valueOf(SystemClock.elapsedRealtime()));
     //SystemClock.elapsedRealtime()會回傳從開機到現在當下所花的時間,手機進入睡眠時間也算在內(單位milliseconds)
    long elapsed = SystemClock.elapsedRealtime() + 60 * 1000; //60秒
    // 發送一個broadcast,類似 Context.sendBroadcast()
    // PendingIntent.FLAG_UPDATE_CURRENT參數表示,如果已存在 PendingIntent,就更新 extra data.
    PendingIntent pi = PendingIntent.getBroadcast(MainActivity.this, 1, intent, 
      PendingIntent.FLAG_UPDATE_CURRENT);
    
    AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
    am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapsed , pi);

   }

  });
 }


}
那Intent,和PendingIntent有什麼不同呢?
一般的Intent,用startActivity(intent)就會直接去啟動和intent。
PendingIntent可以想成延遲Intent,它則是先把某個Intent包好,丟給某個程式,等到一段時間後再去執行Intent。
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapsed , pi);
等到第二個參數(elapsed)所設定的時間到達時,就會執行pi這個PendingIntent
這裡有一點要注意,當 AlarmManager 執行 set() 時,Android 系統會比對已註冊的其他 Intent 的 action、data、type、class、category, 如果這幾個屬性完全相同,則系統會將這兩個 Intent 視為一樣, 這時系統會視 PendingIntent.FLAG_xxx 參數以決定如何處理這個新註冊的 Intent 。

2.接下來建立接收廣播的class:
PlayReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;

public class PlayReceiver extends BroadcastReceiver {

 private SoundPool sp;
 private int sourceid;

 @Override
 public void onReceive(Context context, Intent intent) {
  
  Bundle bData = intent.getExtras();
 
  if (bData.get("msg").equals("play_voice")) {
   sp = new SoundPool(10, AudioManager.STREAM_MUSIC, 5);
   sourceid = sp.load(context, R.raw.harm, 1);
   
   playSounds(1, context);
  }
  
 }

 public void playSounds(int repeatTime, Context context) {
  AudioManager am = (AudioManager) context.getApplicationContext()
    .getSystemService(Context.AUDIO_SERVICE);
  // 獲取最大音量
  float audMaxVolumn = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
  // 獲取目前音量
  float audCurrentVolumn = am.getStreamVolume(AudioManager.STREAM_MUSIC);
  // 左右聲道值範圍為 0.0 - 1.0
  float volRatio = audCurrentVolumn / audMaxVolumn;
  // 下面參數分別為播放音頻,左聲道,右聲道,設置優先級,重撥次數,速率(速率最低0.5,最高為2,1代表正常速度)
  sp.play(sourceid, volRatio, volRatio, 1, repeatTime, 1);
 }
}
播放音效的作法,請參考先前的文章

3. 在Layout裡加入一個按鈕, 來啟動鬧鐘
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_marginBottom="54dp"
        android:text="1分鍾後,鬧鍾啟動" />

</RelativeLayout>
4. 最後在AndroidManifest.xml 的 application 區段寫入要接收廣播的程式:
<receiver android:name="com.example.s.PlayReceiver" >
            <intent-filter>
                <action android:name="play_voice" />
            </intent-filter>
</receiver>

reference
http://oldgrayduck.blogspot.tw/2012/10/androidalarmmanager.html

2013年5月12日 星期日

[Android] 避免彈出的鍵盤檔到EditText (Move layouts up when soft keyboard is displayed)

有時候當鍵盤彈出的時候,會擋到輸入文字的欄位(EditText),
像下面這張圖一樣,輸入Number的欄位有一小部分被鍵盤檔住了。


為避免這樣的情形,我們可以在AndroidManifest.xml,
加入android:windowSoftInputMode="adjustPan",
這個屬性會保持您的游標所在處,不會被鍵盤給檔住,
所以使用者可以看到他正在輸入的文字。
    <activity name="MainActivity"
        android:windowSoftInputMode="adjustPan">
        ...
    </activity>

結果:
我們可以看到當鍵盤彈出時,
整個畫面layout會被向上推,
所以輸入欄位的地方就不會被擋到啦。

2013年5月9日 星期四

[Android] 撥打電話(make a phone call)

-需繼承PhoneStateListener監聽通話狀態(接通/閒置/來電)
-需在AndroidManifest.xml加入android.permission.CALL_PHONE和android.permission.READ_PHONE_STATE二個權限。

1.MainActivity.java
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {

 final Context context = this;
 private Button button;
 private EditText editText;
 public void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  button = (Button) findViewById(R.id.buttonCall);
  editText=(EditText)findViewById(R.id.editText1);  
  Log.e("log",  this.toString());
  // 初始電話服務功能
  PhoneCallListener phoneListener = new PhoneCallListener();
  TelephonyManager telephonyManager = (TelephonyManager) this
    .getSystemService(Context.TELEPHONY_SERVICE);
  telephonyManager.listen(phoneListener,
    PhoneStateListener.LISTEN_CALL_STATE);

  // 監聽按鈕
  button.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {

    Intent callIntent = new Intent(Intent.ACTION_CALL);
    callIntent.setData(Uri.parse("tel:"+editText.getText().toString()));
    startActivity(callIntent);

   }

  });

 }

 private class PhoneCallListener extends PhoneStateListener {

  private boolean isPhoneCalling = false;

  @Override
  public void onCallStateChanged(int state, String incomingNumber) {

   if (TelephonyManager.CALL_STATE_RINGING == state) {
    // 來電時
    Log.i("Log", "RINGING, number: " + incomingNumber);
   }

   if (TelephonyManager.CALL_STATE_OFFHOOK == state) {
    // 通話中
    isPhoneCalling = true;
   }

   if (TelephonyManager.CALL_STATE_IDLE == state) {
    // 結束通話時
    if (isPhoneCalling) {

     // 重新啟動APP,這裡的目的是要回到activity畫面
     // 如果沒有這一段,通話結束則會停在通話明細的畫面(call list)
     Intent i = getBaseContext().getPackageManager()
       .getLaunchIntentForPackage(
         getBaseContext().getPackageName());
     
     i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |Intent.FLAG_ACTIVITY_SINGLE_TOP );
     startActivity(i);

     isPhoneCalling = false;
    }

   }
  }
 }
}


2.在EditText裡需加入inputType="phone"屬性,這是為了啟動電話的鍵盤的風格。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/RelativeLayout1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/buttonCall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="42dp"
        android:layout_toRightOf="@+id/editText1"
        android:text="CALL" />

    <EditText
        android:id="@+id/editText1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/buttonCall"
        android:layout_alignParentLeft="true"
        android:ems="10"
        android:inputType="phone" />

</RelativeLayout>
3.AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.phonecall"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" />

 <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
         >
  
        <activity
            android:label="@string/app_name"
            android:name=".MainActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
結果:

2013年5月8日 星期三

[Android] 檢查SIM卡是否存在(Check if SIM Card Exists)

利用TELEPHONY_SERVICE來獲得電話的相關資訊, 如SIM卡狀態,來電狀態(如通話中或閒置),
網路型態(GPRS/HSDPA),IMEI,IMSI及手機的電話號碼...等等

簡單的例子如下
TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
if (tm.getSimState() != TelephonyManager.SIM_STATE_ABSENT){
  //SIM卡存在
} else {
  //沒有SIM卡
}

More details
http://developer.android.com/reference/android/telephony/TelephonyManager.html

[Android] 放置廣告到APP(Admob BANNER)

一般在APP裡會看到有橫幅廣告出現在上面或下面,那就稱為banner。
基本上有5個步驟:

1.首先需先申請一個admob的帳號,申請完後會得到一組號碼,類似a150e6a6xxxxxxx
2.下載Google AdMob Ads SDK(下載連結), 解壓縮後會得到一個GoogleAdMobAdsSdk-x.x.x.jar,將這個檔案放到project的libs資料夾下
 
3.加入下面codes到onCreate
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.media);

  //初始橫幅廣告 
  //a150e6a6xxxxxxx則是你申請的那一組ID
  AdView adView = new AdView(this, AdSize.SMART_BANNER, "a150e6a6xxxxxxx");
  LinearLayout layout = (LinearLayout) findViewById(R.id.ads);
  layout.addView(adView);
  adView.loadAd(new AdRequest());
}

4.加入一個linearLayout來放置橫幅廣告
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/RelativeLayout1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#000000"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/ads"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        >
    </LinearLayout>
</LinearLayout>

5.AndroidManifest.xml需加入二個permission及一個ads的activity
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.example.vvvvvv"
          android:versionCode="1"
          android:versionName="1.0">
   
    <application android:icon="@drawable/ic_launcher" android:label="@string/app_name">
        <activity android:name="com.example.vvvvvv.MainActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name="com.google.ads.AdActivity"
                  android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/>
    </application>

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>

結果:
避免廣告之嫌,所以馬塞克處理,呵

2013年5月7日 星期二

[Android] 長按選單(ContextMenu)

長按螢幕出現的選單,有點類似滑鼠右鍵選單。
不過平版或手機沒有滑鼠,所以則需要用長按的方法叫出選單。

MainActivity.java
import android.app.Activity;   
import android.os.Bundle;       
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;    
import android.widget.Toast;


public class MainActivity extends Activity 
 {      
  protected static final int MENU_BUTTON_1 = Menu.FIRST;
  protected static final int MENU_BUTTON_2 = Menu.FIRST + 1;
  protected static final int MENU_BUTTON_3 = Menu.FIRST + 2;
  protected static final int MENU_BUTTON_4 = Menu.FIRST + 3;
 
    /* Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //註冊長按選單
        registerForContextMenu(findViewById(R.id.relativelayout));
                     
    }

 @Override
 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
  //設定選單內容
  menu.setHeaderTitle("挑選您要的顏色"); 
  menu.add(0, MENU_BUTTON_1, 0, "紅");
  menu.add(0, MENU_BUTTON_2, 0, "橙");
  menu.add(0, MENU_BUTTON_3, 0, "黃");
  menu.add(0, MENU_BUTTON_4, 0, "綠");
 
  super.onCreateContextMenu(menu, v, menuInfo);
 }
 
 @Override
 public boolean onContextItemSelected(MenuItem item) {
  // 點選項目後,要做的事寫在這邊
  switch(item.getItemId()) {
  case MENU_BUTTON_1:
   Toast.makeText(getApplicationContext(), "您選了紅色", Toast.LENGTH_SHORT).show();
   break;
  case MENU_BUTTON_2:
   Toast.makeText(getApplicationContext(), "您選了橙色", Toast.LENGTH_SHORT).show();
   break;
  case MENU_BUTTON_3:
   Toast.makeText(getApplicationContext(), "您選了黃色", Toast.LENGTH_SHORT).show();
   break;
  case MENU_BUTTON_4:
   Toast.makeText(getApplicationContext(), "您選了綠色", Toast.LENGTH_SHORT).show();
   break;
  default:
   break;
  }
 
  return super.onContextItemSelected(item);
 }
}

我們希望在畫面任何一個地方按下去,都會出現長按選單,
所以要在這裡加入ID。如:android:id="@+id/relativelayout"
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/relativelayout" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="152dp"
        android:text="請長按螢幕"
        android:textAppearance="?android:attr/textAppearanceLarge" />
  
</RelativeLayout>

結果:

[Android] Activity的四種launchMode

Activity有四種launchMode
  • standard
  • singleTop
  • singleTask
  • singleInstance
可在androidManifest.xml中設定
<activity android:name=".actA" android:launchMode="singleTask"</activity>
1.standard
standard為預設值,androidManifest.xml如果沒有設定launchmode,就會以standard來跑,
這個模式,每次都會create一個新的activity

2.singleTop
當你的activity已經是在最上層,就不會在產生一個activity,但如果當下的activity不是在最上層,則會產生一個新的activity。
舉二個例子來說:
A.假設程式只有一個activity,所以無論如何你的activity都是在最上層,這個時候就不會產生新的activity。
B.假設程式有二個activities A->B,這樣候B開啟A時,則會變成A->B->A,這是因為A不是在最上層,所以就會產生一個新的A。

3.singleTask 意思就是指,同一個Task中只能只一個activity。如果在發現Activity的instance已經存在,則清空這個instance则之上的Activity,使其處於最上層。
我們假設B設成singleTask,
舉個例子來說:
假設有A->B->C->D,當D又用startActivity呼叫B時,則B不會產生一個新的activity,且會將在他上面的C-D清掉,所以stack裡只剩下A,B。

4. singleInstance 這個模式較為複雜,activity要已一個新的task來開啟,而且保證不再有其他Activity instance進入。
舉個例子來說:
actA是standard開啟,actB是singleInstance,
開啟順序為actA1(task1) -> actB(task2) ->actA2(task1),
actB會已一個新的task來開啟(如下圖),
所以當按back鍵時,就變成actA2(task1)->actA1(task1)->actB(task2) 。















總結:
- standard : 每次都會產生一個實例(instance)。
- singleTop : 如果activity在最上層,就不會產生新的實例。
- singleTask :永遠只有一個實例,而且只要被呼叫都會保持在最上層。
- singleInstance :永遠只有一個實例,而且該task只有它一個實例。

補充:
如果自己想做實驗,在onCreate利用下面二種方法拿到activity ID及task ID 。
 @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.first);
        //拿activity ID  
        TextView textView = (TextView) findViewById(R.id.textView);  
        textView.setText(this.toString()); 
        //拿task ID  
        TextView taskIdView = (TextView) findViewById(R.id.taskIdView);  
        taskIdView.setText("current task id: " + this.getTaskId()); 
       
    }  

reference:
http://my2drhapsody.blogspot.tw/2012/08/activity-launchmode.html
http://developer.android.com/guide/components/tasks-and-back-stack.html
http://blog.csdn.net/liuhe688/article/details/6754323

2013年5月5日 星期日

[Android] 按鈕(Button)的點擊及長按

Button點擊(click)和長按(long press)的用法
主要差別在OnClickListener()及OnLongClickListener二個方法

MainActivity.java
package com.example.btn;

import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // 點擊寫法
  Button Btn1 = (Button) findViewById(R.id.button1);
  Btn1.setOnClickListener(new View.OnClickListener() {
   public void onClick(View v) {
    // 要做的事寫在這裡
    Toast.makeText(getApplicationContext(), "點擊", Toast.LENGTH_LONG)
      .show();

   }

  });

  // 長按寫法
  Btn1.setOnLongClickListener(new View.OnLongClickListener() {
   @Override
   public boolean onLongClick(View v) {
    // 要做的事寫在這裡
    Toast.makeText(getApplicationContext(), "長按", Toast.LENGTH_LONG)
      .show();

    return true;
   }
  });

 }
}



activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/hello_world" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/textView1"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="39dp"
        android:text="Button" />

</RelativeLayout>

結果: