分類
Android Uncategorized

Android Looper + Handler + Message + MessageQueue

本篇紀錄如何在Android中使用 Handler + Looper + Message + Queue。
為了讓App保持快速的回應性(避免產生ANR),長期任務不可執行在 UI thread上,這些長期任務包括,網路存取,讀寫檔案,資料庫存取,圖形處理等等。
在Android中使用Thread和在一般的Java中使用方式相當類似,但有一些同步化問題必須去解決,如 Race Condition, Blocking等等
為了方便解決這些問題,Android定義訊息傳送機制來確保訊息傳送的正確性以及安全性,Android訊息傳送機制包含4個角色,各為Looper,Handler,Message,MessageQueue。
 
Message :
訊息,由一組描述和一些資料物件所組成,可藉由Handler傳送到MessageQueue中,並由Looper取出再回傳給同一個Handler,當需要傳遞參數時,可夾帶在Message中。
建立Message的方式盡量使用Message.obtain(), 或是Handler.obtainMessage(),因為訊息會被放到緩衝池以重複使用。
 
MessageQueue :
訊息佇列,透過Looper建立,內容則是訊息組成,結構為無限制的連結串列,一個Looper只能有一個MessageQueue,
訊息會藉由Handler插入,並由Looper取出傳送到相對應的Handler。
 
Looper :
管理MessageQueue,會將存放於MessageQueue中的訊息依序送出到Handler上,讓Handler作進一步的處理,
Handler必須和Looper連結,否則無法傳送或接收訊息,而Thread也必須和Looper連結,才能讓MessageQueue和Thread相關連。
簡單的說Looper是Thread和MessageQueue的協調者。一個Thread只能有一個Looper。
 
Handler :
訊息處理者,在Android訊息傳送機制中,Handler是最常使用的類別,Handler可建立訊息,插入訊息到訊息佇列中,處理訊息。
一個Looper可以有多個Handler,Looper會自動將Message傳送給對應的Handler。
 
藉由以下的範例來說明彼此之間的互動,範例會盡量少用匿名內部類別來實作,而是把各個元件拆開為單一類別,比較容易了解
範例只有一個button和一個textview組成,當點擊button時,會去擷取 https://www.google.com.tw/ 的網頁資料
並顯示到textview上。
 
首先是 MainActivity,因為網路存取是長期任務,且android在Honeycomb SDK新增了NetworkOnMainThreadException,
若在Main thread(UI thread)進行網路操作,會丟出Exception。所以把網路操作放到CustomizeWorkerThread中執行,
第18行建立 mCustomizeWorkerThread 物件
第38行建立CustomizeWorkerThread物件並連結到參考,傳入this是為了把取得的網頁資料設定回MainActivity的textview
第39行呼叫start()方法以啟動mCustomizeWorkerThread的run方法。
第48行當按鈕按下時,呼叫sendMessageToWorkerHandler方法,該方法會建立message插入到MessageQueue
第55行取得mCustomizeWorkerThread的Handler,
第56行建立Message物件,其中數字2為Message的what欄位數值,當Handler處理Message時可以依此數值判斷行為。
第57行利用Handler將Message物件插入到MessageQueue中
第69行當activity結束時也告知Looper停止處理訊息

package com.foxx.threads;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity implements OnClickListener
{
    private static final String TAG = "MainActivity";
    private Button mStartThreadButton;
    private TextView mResultTextView;
    private CustomizeWorkerThread mCustomizeWorkerThread;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initUIComponents();
        startWorkerThread();
    }
    private void initUIComponents()
    {
        mStartThreadButton = (Button) findViewById(R.id.startThread);
        mStartThreadButton.setOnClickListener(this);
        mResultTextView = (TextView) findViewById(R.id.textview);
    }
    private void startWorkerThread()
    {
        mCustomizeWorkerThread = new CustomizeWorkerThread(this);
        mCustomizeWorkerThread.start();
    }
    @Override
    public void onClick(View view)
    {
        int uiId = view.getId();
        switch (uiId) {
            case R.id.startThread:
                sendMessageToWorkerHandler();
                break;
        }
    }
    private void sendMessageToWorkerHandler()
    {
        Handler handler = mCustomizeWorkerThread.getHandler();
        if(handler != null){
            Message message = handler.obtainMessage(2);
            handler.sendMessage(message);
        }
    }
    public TextView getResultTextView()
    {
        return mResultTextView;
    }
    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        mCustomizeWorkerThread.getHandler().getLooper().quit();
    }
}

 
以下為CustomizeWorkerThread的內容,
第10行為了取代匿名內部類別,另外建立了CustomizeWorkerHandler,該Handler負責處理從Looper傳送過來的Message
第18行的run方法相當重要,在run方法中連結了Looper,Thread,Handler三者。
第20行的Looper.prepare方法會建立Looper以及在該Looper中的MessageQueue,並連結到當前的Thread(CustomizeWorkerThread)
第21行建立CustomizeWorkerHandler物件並連結Looper
第22行Looper開始循序處理MessageQueue,為無限迴圈。當Message插入到MessageQueue中,Looper就會擷取該Message並發送到相關的Handler。
第25行回傳Handler讓外部可以藉由取得的Handler插入訊息
第31行即為CustomizeWorkerHandler,繼承 Handler
第41行必須複寫HandleMessage方法,當Looper傳送Message時,該方法即會被呼叫(callback)
第43行根據Message的what數值判斷行為
第45行就是網路存取的行為,HttpPageData.getPageData 會回傳google的網頁資料
第46行把取得的網頁資料設定到MainActivity的textview中

package com.foxx.threads;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
public class CustomizeWorkerThread extends Thread
{
    private static final String TAG = "WorkerThread";
    private CustomizeWorkerHandler mHandler;
    private MainActivity mMainActivity;
    public CustomizeWorkerThread(MainActivity activity) {
        mMainActivity = activity;
    }
    @Override
    public void run()
    {
        Looper.prepare();
        mHandler = new CustomizeWorkerHandler(mMainActivity);
        Looper.loop();
    }
    public CustomizeWorkerHandler getHandler()
    {
        return mHandler;
    }
}
class CustomizeWorkerHandler extends Handler
{
    private static final String TAG = "WorkerHandler";
    private MainActivity mMainActivity;
    public CustomizeWorkerHandler(MainActivity activity) {
        mMainActivity = activity;
    }
    public void handleMessage(Message message)
    {
        switch (message.what) {
            case 2:
                String result = HttpPageData.getPageData("https://www.google.com.tw/");
                setResultToMainActivity(result);
                break;
        }
    }
    private void setResultToMainActivity(final String result)
    {
        mMainActivity.runOnUiThread(new Runnable() {
            @Override
            public void run()
            {
                mMainActivity.getResultTextView().setText(result);
            }
        });
    }
}

 
接著簡單描述整個訊息傳送機制的互動過程,

建立訊息傳送機制

在 Activity 中會持有CustomWorkThread的類別成員(mCustomizeWorkerThread),
並在onCreate方法中建立實體,然後呼叫start方法,start方法會啟動CustomizeWorkerThread中的run方法,
該run方法會將worker thread,Looper, Handler 三者連結起來,到目前為止是整個傳遞訊息機制的建立,

發送訊息

在Activity中為了發送訊息到MessageQueue中,必須先建立訊息再藉由取得mCustomerWorkThread的Handler將訊息發送到MessageQueue中。

處理訊息

當訊息已經被發送到MessageQueue中,Looper便會自動擷取訊息,並將訊息發送到相對應的Handler,
相對應的意思為哪個Handler發送訊息,哪個Handler就會接收到訊息。
Handler藉由handleMessage方法來處理訊息。


以下為HttpPageData.java

package com.foxx.threads;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
public class HttpPageData
{
    /**
     * 取得網頁資料
     * @return : 回傳string型態的網頁資料
     */
    public static String getPageData(String httpUrl){
        java.net.CookieManager cm = new java.net.CookieManager();
        java.net.CookieHandler.setDefault(cm);
        URL u = null;
        InputStream in = null;
        InputStreamReader r = null;
        BufferedReader br = null;
        StringBuffer message = null;
        try {
           u = new URL(httpUrl);
           in = u.openStream();
           r = new InputStreamReader(in, "BIG5");//UTF-8
           br = new BufferedReader(r);
           String tempstr = null;
           message = new StringBuffer();
           while ((tempstr = br.readLine()) != null) {
               message.append(tempstr);
           }
        } catch (Exception e) {
           e.getStackTrace();
           System.out.println(e.getMessage());
        } finally {
           try {
              u = null;
              in.close();
              r.close();
              br.close();
           } catch (Exception e) {
           }
    }
        return message.toString();
    }
}

 

分類
Android Uncategorized

Set unban ip in fail2ban

How to set unban ip in fail2ban??

 
1. Edit /etc/fail2ban/jail.local and find ignoreip

[DEFAULT]
# "ignoreip" can be an IP address, a CIDR mask or a DNS host
ignoreip = 127.0.0.1/8 xxx.xxx.xxx.xxx

 
2. Input your unban ip as xxx.xxx.xxx.xxx
 
Note:
1. Remember use space to identify different ip
2. fail2ban commands
 

分類
Android Uncategorized

jenkins build error : error while writing Main: Main.class (Permission denied)

Description:

Build java project in jenkins and show error message

[javac] /path_of_class.java:1: error while writing Main: /path_of_class.class (Permission denied)
[javac] public class Main
[javac]        ^
[javac] 1 error

 

Root caused:

permission of directory is not enough
 

Solution:

1.
Open Terminal and move to parent of director which at java project
2.
sudo chmod 777 director_name
 

分類
Android Uncategorized

如何簡單的從TextView, EditView取得數值

通常輸入數值都會使用EditText等等的文字框當作對象, 如何才能很快速的從這些view取得數值呢??
使用 getText().toString() 加上各個數值類別(Integer, Double, Float, Long, Byte, Short)的ParseXXX() 最快 如下

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class GetValueFromTextView extends Activity
{
    private static final int PERCENT = 100;
    private static final int MONTH_COUNT = 12;
    private EditText mInput_lend;
    private EditText mInputRate;
    private EditText mInputNumber;
    private Button mSubmit;
    private TextView mAmount;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initComponents();
    }
    private void initComponents()
    {
        mInput_lend = (EditText) findViewById(R.id.input_lend);
        mInputNumber = (EditText) findViewById(R.id.input_number);
        mInputRate = (EditText) findViewById(R.id.input_rate);
        mSubmit = (Button) findViewById(R.id.submit);
        mSubmit.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v)
            {
                mAmount.setText("" + countAmount());
            }
        });
        mAmount = (TextView) findViewById(R.id.amount);
    }
    private double countAmount()
    {
        int lend = Integer.parseInt(mInput_lend.getText().toString());
        double rateOfMonth = Double.parseDouble(mInputRate.getText().toString()) / MONTH_COUNT
                / PERCENT;
        int number = Integer.parseInt(mInputNumber.getText().toString());
        return lend * (Math.pow(rateOfMonth + 1, number));
    }
}

重點在 countAmount 方法中, 以下列方式取得string並轉換成數字

int lend = Integer.parseInt(mInput_lend.getText().toString());
double rateOfMonth = Double.parseDouble(mInputRate.getText().toString()) / MONTH_COUNT / PERCENT;
int number = Integer.parseInt(mInputNumber.getText().toString());

但要小心轉型溢位的問題
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/main_tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/title"/>
    <TextView
        android:id="@+id/main_tv_lendHint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/lend"/>
    <EditText
        android:id="@+id/input_lend"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>
    <TextView
        android:id="@+id/main_tv_rateHint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/rate"/>
    <EditText
        android:id="@+id/input_rate"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="numberDecimal"/>
    <TextView
        android:id="@+id/main_tv_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/rate"/>
    <EditText
        android:id="@+id/input_number"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>
    <Button
        android:id="@+id/submit"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn"/>
    <TextView
        android:id="@+id/amount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

以及 strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Count Round-amount savings</string>
    <string name="title">Round-amount savings:</string>
    <string name="lend">capital(NT$):</string>
    <string name="rate">rate of year(%):</string>
    <string name="number">number of periods:</string>
    <string name="btn">count</string>
</resources>

 

分類
Android Uncategorized

取得 wifi scan ap result : ScanResult

在 android 手機中 -> 設定 -> Wi-Fi , 每隔一段時間會自動搜尋附近的wifi ap的訊息並顯示在列表上, 本篇介紹如何同步取得這些wifi ap的訊息
這些wifi ap的訊息是藉由ScanResult來顯示, ScanResult 類別相當簡單, 請參考官網
而每次搜尋wifi ap的動作都是以Broadcast的方式來達成, 所以我們只要建立一個專門來接收該動作的Broadcast Receiver 就能自動同步取得其搜尋結果(ScanResult)

package com.foxx.wifiapreceiver;
import java.util.List;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.util.Log;
public class WifiApReceiver extends BroadcastReceiver
{
    private static final String TAG = "WifiApResult";
    private Activity mActivity;
    public WifiApReceiver(Activity activity) {
        mActivity = activity;
        mActivity.registerReceiver(this, new IntentFilter(
                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
    }
    @Override
    public void onReceive(Context context, Intent intent)
    {
        getScanResults();
    }
    private void getScanResults()
    {
        List<ScanResult> scanResults = getWifiManager().getScanResults();
        Iterator<ScanResult> iter = scanResults.iterator();
        while (iter.hasNext()) {
            showAllFieldOfScanResult(iter.next());
        }
    }
    private WifiManager getWifiManager()
    {
        return (WifiManager) mActivity.getSystemService(Context.WIFI_SERVICE);
    }
    private void showAllFieldOfScanResult(ScanResult scanResult)
    {
        Log.d(TAG, "--------------------------------------");
        Log.d(TAG, "scanResult.SSID:" + scanResult.SSID);
        Log.d(TAG, "scanResult.BSSID:" + scanResult.BSSID);
        Log.d(TAG, "scanResult.capabilities:" + scanResult.capabilities);
        Log.d(TAG, "scanResult.frequency:" + scanResult.frequency);
        Log.d(TAG, "scanResult.level:" + scanResult.level);
        Log.d(TAG, "scanResult.BSSID:" + scanResult.describeContents());
    }
    public void unregisterReceiver()
    {
        try {
            mActivity.unregisterReceiver(this);
        } catch (Exception e) {
            Log.d(TAG, "execption in unregisterReceiver");
        }
    }
}

1. 在constructor註冊了receiver
2. OnReceiver 為 callback function , 只要有搜尋wifi ap的動作, OnReceiver就會被呼叫
 
在AndroidManifest.xml加入2個必要的permission

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>

 
使用的方式相當簡單

package com.foxx.wifiapreceiver;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity
{
    private WifiApReceiver mWifiReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initWifiReceiver();
    }
    private void initWifiReceiver(){
        mWifiReceiver = new WifiApReceiver(this);
    }
    @Override
    protected void onStop()
    {
        mWifiReceiver.unregisterReceiver();
        super.onStop();
    }
}

只要注意在onStop要unregister receiver即可
 
 
 

分類
Uncategorized

How to run Shell Script in Eclipse

Description :

Use eclipse to run shell script file
 

Step :

Open eclipse ->Run -> External tools -> External Tools Configurations… -> select Program ->
click “new launch configuration” -> select your .sh file at “Location” -> select your .sh file root directoy at “Working Directory” ->
click “Apply” -> click “Run” -> now you can see result at eclipse console

分類
Uncategorized

Run "About" sample on Alljoyn for linux version

參考官網步驟
以下為紀錄成功過程

Export path

1. Open terminal and move to root dir
Note: In my case, root dir name is alljoyn-14.12.00a-src
 
2. Terminal type

export AJ_ROOT=`pwd`

 
 
3. Terminal type

export TARGET_CPU=x86

Note: <TARGET CPU> can be either x86_64, x86, or whatever value you set for “CPU=” when running SCons.
value of TARGET CPU must matchas path, e.g.
/alljoyn-14.12.00a-src/build/linux/x86_64/  or  /alljoyn-14.12.00a-src/build/linux/x86/
 
4. Terminal type

export LD_LIBRARY_PATH=$AJ_ROOT/build/linux/$TARGET_CPU/debug/dist/cpp/lib:$LD_LIBRARY_PATH

 
 

Run the AboutService Sample App

1. In root of dir(alljoyn-14.12.00a-src), type

$AJ_ROOT/build/linux/$TARGET_CPU/debug/dist/cpp/bin/samples/AboutService

Note: some message will print on terminal e.g.

BusAttachment started.
   0.037 ****** ERROR NETWORK                   common/os/posix/Socket.cc:346 | Binding (sockfd = 59) to 0.0.0.0 9955: 98 - Address already in use: ER_OS_ERROR
BusAttachment connect succeeded. BusName :AVq3OlHG.2
AboutObj Announce Succeeded.
   0.041 ****** ERROR NETWORK                   common/os/posix/Socket.cc:346 | Binding (sockfd = 58) to 0.0.0.0 9955: 98 - Address already in use: ER_OS_ERROR
   1.863 ****** ERROR NETWORK iodisp2_0         common/os/posix/Socket.cc:463 | Shutdown socket (sockfd = 11): 107 - Transport endpoint is not connected: ER_OS_ERROR

it will show message when client connects.
 

Run the AboutClient Sample App

1. In root of dir(alljoyn-14.12.00a-src), type

$AJ_ROOT/build/linux/$TARGET_CPU/debug/dist/cpp/bin/samples/AboutClient

Note: terminal print message e.g.

BusAttachment started.
BusAttachment connect succeeded.
WhoImplements called.
*********************************************************************************
Announce signal discovered
	From bus :R8gNQJgr.2
	About version 1
	SessionPort 900
	ObjectDescription:
		/example/path
			com.example.about.feature.interface.sample
	AboutData:
		Key: AppId	01 b3 ba 14 1e 82 11 e4 86 51 d1 56 1d 5d 46 b0
		Key: DefaultLanguage	en
		Key: DeviceId	93c06771-c725-48c2-b1ff-6a2a59d445b8
		Key: ModelNumber	123456
		Key: AppName	Application
		Key: DeviceName	My Device Name
		Key: Manufacturer	Manufacturer
*********************************************************************************
...
Calling /example/path/com.example.about.feature.interface.sample
Echo method reply: ECHO Echo echo...

 
2. AboutServer will print message e.g.

...
Session Joined SessionId = 3156666061
Echo method called: ECHO Echo echo...

 

分類
Uncategorized

Build AllJoyn framework for linux version

參考官網提供基本流程
以下是紀錄成功的流程
 

1. Setup

1-1. Build tools and libs

sudo apt-get install build-essential libgtk2.0-dev libssl-dev xsltproc ia32-libs libxml2-dev

 
 
1-2. Install Python v2.6/2.7 (Python v3.0 is not compatible and will cause errors)

sudo apt-get install python

Note:測試python 2.6.5, 2.7.3皆可, 若已安裝過則省略
 
1-3. Install SCons v2.0

sudo apt-get install scons

 
1-4. OpenSSL

sudo apt-get install libssl-dev

Note:似乎和Build tools and libs 有重複
 
1-5. Download the AllJoyn Source zip and extract source. The tree should look like below. Note, extra directories may exist.

root-source-dir/
  core/
      alljoyn/
      ajtcl/
  services/
      base/
      base_tcl/

Note: download source zip 選擇alljoyn-14.12.00a-src.tar.gz 解壓縮完會產生 alljoyn-14.12.00a-src 目錄,目錄內容如下

alljoyn_c     alljoyn_java  alljoyn_objc   build_core  common        README.md   SConstruct
alljoyn_core  alljoyn_js    alljoyn_unity  build.xml   manifest.txt  README.txt  services

除了選擇alljoyn-14.12.00a-src.tar.gz 以外還可使用git clone的方式來下載 source code 參考這裡, 但未測試

2. Build Sample

2-1. 開啟終端機移動到 alljoyn-14.12.00a-src 目錄中並輸入

scons BINDINGS=cpp WS=off BT=off ICE=off

terminal會出現一連串build step, 如下

scons: Reading SConscript files ...
Building bindings: cpp
Building services:
BULLSEYE_BIN not specified
GTEST_DIR not specified skipping common unit test build
BULLSEYE_BIN not specified
GTEST_DIR not specified skipping About Service unit test build
GTEST_DIR not specified skipping alljoyn_core unit test build
scons: warning: Ignoring missing SConscript 'build/linux/x86_64/debug/obj/services/about/SConscript'
File "/home/rcserver/projects/jellyfish/alljoyn-14.12.00a-src/alljoyn_core/samples/eventaction/SConscript", line 35, in <module>
scons: done reading SConscript files.
scons: Building targets ...
Install file: "alljoyn_core/docs/README.linux.txt" as "build/linux/x86_64/debug/dist/README.txt"
...

Note: 完成後會產生build資料夾, 後續 run sample 動作都與此有關
 
最後輸入

SERVICES="about,notification,controlpanel,config,onboarding,sample_apps"

 
 

分類
Uncategorized

AlertDialog + MultiChoiceItems

1.MainActivity.java

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnMultiChoiceClickListener;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity
{
    private Button mShowAlertDialog;
    private TextView mShowSelectedItemsOfAlertDialog;
    private String[] mItemsOfAlertDialog;
    private boolean[] mSelectedItems;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mItemsOfAlertDialog = getResources().getStringArray(R.array.dialog_items);
        mSelectedItems = new boolean[mItemsOfAlertDialog.length];
        mShowAlertDialog = (Button) findViewById(R.id.show_alert_dialog);
        mShowSelectedItemsOfAlertDialog = (TextView) findViewById(R.id.show_selected_items);
        mShowAlertDialog.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                showAlertDialog();
            }
        });
    }
    private void showAlertDialog()
    {
        Resources resources = getResources();
        AlertDialog.Builder ab = new AlertDialog.Builder(this);
        ab.setTitle(resources.getString(R.string.alert_dialog_name));
        ab.setMultiChoiceItems(R.array.dialog_items, mSelectedItems,
                new MultiChoiceClickListener());
        ab.setPositiveButton(resources.getString(R.string.ok),
                new ClickListenerForPositiveButton());
        ab.setNegativeButton(resources.getString(R.string.exit),
                new ClickListenerForNegativeButton());
        ab.show();
    }
    private final class ClickListenerForNegativeButton implements OnClickListener
    {
        @Override
        public void onClick(DialogInterface dialog, int which)
        {
            dialog.dismiss();
        }
    }
    private final class ClickListenerForPositiveButton implements OnClickListener
    {
        @Override
        public void onClick(DialogInterface dialog, int which)
        {
            String selectedItems = "";
            for (int i = 0; i < mSelectedItems.length; ++i) {
                if (mSelectedItems[i]) {
                    selectedItems = selectedItems + mItemsOfAlertDialog[i] + "\n";
                }
            }
            mShowSelectedItemsOfAlertDialog.setText(getResources().getString(
                    R.string.selected_items)
                    + "\n" + selectedItems);
            dialog.dismiss();
        }
    }
    private final class MultiChoiceClickListener implements OnMultiChoiceClickListener
    {
        @Override
        public void onClick(DialogInterface dialog, int which, boolean isChecked)
        {
            mSelectedItems[which] = isChecked;
        }
    }
}

Note:
showAlertDialog(), 其中setMultiChoiceItems()就是設定為Multi choice的重點, 第一個參數mItemsOfAlertDialog為soft coding帶入dialog item, dialog item定義在xml  中 如下
/res/values/arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array
        name="dialog_items">
        <item>item1</item>
        <item>item2</item>
        <item>item3</item>
        <item>item4</item>
        <item>item5</item>
    </string-array>
</resources>

第2個參數 mSelectedItems 則是代表dialog item是否被選中
MultiChoiceClickListener類別則為監聽alertdialog中item被選中的事件,其實比較方便的寫法是直接建立匿名類別,但如此一來showAlertDialog()長度會被大幅增加, 可讀性降低
ClickListenerForNegativeButton類別則為監聽alertdialog中Negative Button被點擊的事件, 可以看到事件啟動之後僅把dialog dismiss,沒有作其他事情
ClickListenerForPositiveButton類別則為監聽alertdialog中Positive Button被點擊的事件, 事件啟動後檢查mSelectedItems的element,如果是true代表在dialog是被勾選的
 
2.main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <Button
        android:id="@+id/show_alert_dialog"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/show_alert_dialog" />
    <TextView
        android:id="@+id/show_selected_items"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/selected_items" />
</LinearLayout>

 

分類
Uncategorized

Spinner + ArrayAdapter

Spinner 加上 ArrayAdapter 的基本使用
1.MainActivity.java

import java.util.Arrays;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
public class MainActivity extends Activity
{
    private Spinner mSpinner;
    private String[] mSpinnerItems = new String[] {
            "item1", "item2", "item3"
    };
    private TextView mShowSelectedItemInSpinner;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initLayout();
    }
    private void initLayout()
    {
        mShowSelectedItemInSpinner = (TextView) findViewById(R.id.selected_item);
        initSpinner();
    }
    private void initSpinner()
    {
        mSpinner = (Spinner) findViewById(R.id.exam_spinner);
        ArrayAdapter adapter = createArrayAdapterByStringArrayXml();
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mSpinner.setAdapter(adapter);
        mSpinner.setOnItemSelectedListener(new ItemSelectedListener());
    }
    private void showSelectedItem()
    {
        Object selectItem = mSpinner.getSelectedItem().toString();
        for (String item : mSpinnerItems) {
            if (selectItem.equals(item)) {
                mShowSelectedItemInSpinner.setText(item);
                break;
            }
        }
    }
    private ArrayAdapter<String> getArrayAdapterByStringArray()
    {
        return new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mSpinnerItems);
    }
    private ArrayAdapter<String> getArrayAdapterByStringArrayXml()
    {
        String[] items = getResources().getStringArray(R.array.spinner_items);
        return new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, items);
    }
    private ArrayAdapter<CharSequence> createArrayAdapterByStringArrayXml()
    {
        return ArrayAdapter.createFromResource(this, R.array.spinner_items,
                android.R.layout.simple_spinner_item);
    }
    private ArrayAdapter<String> getArrayAdapterByList()
    {
        List<String> itemList = Arrays.asList(mSpinnerItems);
        return new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, itemList);
    }
    private final class ItemSelectedListener implements OnItemSelectedListener
    {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
        {
            showSelectedItem();
        }
        @Override
        public void onNothingSelected(AdapterView<?> parent)
        {
        }
    }
}

Note:
注意各種不同取得ArrayAdapter的方式
getArrayAdapterByStringArray() 直接使用Hard Coding帶入Spinner item, android.R.layout.simple_spinner_item為 android 預設的 spinner layout
getArrayAdapterByStringArrayXml() 使用Soft Coding帶入Spinner itme, 其中item定義在R.array.spinner_items中
createArrayAdapterByStringArrayXml() 也是使用Soft Coding帶入Spinner itme
getArrayAdapterByList() 也可以使用 List 帶入 spinner item
這裡只列出常用的幾種,其他請參考官網
setDropDownViewResource() 用來設定點擊Spinner後出現的下拉式選單
ItemSelectedListener類別則是用來監聽Spinner選擇的Item
 
2./res/values/arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array
        name="spinner_items">
        <item>item1</item>
        <item>item2</item>
        <item>item3</item>
    </string-array>
</resources>

Note:
使用Soft Coding定義Spinner Items
 
3.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" >
    <Spinner
        android:id="@+id/exam_spinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/selected_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/exam_spinner"
        android:layout_marginLeft="50dp"
        android:layout_marginTop="20dp"/>
</RelativeLayout>