2010年12月15日 星期三

Android 開發教學筆記 - 排版更新或顯示問題

花了幾個小時,才發現這個蠢問題,特別紀錄一下。


在實作 Android 程式時,新增了 3 個 TextView,想要在特定情況下一起更新三個欄位,但很奇妙的只有第一個 TextView 會顯示出來,後面來兩個 TextView 卻看不到。花時間不斷地確認,最後才發現錯誤的地方是在 layout 的部份:


<TextView android:id="@+id/UpdateDate" android:layout_width="fill_parent" android:layout_height="fill_parent"></TextView>
<TextView android:id="@+id/News1" android:layout_width="fill_parent" android:layout_height="fill_parent"></TextView>
<TextView android:id="@+id/News2" android:layout_width="fill_parent" android:layout_height="fill_parent"></TextView>


有發現到了嗎?因為 layout_height 都是 fill_parent 的數值,將導致只又 UpdateDate 這個 TextView 會佔滿剩下的螢幕空間,導致 News1 和 News2 不會顯示出來,再加上一開始沒有給初始值,剛好沒看到這個現象。


修正方式,改使用 wrap_content 即可:


<TextView android:id="@+id/UpdateDate" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/News1" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/News2" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>


紀錄一下,提醒自己。


2010年12月14日 星期二

[Java] Regular Expression 與 HttpURLConnection 練習

年初,用 PHP 寫了這個,[PHP] 使用官方 Plurk API 實作簡單的機器人 - 靠機器人救 Karma!以 Yahoo News 為例,年終時,給他拿來當作 Android 程式的練習題目,結果弄了半天,發現 Java 語法熟練度很差,因此乾脆跑回去練習 Java 了!需搞懂的就是如何使用 Regular Expression 和網路的連線處理。


簡易範例:


import java.io.*;
import java.util.regex.*;
import java.net.HttpURLConnection;
import java.net.URL;

class Test
{
    public static void report( String message )
    {
        System.out.println( "[Report] " + message );
    }
    public static void main(String argv[])
    {
        HttpURLConnection con = null;
        try
        {
//*
            URL url = new URL("http://tw.yahoo.com");
            con = (HttpURLConnection) url.openConnection();
            con.setReadTimeout(10000);
            con.setConnectTimeout(15000);
            con.setRequestMethod("GET" );
            con.addRequestProperty("User-Agent","Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9");
            con.setDoInput(true);
            con.connect();

            BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8" ));
// */
//            BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream("fetch.html"), "UTF-8" ));
            String n , result = "";
            while( ( n = reader.readLine() ) != null )
                result += n;

//            BufferedWriter out = new BufferedWriter( new FileWriter("fetch.html") );out.write(result);out.close();
//            System.out.println( "Result:" + result );

            String pattern;
            int at;

            pattern = "<label class=\"img-border clearfix\">";
            if( ( at = result.indexOf(pattern) ) < 0 )
            {
                report( "format error 1" );
                return;
            }
            result = result.substring( at );

            pattern = "<ol class=\"newsad clearfix\">";
            if( ( at = result.indexOf(pattern) ) < 0 )
            {
                report( "format error 2" );
                return;
            }
            result = result.substring( 0 , at  );

            pattern = "<h3[^>]*>[^<]*<a href=\"(.*?)\"[^>]*>(.*?)</a></h3>";
            Pattern p = Pattern.compile( pattern , Pattern.CASE_INSENSITIVE | Pattern.DOTALL );
            Matcher m = p.matcher( result );

            while( m.find() )
            {
                report( "\n==== Get === \n" + m.group() + "\n" );
                report( "URL: " + m.group(1) );
                report( "Title: " + m.group(2) );
            }
        }
        catch( Exception e )
        {
            report( "Error:" + e );
        }
        finally
        {
            if ( con != null )
                con.disconnect();
        }
    }
}


執行結果:


$ javac Test.java && java Test
[Report]
==== Get ===
<h3><a href="news/a/h1/t/*http://tw.news.yahoo.com/article/url/d/a/101214/5/2iy8o.html" title="最新!美元弱 新台幣升破30">最新!美元弱 新 台幣升破30</a></h3>

[Report] URL: news/a/h1/t/*http://tw.news.yahoo.com/article/url/d/a/101214/5/2iy8o.html
[Report] Title: 最新!美元弱 新台幣升破30
[Report]
==== Get ===
<h3><a href="news/a/h2/t/*http://tw.news.yahoo.com/article/url/d/a/101214/2/2iy6j.html" title="情人多愛你 手碰觸方式露餡">情人多愛你 手碰 觸方式露餡</a></h3>

[Report] URL: news/a/h2/t/*http://tw.news.yahoo.com/article/url/d/a/101214/2/2iy6j.html
[Report] Title: 情人多愛你 手碰觸方式露餡


說真的,細算的話,五年前 Java 是我最常用的語言,至少用了一年多,但如今什麼都忘光光了!


2010年12月13日 星期一

Android 開發教學筆記 - 簡單設定 Google Maps

參考資料:



想要在 Android 上頭使用 Google Maps 的功能,有幾道流程:



  1. 申請 Android Maps API Key

  2. 建立使用 Google APIs 的模擬器

  3. 建立使用 Google Maps API 的 projects (在此 Build Target 使用 Google APIs/Google Inc./2.2/8 )

  4. 設定 Android Application 環境,使用 INTERNET permissions 及相關使用的 lib

  5. 設定 MapView 的 layout

  6. 設定程式碼


首先關於取得 Google Maps API Key,此部份跟一般申請上的不一樣,需使用 Android 的申請 Sign Up for the Android Maps API - Android Maps API - Google Code,此時會需要一組認證指紋(MD5),而這組序號用在開發程式的階段(debug mode),而當你要將程式上架時,必須在用另一組(release mode),細節請參考 Getting the MD5 Fingerprint of the SDK Debug Certificate,在此就依 debug mode 申請一組:


$ keytool -list -keystore ~/.android/debug.keystore
...
Certificate fingerprint (MD5): 94:1E:43:49:87:73:BB:E6:A6:88:D7:20:F1:8E:B5:98


其中認證指紋(MD5)為 94:1E:43:49:87:73:BB:E6:A6:88:D7:20:F1:8E:B5:98 (此為範例,請輸入自己產生的),帶著這組就可以去申請 Android Maps API Key 囉,請輸入到 My certificate's MD5 fingerprint 欄位。送出後,頁面將顯示 "您的金鑰" 、 "金鑰適合所有使用以下指紋憑證所簽署的應用程式" 和 "此處提供您 xml 配置的範例",共三筆資。請保存好,這邊只會使用"您的金鑰"。


建立 Android API 模擬器,如果你已經有設定一台可運行 Google APIs 2.2 的話,那可以略過此步。


[Eclipse]->[Window]-> [Android SDK and AVD Manager]->[New]

Name: Map
Target: Google APIs(Google Inc.) - API Level 8
    
按下 Create AVD 及建立完成


建立一個專案


[Eclipse]->[File]->[New]->[Android Project]

Project name: MyMap
Build Target: Google APIs, Google Inc., 2.2, 8
Application name: MyMap
Package name: com.test.map
Create Activity: MyMap
Min SDK Version: 8

設定完就可以按 Finish 囉


Android Project Structure


緊接著設定 Application 的權限和相關 lib 部份,請在左邊點選 AndroidManifest.xml 檔案,此時右邊視窗則顯示該檔相關的設定,可以在底部找到 Manifest/Application/Permissions/Instrumentation/AndroidManifest.xml 等子頁面切換,在此切換 AndroidManifest.xml 分頁,直接編輯此 xml 檔案,在 <application> 裡頭增加 <uses-library android:name="com.google.android.maps" /> 資訊,在 <manifest> 裡增加 <uses-permission android:name="android.permission.INTERNET" />,分別代表要使用函式庫:(com.google.android.maps)和需要使用網路資源(android.permission.INTERNET)


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.test.map"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MyMap"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <uses-library android:name="com.google.android.maps" />
    </application>
    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>


設定完應用程式執行環境後,接著設定排版部份(layout),透過左邊視窗請點選 main.xml 檔案,直接用下面的資訊覆蓋掉:


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

    <com.google.android.maps.MapView
        android:id="@+id/mapview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:clickable="true"
        android:apiKey="Your Maps API Key"
    />

</RelativeLayout>


其中 Your Maps API Key 請改填"您的金鑰",如此一來即完成排版的部份


最後,則是修改程式碼的部份,請點選 MyMap.java 檔案



  1. 增加 import com.google.android.maps.*;

  2. 將 public class MyMap extends Activity 修改為 public class MyMap extends MapActivity

  3. 實做必要函數 boolean isRouteDisplayed()


完整程式碼:


package com.test.map;

import android.app.Activity;
import android.os.Bundle;
import com.google.android.maps.*;

public class MyMap extends MapActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    @Override
    protected boolean isRouteDisplayed() {
        return false;
    }

}


如此一來,則可以執行,並看到模擬器顯示 Google Maps 囉!


AndroidMaps


上述算是一個簡單的設定流程,但僅供了解流程而已,因為產生的程式除了呈現一個 Google Maps 外,沒有太多的互動,因此,如果想要增加一些使用者互動部份,如 Zoom In/Zoom Out,請在 onCreate 加上兩行程式碼:


MapView mapView = (MapView) findViewById(R.id.mapview);
mapView.setBuiltInZoomControls(true);


請記得加到 setContentView(R.layout.main); 後面,如果加在前面會導致程式 crash 喔,可以看看 setBuiltInZoomControls method for MapView causes application to crash 的敘述,避免 crash 也可以改成:


MapView mapView;
if( ( mapView = (MapView) findViewById(R.id.mapview) ) != null )
    mapView.setBuiltInZoomControls(true);


但沒擺在 setContentView(R.layout.main); 後面,還是沒用的喔。


AndroidMapsZoom


其他資料:



2010年12月10日 星期五

冰箱 Get!

冰箱、食材


冰箱啊~這是多麼奢侈的設備啊。記得大學時期,好像是升大三還升大四時,室友在校園討論板上,終於!搶到了冰箱,一行人推著推車很高興地去跟學長買二手的,我記得好像賣 1200 的樣子,還附一個烤箱跟平底鍋,當時室友們都很黑皮!對我而言倒覺得還好,因為我只三餐偶爾宵夜而已,並且不怎喝飲料,冰箱對我而言真的有跟沒有是一樣的。後來,過了幾個月後,那台冰箱的使用度真的降到了冰點,除了一開始很黑皮冰的飲料外,好像就沒什麼特別了。


直到幾天前,多虧同事的幫忙,在公司的討論網上終於買了一台二手冰箱,且賣冰箱的正巧一年前坐在我座位附近,真巧啊!就這樣重複著大學時的步驟,借推車、運冰箱和定位。對了,搬冰箱最好搬完靜待一會兒喔,所以我是隔天下班後才正式啟用它。


據同事的說法,擔心冷藏不冷,所以不怎使用,因此,第一件事就是冰塊的測試 XD 空蕩蕩的冰箱只冰那一小盤的生水。慶幸地,一切 OK ,就算我把冰箱的強度降到最低,早上的生水,下班時還是會結冰,看來運氣不錯。


沒多久後,就跟同事去賣場,買了一包僅 49 元的韭菜水餃,成了第二住進冰箱的食材。今晚上班就去附近的果菜賣場買了相關的工具,如鍋、瓢子等設備,順便試了買了一包料理包以及一把青菜和一包豆皮,看來未來又有東西可學了,來煮點東西訓練一下。


2010年12月1日 星期三

[Javascript] 使用 jQuery 把表單資料紀錄在 cookie 裡

做了一個有很多選項要填寫的表單,但由於一些環境上的考量,有點懶的把已填寫過過得表單資料記錄在 server 端,因此就想到稍微惡搞一下,把表單資料都紀錄在 client 上!由於該表單也沒有啥機密資訊,所以也不用太擔心什麼。


紀錄方式:


當使用者按下送出表單的那一刻時,除了會加上一些判斷,確認資料是否有輸入完整,而後可以做的小動作就是把表單的資料都儲存在 cookie 裡,以後有需要可以從 cookie 撈出來幫忙填寫。


相關環境:



  • jQuery

    • <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.3.min.js"></script>



  • jQuery Plugin - Cookie (非必要)

    • <script type="text/javascript" src="http://plugins.jquery.com/files/jquery.cookie.js.txt"></script>

    • 自己管也可以,但我有點懶的回顧 cookie 語法,就改用這個 plugin 啦,用 $.cookie( 'Name' , 'Value' , { path: '/', expires: 365 } ); 儲存,用 $.cookie( 'Name' ) 取回數值




用法:


<form method="POST" onsubmit="return checkData();">
    <dl>
        <dt><button onclick="reload(); return false">讀取前次資料</button></dt>

        <dd>&nbsp;</dd>


        <dt>姓名</dt>
        <dd><input class="cookie" name="builder_name" style="width:50%;"/></dd>
        <dd>&nbsp;</dd>

        <dt>喜愛運動</dt>
        <dd>
            <select class="cookie" name="status">
                <option value="0" selected>否</option>
                <option value="1">是</option>
            </select>
        </dd>
        <dd>&nbsp;</dd>


        <dt>狀態</dt>

        <dd><input type="checkbox" name="status"/> 已婚</dd>
        <dd>&nbsp;</dd>

        <dd>&nbsp;</dd>

       
<dt><button type="submit">執行</button></dt>

    </dl>
</form>


<script type="text/javascript">

function checkData()
{
    // ... do check ...

    // ... do save ...
    save();

    return true;
}

function save()
{
    $( '.cookie' ).each( function(){
        var target_cookie = '__' + $(this).attr('name');
        var data = $(this).val();
        if( $(this).attr('type') == 'checkbox' )
            data = $(this).is(':checked') ? 'on' : 'off';
    
        $.cookie( target_cookie, data , { path: '/', expires: 365 } );
        //console.log( target_cookie +' : ' + $(this).attr('type') + ' : ' + $.cookie( target_cookie ) + ' : ' + data );
    });
}

function reload()
{
    $( '.cookie' ).each( function(){
        var target_cookie = '__' + $(this).attr('name');
        if( $.cookie( target_cookie ) )
        {
            if( $(this).attr('type') == 'checkbox' )
            {
                if( $.cookie( target_cookie ) == 'on' )
                    $(this).attr('checked', true);
                else
                    $(this).attr('checked', false);
            }
            else if( $(this).attr('type') == 'select-one' && $(this).get(0) && $(this).get(0).options && $(this).get(0).options.length )
            {
                var data = $.cookie( target_cookie );
                var options_list = $(this).get(0).options;
                for( var i=0 , cnt = options_list.length ; i<cnt ; ++i )
                {
                    if( options_list[i].value == data )
                    {
                        $( options_list[i] ).attr( 'selected' , 'selected' );
                        break;
                    }
                }
                $(this).trigger('change');  // 有些 select 可以搭配 change 事件
            }
            else
                $(this).val( $.cookie( target_cookie ) );
            //console.log( target_cookie +' : ' + $(this).attr('type') + ' : ' + $.cookie( target_cookie ) );
        }
    });
}
</script>


如此一來,就可以儲存表單文字、checkbox以及 select 等,都可以處理。這邊比較投機的是對要儲存的表單,多設一個 class 數值,用來讓 jQuery 準確地撈出來。