上圖是我自己第一個新創公司所做的產品Demo,叫做Styletrip,是一個AI旅遊平台,使用者只需要輸入「要去的地方、預算(非必要)和去的期間多久(非必要)」,我的AI就可以幫你安排出一套完整的行程(看圖的右邊),包括要去玩的景點、可以去吃的美食和住宿以及交通,當初為何會想做這個平台?我們要解決的問題什麼問題?這平台想解決的問題就在於安排行程的過程太過於繁瑣、累人,有了這個AI的幫忙,使用者只需要告訴電腦一些需求,就可以泡杯咖啡來喝等待AI完成的行程規劃,完全解決人類的特點之一 = 「懶」

這Styletrip產品沒有後續了,簡單來說就是創業失敗了,對這題目有興趣、想做旅遊新創或是想聽聽為何失敗之經驗分享的朋友們都歡迎找我聊聊。

Styletrip所用的核心技術,就是AI (Artificial Intelligence),讓人工智慧幫你找出你要去的景點、推薦你喜愛吃的美食、找到符合預算的機加酒,而人工智慧到底是什麼?為何它要存在?它的存在對你的影響是什麼?

AI = 學習 + 推理 + 行動

因為AI都是從我們人類發展以來各個領域的研究來啟發它的進化,下面列出了一些AI所涵括的領域:

哲學

  • 我們所知道的規則,是否能用來得到有用的結論嗎?我們的經驗能幫助我們什麼?
  • 我們腦袋的思考,是如何從大腦的物質所產生?
  • 知識從哪裡來?這些知識怎麼引導行動?

哲學簡單來說就是AI如何思考,描述當我們有知識或經驗時會如何引導我們的行動?

數學

  • 邏輯:在確定的情況下,我們要怎麼推理、找出下一步的動作?
  • 機率:在不確定的情況下,我們要怎麼推理、找出下一步的動作?
  • 計算:一個問題可以在有限時間內被解決。電腦科學領域有一類的問題歸類為難解問題,何謂難解?就是問題困難的程度可以瞬間暴增(以電腦科學的講法叫做時間複雜度呈現指數成長),舉個例子就是要一年級小學生算數從1+1=?變成算出2^74,207,281 − 1=?並且證明這個數字是質數一樣。

數學在AI當中扮演著一個有理論佐證的角色,讓整個決策是合理、有跡可循的,並且保證可以被解決。

上述難解問題在電腦科學領域有一個經典問題叫做Travel Salesman Problem (TSP),問題舉例一個業務員要從臺北出發,一天之內要去拜訪100個地點都不同的客戶(好血汗喔Orz….),且拜訪完後一樣要回到臺北出發原點,請問他可以走得最短路徑為何? 電腦是否能幫他找出那個路徑?以目前可行的理論能找出相對短的路徑而已(參閱Optimization or Approximation Algorithm)。

Styletrip也是遇到同樣的問題,「你想去台北玩,AI幫你列出可以玩的所有景點,每個景點都要去玩過一次,接下來你可以走的最短路徑為何?」當然這邊問題更複雜,包括要考慮景點的開放時間、使用者對景點是否有興趣、景點加入後會不會超出使用者預算…等,讓整個問題更加的複雜。

經濟

  • 我們會怎麼選擇,來讓目前得到最大效益?
  • 我們會怎麼選擇,來讓未來得到最大效益?

這裡的經濟不是跟金錢、金融相關,而是指人們會如何進行決策,來達到他們所想要的目的,同樣也可以套用在AI上,人類都會選擇最好的選擇,同樣AI也是。

神經科學

  • 大腦是如何學習?如何思考?如何處理資訊?

電腦科學家參考人類的神經系統,設計出「類神經」的計算架構,讓電腦可以接收到資訊後,加總和觸發而做出最後的輸出,並且可以依照輸出再回傳到神經系統,讓電腦可以自主學習,就像人類一樣,透過不斷的訓練肌肉、刺激神經,讓我們的組織或反應更加發達一樣。

類神經的架構就是設計多個層次(上圖的Hidden Layer)神經的計算,每一個神經結點都會接收不同的刺激來源(例如開車看到紅燈,刺激來源來自眼睛、四肢),加總累積到一個臨界值時,就會產生相對應的輸出(大腦要告訴腳該踩剎車了),而每一層神經的計算結果都可以變成下一層神經的輸入。

電腦工程

  • 電腦如何運算,如何儲存和表示知識?
  • 電腦如何加速運算?

這領域舉例來說,Google無人車看到紅燈要停下來,這在電腦裡面要如何表示「看到紅燈」「要停下來」這些現象?以及「看到紅燈要停下來」這個知識?電腦要如何在0.000001秒計算出並且反應看到紅燈要停下來這件事?這都是這個領域要在AI上所做的精進和努力。

AI為何存在?帶來的影響是?

這篇只是一個開頭引導,AI目前還是偏向冷冰冰的電腦,情感或許是我們該要賦予給它,讓它可以更親近人、更有溫度的跟人互動,要讓使用者感覺到他是一個貼心的管家,而非冰冷的機器,就像電影魔鬼終結者5裡面的老爹一樣,也是有情感會對人笑一樣(不好意思暴雷惹)。

AI現在也從原本只能靠人輸入訓練資料來教它,演化到只需要一開始教它、後面它會自己演化、自己學習的境界,最大的突破是在於Deep Learning的出現,或許哪天天網或是奧創的出現,那應該不用太意外吧?!

AI存在的價值,並非只有將繁瑣的操作做成自動化,和傳統自動化的差別在於,AI更突出的是它的「學習能力」、「主動判斷」以及判斷後的「決策」,也就是它可以將需要動手或動腦的地方幫忙你處理掉,你只需要悠閒的喝杯咖啡、聊聊天事情就處理完成,AI可以完全自行運作,依照目前現況去做學習、調整和變化,這才是真正的智慧化。

AI應該要是最懂你的助手,而不只是單純處理繁瑣雜事的替代品而已,使用者面對AI會感受到「你真體貼!還記得我喜歡什麼」。

“Sometimes it is the people no one can imagine anything of, who do the things no one can imagine.”
有時候,被世人遺棄的人,才能成就讓人想像不到的大事。
Alan Turing

我是大白,DualCores Studio的Co-founder & Developer,文章同步分享於Medium

需求

今天要來講Android如何定位取得目前位置後,並且在Google Map上顯示,這是一個看似蠻簡單的範例,但是定位在StackOverflow上面一堆寫法,看了眼花撩亂,這邊是我最後可以完美運作的版本。

思路

  • Google Map如何導入在專案內就請自行參考Google Map Android API - Get Started
  • 手機的GPS和網路皆可以定位,有時候使用者不一定會開啟GPS,所以我們採用兩個定位方式依序取得位置。
  • 在取得定位並且設定好Google Map後,我們就可以把目前位置顯示在地圖上了,就這麼簡單而已。

你也許會問,為何我需要使用GPS和網路定位?
原因有三個:

  1. 使用者不一定會開GPS或網路。
  2. GPS跟Network的精準度也不一。
  3. 兩者取得位置的速度兩者也不同。

程式碼實作

  1. res/layout/activity_main.xml加入MapView

    <com.google.android.gms.maps.MapView
        android:id="@+id/mapview"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        map:cameraZoom="13"
        />
    
  2. Activity新增兩個Instance Variable,並且在onCreate()初始化Google Map。

@Bind(R.id.mapview)
MapView mMapView;
private GoogleMap mGoogleMap;

@Override
protected void onCreate(Bundle savedInstanceState) {
    /* 其他既有程式略 */
    ButterKnife.bind(this); /* 使用ButterKnife做View Injection */
    mMapView.onCreate(savedInstanceState); /* 初始化MapView */
    mGoogleMap = mMapView.getMap();
    int googlePlayStatus = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
    if (googlePlayStatus != ConnectionResult.SUCCESS) {
        GooglePlayServicesUtil.getErrorDialog(googlePlayStatus, this, -1).show();
        finish();
    } else {
        /* 設定地圖 */
        if (mGoogleMap != null) {
            mGoogleMap.setMyLocationEnabled(true); 
            mGoogleMap.getUiSettings().setMyLocationButtonEnabled(true);
            mGoogleMap.getUiSettings().setAllGesturesEnabled(true);
        }
    }
}
  1. 新增LocationListener的Instance Variable,目的用來監聽取得更新位置後的動作。
private LocationListener mLocationListener = new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
        if (location != null) {
            Logger.d(String.format("%f, %f", location.getLatitude(), location.getLongitude()));
            drawMarker(location);
            mLocationManager.removeUpdates(mLocationListener);
        } else {
            Logger.d("Location is null");
        }
    }

    @Override
    public void onStatusChanged(String s, int i, Bundle bundle) {
    }

    @Override
    public void onProviderEnabled(String s) {
    }
  
    @Override
    public void onProviderDisabled(String s) {
    }
};
  1. 重頭戲來了,取得目前位置的實作,我們由網路和GPS來取得定位,因為GPS精準度比網路來的更好,所以先使用網路定位、後續再用GPS定位,如果兩者皆無開啟,則跳無法定位的錯誤訊息。
private void getCurrentLocation() {
    boolean isGPSEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
    boolean isNetworkEnabled = mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

    Location location = null;
    if (!(isGPSEnabled || isNetworkEnabled))
        Snackbar.make(mMapView, R.string.error_location_provider, Snackbar.LENGTH_INDEFINITE).show();
    else {
        if (isNetworkEnabled) {
            mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
                    LOCATION_UPDATE_MIN_TIME, LOCATION_UPDATE_MIN_DISTANCE, mLocationListener);
            location = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
        }

        if (isGPSEnabled) {
            mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
                    LOCATION_UPDATE_MIN_TIME, LOCATION_UPDATE_MIN_DISTANCE, mLocationListener);
            location = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        }
    }
    if (location != null)
        drawMarker(location);
}

/* 在Google Map上放上目前位置的地標圖示。 */
private void drawMarker(Location location) {
    if (mGoogleMap != null) {
        mGoogleMap.clear();
        LatLng gps = new LatLng(location.getLatitude(), location.getLongitude());
        mGoogleMap.addMarker(new MarkerOptions()
                .position(gps)
                .title("Current Position"));
        mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(gps, 12));
    }
}

最後完整程式碼大家可以自行取用,謝謝各位。
我是大白,有任何問題歡迎在FB訊息敲來問我,請大家多多支持。

今天來講Android如何使用Toolbar並且加入Navigation Drawer,首先Navigation Drawer可以直接在Android Studio(以下簡稱AS)新建專案的時候,選擇 Navigation Drawer Activity範例來建立新專案。建立完成之後,AS會自動幫你產生下列檔案:

res/layout資料夾

  • activity_main.xml
  • fragment_main.xml
  • fragment_navigation_drawer.xml

java/package-name資料夾(package-name是你專案的package名稱)

  • MainActivity.java
  • NavigationDrawerFragment.java

接下來要開始修改這些檔案,加入Toolbar而且把Navigation Drawer改成Material Design,讓我們開始吧~


新增Toolbar Layout檔案

我們從Toolbar開始,先在build.gradle裡面加入

compile 'com.android.support:appcompat-v7:22.2.0'

讓gradle同步完後,建立新的layout檔案叫做toolbar.xml,裡面加入

<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/colorPrimary"
    app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    app:popupTheme="@style/Theme.AppCompat.Light.DarkActionBar"
    android:fitsSystemWindows="true"
    />
  • 記得是使用v7的Toolbar而非android.widget.Toolbar!!!
  • android:background="?attr/colorPrimary 這邊是使用@style/colorPrimary來設定背景顏色,這是Android 5.0以後的寫法。
  • android:fitsSystemWindows="true" 這是讓Toolbar可以向上延伸到狀態列,主要是用在Android 4.4可以使用透明的狀態列。

加入Toolbar到主介面

再來activity_main.xml裡面我們要把剛剛建立的toolbar.xml引入,所以用一個RelativeLayoutToolbar用include的方式和主要內容的FrameLayout包起來如下

<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    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"
    tools:context=".MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include
            android:id="@+id/toolbar"
            layout="@layout/toolbar"/>

        <FrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/toolbar"/>
    </RelativeLayout>

    <fragment
        android:id="@+id/navigation_drawer"
        android:name="com.moviebomber.ui.fragment.NavigationDrawerFragment"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        tools:layout="@layout/fragment_navigation_drawer"/>
</android.support.v4.widget.DrawerLayout>

修改style檔案

因為我們要用Toolbar取代Actionbar,所以需要把原本的styles.xml修改如下

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">

改成

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/primary</item>
    <item name="colorPrimaryDark">@color/primary_dark</item>
    <item name="colorAccent">@color/accent</item>
    <item name="android:windowNoTitle">true</item>
    <item name="windowActionBar">false</item>
    <item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
</style>

<style name="DrawerArrowStyle" parent="Widget.AppCompat.DrawerArrowToggle">
    <item name="spinBars">true</item>
    <item name="color">@android:color/white</item>
</style>
  • colorPrimary, colorPrimaryDak, colorAccent顏色對應關係可以見下圖,這是讓Android 5.0可以抓到對應的顏色。

  • android:windowNoTitle把視窗標題移除, windowActionBar不使用Actionbar。
  • drawerArrowStyle和下面DrawerArrowStyle是做出開啟Navigation Drawer漢堡變箭頭的動畫(如下圖)


在MainActivity.java使用Toolbar

MainActivity.java要加入Toolbar並且把Actionbar指向Toolbar,然後設定Statusbar為透明的。

public class MainActivity extends AppCompatActivity 
    implements NavigationDrawerFragment.NavigationDrawerCallbacks {

    @InjectView(R.id.toolbar)
    Toolbar mToolbar;

    /**
     * Fragment managing the behaviors, interactions and presentation of the navigation drawer.
     */
    private NavigationDrawerFragment mNavigationDrawerFragment;

    /**
     * Used to store the last screen title. For use in {@link #restoreActionBar()}.
     */
    private CharSequence mTitle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Window w = getWindow(); // in Activity's onCreate() for instance

            w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);
        this.setSupportActionBar(this.mToolbar);

        mNavigationDrawerFragment = (NavigationDrawerFragment)
                getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
        mTitle = getTitle();

        // Set up the drawer.

        mNavigationDrawerFragment.setUp(
                R.id.navigation_drawer,
                this.mToolbar,
                (DrawerLayout) findViewById(R.id.drawer_layout));
    }
}
  • 增加了mToolbar的成員,這邊是import android.support.v7.widget.Toolbar;,我們都是使用v7的Toolbar!
  • if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {那一段是把Statusbar設為透明的,讓colorPrimaryDark也可以在Android 4.4顯示出來,讓Toolbar可以延伸到Statusbar。
  • 使用setSupportActionBar()把Toolbar傳入。
  • mNavigationDrawerFragment.setUp(...)這方法要傳入Toolbar,所以下列的NavigationDrawlerFragment.java要改掉API。

NavigationDrawerFragment.java使用Toolbar

NavigationDrawerFragment.java要可以知道Toolbar的狀態,得知是否有按下漢堡,所以改動的地方如下

public void setUp(int fragmentId, Toolbar toolbar, DrawerLayout drawerLayout)  {
    mDrawerToggle = new ActionBarDrawerToggle(
                getActivity(),                    /* host Activity */
                mDrawerLayout,                    /* DrawerLayout object */
                toolbar,
                R.string.navigation_drawer_open,  /* "open drawer" description for accessibility */
                R.string.navigation_drawer_close  /* "close drawer" description for accessibility */
        )
} 
  • 原本setup()API多加了Toolbar當傳入參數。
  • ActionBarDrawerToggle要從v4換成v7的,並且把Toolbar傳入。

完成結果

  • 現在上面是Toolbar,不是Actionbar。

  • 展開功能表

完成收工!!