顯示具有 Android 標籤的文章。 顯示所有文章
顯示具有 Android 標籤的文章。 顯示所有文章

2015年7月19日 星期日

[擒猿筆記篇] Android 在 Activity 中實作 Up Navigation

你一定看過以下最左邊這個 Up Navigation 的箭頭:



因為 Android Designer 認為任何非 main entrance 的 activity 都必須加入這個箭頭,讓 user 可以方便回到上一層,完全忽略大部分的 Android devices 有 back key,幹你娘自以為是 iphone 。

Up Navigation 的實作方式如下:

1. 在 Manifest 中 activity 的欄位添加以下屬性:
<!-- Parent activity to support 4.1 and higher -->
android:parentActivityName="com.example.myfirstapp.MainActivity"

<!-- Parent activity meta-data to support 4.0 and lower -->
<meta-data
    android:name="android.support.PARENT_ACTIVITY"
    android:value="com.example.myfirstapp.MainActivity" />

2. 在 onCreate() 中加入以下程式片斷後,在 Action bar 上就會出現一個可按的箭頭了:
@Override
public void onCreate(Bundle savedInstanceState) {
    ...
    // 隱藏 Logo 只顯示箭頭
    getActionBar().setDisplayHomeAsUpEnabled(true);

    // 顯示 Logo 也顯示箭頭
    getActionBar().setLogo(R.drawable.myDrawable);
    getActionBar().setDisplayUseLogoEnabled(true);
    getActionBar().setDisplayShowHomeEnabled(true);
}

3. 還需要實作 onOptionsItemSelected() 讓箭頭按下去之後會有反應:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    // Respond to the action bar's Up/Home button
    case android.R.id.home:
        NavUtils.navigateUpFromSameTask(this);
        return true;
    }
    return super.onOptionsItemSelected(item);
}
其中 NavUtils 是 android support library v4 的 class,使用前必需載入 android-support-v4.jar 這個外部 jar 檔。


4. 如果該 Activty 是可接受外部 intent 呼叫的,則可透過以下方式實作 Up Navigation,使其再點選箭頭後回到外部程式:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    // Respond to the action bar's Up/Home button
    case android.R.id.home:
        Intent upIntent = NavUtils.getParentActivityIntent(this);
        if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
            // This activity is NOT part of this app's task, so create a new task
            // when navigating up, with a synthesized back stack.
            TaskStackBuilder.create(this)
                    // Add all of this activity's parents to the back stack
                    .addNextIntentWithParentStack(upIntent)
                    // Navigate up to the closest parent
                    .startActivities();
        } else {
            // This activity is part of this app's task, so simply
            // navigate up to the logical parent activity.
            NavUtils.navigateUpTo(this, upIntent);
        }
        return true;
    }
    return super.onOptionsItemSelected(item);
}
注意,使用此方法時必須在  Manifest 中宣告 android:parentActivityName 和其對應的 <meata-data> 屬性。

refs: http://developer.android.com/training/implementing-navigation/ancestral.html

2015年7月16日 星期四

[擒猿筆記篇] Android切換全螢幕與非全螢幕的方法

找了好幾個方法終於有一個能用的,特此紀錄一下。

切換為全螢幕
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

切回非全螢幕
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);

以上是把 status bar 和 navigation bar 隱藏,如果連 title bar 都不要就要在 xml 檔中設置:
<item name="android:windowNoTitle">true</item>

或是在 onCreate() 中加入:
requestWindowFeature(Window.FEATURE_NO_TITLE);

補個幹你娘ㄎㄎ

2015年7月8日 星期三

[擒猿基本功] Android 利用 Content Provider 隔山打牛

安卓大陸裡處處是陷阱,必須集合眾人的力量才能擊敗邪惡 Monkey,而 Content Provider 就是一套能夠借力使力,隔山打牛的強大心法,不可不練,非練不可。

翻開擒猿寶典,Content Provider 提供了一個中央控管的資料庫,可以讓其他的 app 可以購過 API 方便存取本身的資料,以下介紹 Content Provider 的原理與實作:

Content Provider 將資料以 Table 的方式儲存,其中一個 row 表示一筆資料,每個  row 會有多個 column,Table 內容如下:
wordapp_idfrequencylocale_ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5

鏡頭一轉,來到安卓大陸的戰場上,火爆猿王肆虐,打得眾人潰不成軍,一位武功強大的老前輩挺身而出,利用獅吼功向眾人發出訊息:幹你娘!大家快來用我的 Content Provider 打王!

這時候身為擒猿手的你,該怎麼提取前輩的內力,借力對邪惡猿王打出致命一擊呢?噎,這時候就要透過 ContentResolver 這個物件,透過他就可以對前輩的 Content Provider 進行 CRUD 操作 (C: Create、R: Retrieve、U: Update、D: Delete)。

Query Data from Content Provider

欲取得 Content Provider 分享資料的 app 必須在 manifest 中宣告 permission,而欲存取資料的 app 則需在 manifest 中去 use 這個 permission。在程式中可以利用以下寫法得到指定 Content Provider 的 Table 中的資料:
// Queries the user dictionary and returns results
Cursor mCursor = getContentResolver().query(
    CONTENT_URI,      // The content URI of the words table
    mProjection,      // The columns to return for each row
    mSelectionClause  // Selection criteria
    mSelectionArgs,   // Selection criteria
    mSortOrder);      // The sort order for the returned rows
  • CONTENT_URI 以 URI 的形式直接指向 Content Provider 的 Table,形如 content://user_dictionary/words,其中 user_diction 為 Content Provider authority,而 words 為 Table name。
  • mProjecttion 定義取出的資料要有那些欄位,寫法如下:
  • String[] mProjection = {
        "_ID",
        "word",
        "locale"
    };
    • mSelectionClause 可定義的篩選資料的條件,寫法如下:
      String mSelectionClause =  "frequency = 200";
      但這樣的寫法非常危險,因為他直接對應到 SQL 語言,譬如 user 可能輸入 "nothing; DROP TABLE *;" ,導致 Table 被清空。
    • 更好的寫法是 mSelectionArgs 與 mSelectionClause 搭配使用,能避免上述的惡意破壞情形,寫法如下,注意那個問號很玄:
    • // Constructs a selection clause with a replaceable parameter
      String mSelectionClause = "frequency = ?";
      // Defines an array to contain the selection arguments
      String[] selectionArgs = {""};
      // Sets the selection argument to the user's input
      selectionArgs[0] = mUserInput;
    • 最後,可以利用 mSortOrder 去控制取回資料的排序方式,寫法如下:
    • mSortOrder = "ORDER BY _ID";

    Getting data from query results

    使用上述的 ContentResolver.query() 會傳回一個 Cursor 元件,以下為從 Cursor 中取得我們要的資料的方法:
    // Determine the column index of the column named "word"
    int index = mCursor.getColumnIndex("world");
    if (mCursor != null) {
        while (mCursor.moveToNext()) {
            // Gets the value from the column.
            newWord = mCursor.getString(index);
            Log.d("Lotus", newWord);
        }
    }
    
    此外可以直接將 Cursor 與 ListView 做連結,如以下寫法:
    // Creates a new SimpleCursorAdapter
    mCursorAdapter = new SimpleCursorAdapter(
        getApplicationContext(),        // The application's Context object
        R.layout.wordlistrow,           // A layout in XML for one row in the ListView
        mCursor,                        // The result from the query
        new Steing[] {"word" "locale"}, // A string array of column names in the cursor
        new int[] { R.id.dictWord, R.id.locale}, // view IDs in the row layout
        0);                             // Flags (usually none are needed)
    
    // Sets the adapter for the ListView
    mListview.setAdapter(mCursorAdapter);

    Inserting data

    利用 ContentResolver.insert() 可達成插入一筆新資料至 Content Provider 的動作,寫法如下 :
    // Defines a new Uri object that receives the result of the insertion
    Uri mNewUri;
    // Defines an object to contain the new values to insert
    ContentValues mNewValues = new ContentValues();
    
    /*
     * Sets the values of each column and inserts the word. The arguments to the "put"
     * method are "column name" and "value"
     */
    mNewValues.put("app_id", "example.user");
    mNewValues.put("local", "en_US");
    mNewValues.put("word", "insert");
    mNewValues.put("frequency", "100");
    
    mNewUri = getContentResolver().insert(
        CONTENT_URI,   // the user dictionary content URI
        mNewValues     // the values to insert
    );

    Updating data

    利用 ContentResolver.update() 可達成對 Content Provider 的資料做更新動作,寫法如下 :
    // Defines an object to contain the updated values
    ContentValues mUpdateValues = new ContentValues();
    
    // Defines selection criteria for the rows you want to update
    String mSelectionClause = "locale" +  " LIKE ?";
    String[] mSelectionArgs = {"en_%"};
    
    // Defines a variable to contain the number of updated rows
    int mRowsUpdated = 0;
    
    /*
     * Sets the updated value and updates the selected words.
     */
    mUpdateValues.putNull("local");
    
    mRowsUpdated = getContentResolver().update(
        CONTENT_URI,      // the user dictionary content URI
        mUpdateValues     // the columns to update
        mSelectionClause  // the column to select on
        mSelectionArgs    // the value to compare to
    );


    Deleting data

    利用 ContentResolver.delete() 可達成對 Content Provider 的資料做刪除動作,寫法如下 :
    // Defines selection criteria for the rows you want to delete
    String mSelectionClause = "app_id" + " LIKE ?";
    String[] mSelectionArgs = {"user"};
    
    // Defines a variable to contain the number of rows deleted
    int mRowsDeleted = 0;
    
    // Deletes the words that match the selection criteria
    mRowsDeleted = getContentResolver().delete(
        CONTENT_URI,      // the user dictionary content URI
        mSelectionClause  // the column to select on
        mSelectionArgs    // the value to compare to
    );

    Reference: http://developer.android.com/guide/topics/providers/content-provider-basics.html

    2015年7月5日 星期日

    [擒猿基本功] Android app 支援多重螢幕

    這個世界存在著許多各式各樣大大小小、長長扁扁、寬寬窄窄的智慧手機,它們佔據了現代人的生活模式、行為思考與社交聯繫,每台手機看似獨立運作,但其實它們內部都連結到了同一個虛擬世界,這個虛擬世界,我們稱之為安卓大陸。

    是的,但今天沒有要寫擒猿記,而是要來寫 Android App 要如何支援多重螢幕,你一定覺得幹你娘很失望,沒關係,不要哭,堅強點。

    Supporting Multiple Screens

    根據 Android 官方文件,Android 定義了:
    • Screen size: 根據螢幕對角線長度定義 4 種 Size:
      • small
      • normal
      • large
      • xlarge
    • Screen density: 實體區域內的 dpi (dots per inch) 可想成是顯示品質,定義了以下 6 種:
      • ldpi (~120dpi)
      • mdpi (~160dpi)
      • hdpi (~240dpi)
      • xhdpi (~320dpi)
      • xxhdpi (~480dpi)
      • xxxhdpi (~640dpi)
    實際螢幕大小與 Screen size、 Screen density 的關係圖如下,會有些許誤差:


    Resolution 代表一個螢幕上實際的 pixel 點數,通常 app 不直接使用這個值,而是改用 dp (density-independent pixel),dp 為基於 160dpi 的螢幕 (medium 品質) 計算得到的 pixel 點數,通常 app 會利用這個值來定義 layout 長度,在 run-time 時 Android 系統則根據這個值與實際螢幕大小來做放大的動作,dp 與  pixel 轉換的公式如下:
    px = dp * (dpi / 160)
    舉例來說,在一個 240dpi 的螢幕上,layout中 1 dp 的長度表示在螢幕是實際顯示為 1.5 pixel 的長度。

    支援多重螢幕的最高境就就是要達成 Density Independence,就是 user 在任何大小不同的螢幕上跑你的 app 看起來都一樣的。欲達成 Density Independence,定義 layout 長度時就不該使用 pixel 為單位,應該改用 dp,如此 Android 系統才能幫我們放大成最適合的大小,然而,系統放大的結果可能會導致 bitmap 圖片過於模糊,因此最好再根據不同解析度螢幕提供不同的圖片。

    官方文件推薦以下幾種方式,能讓 Android 系統更有效處理多重螢幕的支援:
    1. 在 manifest 中指定 app 支援的 screen size
      • 在 manifest 利用 <supports-screens>  宣告可支援的 screen size,如此可以確保有在支援範圍內的裝置才能透過 Google Play 下載到這支 app。
    2. 為不同的 screen size 製作不同的 layout
      • 若需要對不同的螢幕的顯示方式做一些微調,則建議為其各自製作不同的 layout。
      • 不同的 layout 放置在不同的資料夾內,譬如 layout-xlarge/ 內放的就是給 extra-large螢幕顯示用的 layout,Android 系統根據 configuration qualifiers 的定義動態撈出需要的 layout 檔。
      • Android 3.2 (ApI Level 13) 之後應該使用 sw<N>dp 的形式來做更進階的分類,譬如放在 layout-sw600dp/ 資料夾內的 layout 檔案是給螢幕寬度至少 600dp 的裝置所使用。
    3. 為不同的 screen density 製作不同的 bitmap drawable
      • Android 系統會把 bitmap drawable (.png, .jpg, .gif, .9.png) 做放大的動作,若只放一種解析度的圖,則放大後可能會有變模糊的情況,因此最好為同一張圖製作多種解析度的副本。
      • Android 定義了 configuration qualifiers 讓不同解析度的圖可以放在不同資料夾下,譬如 drawable-hdpi/ 內放的圖就是給 high density 的裝置所使用。

    如何使用 configuration qualifiers

    Configuration qualifier 是一個字串可加在 resource 資料夾的名稱後方,協助 Android 系統分辨哪些 resource 是適合抓取來使用的。

    在 Project 的 res/ 下依據不同解析度建立不同資料夾,然後把這些解析度的 layout 或 drawable 放入到對應的資料夾下,檔案名稱必須要跟 default 資料夾下放的一樣。

    資料夾命名的格式如下:
    <resources_name>-<qualifier>
    resources_name 可以是 layout、drawable 等等 Android 的 standard resource
    qulifiler 用來區分不同解析度的螢幕,填入值可以如下表格所定義:
    Screen characteristic
    Qualifier
    Description
    Size
    small
    Resources for small size screens.
    normal
    Resources for normal size screens. (This is the baseline size.)
    large
    Resources for large size screens.
    xlarge
    Resources for extra-large size screens.
    Density
    ldpi
    Resources for low-density (ldpi) screens (~120dpi).
    mdpi
    Resources for medium-density (mdpi) screens (~160dpi). (This is the baseline density.)
    hdpi
    Resources for high-density (hdpi) screens (~240dpi).
    xhdpi
    Resources for extra-high-density (xhdpi) screens (~320dpi).
    xxhdpi
    Resources for extra-extra-high-density (xxhdpi) screens (~480dpi).
    xxxhdpi
    Resources for extra-extra-extra-high-density (xxxhdpi) uses (~640dpi). Use this for the launcher icon only, see note above.
    nodpi
    Resources for all densities. These are density-independent resources. The system does not scale resources tagged with this qualifier, regardless of the current screen's density.
    tvdpi
    Resources for screens somewhere between mdpi and hdpi; approximately 213dpi. 
    Orientation
    land
    Resources for screens in the landscape orientation (wide aspect ratio).
    port
    Resources for screens in the portrait orientation (tall aspect ratio).
    Aspect ratio
    long
    Resources for screens that have a significantly taller or wider aspect ratio (when in portrait or landscape orientation, respectively) than the baseline screen configuration.
    notlong
    Resources for use screens that have an aspect ratio that is similar to the baseline screen configuration.

    Android 3.2 之後擴充了qulifiler 參數的使用,可以更詳細的利用 dp 還制定符合裝置的規格,避免遇到如 5" 和 7" 螢幕都是 large 的 screen size,無法為其制定不同的 layout 或 drawable,其新增的 qulifiler 使用方示如下:
    Screen configuration
    Qualifier values
    Description
    smallestWidth
    sw<N>dp

    Examples:
    sw600dp
    sw720dp
    The fundamental size of a screen, as indicated by the shortest dimension of the available screen area. Specifically, the device's smallestWidth is the shortest of the screen's available height and width (you may also think of it as the "smallest possible width" for the screen). You can use this qualifier to ensure that, regardless of the screen's current orientation, your application's has at least <N> dps of width available for its UI.
    Available screen width
    w<N>dp

    Examples:
    w720dp
    w1024dp
    Specifies a minimum available width in dp units at which the resources should be used—defined by the <N> value. The system's corresponding value for the width changes when the screen's orientation switches between landscape and portrait to reflect the current actual width that's available for your UI.
    This is often useful to determine whether to use a multi-pane layout, because even on a tablet device, you often won't want the same multi-pane layout for portrait orientation as you do for landscape. Thus, you can use this to specify the minimum width required for the layout, instead of using both the screen size and orientation qualifiers together.
    Available screen height
    h<N>dp

    Examples:
    h720dp
    h1024dp
    etc.
    Specifies a minimum screen height in dp units at which the resources should be used—defined by the <N> value. The system's corresponding value for the height changes when the screen's orientation switches between landscape and portrait to reflect the current actual height that's available for your UI.

    下列幾種歸類出來的qulifiler 可以快速對照使用:
    • 320dp: a typical phone screen (240x320 ldpi, 320x480 mdpi, 480x800 hdpi, etc).
    • 480dp: a tweener tablet like the Streak (480x800 mdpi).
    • 600dp: a 7” tablet (600x1024 mdpi).
    • 720dp: a 10” tablet (720x1280 mdpi, 800x1280 mdpi, etc).

    如果還是不知道,請用密招,連機台下以下指令:
    dumpsys window | grep dp
    可以看到如下列結果:
    mCurConfiguration={1.0 ?mcc?mnc en_US ldltr sw400dp w682dp h368dp 240dpi nrml long land finger -keyb/v/h -nav/h skin=/system/framework/theme-res-garmin.apk s.5}
    因此針對該機台的 resources 應該放在 sw400dp 的 folder 底下。


    Android 系統會預設去抓取最符合當前裝置的 layout 和 drawable,若找不到符合該定義的 resource 時才會去抓取 default 的 resource (不指定 configuration qualifiers 的 資料夾),或是利用一些邏輯判斷去抓取 "best matching" 的 resource。但當 Android 系統所找到的 resource 都比目前系統的定義要來的大時,Android 系統會拒絕使用這些 resource 而回報為 crash。

    製作 Alternative drawables

    在為不同大小螢幕製作各自的 drawable 時,必須遵守 3:4:6:8:12:16 scaling ratio 的原則,這是口訣,也是心法,譬如說有一張圖在 medium-density 的螢幕上為 48x48 pixels,則該張圖片的 alternative drawables 大小如下:
    • 36x36 (0.75x) for low-density
    • 48x48 (1.0x baseline) for medium-density
    • 72x72 (1.5x) for high-density
    • 96x96 (2.0x) for extra-high-density
    • 144x144 (3.0x) for extra-extra-high-density
    • 192x192 (4.0x) for extra-extra-extra-high-density (for launcher icon only)

    2015年7月2日 星期四

    [擒猿心法篇] Android 中更新 UI 的方法

    Android 中只有 UI Thread 才能更新 UI,否則會發生 Exception。

    幹你娘,好煩阿!

    以下幾種方法可以協助達成更新 UI 的方法,紀錄一下:

    1. 呼叫 runOnUiThread

    http://developer.android.com/reference/android/app/Activity.html#runOnUiThread(java.lang.Runnable)
    final void
    Runs the specified action on the UI thread.
    使用方式如下:
    runOnUiThread.(new Runnable() {
        public void run() {
            myTextView.setText("update");
        }
    });

    2. 呼叫 View 本身的 post 方法

    http://developer.android.com/reference/android/view/View.html#post(java.lang.Runnable)
    boolean
    post(Runnable action)
    Causes the Runnable to be added to the message queue.
    使用方式如下:
    myTextView.post(new Runnable() {
        public void run() {
            myTextView.setText("update")
        }
    });

    3. 使用 Handler

    http://developer.android.com/reference/android/os/Handler.html
    透過 Handler 可以 發送 Message 請 UI Thread 處理更新 UI。
    使用方式如下:
    mHandler = new Handler() {
       @Override
       public void handleMessage(Message msg) {
          myTextView.setText("update");
          super.handleMessage(msg);
       }
    };
    
    Message msg = new Message();
    mHandler.sendMessage(msg);
    

    4. 使用 AsyncTask

    http://developer.android.com/reference/android/os/AsyncTask.html
    AsyncTask 可以在背景做運算,並把結果傳給 UI thread 進行更新,通會常用在需要處理大量運算等狀況下。
    使用方式如下:
    private class LoadImage extends AsyncTask {
        protected String doInBackground(String... url) {
            return loadImageFormNetwork(url[0]);
        }
        protected void onPostExecute(String result) {
            myImage.setImage(result);
        }
    }
    
    new loadImageTask().execute("www.xxx.com/image.png");
    


    reference: http://gameorprogram.blogspot.tw/2011/12/android-ui.html

    2015年6月28日 星期日

    [擒猿小技巧] JavaImp: VIM 中排版 Java import 的好幫手: JavaImp

    話說真男人就愛用 vim,但是編寫 Android 程式時常會需要自己排版 Java import 檔,揪麻煩,以下介紹一個幫忙排版 Java import 檔好用的 VIM plugin: JavaImp

    JavaImp:
    http://www.vim.org/scripts/script.php?script_id=325

    安裝方式:
    至以上網址下載 JavaImp.vim 放到 ~/.vim/plugin 中

    接著要在 .vimrc 中加一些設定:
    let g:JavaImpPaths = $HOME . "/JavaSrc," .
                       \ $HOME . "/Source/frameworks/base/core/java" 
    let g:JavaImpDataDir = $HOME . "/vim/JavaImp"
    其中 JavaImpPaths 指到 Java 和  Android source code 的根目錄,以逗號區隔
    而 JavaImpDataDir 為儲存 JavaImp 設定的目錄

    安裝完後第一次開啟 vim 需要輸入:
    :JIG
    如此便會建立起 JavaImp 所需要的索引

    使用放方式很簡單,將游標移到要加入 import 的位置,鍵入:
    :JI
    Magic! JavaImp 會把 import Path 加入至檔案頂端。

    鍵入:
    :JIS
    則會幫你把 import file 做好排序,個人喜歡在 .vimrc 中加入
    let g:JavaImpSortPkgSep = 1
    這樣排出來比較漂亮~~

    就醬吧,補個幹你娘 ㄎㄎ

    2015年6月25日 星期四

    [擒猿小技巧] Windows batch 從檔案撈出特定字串

    Yea~ 先罵幹你娘

    介紹一下撰寫 Windows batch 時要如何從檔案撈出特定字串。

    首先來看 C:\Users\WangLotus\AndroidStudioProjects\FuckYou\app\build.gradle 中有一段長這樣:
        defaultConfig {
            applicationId "com.iam.your.father"
            minSdkVersion 14
            targetSdkVersion 22
            versionCode 6
            versionName "3.1"
        }

    要把 versionName "3.1" 中的 "3.1" 抓出來,該如何做呢?寫法如下:

    先用 FINDSTR 指令抓出關鍵字,存成一個檔案
    FINDSTR "versionName" C:\Users\WangLotus\AndroidStudioProjects\FuckYou\app\build.gradle > tmp.txt

    再用 SET /p 指定從檔案中撈出值,存給一個變數
    SET /p VARTEMP=<tmp.txt

    最後再用以下指令取得該變數的 substring
    SET APP_VERSION=%VARTEMP:~21,3%
    其中 21 是 substring 開始位置,3 是 substring 的長度

    試著去把變數印出來就可以得到你要的結果囉~
    ECHO %APP_VERSION%
    3.1

    耶~~~

    2015年6月24日 星期三

    [擒猿手裡劍] Android 即時偵測 wifi 是否開啟連線

    今天要介紹一個小技巧,我們知道想要知道 Device 目前有否網路連線,可以利用 ConnectivityManager 來得到當下的網路資訊:
    private boolean isOnline() {
        ConnectivityManager cm =
            (ConnectivityManager) getSystemService(
                     Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getActiveNetworkInfo();
        return netInfo != null && netInfo.isConnectedOrConnecting();
    }

    不過這個方法需要程式主動去詢問,幹你娘我哪知道 user 何時開網路,所以更進階的作法是宣告一個 Receiver 去接收系統是否有開啟連線的訊息,如以下寫法

    首先在 AndroidManifest.xml 中宣告權限與一個 Receiver
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    .....
    <receiver android:name=".WifiReceiver">
        <intent-filter>
            <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
        </intent-filter>
    </receiver>
    

    WifiReceiver  繼承 BroadcastReceiver,寫法如下
    public class WifiReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            ConnectivityManager conMan =
                (ConnectivityManager) context.getSystemService(
                    Context.CONNECTIVITY_SERVICE);
            NetworkInfo netInfo = conMan.getActiveNetworkInfo();
    
            if (netInfo != null
                && netInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                Log.d("Lotus", "Wifi on");
            } else {
                Log.d("Lotus", "Wifi off");
            }
        }
    }
    

    easy~~

    2015年6月23日 星期二

    [擒猿密技篇] Android 利用 ImageButton 和 ImageView 實現點擊以縮放圖片的動畫效果

    看到標題怕了吧!直接來

    首先宣告 Layout 文件如下:
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <ImageButton
            android:id="@+id/imgBtn"
            android:layout_width="150dp"
            android:layout_height="100dp"
            android:layout_alignParentRight="true"
            android:scaleType="centerCrop"/>
    
        <ImageView
            android:id="@+id/imgBig"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="invisible" />
    </RelativeLayout>
    很簡單的解釋就是一個 container 下有一個 ImageButton 和一個 ImageView,ImageButton 裡放的是縮圖,點下去後,大圖的 ImageView 才會出現。
    
    
    程式中的寫法如下:
    private ImageButton mImgBtn;
    private ImageView mImgBig;
    private Animator mCurrentAnimator;
    
    .....
    
    final RelativeLayout layout = (RelativeLayout) View.inflate(
            getActivity(), R.layout.container, null);
    mImgBtn = (ImageButton) layout.findViewById(R.id.imgBtn);
    mImgBig = (ImageView) layout.findViewById(R.id.imgBig);
    
    mImgBtn.setImageDrawable(Drawable.createFromPath(img_path));
    mImgBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            enlargeImage(mImgBtn, img_path, mImgBig, layout);
        }
    });
    
    
    enlargeImage 的寫法是精隨所在:
    private void enlargeImage(final View thumbView,
                              String image_path,
                              final ImageView imgBig,
                              RelativeLayout container) {
    
        // 先清掉原本的動畫
        if (mCurrentAnimator != null) {
            mCurrentAnimator.cancel();
        }
    
        // 指定 Image 給 ImageView
        imgBig.setImageDrawable(Drawable.createFromPath(image_path));
    
        // 計算 imgBig 動畫開始和結束的範圍
        final Rect startBounds = new Rect();
        final Rect finalBounds = new Rect();
        final Point globalOffset = new Point();
    
        thumbView.getGlobalVisibleRect(startBounds);
        container.getGlobalVisibleRect(finalBounds, globalOffset);
        startBounds.offset(-globalOffset.x, -globalOffset.y);
        finalBounds.offset(-globalOffset.x, -globalOffset.y);
    
        float startScale;
        if ((float) finalBounds.width() / finalBounds.height()
              > (float) startBounds.width() / startBounds.height()) {
            // Extend start bounds horizontally
            startScale = (float) startBounds.height()
                                     / finalBounds.height();
            float startWidth = startScale * finalBounds.width();
            float deltaWidth = (startWidth - startBounds.width())
                                     / 2;
            startBounds.left -= deltaWidth;
            startBounds.right += deltaWidth;
        } else {
            // Extend start bounds vertically
            startScale = (float) startBounds.width()
                                     / finalBounds.width();
            float startHeight = startScale * finalBounds.height();
            float deltaHeight = (startHeight - startBounds.height())
                       / 2;
            startBounds.top -= deltaHeight;
            startBounds.bottom += deltaHeight;
        }
    
      // 隱藏縮圖並顯示大圖
        thumbView.setAlpha(0f);
        imgBig.setVisibility(View.VISIBLE);
    
        // 設定大圖縮放的軸度
        imgBig.setPivotX(0f);
        imgBig.setPivotY(0f);
    
        // 設定並撥放圖片放大動畫
        AnimatorSet set = new AnimatorSet();
        set
            .play(ObjectAnimator.ofFloat(imgBig, View.X,
                    startBounds.left, finalBounds.left))
            .with(ObjectAnimator.ofFloat(imgBig, View.Y,
                    startBounds.top, finalBounds.top))
            .with(ObjectAnimator.ofFloat(imgBig, View.SCALE_X,
                    startScale, 1f)).with(
                    ObjectAnimator.ofFloat(imgBig,
                    View.SCALE_Y, startScale, 1f));
        set.setDuration(500);
        set.setInterpolator(new DecelerateInterpolator());
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mCurrentAnimator = null;
            }
    
            @Override
            public void onAnimationCancel(Animator animation) {
                mCurrentAnimator = null;
            }
        });
        set.start();
        mCurrentAnimator = set;
    
        // 實現再次點擊後縮回原圖並隱藏大圖
        final float startScaleFinal = startScale;
            imgBig.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mCurrentAnimator != null) {
                    mCurrentAnimator.cancel();
                }
    
                // 設定播放圖片縮小動畫
                AnimatorSet set = new AnimatorSet();
                set.play(ObjectAnimator.ofFloat(imgBig,
                        View.X, startBounds.left))
                    .with(ObjectAnimator.ofFloat(imgBig,
                        View.Y,startBounds.top))
                    .with(ObjectAnimator.ofFloat(imgBig,
                        View.SCALE_X, startScaleFinal))
                    .with(ObjectAnimator.ofFloat(imgBig,
                        View.SCALE_Y, startScaleFinal));
                    set.setDuration(500);
                    set.setInterpolator(new DecelerateInterpolator());
                    set.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            thumbView.setAlpha(1f);
                            imgBig.setVisibility(View.GONE);
                            mCurrentAnimator = null;
                        }
    
                        @Override
                        public void onAnimationCancel(Animator animation) {
                            thumbView.setAlpha(1f);
                            imgBig.setVisibility(View.GONE);
                            mCurrentAnimator = null;
                        }
                    });
                set.start();
                mCurrentAnimator = set;
            }
        });
    }
    
    
    幹你娘好難喔。

    參考文件: http://developer.android.com/training/animation/zoom.html#animate