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中的作用域與變量聲明提升