2015年12月13日 星期日

jQuery之Deffered的應用

jQuery有Deferred及Promise物件,可以幫助我們處理異步函式的callback順序關係。
在這邊舉兩個例子來說明其可能的兩個應用:

一、用Deferred實現異步函式的callback順序關係
Deferred可以實現異步函式的callback順序關係,在下例中,我們有三個函式,f1()、f2()、f3(),其中都有setTimeout模擬一秒的異步延遲,並且希望能f1做完才執行f2、f2做完才執行f3。如果我們沒有處理好callback的順序關係的話,就會發生f1的setTimeout還沒執行完就執行f2的情況。

在此例中,我們利用Deferred.then()來確保f1,f2,f3的執行順序關係,在f1,f2,f3中,會個自建立一個Deferred物件並傳回。
而setTimeout結束後,會呼叫Deferred.resolve()來告知函式執行完成,並可以選擇送進一個參數(param)來給之後的callback函式使用,接著會執行then()裡面的函式。

以上說明是簡單的行為了解。其實真正的實作比較複雜,可以參考jQuery之Deferred的原理,在這邊稍微講一下大概的背後過程:

  1. f1()回傳了第一個Deferred物件。
  2. f1('f1').then(f2)建立了第二個Deferred物件,並為第一個Deferred物件設定done(ff2),ff2是callback函式,ff2的內容是執行f2(),並取得f2()回傳的第三個Deferred物件,接著設定第三個物件的done(fff2),其中fff2=第二個Deferred物件.resolve。
  3. 所以當呼叫then(f2)時,會回傳一個Deferred物件(即第二個Deferred物件。
  4. 當f1()裡呼叫defer.resolve()時,會執行f2()。
  5. 當f2()裡呼叫defer.resolve()時,會引發第三個Deferred物件的done(),接著就會引發第二個Deferred物件的resolve()。
  6. 以此類推,第二個Deferred物件被resolve()後就會去執行f3()。


HTML:
<div id='result'>
  Result:
</div>
Javascript:
function f1(param) {
  var defer = $.Deferred();
  setTimeout(function() {
    $('#result').html($('#result').html() + '<br/>f1 got parameter : ' + param);
    //將'f2'當參數傳給don()或then()之後的function
    defer.resolve('f2');
  }, 1000);
  return defer;
}

function f2(param) {
  var defer = $.Deferred();
  setTimeout(function() {
    $('#result').html($('#result').html() + '<br/>f2 got parameter : ' + param);
    //將'f3'當參數傳給don()或then()之後的function
    defer.resolve('f3');
  }, 1000);
  return defer;
}

function f3(param) {
  var defer = $.Deferred();
  setTimeout(function() {
    $('#result').html($('#result').html() + '<br/>f3 got parameter : ' + param);
    //defer.resolve(param + ' f3');
  }, 1000);
  return defer;
}
//Deferred參數的傳遞
f1('f1').then(f2).then(f3);
二、合併多個異步函式的callback處理,即個別異步函式都執行完後才執行callback

在此例中我們用到了$.when(),它可以被傳入多個Deferred或Promise物件,並在所有的Deferred或Promise物件都被resolve()或reject()後才執行callback。

在這裡我們有三個動畫,都為一串文字由右往左移動,每個動畫的時間都不一樣,第一個是用CSS的動畫,其他兩個是用jQuery的animate()做的動畫。

對於每個動畫,我們都建立一個新的Deferred物件給它,並在動畫執行完後將它得到的Deferred給resolve()。而每個Deferred物件我們都放進一個叫des的Array中。

最後我們把Array裡面的三個Deferred物件丟給$.when()裡當參數,利用then()或done()來設定三個Deferred物件都resolve()後才要執行的callback。

P.S.

  1. 因為$.when()不能接受Array型式的參數,所以這裡利用了apply($,des)來將des中的內容傳給when(),其中第一個參數為要代替when()中的this的參考,第二個參數是一個Array,其內容為要傳給when()的參數。
  2. resolveDeferred()接受一個Deferred物件,並傳回一個function,在傳回的function中其this指向呼叫resolveDeferred的物件;而deferred指向function被建立的域(即resolveDeferred())中的deferred。(可參見閉包(Closure))


HTML:
<div class='animSection'>
  <div id='anim1'>Watch me move1</div>
  <div id='anim2'>Watch me move2</div>
  <div id='anim3'>Watch me move3</div>
</div>
<br/>
<div id='text' class="text">Detect Result:</div>
CSS:
.animSection div{
  padding-left: 100%;
}

.animClass {
  animation-name: myAnim;
  animation-duration: 6s;
  animation-fill-mode: forwards;
}

@keyframes myAnim {
  0% {
    padding-left: 100%;
  }
  100% {
    padding-left: 0%;
  }
}
Javascript:
var des = []; //用來放Deferred或Promise物件的array
var deferred;

deferred = $.Deferred();
des.push(deferred.promise()); 
//也可以寫成des.push(deferred),deferred.promise()回傳的為Promise物件,為不可修改狀態只供查詢狀態的物件,即沒有resolve()等方法
//
$('#anim1').addClass('animClass').one('animationend', resolveDeferred(deferred));

deferred = $.Deferred();
des.push(deferred.promise());
$('#anim2').animate({
  paddingLeft: "-=100%"
}, 9000, resolveDeferred(deferred));

deferred = $.Deferred();
des.push(deferred.promise());
$('#anim3').animate({
  paddingLeft: "-=100%"
}, 5000, resolveDeferred(deferred));
//因為$.when()只能接收一個一個用逗號分開的參數,所以利用apply來將array以個別
//參數的方式傳給when(),when()可接受Deferred及Promise物件
$.when.apply($, des).done(function() {
  $('#text').html($('#text').html() + '<br/>All finished!');
});
//也可以寫成以下,then可以給兩個參數,第一個為done()的callback函式,
//第二個為fail()的callback函式
//$.when.apply($, des).then(function(){
//	$('#text').html($('#text').html() + '<br/>All finished!');
//});

function resolveDeferred(deferred) {
  return function() {
    $('#text').html($('#text').html() + '<br/>#' + this.id + ' finished!');
    deferred.resolve();
  }
}

2015年12月12日 星期六

jQuery之Deferred的原理(參看源碼)

了解了jQuery的Callbacks以後,我們可以藉由觀看jQuery的源始碼(jquery-1.11.3.js)來較清楚地了解Deferred到底做了什麼事及怎麼實現的,以更好的運用Deferred及其相關API。

首先,我們先把Deferred的jQuery源碼貼出來:

 Deferred: function( func ) {
  var tuples = [
    // action, add listener, listener list, final state
    [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
    [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
    [ "notify", "progress", jQuery.Callbacks("memory") ]
   ],
   state = "pending",
   promise = {
    state: function() {
     return state;
    },
    always: function() {
     deferred.done( arguments ).fail( arguments );
     return this;
    },
    then: function( /* fnDone, fnFail, fnProgress */ ) {
     var fns = arguments;
     return jQuery.Deferred(function( newDefer ) {
      jQuery.each( tuples, function( i, tuple ) {
       var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
       // deferred[ done | fail | progress ] for forwarding actions to newDefer
       deferred[ tuple[1] ](function() {
        var returned = fn && fn.apply( this, arguments );
        if ( returned && jQuery.isFunction( returned.promise ) ) {
         returned.promise()
          .done( newDefer.resolve )
          .fail( newDefer.reject )
          .progress( newDefer.notify );
        } else {
         newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
        }
       });
      });
      fns = null;
     }).promise();
    },
    // Get a promise for this deferred
    // If obj is provided, the promise aspect is added to the object
    promise: function( obj ) {
     return obj != null ? jQuery.extend( obj, promise ) : promise;
    }
   },
   deferred = {};

  // Keep pipe for back-compat
  promise.pipe = promise.then;

  // Add list-specific methods
  jQuery.each( tuples, function( i, tuple ) {
   var list = tuple[ 2 ],
    stateString = tuple[ 3 ];

   // promise[ done | fail | progress ] = list.add
   promise[ tuple[1] ] = list.add;

   // Handle state
   if ( stateString ) {
    list.add(function() {
     // state = [ resolved | rejected ]
     state = stateString;

    // [ reject_list | resolve_list ].disable; progress_list.lock
    }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
   }

   // deferred[ resolve | reject | notify ]
   deferred[ tuple[0] ] = function() {
    deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
    return this;
   };
   deferred[ tuple[0] + "With" ] = list.fireWith;
  });

  // Make the deferred a promise
  promise.promise( deferred );

  // Call given func if any
  if ( func ) {
   func.call( deferred, deferred );
  }

  // All done!
  return deferred;
 }

再來一步步解析:

jQuery的Callbacks

jQuery有一個對控制異步函式時很好用的類別Deferred,還有它的簡少API版本,Promise(無法更改state)。

我們可以藉由觀看jQuery的源始碼(jquery-1.11.3.js)來較清楚地了解Deferred到底做了什麼事及怎麼實現的,以更好的運用Deferred及其相關API。

在了解Deferred之前,我們可以先來了解Callbacks這個類別。在這邊用形象的方式來了解Callbacks的運作方式。

Callbacks可以被當成是一個佇列式的列隊(先進先出,FIFO),我們可以把想要做的function傳進列隊中(add()),並在想要的時候觸發列隊中的function執行(fire() or fireWith()),在這邊是一次執行所有的function,雖然有照順序開始執行,但並無法保證一個function執行完才執行下一個(例如要花時間的異步函式)。

而Deferred則是利用Callbacks的性質來實作的,基本上就是預先將異步函式做完時要執行的回調函式先放到列隊中,並執行異步函式,等異步函式執行完後手動決定要觸發列隊裡函式並執行的時機。

jQuery的Callbacks有以下常用的API function
  1. $.Callbacks([flags]) : 創建並返回一個Callbacks類別。
    • flags : 可選,可以接受多個,用空格的String來表示,例如有以下幾種:
      •  once:
        • 列隊只能被觸發一次(fire)。
      •  memory:
        • 列隊觸發後,一有函式放進(add)列隊會被馬上執行。
      •  unique:
        • 列隊中的同樣函式(指的是其參考一樣的 reference)再一次觸發時只會被執行一次。
  2. Callbacks.add(callbacks) : 將callbacks函式放進Callbacks列隊中。
  3. Callbacks.fire([arguments]) : 觸發列隊執行其中的函式。
    • arguments : 可選,可傳入參數給列隊中各函式當input value。
  4. Callbacks.fireWith([context] [, arguments]) : 跟fire()相似,也是觸發列隊執行其中的函式,不過可以指定上下文(context),即函式中的this指誰。
  5. Callbacks.disable() : 將callback列隊disable掉,即之後不能在做fire()之類的呼叫(呼叫了也不會有行為)
範例 :

2015年12月5日 星期六

CSS與jQuery的動畫(animation)比較

jQuery與CSS都能實現對網頁上的DOM進行動畫特效,但兩者有一些差異,特別在這邊紀錄:

jQuery的動畫:

  1. 可使用$(selector).animate(styles,speed,easing,callback)來進行動畫,為有數字參數的css屬性進行動畫(無法用於只有字串值的css屬性,例如background-color:red)。
  2. 可使用$(selector).stop(stopAll,goToEnd)來停止動畫,可以只停止當前動畫並繼續 queue中的其他動畫。也可以停止當前動畫並情空動畫列隊(只限queue中的animation列隊成員)(參見queue),此時可以選擇直接將元素(DOM element)的動畫效果跳至當前動畫的最後狀態。
  3. 沒有暫停的方法(pause),即無法中途暫停,之後再從暫停的地方播放剩餘的動畫(resume)。目前要有pasue、resume的方法只能用plug-in等的方式解決,這裡有一些還沒試過的plug-in可以參考: PausejQuery-FxQueues
  4. 動畫結束後並不會引發animation事件。
CSS的動畫
  1. 為DOM element加上附有動畫屬性的CSS來實現動畫。
  2. 在CSS中使用animation-name屬性來指定keyframes (參見CSS3 Animations)。故無法同時在同一個DOM element上指定兩個animation-name的CSS屬性,會被最後決定的屬性覆蓋過去,即一次只能一個動畫。
  3. 可以設定許多不同的屬性來達到不同的效果。例如用animation-iteration-count來設定重覆次數
  4. 可以使用animation-play-state來設定運行(running)、暫停(paused),pased及running可以互相切換,動畫不會被停止無法繼續播(即paused之後再running等同resume效果)。
  5. 動畫播放完以後nimation-play-state會被設為paused。
  6. 動畫無法停止
  7. animation-timing-function可以設定時間與output效果的關係(參見<timing-function>),常用的有可以用來做逐幀動畫的steps(number_of_steps, direction),作用為設定output效果為步進函數(step function),number_of_steps代表切割動畫(動畫指瀏覽器算出來的漸進動畫output效果)為幾幀,direction代表步進的不連續點為每幀的start處還是end處(參見<timing-function>CSS3 timing-function: steps() 詳解小tip: CSS3 animation漸進實現點點點等待提示效果)。
  8. 動畫結束後發出animationend事件,可用JavaScript的addEventListener('animationend',callbackFunction)或jQuery的bind('animationend',callbackFuncion)來獲取事件,但是如果動畫class被移除或animation-play-state被設成paused並不會引發animationend事件。

JavaScript, jQuery 的Event機制 (Capturing, Bubbling)

在Javascript的W3C標準中,當Evnet(事件)發生時,例如click事件、animationEnd事件、hover事件等,Event會以由外到內(Capturing)、再由內到外(Bubbling)的方式傳遞。

我們可以用JavaScript的方法,addEventListener(eventName , callbackFunction , capturOrBubble)
來設置Evnet的監聽器,其中

第一個參數eventName為一個String,表示事件名稱,跟jQuery的bind()及one()不同,不可用空白分隔多個事件。

第二個參數是一個callback方法,可以選擇傳入event參數來得到event資訊,例如可以用event.target或event.srcElement (IE適用) 來得到Event作用的JavaScript對像。

我們也可以用Jquery的方法,bind(eventName,callbackFunction),來做到一樣的事情,不過要要注意bind()的callbackFunction傳入的event不是JavaScript的Event物件,而是被包裹成jQuery版的Event物件,雖然一樣有event.target,但是確沒有event.srcElement,如果要拿到JavaScript版的Event物件,可以使用event.origianlEvent來得到。

現在用下面這個例子來說明:

html:

<div id='1'>
  <div id='2'>
    <div id='3' class='animClass'>Watch me move</div>
  </div>
</div>
<div id='text'></div>


CSS :
.animClass {
  animation-name: myAnim;
  animation-duration: 1s;
}



@keyframes myAnim {
  0% {
    padding-left: 100%;
  }
  100% {
    padding-left: 0%;
  }
}

JavaScript (使用addEventListener) :

document.getElementById('1').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#1 catched animation end from #' + event.target.id + '! (Capturing)</br>');
}, true);

document.getElementById('2').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#2 catched animation end from #' + event.target.id + '! (Capturing) </br>');
}, true);

document.getElementById('3').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#3 catched animation end from #' + event.target.id + '! (Capturing) </br>');
}, true);

document.getElementById('1').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#1 catched animation end from #' + event.target.id + '! (Bubbling)</br>');
}, false);

document.getElementById('2').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#2 catched animation end from #' + event.target.id + '! (Bubbling) </br>');
}, false);

document.getElementById('3').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#3 catched animation end from #' + event.target.id + '! (Bubbling) </br>');
}, false);

其結果是:

2015年11月28日 星期六

Android - 使用MediaPlayer 播放音效

在Android中,MediaPlayer類別可以用來播放音效,只要給定音效源的Uri,不管是SD Card、手機中、還是網路上的聲音源,都可以利用其來播放,不過要注意的是,在Android 4.0以上規定了更嚴格的權限規範,所以如果聲音檔不是用正常管道放進SD Card的(例如:用IDE去強制放進去的),就不可以播放。

下面就來演示一個簡單的播放例子:

AndroidManifest.xml :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.gjun.mediaplayerpractice" >

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".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>
    </application>

</manifest>


MainActivity :
package com.example.gjun.mediaplayerpractice;

import android.media.MediaPlayer;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.SeekBar;
import android.widget.Toast;

import java.io.IOException;

public class MainActivity extends ActionBarActivity {


    private SeekBar control;
    private MediaPlayer mediaPlayer;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //取得元件
        control = (SeekBar) findViewById(R.id.control);
        // 註冊SeekBar元件進度改變事件
        control.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                // 一定要判斷是使用者的操作,因為播放過程也會更改進度
                if (fromUser) {
                    // 移動音樂到指定的進度
                    mediaPlayer.seekTo(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

    public void clickPlay(View view) {
        // 開始播放
        if (mediaPlayer != null){
            mediaPlayer.start();
            // 執行顯示播放進度的AsyncTask物件
            new MyPlayTask().execute();
        }else if (!mediaPlayer.isPlaying()){
            new NetworkTask().execute("http://www.ntust20140311ai2.comuv.com/song01.mp3");
        }
    }

    public void clickPause(View view) {
        // 暫停播放
        if (mediaPlayer != null && mediaPlayer.isPlaying()){
            mediaPlayer.pause();
        }
    }

    public void clickStop(View view) {
        // 停止播放
        if (mediaPlayer != null){
            mediaPlayer.stop();
            // 回到開始的位置
            mediaPlayer.seekTo(0);
            control.setProgress(0);
        }
    }

    // 從指定的網路資源載入音樂並開始播放
    private class NetworkTask extends AsyncTask<String, Void, Void> {
        @Override
        protected Void doInBackground(String... networkPah) {
            // 建立網路資源音樂檔案Uri物件
            Uri uri = Uri.parse(networkPah[0]);
            mediaPlayer = MediaPlayer.create(MainActivity.this,uri);
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);

            control.setMax(mediaPlayer.getDuration());
            // 註冊播放完畢監聽事件
            // 切換按鈕為可播放
            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mediaPlayer) {
                    clickStop(null);
                    Toast.makeText(MainActivity.this, "Play End!",Toast.LENGTH_SHORT).show();
                }
            });
            // 開始播放
            mediaPlayer.start();
            Toast.makeText(MainActivity.this,"Play Start!",Toast.LENGTH_SHORT).show();
        }
    }

    // 在播放過程中顯示播放進度
    private class MyPlayTask extends AsyncTask<Void, Integer, Void> {
        @Override
        protected Void doInBackground(Void... voids) {
            while (mediaPlayer.isPlaying()){
                this.publishProgress(mediaPlayer.getCurrentPosition());
            }
            return null;
        }
        // 設定播放進度
        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            control.setProgress(progress[0]);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}


activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/retangle_drawable"
        android:layout_margin="6sp"
        android:padding="6sp" >
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/play_icon"
            android:onClick="clickPlay" />
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/pause_icon"
            android:onClick="clickPause" />
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/stop_icon"
            android:onClick="clickStop" />
        <SeekBar
            android:id="@+id/control"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/retangle_drawable"
        android:layout_margin="6sp"
        android:padding="6sp" >

        <ImageButton
            android:id="@+id/record_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/record_dark_icon"
            android:onClick="clickRecord" />
        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/play_icon"
            android:onClick="clickRecordPlay" />
        <ProgressBar
            android:id="@+id/record_volumn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="6dp"
            android:layout_marginRight="6dp"
            android:max="15"
            style="@android:style/Widget.ProgressBar.Horizontal" />
    </LinearLayout>

</LinearLayout>

源碼下載:
MediaPlayerPractice.7z

2015年11月21日 星期六

Android - 使用SoundPool播放短音效

有時候我們在寫Android程式時,只是想使用一些短小的音效,
然後利用其做變化來達到不同的效果,例如槍聲,我們只要移用一發的槍聲,
配上次數、間隔、持續時間、頻率(指聲波頻率)等,
就可以組成許多不同的槍聲,
這時我們可能就會選擇不用存一堆龐大的音效在手機中,而是存短小的一發槍聲音效就好,以節省記憶體空間。

Android 有一個類別就可以很好地提供這樣的需求,
SoundPool,
它可以存放多個音效,選擇要播放的音效,指定左、右聲道音量大小(給耳機用才聽得出來)、播放次數、持續時間等。

下面就來演示一個簡單的範例:

MainActivity.java :
package com.example.gjun.audiopractice;

import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.SeekBar;

public class MainActivity extends ActionBarActivity {
    //本例適用於智小音樂,可調節聲音大小及播放時間(只是拉長波型)

    // 控制左右聲道用的SeekBar元件
    private SeekBar volume_left, volume_right;
    // 左右聲道音量,0.0F ~ 1.0F
    private float leftVolume = 1.0F, rightVolume = 1.0F;
    //存放音樂的Pool
    private SoundPool soundPool;
    private int soundId01, soundId02;
    //現在或最近一次播放的soudPool音效ID
    private int playing;

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

        //獲取元件
        volume_left = (SeekBar) findViewById(R.id.volume_left);
        volume_right = (SeekBar) findViewById(R.id.volume_right);

        // 建立控制左右聲道的進度改變監聽物件
        SeekBar.OnSeekBarChangeListener listener =
                new SeekBar.OnSeekBarChangeListener() {
                    @Override
                    public void onProgressChanged(SeekBar seekBar,
                                                  int progress, boolean fromUser) {
                        switch (seekBar.getId()){
                            // 改變左聲道音量
                            case R.id.volume_left :
                                leftVolume = progress/10.0F;
                                break;
                            // 改變右聲道音量
                            case R.id.volume_right :
                                rightVolume = progress/10.0F;
                                break;
                        }
                        // 設定指定編號的左右聲道音量
                        soundPool.setVolume(playing,leftVolume,rightVolume);
                    }

                    @Override
                    public void onStartTrackingTouch(SeekBar seekBar) {}

                    @Override
                    public void onStopTrackingTouch(SeekBar seekBar) {}
                };
        //設置SeekBar的Listener
        volume_left.setOnSeekBarChangeListener(listener);
        volume_right.setOnSeekBarChangeListener(listener);

        // 建立SoundPool物件
        // 第一個參數設定音效最大數量
        // 第二個參數設定音效種類,通常指定為AudioManager.STREAM_MUSIC
        // 第三個參數設定播放音質,目前沒有作用
        // 載入指定資源編號的音樂並取得編號,第三個參數目前沒有作用
        soundPool = new SoundPool(3, AudioManager.STREAM_MUSIC,0);
        soundId01 = soundPool.load(this,R.raw.sound01,0);
        soundId02 = soundPool.load(this,R.raw.sound02,0);
    }

    //以下按下播放事件用activity_main.xml綁定在元件的click事件上
    public void clickPlay01(View view) {
        // 播放第一個音效
        soundPool.play(soundId01,leftVolume,rightVolume,0,0,1.0F);
        playing = soundId01;
    }

    public void clickPlay02(View view) {
        // 播放第二個音效
        soundPool.play(soundId02,leftVolume,rightVolume,0,0,1.0F);
        playing = soundId02;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}


activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/retangle_drawable"
        android:layout_margin="12dp"
        android:padding="8dp"
        android:stretchColumns="1" >

        <TableRow
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp" >
            <TextView android:text="LEFT" />

            <SeekBar
                android:id="@+id/volume_left"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:max="10"
                android:progress="10" />
        </TableRow>

        <TableRow
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp" >
            <TextView android:text="RIGHT" />

            <SeekBar
                android:id="@+id/volume_right"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:max="10"
                android:progress="10" />
        </TableRow>
    </TableLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/retangle_drawable"
        android:layout_margin="12dp"
        android:padding="6sp" >
        <ImageButton
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/play_icon"
            android:onClick="clickPlay01" />
        <ImageButton
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/play_icon"
            android:onClick="clickPlay02" />
    </LinearLayout>

</LinearLayout>

AndroidManifest.xml :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.gjun.audiopractice" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".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>
    </application>

</manifest>

程式碼下載:
AudioPractice.7z

Android Google Map應用 - 使用Android Studio

這裡展示了在Android中使用Google Map的幾個簡單應用:

  1. 貼圖標 (自定內容不含外框、自定樣式含內容和圖標)
  2. 繪置線
  3. 繪置面(區域)
直接看程式碼:

MainActivity.java :

package com.example.gjun.googlemapmarkerpractice;

import android.graphics.Color;
import android.media.Image;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.android.gms.maps.model.PolylineOptions;

public class MainActivity extends ActionBarActivity {
    private GoogleMap map;
    private LinearLayout info_panel;
    private TextView info;
    private Marker myMarker1;
    private Marker myMarker2;

    //設定地圖中心點
    private static final LatLng mapCenter = new LatLng(25.051234, 121.538315);

    // 繪製線條用的座標
    private static final LatLng station01 = new LatLng(25.04657, 121.51763);
    private static final LatLng station02 = new LatLng(25.044937, 121.523037);
    private static final LatLng station03 = new LatLng(25.042293, 121.532907);
    private static final LatLng station04 = new LatLng(25.051702, 121.532907);
    private static final LatLng station05 = new LatLng(25.05971, 121.533251);

    // 繪製區塊用的座標
    private static final LatLng station06 = new LatLng(25.062354, 121.541061);
    private static final LatLng station07 = new LatLng(25.041671, 121.5378);
    private static final LatLng station08 = new LatLng(25.04136, 121.557713);
    private static final LatLng station09 = new LatLng(25.063054, 121.552048);

    private Marker marker01, marker02, marker03, marker04, marker05;

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

        ///取得GoogleMap物件
        if (map == null) {
            map = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
        }
        //移動到地圖中心
        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(mapCenter, 13);
        map.moveCamera(cameraUpdate);

        //--------------------------繪制圖標--------------------------
        MarkerOptions markerOptions1 = new MarkerOptions();
        markerOptions1.position(station01)
                .icon(BitmapDescriptorFactory.fromResource(R.drawable.train));
        myMarker1 = map.addMarker(markerOptions1);

        MarkerOptions markerOptions2 = new MarkerOptions();
        markerOptions2.position(station05)
                .icon(BitmapDescriptorFactory.fromResource(R.drawable.taipei_101_marker));
        myMarker2 = map.addMarker(markerOptions2);
        myMarker2.setDraggable(true);
        //設置圖標的氣泡顯示訊息樣式
        map.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
            @Override
            //自訂樣式,連外框
            public View getInfoWindow(Marker marker) {
                if (marker.equals(myMarker2)) {
                    //從指定的畫面layout檔建立訊息視窗畫面物件
                    View view = getLayoutInflater().inflate(R.layout.info_window, null);
                    //說定圖示、標題和說明
                    ImageView badge = (ImageView) view.findViewById(R.id.badge);
                    TextView title = (TextView) view.findViewById(R.id.title);
                    TextView snippet = (TextView) view.findViewById(R.id.snippet);

                    badge.setImageResource(R.drawable.roc_flag);
                    title.setText("This is title!");
                    snippet.setText("This is snippet");

                    ///傳回自訂的訊息視窗
                    return view;
                }
                return null;
            }

            //自訂樣式,不連外框,只有內容
            @Override
            public View getInfoContents(Marker marker) {
                if (marker.equals(myMarker1)) {
                    //從指定的畫面layout檔建立訊息視窗畫面物件
                    View view = getLayoutInflater().inflate(R.layout.info_content, null);
                    //說定圖示、標題和說明
                    ImageView badge = (ImageView) view.findViewById(R.id.badge);
                    TextView title = (TextView) view.findViewById(R.id.title);

                    badge.setImageResource(R.drawable.train);
                    title.setText("This is title!");

                    ///傳回自訂的訊息內容
                    return view;
                }
                return null;
            }
        });
        //--------------------------------------------------------------------

        // --------------------------繪製點連成的線--------------------
        PolylineOptions polylineOptions = new PolylineOptions();
        polylineOptions.add(station01, station02, station03, station04, station05);
        polylineOptions.width(10);
        polylineOptions.color(Color.BLUE);
        map.addPolyline(polylineOptions);
        //--------------------------------------------------------------------

        //--------------------------繪製點連成的面--------------------
        PolygonOptions polygonOptions = new PolygonOptions();
        polygonOptions.add(station06, station07, station08, station09);
        Polygon polygon = map.addPolygon(polygonOptions);
        polygon.setStrokeWidth(5);
        polygon.setStrokeColor(Color.rgb(102, 153, 0));
        polygon.setFillColor(Color.argb(200, 255, 255, 0));
        polygon.setZIndex(1);
        //--------------------------------------------------------------------

        //--------------------------繪制圓形--------------------------
        CircleOptions circleOptions = new CircleOptions();
        circleOptions.center(mapCenter)
                .radius(2500)
                .zIndex(2)
                .strokeWidth(0)
                .strokeColor(Color.argb(200, 255, 0, 0))
                .fillColor(Color.argb(50, 255, 0, 0));
        map.addCircle(circleOptions);
        //--------------------------------------------------------------------
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>


info_content.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <!-- 圖示 -->
    <ImageView
        android:id="@+id/badge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="5dp"
        android:adjustViewBounds="true"
        android:src="@drawable/train" >
    </ImageView>

    <!-- 標題 -->
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:ellipsize="end"
        android:singleLine="true"
        android:textColor="#ffff0000"
        android:textSize="14dp"
        android:textStyle="bold" />

</LinearLayout>

info_window.xml :
<?xml version="1.0" encoding="utf-8"?>
<!-- 設定背景為自己設計的外框圖形info_bubble -->
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/info_bubble"
    android:orientation="horizontal" >

    <!-- 圖示 -->
    <ImageView
        android:id="@+id/badge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="5dp"
        android:adjustViewBounds="true" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <!-- 標題 -->
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:ellipsize="end"
            android:singleLine="true"
            android:textColor="#ff000000"
            android:textSize="14dp"
            android:textStyle="bold" />

        <!-- 說明 -->
        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:singleLine="true"
            android:textColor="#ff7f7f7f"
            android:textSize="14dp" />
    </LinearLayout>

</LinearLayout>

build.gradle(Module.app):
apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.example.gjun.googlemapmarkerpractice"
        minSdkVersion 15
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:21.0.3'
    compile 'com.google.android.gms:play-services:+'
}

AndroidManifest.xml :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.gjun.googlemapmarkerpractice" >

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >

        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="AIzaSyCBRZamFCDSFNgkk4CKbUBoePEgYvf87FE"/>

        <activity
            android:name=".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>

    </application>

</manifest>

成品:


源碼下載:
GoogleMapMarkerPractice.7z

2015年11月7日 星期六

Google Play Service的Google Map - 使用Android Studio

這裡記錄在Android Studio中使用Google Map的方法:
  1. 設定Google Map的相依性(gradle dependency):

  2. 在module層級的build.gradle中,其dependencies中多加一行如下設定:

    compile 'com.google.android.gms:play-services:+'

     
  3. 設定權限及API Key:
    1. 在AndroidManifest.xml中設定權限,如下:
    2. 
      
      
      
      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      
      
      
      
    3. <application>節點內設定API Key:
    4. 
      
      <meta-data    android:name="com.google.android.maps.v2.API_KEY"    android:value="Your_API_Key"/>
這樣就可以開始使用Google Map了,例如下面是一個簡單的顯示Google Map的例子,使用了SupportMapFragment (API level 12(Android 3.1) 以上可使用 MapFragment):


  1. 首先是Layout, activity_main.xml :
  2. <?xml version="1.0" encoding="utf-8"?>
     <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:paddingLeft="@dimen/activity_horizontal_margin"    
       android:paddingRight="@dimen/activity_horizontal_margin"    
       android:paddingTop="@dimen/activity_vertical_margin"    
       android:paddingBottom="@dimen/activity_vertical_margin" 
       tools:context=".MainActivity">
          <fragment android:id="@+id/map" 
         android:name="com.google.android.gms.maps.SupportMapFragment" 
         android:layout_width="match_parent"       
         android:layout_height="match_parent" />    
    </RelativeLayout>
    
    
    
    
  3. 其實這樣就可以了,在不打程式碼的情況下,足以在畫面上顯示一個地圖版面。

2015年11月2日 星期一

Javascript的變量聲明提昇

最近學到了JavaScript的作用域與變量聲明提昇的觀念,
深感與C語言及Java有很大的不同,
如果不是一開始就學JavaScript的人非常可能會把以前的觀念代入而遭遇匪夷所思的錯誤,
所以特地在此做個紀錄。

在JavaScript中,有兩個跟C語言及Java非常不同的特性,

  1. 變量的作用域不為塊級作用域(block-level scope),而且支持函數作用域(function-level scope)。
  2. 在作用域裡會有變量聲明提昇的現象。
下面做個說明:

1.變量的作用域不為塊級作用域(block-level scope),而且支持函數作用域(function-level scope)

在C語言及Java中,只要在括弧( "()" 及 "{}") 中宣告的變數都屬於區域中的區域變數,例如以下程式碼:

int x = 1;

if (true) {

     int x = 2;

     println(x);  //2

}

println(x);       //1

很顯然地應該輸出為 2和1 ,因為大括弧{}中的int x=2;跟外面的int x=1 無關,兩次println(x)的x是不一樣的。
但是在JavaScript中,變數不是使用塊級作用域的,而是函數作用域的,例如以下的程式碼:

var x = 1;

if (true) {

    var x = 2;

    alert(x);    //2

}

alert(x);        //2

結果會是兩次的alert(x)都會顯示2,這是因為在if的大括弧{}裡的var x=2; 的x就是 外層的var x=1;,兩個x並沒有被當成是不同的x,所以內外層的alert(x)的x都是一樣的x。

因為JavaScript支持函數作用域,所以如果用函數來圍往同名變數的宣告的話,就可以區分內外的同名變數,如以下程式碼:

var x = 1;

function myFun(){

     var x = 2;

     alert(x);               //2

}

myFun();

alert(x);                   / /1


就會先顯示2再顯示1,因為此時myFun()裡的x與外層的x是不同的x變數。


2.在作用域裡會有變量聲明提昇的現象

另外一個JavaScript中獨特的特性是變量聲明提昇,以下兩種宣告情形會變提昇至作用域的頂端,即先被執行:
         
            一、以var宣告變數的動作。
            二、函數的無等號宣告 (如: function myFun(){} 這種,其實就等於 var myFun = function(){})

例如以下程式碼:

function sum() {   
    try{
       alert(sum.arguments.length);   //sum為undefined ,出錯
       var sum = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2); 

會補捉到TypeError: Cannot read property 'arguments' of undefined的錯誤,這是因為變量聲明提昇的作用,在提昇之後,其實真正的程式碼執行狀況長得就像下面這樣:

function sum() {   
    try{
       var sum;                                     //變數的宣告被提至作用域頂端
       alert(sum.arguments.length);     //sum為undefined ,出錯
       sum = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2); 

此時就很容易看出Error發生的原因,因為在sum()中,先宣告了一個sum屬性但未賦予其值,是undefined的,所以在alert(sum.arguments.length)中的sum是undefined的,當然就沒有arguments等屬性了。

再看一個例子,現在使用function的非等號式宣告:

var x = 1;
function myFun(){<
     x = 2;
     return;
     function x(){};
}
myFun();
alert(x);

答案是輸出1,因為被變數聲明提昇後的程式碼長得像這樣:

var x = 1;
function myFun(){
     var x;
     x = 2;
     return;
     x = function){};
}
myFun();
alert(x);

所以myFun()跟本沒有影響到外層的var x = 1的x而輸出原來的1了。

參考資料:

  1. JavaScript Scoping and Hoisting
  2. 翻譯 - JavaScript中的作用域與變量聲明提升

2015年10月24日 星期六

自製jQuery的PlugIn(twinkle演示範例)

JQuery提供外掛(Plug-In)能力,可以讓使用者設計自己所需的功能,跟自己做一個客製化類別(Class)不一樣,外掛就相當於是jQuery物件能夠使用的功能,能夠用selector的方式直接得到jQuery物件然然後使用,就像顯示物件是 $("selector").show();一樣,我們也可以自己寫一個外掛來用,例如$("selector").myPlugInFun();

以下為在寫JQuery的Plug-In時,常使用的其中一種寫法(只是舉例,寫法因人而異):

(function($){

    //這裡可設制function或變數

    $.fn.extend({

           //這裡可設制function或變數

           myPlugInFun1 : function(options){

                                         //這裡可設制function或變數,跟Java或C很不一樣的是,

                                         //JavaScript的function也算一種Object,所以可以在function裡設

                                         //function屬性

                                         var defaultOptions1 = {

                                               option1 : "option1" ,

                                               option2 : "option2"

                                         };

                                          return this.each(function(){

                                              $.extend(defaultOptions,options);

                                              //..........Do something

                                          });

                                      } ,

           myPlugInFun2 : function(options){

                                            ...以此類推

                                      }

    });

})(jQuery);


下面我們來寫一個簡單的jQuery Plug-In,叫做twinkle(),其作用是讓選取的jQuery物件可以依自訂(當然也可以預設)的次數及間隔時間閃爍,且也可以設定執行完後的callback function及擁有串接的特性,相關的說明註解都在程式碼中,直接看程式碼:

2015年10月13日 星期二

在xml中儲存tag類型符號,使用

在XML中,任何Tag型式的東西都會被當成正規的XML結構節點,例如

<message><someText>myText</someText></message>

但是如果我們想要儲存在XML中的內容有Tag型式的東西,但又不希望被XML當成節點呢?例如我們想把一個網頁的html儲起來,內容像這樣:

<html>

<head></head>

<body><p>someText</p></body>

</html>


如果把上述的html直接寫進XML中就會出問題,這時我們需要用<![CDATA[...]]>標籤來告訴XML其包住的內容不要用XML的方式解析,像如下這樣:

<message>

      <someHtml>

      <![CDATA[<html><head></head><body><p>someText</p></body></html>]]>

      </someHtml>

</message>

下面是一些注意事項:
CDATA 區段的內容必須在 XML 內容所允許的字元範圍內;不能用這種方式逸出控制字元和相容性字元。 此外,CDATA 區段內不能出現序列 ]]>,因為這個序列會發出區段結束訊號。 這意味 CDATA 區段不可以是巢狀。 這個序列也出現在部分指令碼中。 在指令碼內,通常可使用 ] ]> 替代 ]]>
             from CDATA 區段 [XML 標準] 

參考資料:

  1. CDATA 區段 [XML 標準] 
  2. Ian 懶惰蟲筆記: CDATA 區段 - XML 標準

2015年10月11日 星期日

CSS3的動畫

這裡紀錄一下利用CSS做簡單動畫的方法:
在CSS裡,可以利用animation-name屬性來指定動畫,並且利用@keyframes檢設定動畫

下面是一個簡單的例子,設定動畫為一秒,使用myAnim的自訂影格,影格內容分三個時間點(0%,50%和100%),動畫為左移動:
html:
<div class="animClass">我在移動</div>


css:
.animClass {

      animation-name: myAnim;   

      animation-duration: 1s;     

    

    }



@keyframes myAnim {

  0% {

      padding-left: 100%;

  }

  50% {

      padding-left: 0%;

  }

  100% {

      padding-left: 100%;

  }

}


線上實例演示:
http://jsfiddle.net/rd0zsy78/1/

CSS的動畫使用方法就跟平常使用CSS定義的一樣,用CSS選擇器來為選擇的元素定義動畫,包括了動畫名稱(animation-name)、持續時間(animation-duration)、重覆幾次(animation-iteration-count)等屬性可以設定。

接著就要自已定義對應影片名的關鍵影格,設定不同時間點(用百分比算)的CSS變化。

下面的參考資料就有詳細的各種參數資料可查詢。

參考資料

  1. CSS 動畫
  2. 使用 CSS Animation 製作網頁上的動畫(隻要 CSS3,不用 JavaScript!)
  3. CSS參考手冊

2015年10月2日 星期五

使用SvnAnt來在Ant中以程式化方式執行SVN行為

今天來紀錄一下如何在Ant中使用SvnAnt來執行SVN的行為動作。

首先,要先去下載SvnAnt,選擇合適的版本下載解壓縮後,把lib資料夾裡的jar檔全部丟掉Ant的lib中,把path還境變數設好,就可以開始撰寫操作SVN的Ant程式碼了。

以下就展示一個簡單的範例:
作用為向SVN export資料出來。

build.xml:
<?xml version="1.0" ?>
<project name="test" default="svnTestTask">
        <!-- 參數設置 -->
 <property name="dir" value="D:\testSvnDir" />
 <property name="svn_User" value="USER_NAME" />
 <property name="svn_Password" value="PASSWORD" />
        <property name="svn_url" value="SVN_URL" />

        <!-- 主要的Target -->
 <target name="svnTestTask">
  <delete dir="${dir}" />            
  
  <taskdef name="svn" classname="org.tigris.subversion.svnant.SvnTask" />
  <svn javahl="true" username="${svn_User}" password="${svn_Password}" >
   <export srcUrl="${svn_url}" destPath="${dir}" />
                </svn>
 </target>
</project>


 把build.xml放在要執行的資料夾,並且用DOS指令到資料夾中用ant指令即可執行,當然你也可以寫一個bat檔來執行:

test_svnant.bat:
cd d:
ant

在官網中,有蠻詳細的指令使用說明可以參考:
http://subclipse.tigris.org/svnant/svntask.html#update

參考資料:

  1. 【Tomcat】Ant與build.xml
  2. 用ant实现SVN代码更新,部署
  3. 官方說明
  4. ant 自定义 task

2015年9月24日 星期四

特定位置浮動Element效果(jQuery)

今天要介紹的使用jQuery來製作"特定位置浮動Element效果"。

首先先看一下影片演示:

其中的紅色框框,我們希望在卷軸往下拉、越過紅色框框時紅色框框能夠停留在上方,而卷軸往上拉回去時,紅色框框能夠回到原位。

這樣的效果常被用在網站的選單上面,讓使用者瀏覽網站時,能夠一直都看得到選單。

程式實現的邏輯是這樣的:
  1. 先取得紅色框框的位置。
  2. 當卷軸滑動時,計算卷軸是否滑超過等於卷軸的位置。
  3. 如果超過的話,固定紅色框框的位置,將其z-index提高,為了避免下面的元素補上來,先製作一個假的、但透明看不見的紅色框框再固定原紅色框框;否則,不固定紅色框框的位置。將假的紅色框框刪掉。

下面上程式碼:
1. html部份:
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
<div class="bar">
This is the div class, top_tab_bar_bg.
</div>
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
Other Content. Other Content. Other Content. Other Content. Other Content.</br>
2. CSS部份:
body{
    height : 1000px;
}

.bar{
    width : 300px;
    border-style:solid;
    background-color: red;
}

3.javascript部份:
 //offset().top 是相對於文檔的偏移量,不會因為滾動卷軸而有變化。
    //CSS裡的top屬性在position為fixed時,是對應到相對於所處螢幕的偏移量
    //,會因滾動卷軸而有變化。
    var barOffsetTop = $(".bar:not(.tempBar)").offset().top;
 var barMarginTop = parseInt($(".bar:not(.tempBar)").css("margin-top"));
 
   $(window).scroll(function() {
     var scrollTopAmount= $(this).scrollTop();     
        //因為bar的position被設成fixed時會使下面的元素跑上來,所以做一個透明的tempBar來填充原來
        //bar的位置
     if (scrollTopAmount >= barOffsetTop){
            //如果卷軸拉超過bar的原始offset.top(),將bar固定住
            //如果沒有tempBar,做一個tempBar
      if ($(".tempBar").length == 0){
       $(".bar").clone().addClass('tempBar').insertAfter(".bar").css({"opacity":"0","z-index":"auto"});
      }      
      $(".bar:not(.tempBar)").css({ "position": "fixed", "top": "-"+barMarginTop+"px" , "z-index" : "999"});
     }else{
            //如果卷軸拉沒有超過bar的原始offset.top(),將bar的css還原,刪除tempBar
      $(".tempBar").remove();
      $(".bar:not(.tempBar)").css({ "position": "static" , "z-index" : "auto"});
     }
   });

線上看成品:
http://jsfiddle.net/a33e97cg/

2015年9月2日 星期三

好用 jQuery 外掛

 各種jQuery外掛庫的搜尋網站:

  1. jQuery plugins

覺得好用的jQuery外掛:

  1. jQuery custom content scroller :為區塊加上滑動的側邊卷軸。
  2. fullPage.js:為網站增加上下滑動(section)及同section左右滑動(slide)的效果。

2015年8月31日 星期一

好用的Google JavaScript 瘦身器

我們在做網站開發時,通常會用到很多JavaScript的檔案,而可能會佔上專案的不少空間,這時就會有把JavaScript裡的內容進行編碼瘦身的需求,經過編碼後,JavaScript裡的一些變量、函式名等之類的值都會度換成沒有意義的文字,並且縮排、換行會全部被取消,所以編碼後的檔案容量大小可以大幅度地被減少。

網路上有許多的JavaScript瘦身器,這裡介紹一個好用的Google JavaScript 瘦身器,可以幫助我們對JavaScript檔進行編碼。

Closure Compiler
它有提供多種編碼的方式,例如線上貼上代碼進行編碼,或是用它提供的API來以程式的方式進行編碼等,下圖為線上貼上代碼進行編碼的擷圖:

其他的JavaScript瘦身器資料:

  1. 5 Excellent JavaScript Minification Tools to Improve your Code's Performance
  2. JS & CSS Minifier (Jane Marie Shepard 網友推薦)

2015年8月27日 星期四

JavaScript的同名函式問題及輸入參數的陣列特性

最近在工作中發現了一個以前沒有注意到的問題,找到了解決方法及解釋,所以特在此做了一個紀錄。

在Java中,很多人都應該有同名函式,不同函式簽名的多載Overload概念,但是在JavaScript中,是不能把這樣的觀念一併套用的。

我們來看一下以下的程式碼:
<html>
<body>

<script type="text/javascript">
function foo(a) {
   alert('foo(a) is called!');
}
function foo(a,b,c) {
    alert('foo(a,b,c) is called!');
}
foo('a');
</script>

</body>
</html>

如果有寫過Java但不熟JavaScript的話,很容易就會想說跳出的視窗應該是 "foo(a) is called!" 的吧,不過事實上並不是這樣的。

事實上跳出的事窗會是 "foo(a,b,c) is called!",也就是說 foo(a) 這個function跟本就沒被呼叫到,很顯然的,JavaScript並不會因為不同的方法簽名呼叫就使用不同方法簽名的方法,也就是沒有Java常見的多載(overload)特性。

其實在JavaScript中,函式的名稱只是一個類似指標(C語言)或參考(Java的reference)的查西,在每一次指定function內容及名稱時,名稱指向的位置都會改變,上述的例子中,第一次foo指向一個function及記憶體位置,而第二次又指向了另外一個function內容及記憶體位置,所以foo(a)就沒用了,最後foo代表的就是foo(a,b,c)。

在JavaScript中,呼叫方法function)時,輸入的參數是以類似陣列的方式傳進方法中的,所以其實方法簽明中的輸入參數個數並不是很重要,甚至可以傳入參數給方法簽名沒有輸入參數的方法(當然在方法簽名上標名變數名稱有利於程式易讀性及使用)。

如以下的程式碼是完全可以成功且正確的執行的:
<html>
<body>

<script type="text/javascript">
function sum() {   
   var inputArguments = sum.arguments;
   var answer = 0;
   for (i=0 ; i < inputArguments.length ; i++){
      answer = answer + inputArguments[i];
   }
   alert('輸入了' + inputArguments.length + '個數字,' + '總合為:' + answer);
}
sum(1);
sum(1,2);
sum(1,2,3);
</script>

</body>
</html>

執行後將可以看到三個視窗跳出來,分別顯示如下文字:
輸入了1個數字,總合為:1
輸入了2個數字,總合為:3
輸入了3個數字,總合為:6

可以發現,在JavaScript中,方法本身其實就像是一個物件,例如在上例中 sum() 本身就像是一個物件,可以用sum.arguments來存取輸入的參數,而輸入的參數也是像一個物件,可以存取其length及各元素的值。

而也因為 sum 就代表名為 sum 的方法,所以如果我們用方法的名稱去宣告一個新變數是會發生問題的,例如像下面這個例子,就會發生 TypeError: Cannot read property 'arguments' of undefined 的錯誤 ,因為此時sum()代表呼叫sum函式,而使用了 var sum=0 時,sum.arguments.length的sum變成指向一個sum()中的sum屬性了,所以是還沒defined的(這時真正執行的順序是var sum; --> alert(sum.arguments.length); --> alert(sum); 請參考 "Javascript的變量聲明提昇"):

<html>
<body>

<script type="text/javascript">
function sum() {   
    try{
       alert(sum.arguments.length);  //這裡的sum是指sum()中的sum屬性,但因沒有定義而會出錯
       var sum = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2); //加括號時,sum指的是名為sum的function
</script>

</body>
</html>

那正確的寫法是什麼呢?事實上argument這個屬性是可以直接在function裡使用而不用在前面特別指定function名稱的,所以這樣寫就不會報錯了:
<html>
<body>

<script type="text/javascript">
function sum() {   
    try{
       alert(arguments.length);
       var sum = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2);
//輸出為:
//2
//0
</script>

</body>
</html>


如果這樣的話,也就是沒有下var sum=0,即沒有跟sum()的名稱衝突的話,也是可以的,此時sum就是function了
<html>
<body>

<script type="text/javascript">
function sum() {   
    try{
       alert(arguments.length);
       var sum2 = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2);
//輸出為:
//2
//function sum() {   
//    try{
//       alert(arguments.length);
//       //alert(typeof(sum));
//       var sum2 = 0;
//       alert(sum);
//    }catch(error){
//       alert(error);
//    }
//}
</script>

</body>
</html>

參考資料:
  1. Javascript同名函式
  2. The arguments array- the secret to robust functions

2015年8月23日 星期日

Google Analytics API的使用

Google Analytics 是 Google 的一個服務,可以幫助開發者來追蹤分析網站的流量、來源、訪問的瀏覽器等資料,Google提供了一個親切的可視化管理介面,並且可以在上面設置各種功能及觀看報表。

但是對於開發者來說,這樣的服務開不夠,開發者有可能會希望利用程式的方式進行Google Analytics 的追蹤查詢,所以Google又提供了Google Analytics API 的服務,讓開發者可以如使用Java等方式用程式來訪問Google Analytics。

最近在工作時,發現Google Analytics API 從2.0版升級到了3.0版本,所以很多以前用2.0版寫的程式都沒辨法跑了,必須要用到3.0版的方式才行。

在這裡我把如何申請Google Analytics及如何使用Google Analytics API做了一個紀錄,以供日後查讀:

一、申請及使用Google Analytics
首先你要先跟Google申請Google Analytics的使用,登入Google Analytics的首頁後,如果是第一次使用Google Analytics,要先設定想要追蹤分析的網頁網址。如果已經設定過了,就會進入報表頁面,如下所示:


按下"管理",可以看到如下畫面,分成"帳戶"、"資源"和"資料檢視"三個部份。

帳戶:為使用Google Analytics的管理者,可以設定不同的權限。

資源:為要追蹤分析的網站、行動應用程式或裝置 (如資訊站或銷售點裝置),在"資源"-->"追蹤資訊"-->"追錝程式碼裡"可以看到Google提供的JavaScript程式碼,需要放到想要追蹤分析的網頁程式碼中。

2015年8月17日 星期一

將Tomcat配置至Eclipse

這裡紀錄了在Eclipse中如何配置Tomcat的步驟:
  1. 配置Server
  2. 在Eclipse的Window-->Preferences-->Server-->Runtime Environments中,將Tomcat設到Server runtime environments中,


    選擇你要設定的Tomcat 版本,


    並選擇Tomcat的根目錄位址


  3. 引入Tomcat的Jar檔
  4. 對專案按右鍵,選擇Properties-->Java Build Path,到Libraries分頁裡面把Tomcat的Jar檔引入進來,如下所示,其中這裡用到了Variable的設置,所以下圖的"TOMCAT_HOME"事實上代表了Tomcat的根目錄位置。





    1. 在Eclipse做Java EE時,要記得把要用到的外步Jar設定到在布署時要放到的地方,如下圖,對專案按右鍵,選擇Deployment Assembly-->Add,再選擇"Java Build Path Entries",然後把引入的Jar選進來,例如下圖設為將外部Jar檔在伂署時放到WEB-INF/lib中,即Tomcat Web規則的位置。
    參考資料:

    1. Eclipse 的 Tomcat 環境設定
    2. JavaWeb:报错信息The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path
    3. 新建java Web 出现的问题 球指点 急急!!!!

    解決在Eclipse無法正常顯示Unicode字元的問題(使用Properties Editor插件)

    在使用Eclipse IDE時,有時會碰到Unicode字元無法正常地顯示,例如:

    "即將推出"       變成      "\u5373\u5c07\u63a8\u51fa"

    這時只要使用Properties Editor這個插件(Plug-In)就可以很好地解決這個問題,下面就詳述解決的步驟:


    在Eclipse中,在上方選單中按下 Help-->Install New Software


    接著按下 "Add" 按鈕,在 "Add Repository"視窗中的 "Location" 打上 插件的位置 "http://propedit.sourceforge.jp/eclipse/updates/ (現在改成這個了: https://free.nchc.org.tw/osdn//storage/g/p/pr/propedit/eclipse/updates/site.xml)" , "Name" 可自取,最後按下 "OK"。




    此時會出現可供下載的Properties Editor選項,最下面的為最新版,其他的是之前的版本。



    按著按下 "Finish" 後將其按裝完成。

    此時會需重新啟動Eclipse,之後就會發現所有有Unicode內容的Property檔案左邊出現了 綠色的"P"圖案,如下所示:
    這樣之後打開檔案就可以正常顯示Unicode字元了,當然你也可以對檔案按右鍵用"Open With"選傳統的Text Editor來看原始的字元編碼內容。


    Note:
    現在官網上好像已經沒再維護且下載點也失連了,所以在存放一下備份以備之後使用,
    只要把 jar 檔放到 Eclipse 的 plugin 資料夾下,重新啟動 Eclipse 就可以了:
    備份下載:
    jp.gr.java_conf.ussiy.app.propedit_6.0.5.jar

    2015年8月2日 星期日

    Spring的注解(Annotation)事務(Transaction)範例與JUnit測試(Spring+Hibernate+JUnit)

    此篇文以一個簡單完整的範例,來說明在Spring中如何使用注解(Annotation)事務(Transaction)與其要注意的事項,並且以JUnit來測試

    首先我們要下載 aopalliance.jar ,不然在執行事務時可能會發生ClassNotFoundException。可以到The Central Repository中搜尋最新版的jar,這是因為aopalliance.jar已經從NetBeans內建的Spring中分離了出來,要自已手動下載並引入至Library。如下圖:

    在這裡,我們想要設計的構想其專案配置如下:
    詳述如下:

    1. DaoClass.java:操控資料庫的CRUD(新增、查詢、修改、刪除)之Class。
    2. Guest.java:代表資料庫中的資料表之ORM(Object-Relational Mapping) 物件。
    3. ServiceClass.java:使用DaoClass進行事務(Transaction)處理的Class。
    4. DaoTest.java:用來測試DaoClass的測試JUnit。
    5. ServiceTest:用來測試ServiceClass的測試JUnit。
    在這裡,Guest代表的Table設計如下圖所示(在此例中Table中不用儲任何紀錄,這裡只是做個示範):


    首先是在applicationContext.xml中配置要給Spring托管的內容,如連線池、SessionFactory、Annotation的支持設定、自動裝配Bean等等:

    2015年7月16日 星期四

    Spring MVC中Controller與View的傳值

    Spring MVC中很有趣的地方就是Controller可以為一個POJO或是一個普通的Java Bean,不用繼承如HttpServlet等類別,而要將請求導向View(例如JSP)時,也只要回傳String或ModeAndView等,不用使用像response.sendRedirect()或request.getRequestDispatcher("XXX").forward(request, response)等語法,使得單元測試變得更為簡單與容易。

    那麼View和Controller之間要怎麼溝通呢?在這裡以一個簡單的例子實作來展示一些在Spring中View和Controller之間溝通的方式。

    例子要實做的內容為,使用者一開始會進入一個表單輸入頁面,可以輸入名字及選擇稱謂(先生或小姐),按下送出按鈕後,會引導到招呼畫面,其中招呼內容包含輸入的名字及稱謂,而如果名字為雨果及稱謂為先生的話,招呼畫面的內容會稍有不同。

    首先先來看專案的配置:



    其中包括:

    1. dispatcher-servlet.xml:Spring的dispatcherServlet設定檔。
    2. FormView.html:一開始的表單輸入頁面。
    3. FormCheckResultView.jsp:招呼畫面。
    4. FormController.java:接收及轉送參數的Controller。
    在web.xml中,我們讓所有的url請求都先讓Spring處理:

    2015年7月15日 星期三

    Java Swing的DnD (Drag and Drop)

    Drag and Drop (DnD) 就是中文的拖放,例如我們平常在Windows下常常會用滑鼠直接將檔案從一個資料夾移到另一個資料夾、或是將影片檔直接移到播放器的介面上以播放影片,這些動作就是所謂的DnD,而在Java Swing裡也提供了DnD的實作功能。

    在這篇文章中,我們要演示一個簡單的DnD實作,在主要Swing畫面上放置一個JTextArea,當我們在Windows下把一個或多個檔案用滑鼠圈選移到JTextArea後,JTextArea裡會顯示所移上去的檔案路徑列表。

    程式非常簡單,首先我們可以建立一個Java Swing的專案(例如使用NetBeans),命名為 "DragAndDropTest"。並在上面放一個JTextArea,我們把它命名為 "dragAndDropJTextArea"。
    接著在DragAndDropTest的建構子中加上如下程式碼即可:

    public DragAndDropTest() {
            //產生各元件
            initComponents();
            //為dragAndDropJTextArea設置DropTarget,其用來接收DropTargetDropEvent,
            //可從DropTargetDropEvent取得Transferable物件,即被包裝的拖曳物件。
            dragAndDropJTextArea.setDropTarget(new DropTarget() {
                public synchronized void drop(DropTargetDropEvent evt) {
                    try {
                        //設置可接受的拖曳類型
                        //如果拖曳物件可支援檔案列表格式(javaFileListFlavor)
                        if (evt.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                            //接受拖曳
                            evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
                            //得到Transferable物件,並將其轉成檔案列表格式(javaFileListFlavor),
                            //轉型成List<File>
                            List<File> droppedFiles = (List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
                            //清空dragAndDropJTestArea的內容
                            dragAndDropJTextArea.setText("");
                            //將拖曳的檔案以檔案列表的方式顯示在dragAndDropJTestArea中 
                            //字顏色為黑色
                            dragAndDropJTextArea.setForeground(Color.BLACK);
                            for (File file : droppedFiles) {
                                //顯示檔案列表
                                dragAndDropJTextArea.append(file.getPath() + "\n");
                            }
                        } else {
                            //若拖曳的物件不能支持檔案列表格式,
                            //拒絕Drop
                            evt.rejectDrop();
                            //顯示錯誤訊息,字顏色為紅色
                            dragAndDropJTextArea.setText("錯誤:拖曳類型不支援檔案列表格式!");
                            dragAndDropJTextArea.setForeground(Color.red);
                        }
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            });
        }
    

    最後試試看結果:

    1. 如果拖曳的是一般檔案,即可在JTextArea中顯示檔案列表,如下所示:


    2.  如果拖曳的是不可轉成檔案列表格式的物件,則在JTextArea中會顯示錯誤訊息:


    最後附上源始碼下載:
    DnDTest.7z

    參考資料:

    1. How to drag and drop with Java 2
    2. Java中的Drag and Drop詳解與代碼示例
    3. java swing中實現拖拽功能示例_java

    2015年7月12日 星期日

    JSP/Servlet中web.xml裡的<welcome-file>與Spring MVC的<mvc:resources>

    在使用Spring框架時,如果在web.xml裡將所有的請求都交給Spring來處理的話,例如下面這樣:
    <servlet>
            <servlet-name>dispatcher</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>2</load-on-startup>
        </servlet>
    <servlet-mapping>
            <servlet-name>dispatcher</servlet-name>
            <url-pattern>/</url-pattern>
    </servlet-mapping>
    

    此時一些靜態資源可能就會因為沒有相應的Url Mapping或Controller來處理而無法回應靜態資源(如html, jpg, css, js等)的請求,這時我們可以有兩種方法來解決:
    1. 在Spring設定檔中,例如dispatcher-servlet.xml,加入如下內容,作用為再交由Spring的url mapping等處理之前,如果是靜態資源就交還給Web應用服務器管理:

    2. <mvc:default-servlet-handler />
      

    3.  在Spring設定檔中,加入以下內容,作用為對於符合"mapping"的靜態資源邏輯路徑(**表示任意子路徑),告知Web應用服務器去"location"的地方查找。例如對於以下設定,邏輯路徑為/resource/img/picture.jpg就會去找/img/picture.jpg:

    4. <mvc:resources location="/" mapping="/resource/**"/>
      
    這裡要注意的地方是,如果我們有在web.xml裡設定歡迎頁面,例如像下面這樣:

    <welcome-file-list>
            <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    

    然後請求Web應用程式的根目錄時,例如 http://XXX.XXX.XXX.XXX/projectName ,根目錄的url請求,即 "/" ,會被Spring所接收處理,假設Spring設定檔中設定如下:

    <mvc:resources location="/" mapping="/**"/>
    

    根目錄 "/" 將會與mapping內容符合,並以location的值 "/" 返回給Web應用服務器,Web應用服務器發現為根目錄("/")的請求時,就會根據web.xml裡的歡迎頁面設定查找index.html。

    也就是說,如果我們現在把index.html放在 "/html/index.html" 的路徑中,並且想要以http://XXX.XXX.XXX.XXX/projectName 的請求找到index.html的話,以下的設定方法是錯的

    2015年7月9日 星期四

    整合Spring、Hibernate和C3P0的配置

    當我們只有使用Hibernate的時候,其中要獲取SessionFactory的話必須要使用Hibernate的API手動獲取,例如像這樣:

    SessionFactory sessionFactory = AnnotationConfiguration().configure().buildSessionFactory();
    

    如果配合Spring的話,就可以使用注入的概念將SessionFactory注入到要使用SessionFactory的類別中。

    整合Spring與Hibernate有兩種方法:

    1. 在Spring配置中直接將原Hibernate的配置檔抓進來。
    2. 在Spring自己配置Hibernate的配置、連接池(例如C3P0)等,可以捨棄原來的Hibernate配置檔,也可把它抓進來,然後覆蓋部份配置。

    讓我們再一次看一次原來的Hibernate配置檔,hibernate.cfg.xml:

    2015年7月8日 星期三

    Hibernate中C3P0連接池的設置

    在一開始使用Hibernate做ORM(Object/Relational Mapping)時,如果我們沒有配置連接池(Connection Pool),可能會發生一些底層連接Database會碰到的問題,例如sessionFactory所產生的連接太久沒有動作而被Database端以Timeout為由強行關閉,而之後在使用連接時就會出錯(在Hibernate端似乎無法用SessionFactory的isClosed()來判斷),此時就可能要自己在做CRUD(Create,Read,Update,Delete)時手動測試連接是否還在等等。

    如果配置了連接池,就可以簡單地受益於連接池帶來的許多好處,例如幫我們管理連線,最小連接數量、最大連接數量、定時檢測連線等等。

    在Hibernate裡已經包含了一個很好用且有名的開源數據庫連接池,C3P0,如果你在NetBeans裡使用了Hibernate框架,那麼你就可以在Library中看到C3P0的jar,如下圖紅框內所示:

    對於C3P0,以下的連結提供了不錯的介紹、範例與相闗資料:

    1. C3P0百度百科
    2. How to configure the C3P0 connection pool in Hibernate
    3. 關於MySQL的wait_timeout連接超時問題報錯解決方案
    4. c3p0 - JDBC3 Connection and Statement Pooling(官方網站)
    在這邊,我把我使用的例子在這邊做了一個紀錄,在Hibernate的配置文件hibernate.cfg.xml中,加入如以下配置,這樣Hibernate就會自動偵測並使用C3P0了:
            <!--==============C3P0的配置=============-->
            <!-- 最小連接數 -->
            <property name="hibernate.c3p0.min_size">5</property>
            <!-- 最大連接數 -->
            <property name="hibernate.c3p0.max_size">20</property>
            <!-- 多久會把無用的連接視為timeout並移除到min_size的連接數量,單位毫秒 -->
            <property name="hibernate.c3p0.timeout">300</property>
            <!-- 最大的PreparedStatement的數量 -->
            <property name="hibernate.c3p0.max_statements">50</property>
            <!-- 多久進行空閒連接的檢查,確定連接還存在,例如有無被Database端關掉,單位是秒-->
            <property name="hibernate.c3p0.idle_test_period">3000</property>
            <!--======================================-->
    

    注意:此處例出來的配置只是C3P0配置的一部份,其功能不只這些,其他的配置可以在上述的資料連接中查到。