Skip to main content

· 6 min read

物質設計 (Material Design)

「物質設計」有人翻作材質設計,但我很容易把「材質」聯想到 3D 遊戲的材質貼圖去,這跟 Material Design 所想要表達的意涵差了十萬八千里。而從相關的英文詞彙聯想,「Material Girl」或譯為拜金女孩或物質女孩,那種對於追求「摸的到的實際東西」有所迷戀的意涵,似乎與 「Material Design」的內在涵義更為貼近,所以我更願意稱之為「物質設計」。

註:官方後來定名為「實感設計」。

物質設計(Material Design)是什麼?

簡而言之,Material Design(物質設計)是 Google 公佈的一套同時適用於 Android、iOS、Web 等各種平台,同時能適用於手機、平板、電視、電腦螢幕等不同裝置的跨平台 / 裝置的設計規則(仍需為各裝置設計,但鼓勵共用更多相同元素)。

讀者也可以透過觀看 Google I/O 2014 - Keynote(從 14:18 分開始,由 Matias Duarte 介紹 Material Design)https://www.youtube.com/watch?v=wtLJPvx7-ys  可以得到對於 Material Design 最精巧的介紹。

我不是設計師,僅從開發者角度分享一些個人想法,如果還有興趣請繼續看下去。

從擬物化、扁平化、到物質化的 App 設計

自從 2007 年 iOS 重新發明手機之後,「擬物」化(Skeuomorphism)一直是 iOS App 的設計原則。「擬物」化的設計,讓新接觸「智慧手機」、「智慧平板」的使用者得以沿用過去實物的使用經驗,愉悅地使用新的數位化工具。

但是這幾年智慧手機 / 平板實在太成功,軟體 App 漸漸吞噬了實體世界的各種工具,造成原本「擬物」所參照的物品已紛紛變成老古董,要年輕人畫出電話的形狀,可能畫的卻是手機的模樣... 總而言之,許多擬物的參照物已經漸漸被時間淘汰了。

微軟的設計師們很早看出這個趨勢,在 2010 年推出的 Window Phone 中使用了極端「扁平」化(Flat)的設計風格。 經過幾年來的大膽嘗試,最後也由市占極高的 iOS/Android 定調了使用者界面「扁平」化的發展潮流。「扁平」化的設計,讓使用者得以減少在使用 App 過程中,辨識「擬物」化界面所產生的認知疲勞。

不管是「擬物」化或者「扁平」化,在每個平台的設計指南中,都明確指出設計的 App 要提供最佳的使用者經驗,必須要符合該平台的風格。iOS 還分別為手機與平板提供不同的設計指南,並指出手機與平板是不同的設備,App 設計必須要符合該設備的使用情境。

其實平台們的意思很簡單:現在開發者必需要對應每個平台,針對平台不同的風格提出相符的設計。對於同平台的不同裝置,也請分開處理。

於是現在開發者(或提出 Material Design 的 Google,別忘了他們得支援多少種平台與裝置)要面對的,是針對不同裝置、多重平台開發設計 App 時所需面對的各種問題。

「物質設計」(Material Design)就是 Google 整理出的新跨平台、跨裝置適用 (For every screen, and for all devices) 的設計指南。並將首先套用到 Android 的最新版本「L」上。

物質化的 App 設計

「物質設計」是以「扁平」化的 App 設計為基礎,加上紙質分層的概念(即以「Google Now」為代表的卡片式設計),整理出字體、色彩、圖標等設計模式,並加入佈局(Layout)、圖像,與動畫效果(effect)等設計模式。紙質分層與「動畫效果」產生出的設計模式,就我所知,是在之前的各種介面設計中所未特別強調的。

想進一步了解 Material Design 上的動畫效果,可由 Google I/O 2014 - Keynote(從 18.25 分開始,由 Matias Duarte 介紹 Material Design Animation)https://www.youtube.com/watch?v=wtLJPvx7-ys  查看。

物質設計(Material Design)工具

看完這些大片的色彩、轉場、動畫效果,不知道身為開發者的你臉綠不綠,反正我綠了。 這樣該如何應用於 app 開發哩?整個開發成本還了得?

還好 Google 還提供了一些配套工具 (但卻不是出現在 Android L)。(未完待續)

參考資料

· One min read

 

There are several source code visualization tools available for choose. I've tried Code Swarm long time ago and this time I'd like to try something that can be done on web directly. So, here's the Gaia source code visulization rendered by CodeFlower via D3.js.

The prosperity of blossom denotes different

 

Orange is the settings app flower. The upperside are modulized setting panels. We can see good order of them. The downside crowds are things that not that modularized.

 

The purple sparkled flower is gaia web components. The main part is gaia-header, the top left part is gaia-grid.

The system app is a flower with a more concrete shape.

The calendar app (in yellow) looks like a pretty Dandelion.

 What about others? go Gaia source code visulization and check by yourself. (The repo is a digest from 2015/1/7 master)

And you can make your own by forking CodeFlower from github.

· 5 min read

2013 年曬了一下書架,2014 年過去了,終於今年沒讀破百本。(今年共讀 86 本,16052 頁)也許是由於今年做了許多不甚成功的專案,佔用了不少時間,或是因為家中小朋友 1 + 1 的效應開始顯現。但其實也看了不少大部頭的書。值得注意的是看的 86 本中,電子版已佔了 32 本(38%)。

來曬一下今年看過的書,順便從中推薦些好書(由近至遠)

今年剛開始看了 Google/Apple 世紀大格鬥與「什麼都能賣!:貝佐斯如何締造亞馬遜傳奇」的 kindle 簡體版。瞭解各大巨頭間的糾葛與發跡史還蠻有意思的。

「超速習法」對於閱讀前的「待機」心理準備部分歸納的非常完備,後半實際閱讀時的方法相對表達地沒那麼清楚。

Kindle 上看完的「哲學家們都干了些什麼?」。透過一邊介紹有代表性哲學家的故事,將哲學起始到近代的哲學思想都點水過了一遍。對於我這樣的初學者來說還是很有幫助。

過年和元旦依舊是漫畫的領地,「銀之匙」實在是部很有趣的漫畫,身在台灣就能體驗日本的農校生活。

為了稍微瞭解 Android 底層開發以完成「Beyond Web and FirefoxOS」的撰寫,翻譯了「Android BSP 與系統移植開發」一書。

在 kindle 上複習了一遍 yy 小說「冒牌大英雄」,另外也拜讀王伯達先生的新書「再見,世界工廠」,「生產才能創造踏實的經濟成長」等想法在年末看到時寒冰先生的「未來二十年,經濟大趨勢」中也有出現。

數位新時代

在過去十年裡,資訊的獲取方式發生了巨大的變化。可汗學院、維基百科、數以億計的部落格,多如牛毛的網站,只要你有興趣,就可以找到任何資訊,不論你在哪裡。學校的資訊媒介作用已經不再像過去那麼重要,培養孩子「發現資訊的願望」在現在更為重要。

再見世界工廠

這些年可以看到對岸的改變,應進一步認識與了解對岸與東南亞的週邊鄰國發展狀況

30 歲前,畫出你的生涯藍圖

我對圖解與量化自我類的書一直沒什麼抵抗力。這本算言之有物,但一年過去了,實踐上還是差了點 /_\。

給中學生的時間管理術

各種時間管理的入門概念,沒看過科維,GTD 的人從這邊入門也不錯

從年初開始看到年中才看完「Javascript Pattern」,作者很仔細地推導各種 Javascript 模式,相當有意思。

「遊戲化時代」 這本書的作者之前有在 coursera 開 MOOC 課程,雖然我沒上完,但可以感覺到課程內容比書中提到的更多元。這次終於讀完整本。

郝明義先生的工作 DNA 三卷也拜讀完了,值得一觀。小米的「參與感」和 360「我的互聯網方法論」書雖然打廣告意味濃厚,但比起台灣的捧 CEO 類型商業書來,多了些實在的想法。

「一個人的經濟」 前半段講到日本即將面對的現況,不久也會出現在台灣。提早察覺到的趨勢,可以讓我做些什麼哩?

小旅行,繪本,工作,時間管理,哲學,趨勢。我的閱讀依然雜食。

PS: 感覺 Anobii 已經幾乎不更新,許多書已經不是輸入 ISBN 就可以自動出現,所以今年的書單裡還是有些漏書。也許看倌可以建議我將書架搬去哪好?

· 5 min read

Marissa Mayer 與起死回生的雅虎 一文中,可以看到雅虎工程師對自家 CEO 的讚美。其中一點提到雅虎內部有很多系統設計是與外界完全不一樣的,直到新 CEO 上任才放下使用多年的 YUI 而擁抱其他 Open Source 團隊維護的專案。

從樂觀的角度看:因為當初 Yahoo 工程師夠強(Douglas Crockford),覺得自己做的東西比別家的好,所以乾脆自己做。有這種「愛自幹」的精神,所以才會做出很多跟其他地方不一樣的東西,這是 feature。從反面看則是因為長期跟人家用的工具不一樣,所以沒辦法直接使用別人做的 Open source 工具,這也是 bug。

以前的 Yahoo「不擁抱其他 Open Source 團隊維護的專案」嗎?Yahoo 當然某種程度是支持 Open Source 的公司。就我所知,Yahoo 是間很早開始支援 Hadoop 開源專案的公司。Yahoo 使用 Hadoop,也延攬 Hadoop 開發者進公司,所以「不擁抱其他 Open Source 團隊維護的專案」這個論點也許是存在的。

我們來看看 Google。類似於 YUI,Google 自己也有一套 Closure Library,多數 Google 的產品都建構於其上,雖然開源,但外面專案會使用它的也少之又少。 而 Android 呢,AOSP專案裡也有不少只能在 Android 工具鏈中使用的工具,但從以前基於 Eclipse 的 Android 開發工具到最近釋出的 Android Studio 1.0 是基於 IntelliJ 的開發工具,部分的 Google 似乎較接受擁抱其他 Open Source 團隊維護的專案。

在寫下這兩段話的同時,我比較這兩家公司擁抱其他 Open Source 團隊維護的專案的原因,竟發現兩者改變的相同點在於「併購」。當 Google 併購 Android,就接受了 Android 團隊與他們提出的開發工具。當 Yahoo 開始大採購,許多新創團隊進入 Yahoo 大家庭的同時,也帶進各團隊使用的「其他 Open Source 團隊維護的專案」。也就是說,當新移民團隊進入這個團體時,帶來了改變。

比起大公司而言,新移民團隊在被併購前的資源有限,使用其他 Open Source 團隊維護的專案是很自然的事。團隊被併購後若需使用併購方的技術重新打造產品,將是相當痛苦的事情。因此在 Yahoo 大採購後許多原有規矩被改寫,恐怕背後也有這些新團隊帶來的影響。

用社會學可能可以解釋為什麼是團隊而不是個人?因為改變一直是困難的事。原有的團隊在過去磨合 / 開發的過程中已經凝聚出了一些共識。移民到新團體的過程中,新移民團隊需要參與和適應團體的一些運作方式,而團體則需接納和適應新移民團隊的一些習俗。

回來想想 Mozilla 也做了很多 Open Source 專案,但整套工具常常僅供自己相關的專案使用。 從自身找原因,自家工具的開源專案原本就是為了滿足自己的需求。當外部出現對這套工具的需求時,由於公司人力資源調配,優先要滿足的還是自身專案時程的需求。 當用到的 Open Source 專案在公司裡就有人可以諮詢,當然比去找其他 Open Source 團隊維護的專案來地可靠(?)。許多專案在這樣的思維下就使用自己寫的工具,當意識到的那天,已經回不去了。

若想避免現在的 feature 變成未來的 bug,在決定自幹專案工具前,先想一想(或 search 一下),也許結果會不一樣。

· 13 min read

前陣子 Facebook 推出一套名為 FLUX 的前端程式架構,期望能幫過去沒有條理,程式多了結構就亂得像一團麵條的 Javascript 程式寫法找到一個理想的組織方法。

FLUX 簡介

視圖(View)-> 操作(Action)-> 分配器(Dispatcher)-> 資料處理器(Store)-> 繪圖者(Renderer)-> 視圖(View) FLUX 的基本原理有別於常用的 MVC(Model/View/Controller)或 MVVM(Model/View/ViewModel)是在 M,V,C(VM)三者之間互相傳遞或修改資料,

 

MVC (image from fluxxer)

FLUX 重新定義整個組織架構為單向的視圖(View)-> 操作(Action)-> 分配器(Dispatcher)-> 資料處理器(Store)-> 繪圖者(Renderer)->  視圖(View)的運作流程。

FLUX (image from fluxxer)

就我的理解,FLUX 的架構可以拆分為三個重點流程:

  • 跟視圖(View)有關的操作(Action),都透過事件註冊到分配器(Dispatcher)去。
  • 分配器 (Dispatcher)負責將操作(Action)傳遞給需要的資料處理器(Store)。
  • 資料處理器(Store)負責跟資料直接相關的操作。若資料處理器(Store)修改的結果需要反映到視圖(View)上,可以透過發送訊息通知給繪圖者(Renderer)處理。這邊講到了原本 FLUX 概念圖中沒有提到的繪圖者(Renderer)這個角色,在 Facebook 中他們是用 ReactJS 處理。

瞭解其基本架構之後,我發現其實就算不用他們提供的函式庫,用 Javascript 內建的 addEventListener, handleEvent, customEvent 等方法,也可以利用前面所提的三個重點,漸進寫出符合 FLUX 精神的程式。

目前的 JS 組織方式

一個常見的 JS 檔案,一般的架構是

var App = {    init: function app_init() {       // get view       this.view1 = document.getElementById('xxx1');       this.view2 = document.getElementById('xxx2');             // do stuff       this.view1.addEventListener('click', function() {         // do something       });       this.view2.addEventListener('keyup', function() {         // do something       });     } }; 若我們想要將視圖(View)的操作從 init 分離開來,大部分的人會這樣做

 var App = {     init: function app_init() {       // get view       this.view1 = document.getElementById('xxx1');       this.view2 = document.getElementById('xxx2');             // do stuff       this.view1.addEventListener('click', this.clickView1);       this.view2.addEventListener('keyup', this.keyupView2);     },

    clickView1: function app_clickView1() {        // do something     },

    keyupView2: function app_keyupView2() {        // do something     }

}; 如果在 clickView1 或 keyupView2 中要呼叫到 App 裡的參數或方法,那麼我們需要在 addEventListener 時使用 bind (this)

 var App = {     init: function app_init() {       // get view       this.view1 = document.getElementById('xxx1');       this.view2 = document.getElementById('xxx2');             // do stuff       this.view1.addEventListener('click', this.clickView1.bind(this));       this.view2.addEventListener('keyup', this.keyupView2.bind(this));     },

    clickView1: function app_clickView1() {        // do something     },

    keyupView2: function app_keyupView2() {        // do something        this.clickView1();     }

}; 大多數書籍的範例大概就停在這裡,沒有再進一步探討程式的組織架構了。即使是龐大的 Javascript 專案如 Gaia,不少部分的程式碼組織方式也是如此。在這種組織方式裡,若有很多的視圖(View)需要操作或修改,我們的程式碼就會開始亂起來。

下面來試著將以上程式漸進改為 FLUX 架構。

改進建議一:將 handleEvent 當作 Dispatcher 來使用

跟視圖(View)有關的操作(Action),都透過事件註冊到分配器(Dispatcher)去。 (溫馨提示:IE 9 以上版本才有支援 handleEvent 方法,在之前版本上使用要加 polyfill)

我們先來想想看視圖(View)跟操作(Action)在前端 Javascript 程式中分別代表著什麼。 視圖(View)很明顯,就是透過 getElementById 等方法,從 HTML 中取得代表對應節點(Node)的元素(element)。

若想要套用 FLUX 架構,我們可以將附加在各個元素(element)上的事件行為分離,將事件註冊到一個統一的地方(分配器),在這個地方對不同的事件進行操作。

 Javascript 內建的分配器叫做 handleEvent,它可以拿來處理任何事件 Event,寫法如下。

 var App = {     init: function app_init() {       // get view       this.view1 = document.getElementById('xxx1');       this.view2 = document.getElementById('xxx2');             // do stuff       this.view1.addEventListener('click', this);       this.view2.addEventListener('keyup', this);     },

    handleEvent: app_handleEvent(evt) {        switch (evt.type) {          case 'click':            switch (evt.target) {               case this.view1:                 this.clickView1();                 break;            }            break;          case 'keyup':             switch (evt.target) {               case this.view2:                 this.keyupView2();                 break;             }        }     },

    clickView1: function app_clickView1() {        // do something     },

    keyupView2: function app_keyupView2() {        // do something        this.clickView1();     }

}; 這麼做帶來的明顯好處是所有的呼叫都統一在 handleEvent 中,可以更容易地追查到。

這麼寫也可以在 addEventListener/removeEventListener 時不用使用 bind (this),而 bind (this) 經常有些 side effect 需要特別留意。

例如假使我們想要反註冊 view1 上的 click 方法,使用以下寫法

 this.view1.removeEventListener ('click', this.clickView1.bind (this)); 其實並沒有將第一個 event 移除。因為使用了 .bind(this) 後,回傳的其實是一個新的 instance...。 正確的寫法是 this.bindClickView1 = this.clickView1.bind (this) this.view1.addEventListener ('click', this.bindClickView1); ... this.view1.removeEventListener ('click', this.bindClickView1); 用 handleEvent 可以省點事,要反註冊時也傳入 this 即可。 this.view1.removeEventListener ('click', this);

改進建議二:將資料處理的部分分離,使用自訂事件來改變 Store 狀態

分配器 (Dispatcher)負責將操作(Action)傳遞給需要的資料處理器(Store) 資料處理器(Store)負責跟資料直接相關的操作。在稍大的 Web App 中,我們可以另外定一個 Object 來處理資料相關的事宜。一般我們的寫法會是

// Store.js function Store() {   this._data: 0; };

Store.prototype = {   getSomething: function s_getSomething() {     return this._data;   },

  doSomething: function s_doSomething() {     this._data += 1;   },

  setSomething: function s_setSomething(val) {     this._data = val;   } };

// App.js var App = {   this.store = new Store();   this.store.init();   this.store.getSomething();   this.store.doSomething();   this.store.setSomething (2); }; 若想要套用 FLUX 架構,首先我們要避免從資料處理器(Store)外部直接改變資料處理器(Store)。我們可以透過在呼叫端使用 window.DispatchEvent 發送自訂事件(CustomEvent),並在資料處理器(Store)中接收自訂事件來做到。

如此一來,資料處理器(Store)將只留下 get 方法來讓外部取得 Store 想提供的資料。

另外如果程式碼改善進入到下一個重點,在操作(Action)時應該不需要再呼叫 Store.getSomething 了,我們將資料處理器(Store)的 getSomething 方法留著給繪圖者(Renderer)使用 。

// Store.js function Store() {   this._data: 0; };

Store.prototype = {   init: function s_init() {      window.addEventListener('store_do', this);      window.addEventListener('store_set', this);   },

  handleEvent: s_handleEvent(evt) {     switch(evt.type) {       case 'store_do':         this.doSomething();         break;       case 'store_set':         this.setSomething(evt.detail.val);         break;     }   },

  getSomething: function s_getSomething() {     return this._data;   },

  _doSomething: function s_doSomething() {     this._data += 1;   },

  _setSomething: function s_setSomething(val) {     this._data = val;   } };

// App.js var App = {   this.store = new Store();   this.store.init();   //this.store.getSomething();   window..dispatchEvent(new CustomEvent('store_do'));   **window..dispatchEvent(new CustomEvent('store_set',**     {'detail':{'val':2}}   )); }; 這麼做帶來的明顯好處是測試時可以簡單地將 Store 與 App 分開來測試,這對大型 App 是很重要的。

改進建議三:讓 Renderer 來處理視圖

若資料處理器(Store)修改的結果需要反映到視圖(View)上,可以透過發送訊息通知給繪圖者(Renderer)處理 > //  Renderer.js var ClickRenderer = {   init: function s_init(element, Store) {      this.element = element;      this.store = Store;      window.addEventListener('render_view1', this);   },

  handleEvent: s_handleEvent(evt) {     switch(evt.type) {       case 'render_view1':         this.element.textConent = this.store.getSomething();         break;     }   }};

// Store.js function Store() {   this._data: 0; };

Store.prototype = {   init: function s_init() {      window.addEventListener('store_do', this);      window.addEventListener('store_set', this);   },

  handleEvent: s_handleEvent(evt) {     switch(evt.type) {       case 'store_do':         this.doSomething();         break;       case 'store_set':         this.setSomething(evt.detail.val);         break;     }   },

  getSomething: function s_getSomething() {     return this._data;   },

  _doSomething: function s_doSomething() {     this._data += 1;     window..dispatchEvent(new CustomEvent('render_view1'));   },

  _setSomething: function s_setSomething(val) {     this._data = val;   } };

// App.js var App = {   init: function a_init() {     // get view     this.view1 = document.getElementById('xxx1');     this.view2 = document.getElementById('xxx2');     this.store = new Store();     this.store.init();     ClickRenderer.init(this.view1, **this.store);**   },

    handleEvent: a_handleEvent(evt) {     window..dispatchEvent(new CustomEvent('store_do'));     //Store.setSomething(2)     window..dispatchEvent(new CustomEvent('store_set',       {'detail':{'val':2}}     )); }; 上段程式在 App 中註冊了 ClickRenderer,並傳入 Store 與 所需的 View 元件。所有的介面更新全交由 ClickRenderer 處理。

(另一個方法是讓繪圖者(Renderer)監看資料處理器(Store)的狀態,然後去改變視圖(View))  http://fluxxor.com/documentation/store-watch-mixin.html

總結

整理完後,一般 javascript 套用 flux 架構的運作流程如下:

簡而言之,上面的各種建議是鼓勵大家多使用 Javascript 內建的 addEventListener, handleEvent, customEvent 等方法。透過大量使用 event,我們可以改善 Javascript 程式邏輯,資料,與介面元件之間的關聯程度。

將 FLUX 架構拆分為三個重點流程來理解或實踐的好處,是我們能漸進地遵循其中一些方法來改善我們現有的程式架構。

以上是我關於如何使用 FLUX 架構在一般 Javascript 組織方式的第三個版本,可能有些錯謬之處,還迎大家討論或給予建議。