<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2305840094117072816</id><updated>2026-10-15T01:30:00.407+08:00</updated><category term="windows phone"/><category term="cpp"/><category term="windows runtime"/><category term="android"/><category term="mfc"/><category term="store apps"/><category term="windows api"/><category term="c#"/><category term="oo"/><category term="working"/><category term="java"/><category term="visual studio"/><category term="wpf"/><category term="design pattern"/><category term="silverlight"/><category term="windows mobile"/><category term="direct x"/><category term="uwp"/><category term="eclipse"/><title type='text'>程序員筆記</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>226</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-7801424950167694776</id><published>2021-03-17T00:30:00.014+08:00</published><updated>2021-03-17T22:31:50.263+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="working"/><title type='text'>軟體工程師是否夠資深？是否夠格當主管？</title><content type='html'>&lt;p&gt;最近還算是年後轉職潮或年度組織調整潮的尾聲，仍然會有一些後輩私下來問這題&lt;b&gt;&lt;i&gt;「我最近跟主管提升遷，但主管總是叫我再等等，說我的績效還不夠好，但我實在無法從主管的口中問到資深工程師到底需要有怎樣的條件，連要加強哪些方面都不明確，Ascii 能說說你的想法嗎？」&lt;/i&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;軟體工程師具備怎樣的條件後可以獲得資深工程師的職稱呢？&lt;/li&gt;&lt;li&gt;要具備怎樣的條件才叫能夠勝任軟體工程團隊主管一職呢？&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;雖然這已經是個被寫到爛的題目了，但我還是想寫些&lt;b&gt;&lt;i&gt;「不那麼抽像、容易理解與想像得到具體行為」&lt;/i&gt;&lt;/b&gt;的答案，提供給對這題目有興趣的同學們從中挑選出具體可執行的下一個步驟。&lt;/p&gt;&lt;p&gt;集合 Senior Engineer、Staff Engineer、Principal Engineer 等這些工程師職稱，我個人認為該擁有的額外技能是一致的，差別只在於這些額外技能的等級高或低，例如導入新技術這塊，導入新版本語言的 Threading 新語法是這個技能中的最初級，導入整個新 Library 並實作完成且無痛轉換也許是這個技能的中高級，所以一併談即可。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;熟練&lt;/b&gt; - 想像自己剛開始接觸一個新的程式語言或技術框架時，時常需要翻書或搜尋找答案，找範例，甚至是翻出自己過去寫過的程式碼來回顧語法，各個領域的資深工程師是不應該有這種行為的，你應該對自己負責領域的語言、語法足夠熟悉，這才能讓你接獲一項新任務時，所花費的實作時間比一般工程師要來得快速，進而產生較高的績效，你不必依賴別人或是以前的自己來指導如何工作。&lt;/li&gt;&lt;li&gt;&lt;b&gt;熟悉&lt;/b&gt; - 和熟練很類似，但從 Coding 擴大為 Domain Know-how 的層次。想像你的專業領域中，接獲了一項新的功能開發，其中有許多只聽過但沒實作過的技術協定或架構，大部份新手會在每個領域各撰寫一個拋棄式的 POC 專案，等到大部份沒接觸過的領域都 POC 完了，才開始著手進入開發主線，就我長久以來的觀察，這也是新手績效遠低於資深工程師的原因之一，要想解決這個狀況，得靠閱讀、練習、紀憶、熟悉，這種擴大知識圈的工作會很快速的反饋到你的績效上。&lt;/li&gt;&lt;li&gt;&lt;b&gt;營造強者願意共事的環境&lt;/b&gt; - 面試工程師時，時常會有面試者問我們偏好讓 Library 維持穩定版本，還是儘可能的追新版本，面對這題，我的回答是&lt;i&gt;&lt;b&gt;「我希望工程師們不是用偏好來決定這題的答案」&lt;/b&gt;&lt;/i&gt;，你必須要有能力評估專案中眾多的 Library 各自夠穩定並且夠新的版本，並且追上那些版本，否則你們所打造出來的工作環境，遲早會因為使用的技術過舊，而碰到徵才時強者不願意加入的狀況，在這種想法之下，你會發現工作不只是把功能 implement 出來就好，該做的事情還有很多很多，適當的追逐 Library 版本只是其中一個例子。&lt;/li&gt;&lt;li&gt;&lt;b&gt;追求極緻&lt;/b&gt; - 這又和上前面營造環境這點很類似了，相同的是從純萃的 Coding 再往外跨，你可以用你的專案去思考，例如一個 App 從 Coding 開始到 Commit 一筆修改，到發了一個 Pull Request 再到編譯，再到軟體部署甚至用戶行為收集的整個過程，除了在 Coding 這方面以熟練和熟悉增加效率之外，你還能夠如何提高生產效率呢？例如，你撰寫了多少測試來讓出版的驗證過程更有效率？你幫多少環節做了自動化來幫助 Code Review 等行政工作更有效率？你導入了什麼新工具幫助版本驗證過程更快的發現人類可能無法發現的問題？你的產品網站容易被關鍵字搜尋到嗎？你的網站在行動裝置上執行分數高嗎？你的架構擴充性足夠高嗎？你的產品能搭配上各平台適當的人機交互功能嗎？你的產品符合目前主流的操作流程甚至是 UI Design Guideline 嗎？&lt;/li&gt;&lt;li&gt;&lt;b&gt;預知災害並避免&lt;/b&gt;&amp;nbsp;- 軟體工程和大多數的工程一樣，總是會有非常多種方式能夠做到外表很相似的成果，但各個作法有其各自的優缺點，愈是簡單便宜的作法，也愈容易被初階的駭客找到攻擊弱點，也愈容易在用戶的資料量累績到一定程度時因為效能問題湧入大量客訴干擾後續專案開發進度。你必須具備足夠多的資訊安全、效能問題等知識，知道各專案適合的實作方式有哪些，在組織能夠負擔的成本與風險下，挑選最適合的實作方式，這比追求極緻還需要再往外跨一些，但如果你負責的專案總是能在產出成果中涵蓋不易有資安漏洞、執行架構具備高效率的等細節，客觀來看，你肯定是個資深工程師。&lt;/li&gt;&lt;li&gt;&lt;b&gt;持久力&lt;/b&gt; - 天生的資深工程師很少，天生的資深工程師也不需要來看我寫的這些文章，工程師們在有意願爭取升遷時，向主管或向前輩問到應該加強的項目或應該多承擔的項目後，通常會熱血的列出一些工作目標。但是，大部份的人只能維持不到一個月的時間，例如有人可能會說&lt;i&gt;&lt;b&gt;「為了達成熟練，並且提高第一時間就使用正確架構的機率，我要來讀 OOO 和 XXX 這兩本書」&lt;/b&gt;&lt;/i&gt;，但往往連第一本都看不完，甚至只看了兩章就又回復以往的生活，相似的例子很多，例如開出一堆要撰寫測試的票，但只實作了一張。建了好幾篇技術文章或教學文件的主題，但幾個月過去了還停在第一篇的草稿。不是天生的資深工程師真的完全沒關係，但你要肯演，演得夠久就變真的了。&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;以上各種資深工程師之於我來說，都還是如何成為一個更好的 IC (Individual Contributor) 的層次，不太有什麼團隊管理的行為被納入評等，所以我個人覺得自己的要求不算高，如何在工程師職務中升等就先簡單列出上述幾點。接下來講轉職這題，如果你想要爭取的是工程團隊主管，簡單描述一些具體的事例如下。&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;有主見並且符合普世價值&lt;/b&gt; - 簡單來說，你要盡可能想過下屬可能問你的問題，想過面試時面試者可能問你的問題並擁有自己的答案。例如今天有個下屬來問你文章標題這個題目時，你能有結構的說出自己的想法嗎？而不是和文章第一段所寫的只能只能說出效率還不夠、你還不夠強等等抽像的回應。除了有自己的想法，也要做到能夠紀錄下成員的具體事項給予分類與建議，並協助理解弱點與協助改善。&lt;/li&gt;&lt;li&gt;&lt;b&gt;教育訓練與文化塑造&lt;/b&gt; - 維持團隊成員對於組織、產品的夢想肯定是團隊主管的責任，維持團隊內部技術交流的活絡也是，打造每個成員都願意具名有話直說的文化也是，尋找團隊新血，根據此時此刻組織的目標做策略，有效的考評並做教育訓練也都是團隊主管的責任，這些工作你有自己的具體執行方向嗎？順帶一提，面試技術主管時，我一定會問對方&lt;b&gt;&lt;i&gt;「你計劃怎麼做教育訓練」&lt;/i&gt;&lt;/b&gt;。&lt;/li&gt;&lt;li&gt;&lt;b&gt;肯做決定&lt;/b&gt; - 延伸上一個項目，你難免需要中斷一個正進行到一半的專案，也難免要把一個工程師們都好想玩的酷東西的優先權往後排，甚至你為了公平性，為了接下來組織的方向改變，需要請一位當初你面試進來的同事離開團隊，你能夠不再被以前一起吃飯、玩手遊的各別工程師情緒勒索，做出對組織、對整體團隊最適合的決定嗎？&lt;/li&gt;&lt;li&gt;&lt;b&gt;做好與做完的平衡&lt;/b&gt; - 大部份的書籍文章都會教我們要把事情做好，而不是做完，假設你已經是一個技術能力夠強的資深人員了，那你肯定能提出把事情做到理想狀態的方案，但身為技術團隊的主要窗口，參與各項專案開發的評估過程，在理解公司能負擔的成本與策略即時性的重要程度後，是否能夠給出平衡的解決方案，幫助組織獲得利益，幫助組織解決能夠活下去、活得好的問題，而不是單純的猛開副本、把事惹大。這其實也是常見的因應組織目標改變時，過去很適合的主管可能接下來就不適合了，必須重新找另一個新主管接手的主要原因。&lt;/li&gt;&lt;li&gt;&lt;b&gt;持久力&lt;/b&gt; - 持久力又要被我提了一次，因為持久且穩定的輸出能力絕對是技術主管該有的重要表現。舉一個實際例子，大概在五、六年前我開始當技術主管職時，我很快的發現團隊主管會因為外部會議與身兼團隊主要窗口的因素獲得最多的資訊及權限，手中擁有最多資訊的就是主管本人，要做到資訊足夠透徹的佈達，由我來撰寫週報提供給所有成員，會比各個工程師寫週報提交給自己來得更透明，這件事我一做就是好幾年從來沒斷過。又例如你想為團隊安排定期讀書會時，但某一陣子若成員都在忙碌時，你能不能持續堅持下去自己找題目講，而不是逐漸的讓定時讀書會消失。又或者你覺得每週有固定的時間 Code Review，你有一直持續帶下去嗎？還是只做了 1、2 次呢？除非你想到更好的作法，或需要停止的理由，否則你必須把上述一切你認為好的事情都內化成為習慣，維持穩定的成果輸出，在這之前組織是不會放心把團隊交給你的。&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/7801424950167694776/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2021/03/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/7801424950167694776'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/7801424950167694776'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2021/03/blog-post.html' title='軟體工程師是否夠資深？是否夠格當主管？'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-4133739920348687657</id><published>2018-04-14T15:34:00.000+08:00</published><updated>2018-04-14T18:11:24.588+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="working"/><title type='text'>組織用人與帶人</title><content type='html'>每次農曆年後總有不少公司放出新職缺，各公司內部也有不少平時累積了一定負能量而續勢待發、或是對階段性收入有明確規劃的職員，在領完年終獎金後開始尋找新的機會，小的在工作了近十年，累積一定的經驗後，開始會有些晚輩在年後約我聊聊天問問意見，今年也許是碰巧遇到組織有些調整和特殊事件，來找小的聊天談心的晚輩又比以往還多，想想那不如趁著這個機會把一些常見問題與我自己的看法記錄下來，也方便未來碰到相關的情境時能夠自我監督與反省。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;
&lt;br /&gt;
今年被問到的問題大概有這幾項&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;某些事情對於自己主管的處理方式有不同意見，想聽聽看如果是我來處理會怎麼做？&lt;/li&gt;
&lt;li&gt;已經有明確的外部機會且薪資較高，希望列出比較聽聽意見？&lt;/li&gt;
&lt;li&gt;想了解我自己的部門成員如何利用專案與專案之間的空檔時間？&lt;/li&gt;
&lt;li&gt;有明確針對的同事，想從我的角度分析兩人的差異，為什麼有些人升遷總是順利？&lt;/li&gt;
&lt;li&gt;上級主管是用什麼樣的標準在衡量成員，除了把程式寫好還要做什麼？&lt;/li&gt;
&lt;li&gt;向上級要求調職或調薪不成，卻又得不到明確的原因或可以怎麼做的建議怎麼辦？&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
職場上行為很多是無法定義好壞的，要看每個人對於生活與時間利用的價值觀如何，有些人的價值觀是每天盡全力在下班前把事情做對做好準時下班、假日不會有狀況發生，能夠自在的享受休閒生活。有些人的價值觀是期許自己每兩年都要有職位與薪資上的升遷。有些人是期許自己一定歲數之前能夠將某些環境變好，為組織和社會留下點什麼。但不論哪一種價值觀，對於在職場上工作的達成率是無關的，哪一種都能夠讓事情做得又好又快。&lt;br /&gt;&lt;br /&gt;
排除掉特別目標該怎麼解決的問答之後，我將自己認為「如何在工作上讓自己成為一個可靠同事」的方法做成一份簡報硬塞給部門的成員，有需要參考的朋友歡迎品味看看，在一個正常的組織內如此自我要求後，理論上碰到突發狀況你會能夠應付、碰到新的需求時也能提供有效的解決方案，甚至能成為有效避免危機的重要人物。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;簡報位置：&lt;a href=&quot;https://www.slideshare.net/itsAscii/ss-93770609&quot;&gt;&lt;u&gt;職場闖關經驗分享&lt;/u&gt;&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.slideshare.net/itsAscii/ss-93770609&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnwV_Lsm7eB8i67KATnqIcvw4G0k_fmgV4fsHNfpIlLruA_TnK6K1xzCc4f1ELG3R4WA2YUmdo9CW5y4tCmXs5fuxMbMkp9RSF8vyN39xdYLisrkMoMwnPIGxwXKbg1LxOYF8KrpSjzzUG/s320/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2018-04-14+%25E4%25B8%258B%25E5%258D%25886.08.56.png&quot; width=&quot;320&quot; height=&quot;192&quot; data-original-width=&quot;1600&quot; data-original-height=&quot;958&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
大部份的重點都在簡報裡了，但有些帶人與用人的觀念我想比較詳細的記錄下來，有興趣的朋友也歡迎參考看看…&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;執行帶人的義務，不要只使用挑人的權利&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;
有些帶人主管在面對狀況時經常會下達抽像的指令，抽像的指令大部份情況沒什麼不對，但要清楚的了解抽像等於提供了被誤解或失敗的空間。當結果真的發生失誤時，帶人主管應該要檢討自己錯估這件事的立即性或重要性而未給出明確的解決方案，但很多帶人主管往往不是先檢討自己的失誤，而是認為這些工作的執行者本來就該處理好這些事，漸漸的失誤少的成員愈來愈獲得重用而被挑選到新的職位上，聽起來好像也很合理，但仔細想起來這種主管一直在做的事就是放任事情的發生並挑選能把事做好的人，這個不行就挑下一個，這種做法必須要靠相當大量的好運氣與大量的組織人數才能一直碰到不錯的成員幫忙解決各種難題，在資源較有限的中小型公司內容易發生願意主動做事卻失誤的成員逐漸離開自己，而且你的組織年齡層會愈來愈老，因為你花在讓比自己年輕的人追到自己與取代自己的時間太少了。&lt;br /&gt;
&lt;br /&gt;
如果工作十年多了，卻不曾有人說過你帶人帶到心裡，或是幾年下來沒有成員們因為在你的幫助下明顯獲得成長而真心的感謝你，該想想是不是忘了自已所處角色身上有著帶人的義務，否則很多離開你身邊的人會是以失望的心情離開的。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;將相處看得太重要，很可能導致平傭的結果&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;
商業組織大多數的成員很怕政治問題甚至看不起政治問題，但政治這門學問是用來處理人與人之間相處與如何分配有限資源的好方法。商業組織內很多的政治行為想單純一點，只是出於對有限資源的競爭而已，沒有人的目的是為了毀滅組織，既然沒有資源無限的方案，那麼參考政治方法會有許多可以學習的地方。&lt;br /&gt;&lt;br /&gt;
這裡要以法律給予議員與立委言論免責的權利為例，言論免責是一個對於方案提供監督、質詢時很重要的政策。大多數和自己想法不相符的意見，指出自己缺失的言論都會讓人感到不愉快，時常提出質詢的成員往往會變成所謂脾氣差、難相處的同事，但只要陳述意見時不涉及攻擊或法律層面且的確讓任務的結果更加完美時，我覺得這類標籤不該貼到這類形的同事身上，但許多主管反而會選擇好管理，容易配合與好相處的成員來擔任要職，這樣的情況在我看來非常可惜，如同上一點，這樣子的作法並沒有盡到帶人的義務，主管們只是挑了比較容易處理的作法，後果是放棄了真正對組織未來比較好的結果。&lt;br /&gt;&lt;br /&gt;碰到這種情況，該做的事情應該是放一些心思在這類成員身上，試著和他們溝通組織的價值觀與提供建議的作法讓他們了解好處，也許這項任務很難也很花時間，但我的信念是投入夠多的力氣後，連殺人犯都能夠被教化了，不能試也不試的就挑選簡單但會造成平傭組織的作法。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;善良且誠實的面對每個成員&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;
有些晚輩提到，他們的主管在拒絕調職調薪的請求後，給出的建議往往是「你要再積極一點、你在工作上要再有想法一點」這種抽像的答案，我覺得這種答案是很糟糕的。能進來我們公司的人基本上都是聰明人，這種答覆很明顯代表主管對於每位成員的未來是沒有想法、沒有規劃的，甚至會讓成員發現自己在組織內被定位為可有可無的雜事支援角色，相當令人灰心。健康的狀況是每個人都可以找自己的主管討論自己想到達的目標，即便是幾年後要離開公司到某處也可以被友善討論與從主管角度來得到建議，明確的了解自己還欠缺什麼能力，主管也應該能夠從現況提供不同的路徑與明確建議，例如明確的改善 App 的效能至什麼等級這種粒度的目標，讓成員清楚組織的目標、公司的價值觀與從旁人的角度來看自己的優缺點是如何的。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;總是把「官大學問大」這句話放在心上有助於組織共同成長&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;
許多人會列出一些主管竟然不知道某事的案例來質疑主管不適任，這種狀況很值得教教晚輩們用「官大學問大」這句簡單的話來做思考。在文章前面所附的簡報第 10 頁中，我畫了一張多顆齒輪串聯的圖案表示組織階層的概念，稍微算一下可以知道最大顆的齒輪移動 4 格，最未端的齒輪就足夠轉 2 圈了，這是我自己很喜歡用來想像組織的方式，最上層的老闆一句話，到最未端的執行人員可能就要忙翻的做一堆事，不論怎樣所有人都無法知道所有的細節。但許多主管卻很在意自己無法得到完整的資訊，許多成員也習慣用上級知不知道某事、某件主管原本在做的事怎麼後來就交給別人做了來評量是否稱職，這種狀況適當可以，但用錯了或過度了是會讓組織發生空轉的。&lt;br /&gt;&lt;br /&gt;
比較健康的狀況是各階層的成員都應該清楚自己所在位置該做好什麼工作項目，舉一個簡單例子，技術部門主管需要在專案前期就幫忙弄清楚錯誤邏輯無法實作的需求與提出可能的方案，如果有個人被指派為主管後卻還是花了大量的時間做資深工程師在做的事，那這個人自然練習不到這項技能，何況這只是技術部主管需具備技能中的一小項，每位工程師大概不會希望自己的主管在接需求、幫自己向上爭取薪資、成本控制、優先權決策等工作做不好吧。所以先想像一個位置本來該做好的事是什麼再做評量才對，如果無法想像，去問問老闆的老闆或與主管同層的其他部門主管來得到答案，避免誤會的同時也讓自己了解哪些部份可以先開始加強了。&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/4133739920348687657/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2018/04/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4133739920348687657'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4133739920348687657'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2018/04/blog-post.html' title='組織用人與帶人'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnwV_Lsm7eB8i67KATnqIcvw4G0k_fmgV4fsHNfpIlLruA_TnK6K1xzCc4f1ELG3R4WA2YUmdo9CW5y4tCmXs5fuxMbMkp9RSF8vyN39xdYLisrkMoMwnPIGxwXKbg1LxOYF8KrpSjzzUG/s72-c/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7+2018-04-14+%25E4%25B8%258B%25E5%258D%25886.08.56.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-3950700541718388539</id><published>2017-02-02T00:45:00.000+08:00</published><updated>2018-04-14T14:21:03.618+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="working"/><title type='text'>中階技術團隊管理的經驗分享</title><content type='html'>2016 的農曆新年後，我的職位稍微變動了，職務內容同樣是技術團隊的主管，但職稱由副理改為經理，同時我也從部門中挑選一名適合的人選調職為副理。至今已滿一年，在這份變化中，我自己對工作內容做了一些調整，雖然我的觀念大致上和三年前所寫的內容 (&lt;a href=&quot;http://ascii-iicsa.blogspot.tw/2015/02/blog-post_28.html&quot;&gt;初級技術團隊管理的經驗分享&lt;/a&gt;) 無異，但多了一些想法，希望再次將這些想法記錄下來再讓兩、三年後的我審視並調整。&lt;br /&gt;
&lt;br /&gt;
同時，在這兩、三年我面試了上百名求職者，除了必問的技術問題外，我也會透過一些聊天內容來了解求職者的個性、生涯規劃，並在這些過程中查覺到不少年青求職者共同的盲點，希望透過記錄和分享我自己的看法，讓這些經驗能幫助到一些同樣為軟體工程師的後輩。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;生涯規劃要有多份且步驟明確&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
在和求職者或部門內成員的面談經驗中，我發現很大一部份的人不清楚自己擅長什麼，或是不想承認自己擅長什麼，有點像在理組念到大三的學生，很不想承認自己適合走商科、文科，於是大部份的人會朝著希望自己擅長什麼來做生涯規劃和求職，這沒什麼不對，但非常可惜。這方面我給的建議是寫一份以上的生涯規劃，接著每一年比對看看自己走到哪條路上面並做出修正或加強，找到自己能走得最好的那一條路。如果對於寫哪幾種路線沒有想法，建議就從以前到現在你看了別人的工作表現後，覺得自己來做應該可以做得更好的那種就把他寫下來吧。&lt;br /&gt;
&lt;br /&gt;
另外生涯規劃除了要有多份之外，最好在目標與目標之間要有明確的步驟，以我很常聽到的一個目標「創業」來說，絕大多數的人只會訂出自己 30 歲要創業這個目標所以 30 歲之前想要學好撰寫應用程式的所有技能，聽起來很合理。但如果你有明確的步驟，你會發現 30 歲開始創業有很多事情若沒事先準備，將會發生嚴重的後果。舉例來說，假設你想 30 歲開始創業，那 25 歲就該開始看相關的訊息，例如閱讀創業討論社群文章、訂閱新創相關網站的電子報等，從目標時間的五年前就開始學習其他前輩失敗、成功的經驗、看看這個世界朝著什麼方向在移動，這樣你的身心在 30 歲時才是準備好上戰場的狀態，而不是從 30 歲時由零開始把錢燒光。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;準備好再上場，因為準備好的人比你想得還更多&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
有兩個我在面談時最常忍不住和對方多分享想法的情況，第一種是因為學校給的實作經驗比較少所以回答不出基礎技術問題的應屆畢業生。另一種是工作好多年的工程師希望 1 到 2 年後能有擔任主管的機會，但就面談的過程來判斷，這個人並不適合擔任團隊主管的狀況，這兩種狀況我歸納為「還沒準備好就上場」。&lt;br /&gt;
&lt;br /&gt;
其實我們的面試題目並不難，但回答得出來的同學，大多是透過比賽、實習、工讀等課外活動求得答案，若應徵者抱怨課程上沒機會實作導致無法作答時，我會提醒他該找一些方法來把「談到工作」這件事準備好，且我們其實有很多機會碰到已經準備好的畢業生。同樣的，書局的架上有很多管理類、商業類的暢銷書，想以擔任主管為目標的工程師面試者讀了嗎？如果你沒有讀那些書，那請了解一件事，那些書籍之所以為暢銷書是代表很多人買來讀了，等於準備得比你好的人其實很多，我們不難遇到。&lt;br /&gt;
&lt;br /&gt;
如果你只把目標定在小公司的高階或高薪職位，那靠運氣就夠了不太需要確實準備好，但通常環境會小得同一個專業職務只有 1 或 2 個人的狀況，如果你想要到大一點、整體能力值高、隊形完整的環境，那建議準備好再上場談，因為愈大的公司，愈容易面試到準備好的人，沒準備的人，他們是不會有興趣的。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;現在是學習不一定要靠環境的時代&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
負面的人會說自己沒機會學習某技能，保守的人會懷疑自己學得還不夠多，但仔細想想現在社交平台上幾乎所有的技能、職業、興趣都能找到一個以上的社群，甚至有專屬的刊物、網站定期發表文章、書籍。以軟體工程師的領域來說就例如 GitHub 上面有各種技術領域的 awesome repository 一樣。&lt;br /&gt;
&lt;br /&gt;
任何傳統產業難以文字記錄的技能除外，其實都不難找到別人幫忙整理好的資訊，而且這些資訊不是業餘的，那些就是該技能的專業人士也在看的資料，機會一直就擺在每個人的眼前，甚至非常多的書籍對於專業人士來說也是必須研讀的新東西，例如你計劃明年從工程師轉職為企劃，那挑幾本企劃力的暢銷書來看看是不錯的第一步，如果你用這種方式在學習，那其實不太需要擔心自己準備得不夠。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;對薪資與職位的行動力要不同&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
在軟體工程師的世界裡很常有人會立下 30 或 35 歲要當部門主管這種目標，而且有這種想法的人數比例遠高於其他行業，例如我們很難聽到有醫師說 35 歲要當主管，也很難聽到電視台攝影師說 35 歲要當主管這種想法，在歐美國家這種想法也比較少，除了華人的薪資結構之外，另一種可能是因為在台灣軟體屬於比較新的產業，這十年內出現很多裝滿年青人的新創公司，導致於沒待過其他產業的年青人看不到白頭髮的前輩。但如果到硬體或傳產走一趟其實這類想法會少很多，我自己面試過上百人的經驗中，這想法出現的比例也和面試者見過的環境也恰巧是相關的。再考量到台灣人的退休年齡是 65 歲這件事就會發現訂下 35 歲要當主管且不再寫 Code 這個目標基本上非常難在有規模的公司實現，所以我通常會建議先不要對這件事做年齡限制，但我很鼓勵對薪資這件事做年齡目標的規劃，例如 35 歲前年薪要破百萬之類的計劃行動力就要高一些。與市場相比較，薪資沒到位要以高行動力去爭取甚至跳巢，因為這和你與家人的生活品質有直接關係，但職位沒達成時，要去思考是否真的必要？是不是還缺了什麼再進行下一步動作。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;嘗試做點以前沒做的事&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
呼應前面的幾個主題，許多人在訂目標時最常出現兩種路線，一種是目前技能的資深職位，另一種是跨專長的路線，計劃升任部門主管也算是跨專長的路線，因為工作型態中會有 30% 以上變成管理類型的事務。寫下跨專長計劃的人們，常常會產生一種時間到了我就準備好的錯覺，例如「我工作了 10 年所以差不多該當主管了」這種想法，但事實上大家的時間都以同樣的速度在流動，這十年的工作經驗只能證明你們都把原本在做的事情做得更好了，沒辦法用來證明誰在工作內容轉變後也能做得好。如果你想要獲得原本沒有的東西，那就該去做點原本沒有在做的事，跨出自己熟悉的生活模式，挑戰一些自己目前不精通的事，從你想成為的目標職位身上找找他們比你多做了些什麼，嘗試著去做看看。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;不要搶了別人的練習機會&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
每一個職位，都有別人對它的不同期待，如果某天發生職位調整，記得去觀察或詢問已經在這個職位上幾年的前輩，甚至跨出公司看看整個社會對這個職位的期待是什麼，假設今天有個資深工程師調職為部門主管，而他做的事情只是把資深工程師的工作做得更好，我認為這並不是一個好的結果，因為這並不符合社會對部門主管的期待，同時也搶了其他升任資深工程師的人練習的機會，如果你只是把原本在做的事做得更好，那幫你加薪就可以了，何必調職呢？所以比較好的做法是把以前在做的某些事放心交給別人去做，並協助他做好，協助他們練習新職位該做的事，而你空出來的時間要拿去做其他新的事情。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;好的人才不要留著自己用&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
這標題有點奇怪，因為每個主管都希望自己身邊有好的人才，但如果你和我一樣，是一個領薪水的部門主管，請注意我們並沒有太多的資源滿足別人生涯規劃中各個階段的需求。於公，好的人才你必須貢獻出來給公司，讓這個人有機會帶頭感染一群人，創造出另一個能把事情做好的團隊。於私，做為朋友或前輩，你要拉他一把，為他找機會，並在適當的時機把這個人狠心的拋出去，最好是斷了他回頭的後路讓他成為和自己一樣甚至更好的領導者，千萬不要以負面的心態看待自己訓練好的人要被挪作他用這件事，也不要只是把人才留在身邊，這樣子太浪費也太自私了。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;儘可能只看優點並協助他發揮&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
面試求職者時，如果對方的優點已經很明確是我現階段需要的，我通常就不會去挑他的缺點，面對部門同事，我也是儘可能的只看優點，很少挑著幾個缺點要求成員一定要改或一定要變成怎樣類型的人。我的觀念是尊重每個人的養成過程與個性，讓每個人的優點能夠徹底發揮為重。&lt;br /&gt;
&lt;br /&gt;
我在過去自己找工作時和現在與其他同事合作面試的經驗中，有碰過不少次面試官挑著幾個求職者不知道的點猛攻的狀況，似乎在宣示我們這裡的人就是比較強的意思，這種心態非常不好，卻很多新手主管會犯下這種錯。有個重要的簡單觀念是這樣的，我們的確曾經教導出幾個能力不錯的晚輩，但也就那幾個而已，更多的人他們是靠著自己變強的，這種人在今天面談時不知道這幾個詞，但他靠著自己不久後也會知道，且知道後的應用面說不定會比我們更有創意。主管們不要太認為只有自己能教出人才，我建議的作法是找到這些自己就能變強的人，給他們機會，直接當面誇獎他們的優點讓他們知道你需要他們做好的就是這些，創造一個他們能把優點充份貢獻出來的環境。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;維持正面的態度去做經驗、格局的訓練&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
以上很多經驗分享其實也可以從負面去思考，例如「這人奴性太強了，還要花額外的時間思考、學習，根本血汗公司」。但我很慶幸自己到目前為止仍然可是用正面的態度去想每一件事和每一個改變，進而想出自己還可以多做點什麼，免於碰到挫折即陷入抱怨的情緒中。&lt;br /&gt;
&lt;br /&gt;
觀察工作這七年多看到的前輩們，我發現能夠獲得理想的職位或薪資的機會有兩種可能，一種是運氣好，一種是訓練好，在我這兩年參與比較多跨部門的討論後，我把自己定位於運氣好的這種可能，因為當我以工程師的身份參與行銷業務、內容編輯等跨部門的討論時，我發現自己常常處於聽到每一件新的事物都很新鮮、很興奮的心情，每一件事每一個想法我都覺得很棒，在這種情況下，我根本沒有足夠的理性去判斷哪些事是該做與不該做的，我發現自己根本還沒具備足夠多的人生經驗，在想法格局上也遠遠不足以讓我擔任做決定的角色，每次這種情況下我反倒會很慶幸自己現在還不是一個要做這個決定的職位，否則我可能不僅僅是自己搞砸一件事，還會拖累所有同專案的成員們。&lt;br /&gt;
&lt;br /&gt;
在這些經驗中，我開始了解大型企業輪調制度的重要性，愈高的職位，管理人員愈多的職位，愈需要以輪調或其他方式進行經驗及格局的訓練，而這種訓練過程是急不得，也沒辦法由自己判斷是否已經完成磨練的，只能等待，正面的將他人當作自己的老師，同時也記得替成員尋找可以學習的老師。
&lt;br /&gt;
&lt;br /&gt;
如果覺得我的觀念還不錯，歡迎參考我前幾年各別在兩種職位寫下的心得分享
&lt;br /&gt;&lt;br /&gt;
&lt;a href=&quot;http://ascii-iicsa.blogspot.tw/2015/02/blog-post_28.html&quot;&gt;初級技術團隊管理的經驗分享&lt;/a&gt;
&lt;br /&gt;
&lt;a href=&quot;http://ascii-iicsa.blogspot.tw/2015/02/blog-post.html&quot;&gt;工程師自我管理經驗分享&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/3950700541718388539/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2017/02/blog-post.html#comment-form' title='10 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/3950700541718388539'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/3950700541718388539'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2017/02/blog-post.html' title='中階技術團隊管理的經驗分享'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-5151661281668547899</id><published>2015-12-28T00:40:00.000+08:00</published><updated>2015-12-28T21:27:11.404+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><title type='text'>為 Android 專案加上 Material Design 風格的 Navigation Drawer</title><content type='html'>最近手機從 htc 換成 Nexus 5x，很多原本用慣的 htc 內建 App 都要自己重新找替代品，在氣象方面找到的大部份畫面我都不喜歡，於是打算利用幾個週未晚上的時間自己製作了一個基於 Material Design 的版本，內容當然是來自中央氣象局，所以資訊的豐富程度應該會比 Google 氣象來得充足，正好是一個從頭寫起的乾淨專案，所以很適合用來一邊寫一邊 copy 程式碼做紀錄，這篇就是在記錄怎麼從單一 MainActivity 改到有 Material Design 規範的 Navigation drawer 並將 ActionBar 換為 Toolbar。&lt;br /&gt;
&lt;br /&gt;
首先來讀一下 Material Design 的 Drawer guideline：&lt;a href=&quot;https://www.google.com/design/spec/patterns/navigation-drawer.html&quot;&gt;Navigation drawer&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;如何加入 Navigation drawer 的過程可以直接用 Android Studio 開啟包含 Drawer 與 Fragment 的專案就很清楚了，那個空專案將 Drawer 相關流程管理在一個叫 NavigationDrawerFragment 的 class 中，可以切得很乾淨，但不利於做紀錄，所以這裡用一個最單純的 Drawer Layout 檔案分佈來當範例&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;&lt;b&gt;activity_main.xml&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
由單一&amp;nbsp;RelativeLayout 為 root 改為使用 v4.widget.DrawerLayout 為 root，並且包含一個放置 App 內容的 View 與一個 Drawer 內容的 View，而放 Drawer 內容的 layout 描述我們習慣用 include 的，以免 MainActivity 的 Layout 過長。&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;android.support.v4.widget.DrawerLayout&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:id=&quot;@+id/drawer&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_width=&quot;match_parent&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_height=&quot;match_parent&quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;FrameLayout&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;android:id=&quot;@+id/main_layout&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;android:layout_width=&quot;match_parent&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;android:layout_height=&quot;match_parent&quot;&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;/FrameLayout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;include layout=&quot;@layout/layout_drawer&quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/android.support.v4.widget.DrawerLayout&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;span style=&quot;color: #cc0000;&quot;&gt;layout_drawer.xml&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
當你的 Drawer 被拉出來時想長怎樣就怎麼描述這個 Layout 檔，注意新手常卡關的白痴錯誤，是 android:layout_gravity=&quot;start&quot; 不是 android:gravity=&quot;start&quot;，如果希望 Drawer 由右側拉進可以將值改為 end&lt;br /&gt;
&lt;code&gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;br /&gt;
&amp;lt;LinearLayout &lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:id=&quot;@+id/navigation_drawer&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:orientation=&quot;vertical&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_width=&quot;@dimen/drawer_width&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_height=&quot;match_parent&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:background=&quot;@android:color/darker_gray&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_gravity=&quot;left&quot;&amp;gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;!-- dimen/drawer_width = 320dp --&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;!-- 這裡擺上你希望出現在 Drawer 內的元件 --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&amp;lt;/LinearLayout&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;&lt;b&gt;MainActivity.java&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
新增 ActionBarDrawerToggle 管理 Drawer 開合等相關事件，與在 onPostCreate、onConfigurationChanged 事件中更新狀態&lt;br /&gt;
&lt;code&gt;
import android.content.res.Configuration;&lt;br /&gt;
import android.support.v4.widget.DrawerLayout;&lt;br /&gt;&lt;span style=&quot;color: #6aa84f;&quot;&gt;
// v4 的 ActionBarDrawerToggle 已 Deprecated&lt;br /&gt;
// 需使用 v7 的才可以整合 Toolbar 完成接下來的調整&lt;/span&gt;&lt;br /&gt;
import android.support.v7.app.ActionBarDrawerToggle;&lt;br /&gt;
import android.support.v7.app.AppCompatActivity;&lt;br /&gt;
import android.os.Bundle;&lt;br /&gt;
&lt;br /&gt;
public class MainActivity extends AppCompatActivity {&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;private ActionBarDrawerToggle drawerToggle;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;private DrawerLayout drawerLayout;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;@Override&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;protected void onCreate(Bundle savedInstanceState) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;super.onCreate(savedInstanceState);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;setContentView(R.layout.activity_main);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;initDrawer();&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;}&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;@Override&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;protected void onPostCreate(Bundle savedInstanceState) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;super.onPostCreate(savedInstanceState);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;drawerToggle.syncState();&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;}&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;@Override&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;public void onConfigurationChanged(Configuration newConfig) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;super.onConfigurationChanged(newConfig);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;drawerToggle.onConfigurationChanged(newConfig);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;}&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;protected void initDrawer() {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;drawerLayout = (DrawerLayout) findViewById(R.id.drawer);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;drawerToggle = new ActionBarDrawerToggle(this,&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;drawerLayout,&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;R.string.navigation_drawer_open,&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;R.string.navigation_drawer_close);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;drawerLayout.setDrawerListener(drawerToggle);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;drawerToggle.syncState();&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;}&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
加上適當的顏色後，以上的程式碼執行結果可以得到一個單純的 navigation Drawer 如下&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirEsEXkrQQTDjv43aiXcwpfg47iKABSjXagYu1yGSGTDHZ6KIiovAzK1pUBTlgHrAY5D-4SwPOgu0WJvmp8rsKkqEp7kZufWAJE7jayDNmxse7XS0yQoObaWpfbbjhDPxt2yMpHYW5TELj/s1600/Screenshot_20151228-002725.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirEsEXkrQQTDjv43aiXcwpfg47iKABSjXagYu1yGSGTDHZ6KIiovAzK1pUBTlgHrAY5D-4SwPOgu0WJvmp8rsKkqEp7kZufWAJE7jayDNmxse7XS0yQoObaWpfbbjhDPxt2yMpHYW5TELj/s320/Screenshot_20151228-002725.png&quot; width=&quot;180&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
接下來要開始加入 Material Design 的相關參數&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;&lt;b&gt;style.xml&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
首先在 style.xml 中將 parent 改為 NoActionBar 或是將 windowsActionBar 設為 false 的方式去除 ActionBar，接著加入以下 Level 21 或 19 才支援的項目，這個專案的 minSdkVersion 是 14 理論上我應該使用 values-v21 這種方式來撰寫，但這裡我使用 tools:targetApi 來避免編譯錯誤的方式將所有 style 項目都寫在同一個 xml 檔案中。&lt;br /&gt;
&lt;code&gt;
&amp;lt;resources&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;xmlns:tools=&quot;http://schemas.android.com/tools&quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;!-- 將 Parent 由 DarkActionBar 換成 NoActionBar --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;style name=&quot;AppTheme&quot; parent=&quot;Theme.AppCompat.NoActionBar&quot;&amp;gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!-- Action Bar 會吃這個顏色 --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;item name=&quot;android:colorPrimary&quot; tools:targetApi=&quot;21&quot;&amp;gt;@color/primary&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!-- status bar 會吃這個顏色 --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;item name=&quot;android:colorPrimaryDark&quot; tools:targetApi=&quot;21&quot;&amp;gt;@color/primary_dark&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!-- Toolbar title 會吃這個顏色 --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;item name=&quot;android:textColorPrimary&quot; tools:targetApi=&quot;21&quot;&amp;gt;@color/text_primary&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!-- Toolbar title Home button 會吃這個顏色 --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;item name=&quot;android:textColorSecondary&quot;&amp;gt;@color/text_secondary&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!-- 特定 UI 元件，例如 checkbox 會吃這個顏色 --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;item name=&quot;android:colorAccent&quot; tools:targetApi=&quot;21&quot;&amp;gt;@color/color_accent&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!-- 設定畫面的背景色 --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;item name=&quot;android:windowBackground&quot;&amp;gt;@android:color/white&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!-- 在 KitKat 以上支援將 status bar 設為半透明 --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;item name=&quot;android:windowTranslucentStatus&quot; tools:targetApi=&quot;19&quot;&amp;gt;true&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!-- 如果基於設計等原因無法使用 NoActionBar 系列的 Base Theme 可以加入這兩行移除 ActionBar --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!--&amp;lt;item name=&quot;windowActionBar&quot;&amp;gt;false&amp;lt;/item&amp;gt;--&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;!--&amp;lt;item name=&quot;windowNoTitle&quot;&amp;gt;true&amp;lt;/item&amp;gt;--&amp;gt;&lt;/span&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/resources&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
在上面的 style.xml 中可以看到一些註解，其中 windowTranslucentStatus 這個項目比較特殊，這個項目是在 Level 19 就支援了，但在 Level 19、20 的表現效果與 Level 21 以上是不同的，在 Level 21 時 status bar 會畫上 colorPrimaryDark 這個顏色加上 20% 的半透明效果，但在 19、20 的環境會畫上黑色到透明的漸層，無法指定顏色。其他 Level 21 新增的項目各會被用在哪裡，可以參考&lt;a href=&quot;http://developer.android.com/intl/zh-tw/training/material/theme.html#StatusBar&quot;&gt;這張官方的圖片&lt;/a&gt;。&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;
&lt;a href=&quot;http://developer.android.com/training/material/images/ThemeColors.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://developer.android.com/training/material/images/ThemeColors.png&quot; height=&quot;320&quot; width=&quot;179&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
比較需要注意的是圖片中說明 ActionBar 會使用 colorPrimary 這點沒錯，但我實際測試這個值對於 Toolbar 並不起作用，所以需要在 Toolbar 的 Layout 中自行撰寫背景色。&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;&lt;b&gt;activity_main.xml&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
在 MainActivity 的 Layout 中所做的修改還算單純，我們只需要在放置 fragment 的 Layout 中加入一個 v7.widget.Toolbar，這裡一樣使用 include 的方式避免檔案過長不好閱讀。&lt;br /&gt;
&lt;code&gt;
&amp;lt;android.support.v4.widget.DrawerLayout&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:id=&quot;@+id/drawer&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_width=&quot;match_parent&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_height=&quot;match_parent&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:fitsSystemWindows=&quot;true&quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;FrameLayout&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;android:id=&quot;@+id/main_layout&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;android:orientation=&quot;vertical&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;android:layout_width=&quot;match_parent&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;android:layout_height=&quot;match_parent&quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;include layout=&quot;@layout/layout_toolbar&quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;/FrameLayout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;include layout=&quot;@layout/layout_drawer&quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/android.support.v4.widget.DrawerLayout&amp;gt;&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;&lt;b&gt;layout_toolbar.xml&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
上面說過了 Toolbar 並不會像 ActionBar 一樣自動拿 android:colorPrimary 顏色來當作底色，所以需要自行指定，其他文字和漢堡 (Home) 的顏色就不需再另外指定了。&lt;br /&gt;
&lt;code&gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;br /&gt;
&amp;lt;android.support.v7.widget.Toolbar&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:id=&quot;@+id/toolbar&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_height=&quot;wrap_content&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_width=&quot;match_parent&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:background=&quot;@color/primary&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:elevation=&quot;2dp&quot;&amp;gt;&lt;br /&gt;
&lt;span style=&quot;color: #6aa84f;&quot;&gt;&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;!-- 需自行指定 background 額外再加上 2dp 的高度製造陰影 --&amp;gt;&lt;/span&gt;&lt;br /&gt;
&amp;lt;/android.support.v7.widget.Toolbar&amp;gt;&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;&lt;b&gt;layout_drawer.xml&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
Drawer Layout 的部份也沒什麼特別的修改，唯一需要注意的是我們指定了 android:fitsSystemWindows 為 true，這是因為我們將 status bar 透過 android:windowTranslucentStatus 設為半透明後，視窗的邊界會延伸到螢幕的最頂端，而非以往的 status bar 底部，會導致 Toolbar 與 status bar 重疊的狀況，有兩種方法可以解，一種是為 Toolbar 設 padding 值，另一種是指定這個屬性值讓 windowTranslucentStatus 的行為改變，上面的 activity_main.xml 也是因此目的而加上這個值。&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;br /&gt;
&amp;lt;LinearLayout&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:id=&quot;@+id/navigation_drawer&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:orientation=&quot;vertical&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_width=&quot;@dimen/drawer_width&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_height=&quot;match_parent&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:layout_gravity=&quot;start&quot;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;android:fitsSystemWindows=&quot;true&quot;&amp;gt;&lt;br /&gt;
&amp;lt;/LinearLayout&amp;gt;&lt;/code&gt;
&lt;br /&gt;
沒有指定 android:fitsSystemWindows=&quot;true&quot; 看起來會是下圖這種奇怪的狀態。但是如果在 Level 19 的環境上沒有特別想要讓 status bar 變成漸層透明的，可以把 windowTranslucentStatus 為 true 的這個項目移除，並且把 colorPrimaryDark 改為 ARGB 的格式，可以在 Level 21 的環境上產生和最底下的動圖一樣的正確效果，因為讓 Drawer Layout 出現在 status bar 底下主要是 fitsSystemWindow 設定後填滿螢幕的效果。&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcZCBBD95C_Nf-2mYMDC2CZqpTdYUSswpcYPzknKpWCyj7UAVL2qDpJpcwiTraDWfr-rxbnGXgDqMoJ7Fxxxkd9XdHqXkAg374N_8NUenwQCJ01Rir9c5_d-U37-wl0t7Xxgj8Zb2nor07/s1600/Screenshot_20151228-000016.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;132&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcZCBBD95C_Nf-2mYMDC2CZqpTdYUSswpcYPzknKpWCyj7UAVL2qDpJpcwiTraDWfr-rxbnGXgDqMoJ7Fxxxkd9XdHqXkAg374N_8NUenwQCJ01Rir9c5_d-U37-wl0t7Xxgj8Zb2nor07/s320/Screenshot_20151228-000016.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;&lt;b&gt;MainActivity.java&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
在 Activity 中唯一的變化只有初始化 Toolbar 的過程，並指定為 drawerToggle 的參數之一&lt;br /&gt;
&lt;code&gt;
protected void initDrawer() {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;setSupportActionBar(toolbar);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;toolbar.setTitle(R.string.app_name);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;drawerLayout = (DrawerLayout) findViewById(R.id.drawer);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;drawerToggle = new ActionBarDrawerToggle(this,&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;drawerLayout,&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;toolbar,&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;R.string.navigation_drawer_open,&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;R.string.navigation_drawer_close) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;};&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;drawerLayout.setDrawerListener(drawerToggle);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;drawerToggle.syncState();&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
經過這個步驟後，就可套用 Toolbar 取代 ActionBar 並將 Drawer 修改為符合 Material Design guideline 的樣式如下圖&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOn0jxrNZSFO4PyENlKxDQOXYYrM-kWuTy3rTAbtO15qxvaQtL6IApEyvBypM_0scBxuGWYQ9pGRrvJzLFm60yc7nfTlYhnG8SacWfWOvn4I5ttwyw06D6JJP1Xakal8fpzor7_t5LcMKM/s1600/bullheadMMB29Kasciihuang12282015010002.gif&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOn0jxrNZSFO4PyENlKxDQOXYYrM-kWuTy3rTAbtO15qxvaQtL6IApEyvBypM_0scBxuGWYQ9pGRrvJzLFm60yc7nfTlYhnG8SacWfWOvn4I5ttwyw06D6JJP1Xakal8fpzor7_t5LcMKM/s320/bullheadMMB29Kasciihuang12282015010002.gif&quot; width=&quot;180&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
改用 Toolbar 後乍看之下會覺得 colorPrimary 沒有必要，但這個值非常建議不要省略，在往後很多 Material Design 的相關元件都會用到，除了這個原因之外，在 Recent Apps 列表中也會拿這個值來顯示縮圖，在上面的動畫應該可以發現。最後，我想整理一下兩個會互相衝突的屬性之間搭配下的結果，因為這裡我花了一些時間嚐試效果。&lt;br /&gt;
&lt;br /&gt;
這種配置可以讓 Android 4.4 status bar 顯示半透明漸層，並可讓 Android 5.0 以上 status bar 畫出 20% 透明的 #666666&lt;br /&gt;
&lt;code&gt;&lt;span style=&quot;color: #6aa84f;&quot;&gt;
// style.xml 中&lt;/span&gt;&lt;br /&gt;
&amp;lt;item name=&quot;android:colorPrimaryDark&quot; tools:targetApi=&quot;21&quot;&amp;gt;#666666&amp;lt;/item&amp;gt;&lt;br /&gt;
&amp;lt;item name=&quot;android:windowTranslucentStatus&quot; tools:targetApi=&quot;19&quot;&amp;gt;true&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;br /&gt;&lt;span style=&quot;color: #6aa84f;&quot;&gt;
// activity_main.xml 中&lt;/span&gt;&lt;br /&gt;
android:fitsSystemWindows=&quot;true&quot;&lt;/code&gt;
&lt;br /&gt;
這個配置在 Android 4.4 顯示傳統的不透明深色 status bar，但可讓 Android 5.0 以上顯示 #CCFF0000 這種 20% 半透明紅色&lt;br /&gt;
&lt;code&gt;&lt;span style=&quot;color: #6aa84f;&quot;&gt;
// style.xml 中&lt;/span&gt;&lt;br /&gt;
&amp;lt;item name=&quot;android:colorPrimaryDark&quot; tools:targetApi=&quot;21&quot;&amp;gt;#CCFF0000&amp;lt;/item&amp;gt;&lt;br /&gt;
&amp;lt;item name=&quot;android:windowTranslucentStatus&quot; tools:targetApi=&quot;19&quot;&amp;gt;false&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;br /&gt;&lt;span style=&quot;color: #6aa84f;&quot;&gt;
// activity_main.xml 中&lt;/span&gt;&lt;br /&gt;
android:fitsSystemWindows=&quot;true&quot;&lt;/code&gt;
&lt;br /&gt;
這種配置在 Android 5.0 可以自行在 status bar 上色 #CCFF0000 但無法讓 Drawer 疊在 &amp;nbsp;status bar 底下&lt;br /&gt;
&lt;code&gt;&lt;span style=&quot;color: #6aa84f;&quot;&gt;
// style.xml 中&lt;/span&gt;&lt;br /&gt;
&amp;lt;item name=&quot;android:colorPrimaryDark&quot; tools:targetApi=&quot;21&quot;&amp;gt;#CCFF0000&amp;lt;/item&amp;gt;&lt;br /&gt;
&amp;lt;item name=&quot;android:windowTranslucentStatus&quot; tools:targetApi=&quot;19&quot;&amp;gt;false&amp;lt;/item&amp;gt;&lt;br /&gt;
&lt;br /&gt;&lt;span style=&quot;color: #6aa84f;&quot;&gt;
// activity_main.xml 中&lt;/span&gt;&lt;br /&gt;
android:fitsSystemWindows=&quot;false&quot;&lt;/code&gt;
&lt;br /&gt;
也可以在 Activity 中 call 這個 Method 對 status bar 著色&lt;br /&gt;
&lt;code&gt;@TargetApi(Build.VERSION_CODES.LOLLIPOP)&lt;br /&gt;
private void drawColorToStatusBar() {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;Window window = getWindow();&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;window.setStatusBarColor(getColor(R.color.primary_dark));&lt;br /&gt;
}&lt;br /&gt;&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/5151661281668547899/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/12/android-material-design-navigation.html#comment-form' title='1 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/5151661281668547899'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/5151661281668547899'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/12/android-material-design-navigation.html' title='為 Android 專案加上 Material Design 風格的 Navigation Drawer'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirEsEXkrQQTDjv43aiXcwpfg47iKABSjXagYu1yGSGTDHZ6KIiovAzK1pUBTlgHrAY5D-4SwPOgu0WJvmp8rsKkqEp7kZufWAJE7jayDNmxse7XS0yQoObaWpfbbjhDPxt2yMpHYW5TELj/s72-c/Screenshot_20151228-002725.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-1536344793994809479</id><published>2015-08-27T00:06:00.004+08:00</published><updated>2015-08-27T00:06:58.476+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="uwp"/><category scheme="http://www.blogger.com/atom/ns#" term="windows phone"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>給 iOS, Android 開發者的 Windows App 開發基本功</title><content type='html'>前陣子在 Channel 9 上了一篇 Windows App 的開發入門影片，再將片中的 slide 與 sample 記錄在此，供有興趣入門 Windows App 的 iOS、Android 開發者看看。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;b&gt;Channel 9 影片位置：&lt;/b&gt;&lt;a href=&quot;https://channel9.msdn.com/events/MVPAsiaCommunity/ComCamp2015May/TW06&quot;&gt;給 iOS, Android 開發者的 Windows App 開發基本功&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;影片中的投影片檔案：&lt;/b&gt;&lt;a href=&quot;http://www.slideshare.net/itsAscii/windows-app-development-for-ios-android-developer&quot;&gt;Windows App development for iOS, Android developer&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Sample Project：&lt;/b&gt;&lt;a href=&quot;https://github.com/AsciiHuang/BasicWindowsAppPractice&quot;&gt;BasicWindowsAppPractice&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/1536344793994809479/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/08/ios-android-windows-app.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/1536344793994809479'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/1536344793994809479'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/08/ios-android-windows-app.html' title='給 iOS, Android 開發者的 Windows App 開發基本功'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-4627292289984934957</id><published>2015-08-26T23:37:00.000+08:00</published><updated>2015-08-29T13:15:19.472+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="store apps"/><category scheme="http://www.blogger.com/atom/ns#" term="uwp"/><category scheme="http://www.blogger.com/atom/ns#" term="windows phone"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>Universal Windows Platform (UWP) App 開發資源整理</title><content type='html'>Windows 10 推出後，不論是針對開機與運行速度或操作直覺性，的確獲得非常多好評，近期在社群上也看到一些公司開始招聘 UWP 應用程式開發人員的消息。&lt;br /&gt;
&lt;br /&gt;
此外，也許還有不少未入門的開發者不清楚，所以特別提兩件事，第一，使用 Visual Studio&amp;nbsp;Community 開發 UWP 是完全免費的。第二，建立 UWP 的開發者帳號上傳 App 只需要付一次性的 19 USD 費用，是目前所有平台中成本最低的。&lt;br /&gt;
&lt;br /&gt;
底下整理了一些 Universal Windows Platform 的開發與設計資源讓有興趣入門的開發者們參考，如果希望看到繁體中文的內容，可以把 en-us 改為 zh-TW 即可。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;b&gt;開發工具下載與介紹：&lt;/b&gt;&lt;a href=&quot;https://www.visualstudio.com/?Wt.mc_id=5000557&quot;&gt;Developer Tools Download&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Windows 10 新功能介紹：&lt;/b&gt;&lt;a href=&quot;https://dev.windows.com/en-us/getstarted/whats-new-windows-10/?Wt.mc_ic=5000557&quot;&gt;Developer for Windows 10 - What&#39;s New&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;開發入門：&lt;/b&gt;&lt;a href=&quot;https://dev.windows.com/en-us/getstarted/?Wt.mc_ic=5000557&quot;&gt;Get Started&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;設計規範：&lt;/b&gt;&lt;a href=&quot;https://dev.windows.com/en-us/design/?Wt.mc_ic=5000557&quot;&gt;Design UWP Apps&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;開發與其他 Windows Framework 的比較：&lt;/b&gt;&lt;a href=&quot;https://dev.windows.com/en-us/develop/?Wt.mc_ic=5000557&quot;&gt;Develop for Windows&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;如何發佈你的 App：&lt;/b&gt;&lt;a href=&quot;https://dev.windows.com/en-us/publish/?Wt.mc_ic=5000557&quot;&gt;Publish Windows Apps&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;微軟虛擬學院的相關影片：&lt;/b&gt;&lt;a href=&quot;https://www.microsoftvirtualacademy.com/en-US/training-courses/getting-started-with-windows-10-for-it-professionals-10629/?Wt.mc_ic=5000557&quot;&gt;Windows 10 courses in MVA&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;MVA 開發者指南：&lt;/b&gt;&lt;a href=&quot;https://www.microsoftvirtualacademy.com/en-us/training-courses/a-developers-guide-to-windows-10-12618/?Wt.mc_id=5000557&quot;&gt;A Developer’s Guide to Windows 10&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;微軟 Channel 9 相關影片：&lt;/b&gt;&lt;a href=&quot;https://channel9.msdn.com/windows/?Wt.mc_ic=5000557&quot;&gt;Channel 9&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Win 10 快速入門：&lt;/b&gt;&lt;a href=&quot;http://www.microsoftvirtualacademy.com/liveevents/preparing-your-enterprise-for-windows-10-as-a-service/?Wt.mc_ic=5000557&quot;&gt;Windows 10 Jumpstart&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;跨平台解決方案 Xamarin：&lt;/b&gt;&lt;a href=&quot;https://blog.xamarin.com/free-xamarin-subscriptions-for-windows-phone-developers/?WT.mc_id=5000557&quot;&gt;Free Xamarin Subscriptions for Windows Phone Developers&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Microsoft Edge 與 IE 11：&lt;/b&gt;&lt;a href=&quot;http://blogs.windows.com/msedgedev/2015/08/26/how-microsoft-edge-and-internet-explorer-11-on-windows-10-work-better-together-in-the-enterprise/?WT.mc_id=5000557&quot;&gt;How Microsoft Edge and Internet Explorer 11 on Windows 10 work better together in the Enterprise&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/4627292289984934957/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/08/universal-windows-platform-uwp-app.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4627292289984934957'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4627292289984934957'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/08/universal-windows-platform-uwp-app.html' title='Universal Windows Platform (UWP) App 開發資源整理'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-213635175498504204</id><published>2015-04-18T23:31:00.000+08:00</published><updated>2015-07-26T23:18:30.551+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><title type='text'>Android Accessibility</title><content type='html'>關於 Android 的無障礙支援文章非常非常的少，中文搜尋結果大多都是將 Android Developer 官方文件翻譯成簡中的資源，繁體中文的資料文章和範例似乎一個都沒有，所以我想還蠻適合來仔細的寫一篇 Android Accessibility 實作教學…
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
Accessibility 的中文意義是無障礙支援，意指針對聽障、視障、肢障人事所增加實作項目，可以方便社會上此類有需要更進階操作的人士有機會成功使用我們的 App，目前中文方面有幾項工具可增加 App 的無障礙支援，而我這篇主要針對 TalkBack 的部份做說明：&lt;br /&gt;
&lt;br /&gt;
1、Screen reader ( TalkBack )&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 讀出畫面上的資訊，主要針對全盲的人士所設計&lt;br /&gt;
2、Braille support ( BrailleBack )&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 將文字轉為點字，需要相關的硬體設備&lt;br /&gt;
3、放大手勢&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 通常是連敲畫面 3 下即可放大畫面局部，主要針對弱視群眾&lt;br /&gt;
4、輕觸並按住的延遲時間&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 延期 OnLongClicked 事件的時間，主要針對肢體動作不流暢的群眾&lt;br /&gt;
&lt;br /&gt;
除了全盲的人士，其他狀況大部份都能完成一支全新手機的初始化作業，所以 Android 手機在起始過程就提供了開啟 TalkBack 的功能，在 Android 4.0 是使用手指在螢幕上畫一個矩型，而 Android 4.1 以上是改為以雙指長按畫面任一處，等到語音出現後即開啟 TalkBack 功能，在初始化完的裝置，可以進入設定、協助工具、TalkBack 開啟，此外建議將 &quot;協助工具捷徑&quot; 也一併開啟，即可在裝置的任何畫面上透過 &quot;長按電源&quot; + &quot;雙指長按&quot; 的方式來快速啟動 TalkBack 方便做測試。&lt;br /&gt;
&lt;br /&gt;
啟動 TalkBack 後，原本手勢的單擊 ( Click ) 動作，會變成選擇 ( Hover ) 這個作用，手指所移動到或是單點到的控制項，都會觸發 onFocus 事件並透過 TalkBack 將該控制項的資訊念出來。可以嚐試一下閉著眼睛在 TalkBack 的模式下操作裝置，相信各位會發現要找到 Back 鍵是一件不容易的事，尤其需要等到按鍵資訊被念完，再雙擊點選是一個需要時間且密集操作的過程，以下列出一些 TalkBack 開啟後常用的操作方式，熟悉後會方便一些：&lt;br /&gt;
&lt;br /&gt;
選擇 ( Hover )：單擊&lt;br /&gt;
開啟 ( Click )：雙擊&lt;br /&gt;
捲動：雙指往上、下、左、右&lt;br /&gt;
選擇下或上一個項目：單指往上、下、左、右 ( 相當於遙控器 )&lt;br /&gt;
快速回到主畫面：單指上滑 + 左滑&lt;br /&gt;
返回鍵：單指下滑 + 左滑&lt;br /&gt;
最近畫面鍵：單指左滑 + 上滑&lt;br /&gt;
通知欄：單指右滑 + 下滑&lt;br /&gt;
&lt;br /&gt;
如果開發者計畫將元件加入 TalkBack 支援，而不增加額外的操作，事實上只要注意 android:contentDescription 和 android:hint 兩個屬性就可以了，之所以有兩個不同的屬性是因為有些特別的元件在被念出資訊時 contentDescription 並不是第一順位。&lt;br /&gt;
&lt;br /&gt;
絕大多數的元件都會以 &lt;span style=&quot;color: #cc0000;&quot;&gt;contentDescription &amp;gt; text &amp;gt; hint&lt;/span&gt; 這樣的優先順序將資訊念出來，像 TextView 這種元件因為本身就是顯示文字，所以不設定 contentDescription 往往還是能正確的讓人透過 text 的值聽出意義，但像 ImageView、ImageButton、CheckBox 等元件上面經常不會有文字，所以沒有設定 contentDescription 的狀況就會讓人無法理解現在的畫面資訊。而有些特殊元件像 EditText，被念出的優先權是 &lt;span style=&quot;color: #cc0000;&quot;&gt;text &amp;gt; hint &amp;gt; contentDescription&lt;/span&gt;，也就是被填上文字時念文字，沒文字時念預設提示資訊，聽起來好像很複雜，我們需要特別去注意每種元件被念出來的屬性是哪一個，事實上有一些實作經驗後大概就會發現我們只需要照著直覺去做就行了，並不太需要去擔心將資訊寫在哪個屬性的問題。&lt;br /&gt;
&lt;br /&gt;
如果在自己 extend View 去實作控制項的狀況下，有一些針對 Accessibility 的事情必須注意，或是我們所製作的控制項如果需要在使用遙控器的裝置上執行，同樣需要注意到 setFocusable()、isFocusable()、requestFocus()、android:nextFocusDown、android:nextFocusLeft、android:nextFocusRight 這些屬性的值必須設定正確，否則控制項將無法被選擇到，自然也不會觸發 TalkBack 的事件念出資訊。&lt;br /&gt;
&lt;br /&gt;
我針對一些常見的狀況寫了一個 Demo Project 放在 GitHub 上，有興趣可以 clone 下來跑看看：&lt;a href=&quot;https://github.com/AsciiHuang/AndroidAccessibilityPractices&quot;&gt;https://github.com/AsciiHuang/AndroidAccessibilityPractices&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
這個 Demo 中第一個範例是說明 ArrayAdapter 這種 Class 並不適合用在需要實作無障礙支援的環境中，以 Drawer 上面的 ListView 為例，我們經常會顯示很簡短 ( 或非中文 ) 的資訊在畫面上，這種狀況下被念出來的資訊無法讓人清楚的了解到該項目的意義，更何況外語的字串被以中文的 TalkBack 念出來時，會呈現奇怪的結果，例如 Label 這個字會被念成 &quot;拉貝爾&quot; 這樣的語音訊息。在這種狀況下我們盡可能的使用 BaseAdapter 來取代 ArrayAdapter 例如以下這樣，我們才可以針對 contentDescription 去額外設值。&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
listView.setAdapter(&lt;br /&gt;
 new ArrayAdapter&amp;lt;String&amp;gt;(&lt;br /&gt;
&amp;nbsp; getActionBar().getThemedContext(),&lt;br /&gt;
&amp;nbsp; android.R.layout.simple_list_item_activated_1,&lt;br /&gt;
&amp;nbsp; android.R.id.text1,&lt;br /&gt;
&amp;nbsp; new String[] {&lt;br /&gt;
&amp;nbsp; &amp;nbsp;&quot;Custom View&quot;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp;&quot;Bad List&quot;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp;&quot;Grid And Navigation&quot;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp;}&lt;br /&gt;
&amp;nbsp; )&lt;br /&gt;
 );&lt;br /&gt;
&lt;br /&gt;
// 以 BaseAdapter 取代&lt;br /&gt;
&lt;br /&gt;
private class DrawerItemAdapter extends BaseAdapter {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private LayoutInflater inflater;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; String[] drawerItems = new String[] {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; getString(R.string.title_section1),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; getString(R.string.title_section2),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; getString(R.string.title_section3),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; };&lt;br /&gt;
&amp;nbsp; &amp;nbsp; String[] drawerItemDescriptions = new String[] {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; getString(R.string.description_section1),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; getString(R.string.description_section2),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; getString(R.string.description_section3),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; };&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public DrawerItemAdapter(Context context) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; inflater = LayoutInflater.from(context);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public int getCount() {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return drawerItems.length;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public Object getItem(int position) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return drawerItems[position];&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public long getItemId(int position) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return position;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public View getView(int position, View convertView, ViewGroup parent) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (convertView == null) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; convertView = inflater.inflate(&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; android.R.layout.simple_list_item_activated_1,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; parent, false);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ((TextView) convertView).setText(drawerItems[position]);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; convertView.setContentDescription(drawerItemDescriptions[position]);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return convertView;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
在自製控制項的部份，我們可以透過 setFocusable、setScrollalbe、setCheckable 等 method 讓此控制項被 focus 時念出像 CheckBox 一樣自動在 contnetDescription 後加上已勾選或未勾選的資訊，或像 ListView 一樣在捲動完後，念出 &quot;總共 X 項，現在顯示第 X 項&quot; 這樣子的資訊，當然除了將以上 method 設為 true 之外，還要在相對應的事件中做 setChecked、setItemCount、setFromIndex 等設定更新目前的狀態，才能正確的念出我們想表達的資訊。&lt;br /&gt;
&lt;br /&gt;
而上述相對應的事件，指的是 View 的這幾個 function ( 務必配合 Demo 學習 )&lt;br /&gt;
&lt;br /&gt;
sendAccessibilityEvent：&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 通常用來針對 eventType 做更新資訊的動作&lt;br /&gt;
onPopulateAccessibilityEvent：&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 透過&amp;nbsp;event.getText().add() 讓 TalkBack 在相對事件時念出資訊&lt;br /&gt;
dispatchPopulateAccessibilityEvent：&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 與上一個類似的事件，可針對 child view 做動作&lt;br /&gt;
onInitializeAccessibilityNodeInfo：&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 初始化元件的支援狀態，例如設定&amp;nbsp;info.setScrollable(true);&lt;br /&gt;
onInitializeAccessibilityEvent：&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 更新元件預設支援項目的狀態，如&amp;nbsp;event.setFromIndex(5);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
若我們的 Custom View 在選取後會有勾選的效果，可以加入以下程式碼，讓 contentDescription 被念完後，再補充 &quot;已選取&quot; 或 &quot;未選取&quot; 的資訊&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
private boolean mChecked = false;&lt;br /&gt;
&lt;br /&gt;
private View.OnClickListener onChangeContentBClicked = new View.OnClickListener() {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void onClick(View v) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (mChecked) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ((Button) v).setText(getString(R.string.default_button_text));&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; } else {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ((Button) v).setText(getString(R.string.changed_button_text));&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; mChecked = !mChecked;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
private View.AccessibilityDelegate buttonAccessibilityDelegate = new View.AccessibilityDelegate() {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super.onInitializeAccessibilityEvent(host, event);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; event.setChecked(mChecked);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super.onInitializeAccessibilityNodeInfo(host, info);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; info.setCheckable(true);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; info.setChecked(mChecked);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
};&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
若我們的 Custom View 具備 scroll 的功能，可以加入以下程式碼，在捲動的流程結束後念出像 ListView 一樣表示資料總數和目前顯示資料區間的資訊&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
protected int currentIndex = 0;&lt;br /&gt;
private AccessibilityDelegate accessibilityDelegate = new AccessibilityDelegate() {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void sendAccessibilityEvent(View host, int eventType) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; currentIndex = // 計算目前捲到的位置&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super.sendAccessibilityEvent(host, eventType);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return super.dispatchPopulateAccessibilityEvent(host, event);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Log.e(&quot;onPopulateAccessibilityEvent&quot;, event.toString());&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; event.getText().add(&quot;向左或向右滑動可選擇不同圖片&quot;);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super.onPopulateAccessibilityEvent(host, event);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; event.setScrollable(true);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; event.setItemCount(10);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; event.setFromIndex(currentIndex);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super.onInitializeAccessibilityEvent(host, event);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; info.setScrollable(true);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super.onInitializeAccessibilityNodeInfo(host, info);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return super.onRequestSendAccessibilityEvent(host, child, event);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
};&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
如果實作 Custom View 時程式碼過長，可以考慮 extned&amp;nbsp;AccessibilityDelegate 這類類別，裡面包含了所有 Accessibility 必須的 callback function，我們可以透過簡單的&amp;nbsp;setAccessibilityDelegate() function 將無障礙支援的程式碼搬到另一個檔案中讓程式碼更單純。&lt;br /&gt;
&lt;br /&gt;
此外，在 API 16 以上，有一個新的 announceForAccessibility 可以使用，這個 API 非常方便，可以讓開發者在任何狀況透過 TalkBack 念出資訊，例如 Timer 的事件，Resume 事件等以往不容易實作的狀態改變時機，例如我在範例中做了一個方型的 View，並且在 onHoverEvent 時透過 announceForAccessibility 念出目前用方的位置，這在實作一些複雜操作 Canvas 的控制項時非常好用，例如日曆元件&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)&lt;br /&gt;
@Override&lt;br /&gt;
public boolean onHoverEvent(MotionEvent event) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;int currentPos = getBlock(event.getX(), event.getY());&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;if (currentPos != prePos) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;prePos = currentPos;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;if (currentPos == 0) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;this.announceForAccessibility(&quot;左上方&quot;);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;} else if (currentPos == 1) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;this.announceForAccessibility(&quot;左下方&quot;);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;} else if (currentPos == 2) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;this.announceForAccessibility(&quot;右上方&quot;);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;} else if (currentPos == 3) {&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;this.announceForAccessibility(&quot;右下方&quot;);&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;}&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;}&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;return super.onHoverEvent(event);&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
先前在某社群做的分享簡報：&lt;a href=&quot;http://www.slideshare.net/itsAscii/ui-accessibility-45942579&quot;&gt;http://www.slideshare.net/itsAscii/ui-accessibility-45942579&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/213635175498504204/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/04/android-accessibility.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/213635175498504204'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/213635175498504204'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/04/android-accessibility.html' title='Android Accessibility'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-8207031428822345182</id><published>2015-03-22T00:21:00.000+08:00</published><updated>2015-04-13T00:31:12.074+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><title type='text'>9-patch 紅線標示作用</title><content type='html'>9-patch 圖檔上下左右的黑線，對於 Android App 的開發者來說應該是相當的熟悉，但在官方的一些 sample 專案中可能會看到畫著紅線的 9-patch 圖檔，這是在 Android 4.3 ( Level 19 ) 才開始支援的標示方法。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
在 &lt;a href=&quot;http://developer.android.com/tools/help/draw9patch.html&quot;&gt;9-patch 的 Document&lt;/a&gt;&amp;nbsp;中說明了黑線的標示規則，紅線的標示規則文件在 &lt;a href=&quot;http://developer.android.com/about/versions/android-4.3.html#OpticalBounds&quot;&gt;Optical bounds layout&lt;/a&gt;&amp;nbsp;這份 4.3 API 的文件中，用途為去除 9-patch png 圖檔外圍多餘的區塊，比較常用的情境是多餘的透明邊框，下面左圖是從官方文件存下來具有紅線的 png 圖檔，圖上的四條紅線意思是用於 layout 上時，會去除紅色區塊，可達到不更改 png 圖檔的情況下，使背景圖佔滿 layout，類似於負值 margin 的效果。&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLH6PKJr0IG0eqNnVjdzxbUEz-_fL_JQG0zmdj4p6n3AtB8TMR-P1fBCTzhvakDONyalh8gH7C3fsr_Lj-VVBYhDCSov9GoJcjLFm9foqQLSSD4P56DCc5gkKeoGpMikf2-SOqfn8XtXlr/s1600/ninepatch_opticalbounds.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLH6PKJr0IG0eqNnVjdzxbUEz-_fL_JQG0zmdj4p6n3AtB8TMR-P1fBCTzhvakDONyalh8gH7C3fsr_Lj-VVBYhDCSov9GoJcjLFm9foqQLSSD4P56DCc5gkKeoGpMikf2-SOqfn8XtXlr/s1600/ninepatch_opticalbounds.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&amp;nbsp;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF6CbfnZ5gtCdSfldd3uzWiJN1gYXaFxKA6l5BFSdF9x98RvrZpgsVDP_posME9bnkA5a5rjbeKxr5O9mg0E9QjaWvyg9cw7QwUKF3MTgkpGLEH_-dFsD1pK1RBpNOriA_G1cicRQNYaTZ/s1600/ninepatch_opticalbounds_1.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; display: inline !important; margin-bottom: 1em; margin-left: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF6CbfnZ5gtCdSfldd3uzWiJN1gYXaFxKA6l5BFSdF9x98RvrZpgsVDP_posME9bnkA5a5rjbeKxr5O9mg0E9QjaWvyg9cw7QwUKF3MTgkpGLEH_-dFsD1pK1RBpNOriA_G1cicRQNYaTZ/s1600/ninepatch_opticalbounds_1.png&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
順代一提，使用 SDK 中的 draw9patch Tool 時，滑鼠左鍵是畫黑線，而鍵盤 Ctrl + 滑鼠左鍵是畫紅線。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/8207031428822345182/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/03/9-patch.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/8207031428822345182'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/8207031428822345182'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/03/9-patch.html' title='9-patch 紅線標示作用'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLH6PKJr0IG0eqNnVjdzxbUEz-_fL_JQG0zmdj4p6n3AtB8TMR-P1fBCTzhvakDONyalh8gH7C3fsr_Lj-VVBYhDCSov9GoJcjLFm9foqQLSSD4P56DCc5gkKeoGpMikf2-SOqfn8XtXlr/s72-c/ninepatch_opticalbounds.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-3570451578238345437</id><published>2015-03-21T01:08:00.000+08:00</published><updated>2015-04-12T01:23:51.018+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><title type='text'>Android TV 入門</title><content type='html'>前陣子公司某個產品線開發 Android TV 的版本時和同事一起 survey 了有關 Android TV 的 Leanback API，趁著記憶還在寫篇筆記。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
在 Nexus Player 上市前，還有另一款叫 &lt;a href=&quot;https://developer.android.com/tv/adt-1/index.html&quot;&gt;ADT-1 開發套件&lt;/a&gt;是 Google 所開發的硬體，也就是工程版的 Nexus Player，提供給第一波跟著 Android TV 一起上市的合作廠商申請。在 Nexus Player 推出前雖然市面上很多以 Android 為基礎的電視盒、電視棒，這些都是其他廠商所推出作業系統為 Android 的機上盒，控制器與操作習慣並未統一。而市售的 Google 官方 Android TV 作業系統目前只有 Nexus Player 一台裝置，但&lt;a href=&quot;http://www.android.com/tv/&quot;&gt;官網中有提示接下來 Sony、Sharp 等廠商也將推出 Android TV 的設備&lt;/a&gt;。&lt;br /&gt;
&lt;br /&gt;
Android TV 的設備因為具有完整的環境，所以現有的 mobile/tablet App 不需要經過改寫就可以 deploy 到 Nexus Player 上執行，但官方的 TV Controller 的按鍵只有五向鍵、首頁及返回、播放，在沒有觸控的情況下並不適合將現有 App 直接執行於 TV 上，何況大部份的 App 並不會多花時間處理 focus 的順序，直接上架後無法正常使用的問題想必不少。&lt;br /&gt;
&lt;br /&gt;
若要讓用戶方便的使用這幾顆按鍵操作 App，可以參考目前&lt;a href=&quot;https://developer.android.com/design/tv/index.html&quot;&gt;官方建議的 Design Guideline&lt;/a&gt;。目前最快速設計出符合規範的方式是使用 &lt;a href=&quot;https://developer.android.com/tools/support-library/features.html#v17-leanback&quot;&gt;Google 所推出的 Leanback Library&lt;/a&gt;，比較需要注意的是一旦使用了 Leanback Library 之後，應用程式的 minSDK 至少需要是 17。&lt;br /&gt;
&lt;br /&gt;
為現有的 App 加入 Android TV 有兩種方式，第一種是增加一個 Activity 並將其指定為&amp;nbsp;LEANBACK_LAUNCHER 並在 application 加入 android:banner 的 attribute 如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;application&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; android:allowBackup=&quot;true&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; android:icon=&quot;@drawable/ic_launcher&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; android:label=&quot;@string/app_name&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; android:banner=&quot;@drawable/app_banner&quot; &amp;lt;!-- 這是 TV 上的 App Icon --&amp;gt; &lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; android:theme=&quot;@style/AppTheme&quot; &amp;gt;&lt;br /&gt;
&amp;lt;/application&amp;gt;&lt;br /&gt;
&amp;lt;activity&lt;br /&gt;
&amp;nbsp; &amp;nbsp; android:name=&quot;.TVMainActivity&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; android:screenOrientation=&quot;landscape&quot; &amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;lt;intent-filter&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;action android:name=&quot;android.intent.action.MAIN&quot; /&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;category android:name=&quot;android.intent.category.LEANBACK_LAUNCHER&quot; /&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;lt;/intent-filter&amp;gt;&lt;br /&gt;
&amp;lt;/activity&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
這種方式即可實現在同一個 apk 下，於 mobile、tablet 開啟時走原本的 LAUNCHER Activity，而在 Nexus Player 上走 LEANBACK_LAUNCHER Activity 的效果，直接從啟動的入口導至不同操作介面。但需要注意一點，上架為 TV App 前你可能會在 AndroidManifest 加入下列描述，這種情況可能會使你的 App 消失在 Google Play 上面，必須修改為 required=&quot;false&quot; 才對。&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;uses-feature android:name=&quot;android.software.leanback&quot; android:required=&quot;true&quot; /&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
另一個加入 TV Support 的方法是在 Android Studio 中替專案加入一個新的 TV module 管理電視上的所有資源，在使用 Leanback Library 需將 minSDK 調至 17 的前提下，增加 TV module 似乎是個比較有彈性的做法，這樣專案的 mobile module 就可以維持著原本 min 14 或其他更低的版本號。但因為 TV module 和原有的 mobile module 分屬不同的 module 且都是 Application Project 故不適合互相 reference，多花時間把共用的 class 抽離到另一個 Android Library 的 module 是必須額外進行的工作，另一方面，這種方式所產出的是兩個 apk 而非一個 apk ( 我們產品目前是用這種方式，彈性較大 )。&lt;br /&gt;
&lt;br /&gt;
因為上架到 TV Play 的應用程式大多數是影視、音樂類型的服務，所以如果想要做出和官方 App 中例如 Play、Youtube 一樣的 Layout 時，大致上離不開 Leanback Library 的 BrowseFragment, SearchFragment, DetailFragment, PlayFragment 這幾個類別，這幾個 Fragment 在我的 Slide 中有簡單的介紹：&lt;a href=&quot;http://www.slideshare.net/itsAscii/android-tv-44018038&quot;&gt;http://www.slideshare.net/itsAscii/android-tv-44018038&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
除了熟悉幫助製作 App 的 Leanback Library 之外，善用 Android TV 首頁上的推薦內容也是提高 App 使用量的重要手法，而 TV 首頁上的推薦內容事實上每一則都是由個別的 App 所發出來的 notification，所以不需要經過任何的審核或檢查制度，任何時候都可以將內容 Push 到 TV 的首頁引導用戶進入你的 App 觀看某些內容或播放某張歌單，在這個方便及自由的制度下其實反倒要更小心控制 Push 資訊的數量，如果太常將不重要或是不具吸引力的資訊推到首頁上擠壓到其他資訊，反而會造成用戶將你的 App 移除以換取乾淨的首頁。至於將資訊呈現到首頁的範例如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
NotificationManager mNotificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);&lt;br /&gt;
&lt;br /&gt;
Notification notification = new NotificationCompat.BigPictureStyle(&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; new NotificationCompat.Builder(getApplicationContext())&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setContentTitle(&quot;Recommend Title&quot;)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setContentText(&quot;Recommend Description&quot;)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setPriority(4)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setLocalOnly(true)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setOngoing(true)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setColor(getApplicationContext().getResources().getColor(R.color.primary))&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setCategory(&quot;recommendation&quot;)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setLargeIcon(/* get a Bitmap */)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setSmallIcon(/* get a drawable */)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setContentIntent(/* get a new Intent */)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .setExtras(/* get a new Bundle */))&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .build();&lt;br /&gt;
&lt;br /&gt;
mNotificationManager.notify(count, notification);&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
關於發送 Recommandation 到首頁的流程，官方的教學也特別建議可以在接到 BOOT_COMPLETED Intent 時啟動 Service 在背景處理固定推送資訊的流程：&lt;a href=&quot;http://developer.android.com/training/tv/discovery/recommendations.html#run-service&quot;&gt;http://developer.android.com/training/tv/discovery/recommendations.html#run-service&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
上次在某個活動介紹 Android TV 入門時剛好主辦單位有錄影，所以順便附上：&lt;a href=&quot;https://livehouse.in/channel/180868/record/-JgkFK-PoqisXwjeJ5AW&quot;&gt;https://livehouse.in/channel/180868/record/-JgkFK-PoqisXwjeJ5AW&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/3570451578238345437/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/03/android-tv.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/3570451578238345437'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/3570451578238345437'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/03/android-tv.html' title='Android TV 入門'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-7457353210235576976</id><published>2015-02-28T23:50:00.002+08:00</published><updated>2015-03-01T12:52:41.611+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="working"/><title type='text'>初級技術團隊管理的經驗分享</title><content type='html'>2014 年 3 月被指派接任一位離職主管的空缺，至今滿一年了，記錄下這段時間產生的一些心得及經驗。這些都只是個人想法，沒有任何理論根據，大概不認同的聲音會很多。但就如同一直以來撰寫技術筆記的想法一樣，想到可能還是會幫助到一些晚輩，也可以讓 2、3 年後的自己回頭看看有沒有進步，那就來分享一些我期望自己在這個角色上做的事。
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;只做傳統管理工作是在加速退化&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
公司曾經送我去上了 16 小時的管理課程，我發現坊間教授管理課程的訓綀中心，目前都是以傳產角度來教授，這上面很多觀念根本沒辦法用在近幾年的資訊產業上，在這個產業每一年都會翻新一大部份，如果你花了大量的時間在做管理工作，專業能力一定會極速的退化，專業能力不札實的情況下你會被太多的事情牽制住無法像以前還是一個單純的工程師時任意發揮力量，要避免這種情況發生，你得說服部門成員自己管理自己，而你一樣把工作時間拿來當一個工程師，主管的工作排在下班後抽一些時間處理，盡可能不要把坐在辦公桌上的時間拿來做管理，聽起來很辛苦嗎？是呀，但我認為所謂的 &quot;需承擔管理責任&quot; 就是這個意思。&lt;br /&gt;
&lt;br /&gt;
每次部門有新人完成試用期訓綀後，我總是會告知他們，敝部門採取半放任管理，也就是我在骨子裡還是很想當個工程師，所以不會像傳統企業一樣花很多時間在管理的工作上一直追大家的進度，基本上需要做到的就是每個人都是自己的主管，除非真的得要上一個層級的人出面，其他請展現各位在寫程式以外的能力，自己去調用資源在時間內完成手上的專案。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;改變對忠誠度的認知&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
很多情況成員學習到某個程度離開了團隊，主管可能會抱怨年青人不忠誠，員工的忠誠度當然要高，但忠誠的對像應該是產品、專案、業務領域，若要說對組織忠誠，那預設立場也只能是對公司，而非部門，一個人進到一間公司是因為想要做這項產品，並非想要和你工作，所以如果某個成員對這項領域的熱誠還在，但不滿意部門的制度，請以正面的態度幫忙引薦另一個他有興趣的部門，這好過讓公司失去一個優秀的人才。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;教導別人自己會進步，管人不會&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
我比較建議各位主管能夠把說服上層將部門成員的薪資提高，好讓部門內大多數的成員是可以 “自己管理自己” 且“心智成熟”&amp;nbsp;的組員，不論如何，你都會經歷指導新人工作流程與專業技能的過程，這個過程會使你得到成長與更加熟悉一切。但花時間在盯進度、煩惱工作達成率、畫面細節對不對，甚至生氣、罵人，這些都不會使任何人進步，所以如果我發現部門內有組員並沒辦法自我管理，例如在工作進度落後時無法主動回報，對指派的工作重視度不高，我通常會詢問原因，如果幾次後原因導向無法自主管理或不重視團隊或產品目標，這時我就會忍痛請這位同學尋找其他適合的團隊，因為我並不想要把寶貴的上班時間拿去密集的詢問每個成員的工作進度，更不想把心思浪費在擔心上面。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;不要花太多時間照顧成員的表情&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
一直到前陣子，我看到夥伴們在解決一些很難的問題，我都會忍不住想要關心一下，看看能不能幫忙搓掉，後來我想想，以前我自己是工程師時也碰到很多很賽很難的事，這種事解決後反而爽度是最高的，也許所有人都很需要這種機會來獲得成就感，別因為看到成員苦惱的表情或最近比較晚下班，加上自己莫名的心軟就把他人獲得成就感的機會毀掉，只需要告訴各位，吃不下記得反應，偶爾幫忙看一下貢獻一些經驗就夠了。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;不需要打工作報告&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
我始終覺得打工作報告給主管是一件蠻奇怪的事，一直到了我成為一個部門主管後始終沒改變這個想法，還順便把上任主管留下來的工作狀態追縱系統，及每週一封週報的制度給廢了，工作是自己分派下去的，程式碼 commit 及細節討論的信件每天高達上百封，怎麼會有不知道成員工作進度的狀況呢？如果一個主管在有盡力了解的狀況下，仍然對於每個成員目前在進行的工作無法掌握，那我只想到一個可能，部門成員或事情太多了必須再分一層出來。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;薪資低仍然要努力表現等待被注意到嗎？&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
有些文章說薪資低的情況下，依然要盡全力表現直到被注意，這個想法基本上是對的，但自己記在心裡就好。每個人背景、壓力、環境、價值觀不同，不要對自己以外的任何人施加這種期待，真正要做的是幫忙爭取合理的報酬，而不是拿這個觀念出來講。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;能夠帶頭做事與一起進步的正面效益最大&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
每個人都是隨時在進步，在這個行業裡每天都可能發現一個老早就存在的好東西，你要做的事情就是分享出來，甚至直接把他做完，並且告訴大家可以這麼做，未來請那麼做，而不是以高姿態質疑以前為什麼沒有這麼做，這樣的效果只有自己打臉，沒有任何好處，因為以前的你就是其中沒有這麼做的一員。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;技術部門負責人的交叉路口每層都不太一樣&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
目前為止我覺得最基層的主管也許是最難做的工作，你所管理的成員有各式能力與個性的人，不穩定性高，再上一層的主管所管理的都已經是被挑選出來的角色，不穩定的壓力自然小了數倍。光是舉兩層的差異就可以知道各層主管的時間和力氣該放在哪裡是差很多的，必須換位子就跟著換腦袋，沒辦法同個腦袋一直用下去。舉例來說，目前我是一個最基層的主管，所以我把力氣大多放在技術上，如果再上一、兩層，我也許會抽 5 成以上的力氣去加強企劃能力，把腦力放在讓成員的技術可以發展出新的價值或更高產值甚至營運方面。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;不要輕易指派不適合的職務&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
如果組織必須再長一層出來，請針對各種面向慎選，因為塑造部門的形像也是自己的責任，組織的負責人會間接代表組織整體的形像，今天選了某甲擔任部門或專案負責人，他的能力真的有強到能和其他同等級的部門負責人對談甚至辯論嗎？具備主動花力氣處理人群問題的個性嗎？具備過人的同理心嗎？是否夠明理？若沒有，情願從外面找，也不要為了切組織而升任不適合的人選，這後果會比想像中的嚴重，每天在各個角落都會有咒罵主管的聲音，排除抱怨者自己太弱的情況外，極大部份都是將不適合的人擺到不對的位置所造成的。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;擴大解釋雙黃線文化&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
所謂的雙黃線就是不能超車，升職加薪優先考量年資，這是一種錯誤觀念應該大部份的人都同意，除此之外還要再擴大解釋，例如有些人會以自己在同樣的年紀時沒有領到這種薪資或這種職等，就暫緩成員的升遷機會等類似雙黃線心態。每個組織理論上人數和業務都會逐漸成長，愈晚進來的成員其實已經在不知不覺中面臨著比以前更嚴格的面試標準，且每一批都會愈來愈嚴格，成員的技術能力超越以前同齡的自己，甚至是現在的自己這種情況極有可能存在，那麼他當然應該獲得比你當時更好的報酬。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;工作型態不同並非完全反映報酬&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
同上的敘述，主管與成員一天都是 24 小時，多做某些事肯定會少做另一部份的事，所以投入 80% 時間進行開發 + 20% 時間進行部門內教育訓綀的資深工程師，如果技術能力在自己之上，他所應該獲得的報酬當然也應該比 60% 開發 + 40% 管理事務的自己還要高，這麼做有助於部門生態的正面發展及每個成員的權益。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;敏感的薪資問題要主動面對&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
身為主管或老闆，不要對於成員要求加薪存有任何負面想法，講粗俗一點，我一直認為能夠忍受外面流傳著在你這工作酬勞很低的老闆臉皮很厚，甚至有些例子，仔細為對方算一算，某個猛將可能真的只領著僅供生活的薪資，對產品的熱誠支持著他繼續留在團隊裡，但生活除了食、衣、住、行之外沒能力做任何休閒，這種情況發生在自己的部門或公司一點也不優。隨時依照現實情況主動替成員想這件事，只需要簡單的加、減法就行了，一點也不難，盡可能不要等到成員來提這種收支不平衡的狀況，因為等到主動提，幾乎是已經找好另一份工作的情況了。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在擔任工程師的職務上我也有些心得，一併記錄下來在另一篇可供參考：&lt;a href=&quot;http://ascii-iicsa.blogspot.tw/2015/02/blog-post.html&quot;&gt;工程師自我管理經驗分享&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/7457353210235576976/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/02/blog-post_28.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/7457353210235576976'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/7457353210235576976'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/02/blog-post_28.html' title='初級技術團隊管理的經驗分享'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-4628198144853522260</id><published>2015-02-28T23:50:00.001+08:00</published><updated>2015-03-01T12:52:00.932+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="working"/><title type='text'>工程師自我管理經驗分享</title><content type='html'>2014 年初，大概距離現在一年前吧，工作型態變得和以往很不一樣，編程的時間少了許多，這陣子時常會回想自己還是一個單純的工程師時做的事而懷念起那種日子，有點擔心日子久了，一些細節和從工程師角度思考的初衷會慢慢忘掉，所以我想寫一篇當了 5 年工程師所累積的心得，也許可以幫助到一些晚輩，甚至未來的自己。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
當然也一併記錄了工頭篇的心得供對照與參考：&lt;a href=&quot;http://ascii-iicsa.blogspot.tw/2015/02/blog-post_28.html&quot;&gt;初級技術團隊管理的經驗分享&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.blogger.com/null&quot; name=&quot;more&quot;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;b&gt;&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;用最笨的方式複製程式碼&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
複製一段網路連線的程式碼，把 URL 和參數改掉，這方法最聰明，超有效率，你得到 1 個單位的工作經驗，但以後不論再做多少次同樣的工作都沒經驗值了。換一個方式，看著一段網路連線的程式碼，自己用鍵盤敲一遍，你可以得到 1.5 個單位的工作經驗，把這段程式碼每個 API 弄懂可以得到 2 單位的工作經驗，從查詢 API 開始自己完成這段程式碼，可以得到 5 單位的面試經驗，過一段時間，再重新想一個方式來寫一模一樣功能的程式碼，可以再得到 5 單位的工作經驗。如果你工作了 7 年，還在領 3XK 的薪資，面試也總是碰壁，想想你這 7 年來怎麼寫程式的。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;留一手已經退流行了&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
如果你花了很多力氣搞定某件事，並且得到了經驗值，那你應該讓所有成員也一起得到，不論是簡報還是提供 Sample Project 都很好。當然這些東西沒有人會讓你用上班時間弄，大多是犧牲自己的休息時間，很辛苦，自己獨特的價值也會因此消失，但這對於培養工程師的綜合能力有很大的幫助，如果你有一個好的主管，這個行為也會讓他看得出一個人的個性及你重視的是團隊更勝於個人。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;利用資訊落差來取勝並不光榮&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
有些同事喜歡私藏一些手法、工具、甚至資訊來源，到適合的時機再拿出來用，這其實是一種很自以為是的心態，就像今天我把這些心得寫出來，會造成全省的人都照著做嗎？其實從人性的角度來思考並不會發生這種事，因為看到這標題還會點進來看文章的人，在一段時間後自己也會漸漸產生類似的心得。而另外一群永遠也想不到的人，大概也不會點進來看這篇文章，甚至他的好友圈內從來沒有流通過這類的資訊。除非你的同事都是第二種人，否則製造資訊落差對個人有任何好處嗎？&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;還有舞台才是適合待著的地方&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
到網路上搜尋 &quot;好公司&quot;，似乎大部份都是以投資者的角度來撰寫的，鮮少以勞方的角度來定義什麼樣才叫一家好公司，從軟體業的角度來定義又更少了，思考一間公司值不值得繼續待下去，是否還有技能或前輩可以繼續學習、有沒有可發揮自己才能以得到相對應報酬的舞台，這些都比起 &quot;提供安全感&quot; 來得更重要。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;盡可能做對的事，而不是做主管交待的事&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
面對一個工作項目，如果你很確定自己是對的，那就去說服主管或前輩依自己的方式進行，這麼做的原因在於任何人都想要看到你的潛力，包括你的主管和你自己，如果你是對的，不論對你還是你的主管來說，這會是一個很好的經驗，如果你是對的機率比較高，未來成為決策角色機會自然就高。&lt;br /&gt;
如果說服不了你的主管照著你的意思做，有三種可能，第一是你說服別人的能力還需要加強，第二是你的經驗還不足判斷錯誤，第三是別人講不聽。不論哪一種，結論都是先照著交辦事項做吧。因為不論怎麼做，事實總會證明你是對的或錯的，一定都有收獲。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;別掛著開發者的職稱卻像個作業員&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
一直以來我對於那些經常抱怨細節不夠明確的同事有些反感，對於根據粗糙的需求就實作出粗糙成品的同事也一樣有些反感，這很像作業員而非開發者。一個好的開發者應該具備知道整個願景後即可去調用資源與實作出成果的能力，至於不明確的細節，你應該積極去問對的人，找問題和問問題是工程師培養綜合能力很重要的過程。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;當回以前那個在書局買參考書的高中生&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
想想以前國中、高中時，似乎大多數的人都會在念完學校的課本和完成作業後，再念一些從書局買回來的參考書，這樣才能獲得比其他人更好的成績。出社會工作後，把做好白天的工作想作學校課本及作業，卻把下班的時間完全給了休閒活動不做其他的學習，這也許就是自己成績不如人的原因，至少相關領域的官方 Blog 要定期去看一下。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;展現反省能力&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
太多人的父母並沒有好好的教導他們反省的能力與重要性，真正有反省能力的人其實比想像中的還少，大多數的人受到主管的指責或接受到意見時第一個念頭是反感，這並沒有什麼錯，人類本來就是這麼被設計的，但如果這件事的確是自己的錯，卻持續以抱怨的心態面對這些意見，那麼將會嚴重的阻礙心態發展。開發團隊裡並不是太在意誰犯了什麼錯，但若犯了錯誤卻無法修正，那以後重要的事大概很難交到你手上。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;多花時間想一些工作之外的事情發現自己的長處&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
或者是說，幫別人或團隊想想可以如何更好，不要光想自己，如果你發現自己很少或幾乎沒在想這些事，那你可能要提早了解到自己不適合走管理職，朝一個資深工程師或架構師發展可以更事半功倍，就我認識且技術、管理都做得不錯的前輩，他們幾乎是天天都在花時間想這些事，而不是碰到狀況或別人提醒了才花時間想一下。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;儘可能找事情塞滿自己上班時間的空檔&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
很多行為雖然並非完全是壞事，但很有機會會讓老闆的觀感不好，例如上班時間內聊 Line、看 Facebook 上的有趣短篇漫畫，甚至度姑 (打瞌睡)。雖然工作都做完了，或是你選擇用別人的下班時間來補進度讓自己和其他人的積效相同。雖然最後對組織來說結果一樣，但說實在話，大部份的主管會選擇上班時不做雜事，利用高效率做完事情準時下班的人來擔任要職，至少他在生活上可以少擔心你會不會破壞部門形像這件事。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;在一開始就把自己當自己的主管&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
只要不是老闆，部門主管也是被指派工作的授薪階級，講白一點，大家都是個打工仔。某方面來說你們做的事都是運用部門內的資源來完成目標，如果部門內有六個成員，那這位主管就有六個人力可以使用，對於個人而言其實也是一樣的，只是你的人力資源從六個變成一個，所以是不是主管其實並不會讓你的工作經驗少掉多少，一開始就用這種方式工作，可以讓你不知不覺看到一般人不會看到的問題與想法。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;讓自己具備一些公司職員以外的職稱&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
外國的月亮比較圓，外人的意見參考價值比較高，這是人性。想要讓上級多重視一點自己的意見，讓自己的身份除了 XX 部門 XX 工程師之外，再多個 XX 公司兼職顧問或 XX 認證是個好方法。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;在尊重專業的前提下用正當方法抱怨&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
舉例來說，如果你目前的專案 UI 很醜，流程不友善，而你剛好擅常這塊，無奈於必須尊重別人的專業而實作出自己也不認同的產品，那麼你應該要做的不是在任何地方抱怨，而是花額外的時間實現自己的想法，用自己的專案與數據證明你的意見值得重視，這種經驗愈多，愈有可能讓你獲得一份能完全掌握專案的工作。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #3d85c6; font-size: large;&quot;&gt;&lt;b&gt;要求薪資時要堅持，要求職稱時別太堅持&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
任何職稱可能都有一些必需符合的隱性條件，主管在調整職務時必須要考量到整體平衡，如果你一直沒有獲得某職務，絕大情況是真的缺少了什麼，請比較看看其他部門該職務的人比自己多了什麼優點，或以聽取建議的角度正面去問清楚，而不是用負面的想法強硬爭取超過能力的職稱。舉例來說，單純只有專案工作做得好，要求升任資深工程師絕對沒問題，但想要求主管職稱，必須反思一下是否具備足夠的溝通能力，是否具備主動察覺人群問題的天份等條件。職稱不是影響生活的必須條件，但薪資是，如果你在此外其他方面又已經夠優秀了，甚至單純的收支不平衡，當然應該主動要求業界認可水準的薪資。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;先記錄想得到的，若有回想到其他項目並且有空就會補上…&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/4628198144853522260/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/02/blog-post.html#comment-form' title='5 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4628198144853522260'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4628198144853522260'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/02/blog-post.html' title='工程師自我管理經驗分享'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-3639370773458099756</id><published>2015-02-18T23:46:00.000+08:00</published><updated>2015-02-18T23:46:20.398+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><title type='text'>避免 Lint 發出 Incomplete translation 錯誤</title><content type='html'>在編譯 apk 時若有跑 Lint 檢查，偶爾會被提示字串未翻譯的問題，大致上類似以下訊息&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;code&gt;
&quot;qrcode_login&quot; is not translated in &quot;zh-rtw&quot; (taiwan), &quot;zh-rcn&quot; &amp;nbsp;(chinese china)&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
要全面避免這種錯誤提示可以至 Lint 選項中將 MissingTranslate 項目的值從&amp;nbsp;Fetal &amp;nbsp;改為 Warming 即可，但大多數情況我們還是需要這個工具來幫我們檢查各語系的翻譯完整度，以上面的 qrcode_login 這個字串為例，可以在 string 加上&amp;nbsp;translatable=&quot;false&quot; 的&amp;nbsp;attribute 來讓這個 key 略過檢查，如此一來，qrcode_login 這個 key 即便在 zh_rTW、zh-rCN 中沒有翻譯也不會出現錯誤，實際例子如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;string name=&quot;qrcode_login&quot; translatable=&quot;false&quot;&amp;gt;QR Code Login&amp;lt;/string&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/3639370773458099756/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/02/lint-incomplete-translation.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/3639370773458099756'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/3639370773458099756'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/02/lint-incomplete-translation.html' title='避免 Lint 發出 Incomplete translation 錯誤'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-4988624415274496252</id><published>2015-01-30T18:46:00.000+08:00</published><updated>2015-02-19T00:38:48.877+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><title type='text'>使用 Google Play-Service 而造成 Unable to execute dex</title><content type='html'>Google Play-Service Library 算是撰寫 App 必用的幾個 Library 之一，但很麻煩的一點是它很大，很多東西不一定會用到，以我的專案為例，目前就&amp;nbsp;Google Analytics、GCM、Google Cast 這三個而已，而一個 Java dex 檔案中受限於 65536 個 methods，身邊不少開發者朋友因為使用了 Play Service 而發生爆 method 的狀況。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;
&lt;br /&gt;
根據聊天時收集到的情報，之前最常見的解決方法是拆成多個 dex，再來是公司本身和 Google 為&amp;nbsp;partner 關係，可以獲得少部份必需的 source code 使用即可。這個狀況在新版的 play-services:6.5.87 有另一個官方提供的新解法，詳細情況可以參考：&lt;a href=&quot;http://android-developers.blogspot.tw/2014/12/google-play-services-and-dex-method.html&quot;&gt;Google Play services and DEX method limits&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
大致上我們先前使用 play-services library 時有兩種方式，直接使用 .jar 檔案或在 build-gradle 撰寫&amp;nbsp;com.google.android.gms:play-services:6.5.87 從&amp;nbsp;mavenCentral 或 jcenter 取用，而在新版的 6.5.87 時，可以加上 -base 或 -cast 取用部份的 API 來使用，如果專案還沒有改用 Gradle，也可以趁這個機會做轉移，以我的專案為例會這樣子寫&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
dependencies {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; compile fileTree(dir: &#39;libs&#39;, include: &#39;*.jar&#39;)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; compile &#39;com.android.support:support-v4:20.0.0&#39;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; compile &#39;com.android.support:appcompat-v7:20.0.0&#39;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; compile &#39;com.android.support:mediarouter-v7:20.0.0&#39;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; compile &#39;com.google.android.gms:play-services-base:6.5.87&#39;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; compile &#39;com.google.android.gms:play-services-cast:6.5.87&#39;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; compile project(&#39;:facebook&#39;)&lt;br /&gt;
}
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/4988624415274496252/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/01/google-play-service-unable-to-execute.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4988624415274496252'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4988624415274496252'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/01/google-play-service-unable-to-execute.html' title='使用 Google Play-Service 而造成 Unable to execute dex'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-876100836560622340</id><published>2015-01-27T01:46:00.000+08:00</published><updated>2015-02-19T00:57:50.940+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="working"/><title type='text'>在 Jenkins Server 使用 git 的注意事項</title><content type='html'>如果選擇在 mac 環境上建立 Jenkins 服務，且是使用&amp;nbsp;&lt;a href=&quot;http://jenkins-ci.org/&quot;&gt;官網提供的自動安裝包&lt;/a&gt;&amp;nbsp;來建立，在使用 git plugin 時可能會碰到一些情況
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
假設在 mac (其實 windows 上也一樣) 上面使用 Ascii 這個具管理者權限的身份執行 java -jar jenkins.war 指令裝好了 Jenkins 的環境，並在 ascii/.ssh/ 底下建立了正確的 rsa key 並上傳至 git server 上，執行 Jenkins 的 clone 作業時仍然會發生無法 clone 的錯誤，大致上訊息是權限問題之類的，這問題在於安裝 Jenkins 的過程中，系統會被建立一個名為 share 的帳號，而 Jenkins Service 所有的設定都必須在 share 這個帳號底下做設定，當然 rsa key 也必須放在 share/.ssh/ 底下。&lt;br /&gt;
&lt;br /&gt;
所以做法通常必須使用原本的管理者帳號例如我的 Ascii 帳號去修改 share 這個帳戶的密碼，再登入 share 這個帳戶去做所有 Jenkins Service 所需的系統參數設定，例如將 git server 做認證的 rsa key 擺到 share/.ssh/ 底下，這才有辦法讓 Jenkins Server 正常的作業。</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/876100836560622340/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/01/jenkins-server-git.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/876100836560622340'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/876100836560622340'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/01/jenkins-server-git.html' title='在 Jenkins Server 使用 git 的注意事項'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-580941812150235719</id><published>2015-01-24T00:49:00.000+08:00</published><updated>2015-01-24T00:54:53.615+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><title type='text'>使用 Lint 檢查是否使用大於 minSDKVersion 的 API</title><content type='html'>Lint 是一個 Android SDK 內建的靜態分析工具，放在 SDK 的 tools 資料夾底下，如果已經將編譯工具轉換至 Gradle 後，每次編譯皆會自動執行 Lint 來檢查專案的安全性、效能等潛在問題。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
一般來說，在專案的 AndroidManifest.xml 我們會撰寫 minSdkVersion 和 targetSdkVersion 兩個值，假設我們 min 設為 14 且 target 設為 21 時，在使用了某些 Level 19 才提供的 API 例如 PackageManager.queryIntentContentProviders 時，將會讓 Level 14~18 的 Device 發生 Exception 而 Crash，這種狀況預設的 Lint 檢查是無法預防的，必須加上特定的參數如下。&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
lintOptions {&lt;br /&gt;
&amp;nbsp; &amp;nbsp;abortOnError false&lt;br /&gt;
&amp;nbsp; &amp;nbsp;fatal &#39;NewApi&#39;, &#39;InlineApi&#39;, &#39;ResourceType&#39;&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
在設定了 NewApi 為 fatal 後，在 min 14 與 + target 21 專案若使用了上一段所說的 queryIntentContentProviders API 編譯完後，即會在 Linet Report 中被列為以下 Error&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
Call requires API level 19 (current min is 14): `android.content.pm.PackageManager#queryIntentContentProviders`&lt;br /&gt;
This check scans through all the Android API calls in the application and warns about any calls that are not available on *all* versions targeted by this application (according to its minimum SDK attribute in the manifest). If you really want to use this API and don&#39;t need to support older devices just set the `minSdkVersion` in your `build.gradle` or `AndroidManifest.xml` files. If your code is *deliberately* accessing newer APIs, and you have ensured (e.g. with conditional execution) that this code will only ever be called on a supported platform, then you can annotate your class or method with the `@TargetApi` annotation specifying the local minimum SDK to apply, such as `@TargetApi(11)`, such that this check considers 11 rather than your manifest file&#39;s minimum SDK as the required API level. If you are deliberately setting `android:` attributes in style definitions, make sure you place this in a `values-vNN` folder in order to avoid running into runtime conflicts on certain devices where manufacturers have added custom attributes whose ids conflict with the new ones on later platforms. Similarly, you can use tools:targetApi=&quot;11&quot; in an XML file to indicate that the element will only be inflated in an adequate context.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: red;&quot;&gt;&amp;nbsp; return getPackageManager().queryIntentContentProviders(null, 0);&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;color: red;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;~~~~~~~~~~~~~~~~~~~~~~~~~~~&lt;/span&gt;&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
如果你不想讓這段 Error 持續出現，有兩個解法，一是換另個 API，另一個是為使用這支 API 的 function 或 class 加上 annotation 如下範例&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
private void checkPackageInformation() {&lt;br /&gt;
&amp;nbsp; List&amp;lt;ResolveInfo&amp;gt; list = getResolveInfoList();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@TargetApi(19)&lt;br /&gt;
private List&amp;lt;ResolveInfo&amp;gt; getResolveInfoList() {&lt;br /&gt;
&amp;nbsp;// 在使用此 API 的 function 加上對應 Level 值的 annotation&lt;br /&gt;
&amp;nbsp;return getPackageManager().queryIntentContentProviders(null, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/580941812150235719/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/01/minsdkversion-api.html#comment-form' title='1 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/580941812150235719'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/580941812150235719'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2015/01/minsdkversion-api.html' title='使用 Lint 檢查是否使用大於 minSDKVersion 的 API'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-8911212072284683139</id><published>2014-12-03T23:59:00.000+08:00</published><updated>2014-12-04T00:05:53.976+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="store apps"/><category scheme="http://www.blogger.com/atom/ns#" term="windows phone"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>使用 MSBuild 編譯 Universal Apps 發生 error MSB3325, MSB3321</title><content type='html'>如果要在編譯工作中加入一些特別的行為，通常會透過 Visual Studio 加入專案建置前或建置後的工作，或是寫成 proj 檔案讓 MSBuild.exe 執行編譯工作再加上一些 Script，在使用 MSBuild 進行 Universal Apps 的 Release 編譯時，因為必須選擇 pfx 憑證，可能在編譯時期會出現以下錯誤訊息&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;code&gt;
&quot;d:\GitHub\hubapp_windows_store\msbuild.proj&quot; (預設目標)&lt;br /&gt;
(1) -&amp;gt;&quot;d:\GitHub\hubapp_windows_store\HubApp\HubApp.Windows\HubApp.Windows.csproj&quot; (ReBuild 目標)&lt;br /&gt;
(2) -&amp;gt;(ResolveKeySource 目標) -&amp;gt; &amp;nbsp;C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(2482,5):&lt;br /&gt;
error MSB3325: 無法匯入下列金鑰檔: code_signing.pfx。此金鑰檔可能有密碼保護。若要改正這種情況，請嘗試再次匯入憑證，或以手動方式將憑證安裝到具有下列金鑰容器名稱的強式名稱 CSP: VS_KEY_F11A3882EC084A59&lt;br /&gt;
[d:\KKGitHub\hubapp_windows_store\HubApp\HubApp.Windows\HubApp.Windows.csproj] &amp;nbsp;C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(2482,5): error MSB3321: 已取消匯入金鑰檔 &quot;code_signing.pfx&quot;。 [d:\GitHub\hubapp_windows_store\HubApp\HubApp.Windows\HubApp.Windows.csproj]&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 0 個警告&lt;br /&gt;
&amp;nbsp; &amp;nbsp; 2 個錯誤&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
其實這個錯誤訊息的指示非常清楚，要做的事情就是所謂的 &quot;&lt;span style=&quot;color: #3d85c6;&quot;&gt;以手動方式將憑證安裝到具有下列金鑰容器名稱的強式名稱 CSP: VS_KEY_XXXXXXXXXXXXXXXX&lt;/span&gt;&quot; 這件事，但是這件事到底怎麼做還真的從字面上看不出來，這也是特別寫筆記錄的原因。&lt;br /&gt;
&lt;br /&gt;
首先必須找到 Sn.exe 這個工具，這個 Sn.exe 是 Visual Studio 內建的 tool，以 VS 2013 為例，位置在&amp;nbsp;C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools&lt;br /&gt;
&lt;br /&gt;
利用管理員身份打開 Cmd 後切換到這個目錄底下，輸入 sn -i 憑證位置 強式名稱&lt;br /&gt;
&lt;br /&gt;
舉我的例子就是這個樣子&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
C:\WINDOWS\system32&amp;gt;&lt;span style=&quot;color: #3d85c6;&quot;&gt;cd C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools&amp;gt;&lt;span style=&quot;color: #3d85c6;&quot;&gt;sn -i&lt;br /&gt;
D:\GitHub\hubapp_windows_store\HubApp\HubApp.Windows\code_signing.pfx VS_KE&lt;br /&gt;
Y_F11A3882EC084A59&lt;/span&gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
此時 sn 工具會詢問你這個 pfx 的密碼，輸入後即完成錯誤提示的動作&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
Microsoft (R) .NET Framework Strong Name Utility &amp;nbsp;Version 4.0.30319.33440&lt;br /&gt;
Copyright (c) Microsoft Corporation. &amp;nbsp;All rights reserved.&lt;br /&gt;
&lt;br /&gt;
Enter the password for the PKCS#12 key file:&lt;span style=&quot;color: #3d85c6;&quot;&gt; // 輸入你的密碼&lt;/span&gt;&lt;br /&gt;
Key pair installed into &#39;VS_KEY_F11A3882EC084A59&#39;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
在完成上述動作後，將這個 pfx 安裝 (直接雙擊) 到啟動 Jenkins 的使用者身份上即可解決這兩個 error。&lt;br /&gt;
&lt;br /&gt;
Sn.exe (強式名稱工具) 官方文件：&lt;a href=&quot;http://msdn.microsoft.com/zh-tw/library/k5b5tt23(v=vs.110).aspx&quot;&gt;http://msdn.microsoft.com/zh-tw/library/k5b5tt23(v=vs.110).aspx&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
順代一提，編譯 Universal Apps 時必須使用 VS 2013 中的 MSBuild，位置在&amp;nbsp;&lt;span style=&quot;color: #3d85c6;&quot;&gt;C:\Program Files (x86)\MSBuild\12.0\Bin&lt;/span&gt; 和以前常用的 MSBuild 3.5、4.0 擺放的位置不太一樣。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/8911212072284683139/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/12/msbuild-universal-apps-error-msb3325.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/8911212072284683139'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/8911212072284683139'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/12/msbuild-universal-apps-error-msb3325.html' title='使用 MSBuild 編譯 Universal Apps 發生 error MSB3325, MSB3321'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-5803421377814838700</id><published>2014-11-29T00:13:00.000+08:00</published><updated>2014-12-02T14:07:09.883+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="store apps"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>Windows Store App Jenkins Error APPX0105、APPX0102、APPX0107</title><content type='html'>如果是比較窮的開發者，通常會使用 Jenkins 來做為 CI 的平台，前陣子在 Jenkins 上建立 Windows Store App 的建置工作時噴出三個錯誤訊息，很神奇的是，若使用 Cmd 操作 MSBuild.exe 編譯同一份 sln、proj 檔是正常的，一定要透過 Jenkins 啟動 MSBuild 才會碰到這個問題。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;在 Jenkins 從 Version Control Server 把程式碼 Clone 下來後，使用 MSBuild 編譯不到幾秒就停止且出現以下錯誤訊息：&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;nbsp; C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v12.0\AppxPackage\Microsoft.AppXPackage.Targets(2241,9): error APPX0105: Cannot import the key file &#39;code_signing.pfx&#39;. The key file may be password protected. To correct this, try to import the certificate manually into the current user’s personal certificate store. [D:\Jenkins\jobs\HubApp\workspace\HubApp\HubApp.Windows\HubApp.Windows.csproj]&lt;br /&gt;
&amp;nbsp; C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v12.0\AppxPackage\Microsoft.AppXPackage.Targets(2241,9): error APPX0102: A certificate with thumbprint &#39;1E422D2EB0E02ABD8B5F0C4B77EEB30111C9D365&#39; that is specified in the project cannot be found in the certificate store. Please specify a valid thumbprint in the project file. [D:\Jenkins\jobs\HubApp\workspace\HubApp\HubApp.Windows\HubApp.Windows.csproj]&lt;br /&gt;
&amp;nbsp; C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v12.0\AppxPackage\Microsoft.AppXPackage.Targets(2241,9): error APPX0107: The certificate specified is not valid for signing. For more information about valid certificates, see http://go.microsoft.com/fwlink/?LinkID=241478. [D:\Jenkins\jobs\HubApp\HubApp\HubApp.Windows\HubApp.Windows.csproj]&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
在搜尋後發現很多不同的解決，但我試了沒效，最後找到某篇文章的解法照著做終於解決了這三個錯誤，會出現這三個錯誤的原因在於專案本身沒有指定 Sign Key，憑證的規格有很多種，在 Windows Store App 上是使用 pfx 或 p12 這種，產生的方法可以參考官方文件：&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/windows/desktop/jj835832(v=vs.85).aspx&quot;&gt;How to create an app package signing certificate&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
通常在建立 Store App 時專案內就會有一個 XXX_TemporaryKey.pfx 的檔案可以直接使用，這個憑證的資訊會被記錄於&amp;nbsp;Package.appxmanifest 中，但不會被記錄在 Project 內，這三個錯誤訊息就是因為這個原因而產生的。&lt;br /&gt;
&lt;br /&gt;
要解決這個問題，只需要在 &quot;&lt;span style=&quot;color: #3d85c6;&quot;&gt;專案上點右鍵&lt;/span&gt;&quot;、選擇 &quot;&lt;span style=&quot;color: #3d85c6;&quot;&gt;Properties&lt;/span&gt;&quot; 再切到 &quot;&lt;span style=&quot;color: #3d85c6;&quot;&gt;Signing&lt;/span&gt;&quot; 這個 Tab 並且將 &quot;&lt;span style=&quot;color: #3d85c6;&quot;&gt;Sign the assembly&lt;/span&gt;&quot; 打勾後，選擇和 Package.appxmanifest 內 Packaging、Publisher 同樣的 pfx 即可，畫面如下&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoRwzwf3lKfPALMPrxTcQicBq_4yavFTf8-hTcJG-MeWWWS7xjUGT0AeuuK2qYTzfU7m5BJwQAqg0CC6Xtr0s5d2R03rUoN8_UBBIWu4iOP5072h8jvnnATIAo9bopEClU2DRb7CH1jumq/s1600/MSign.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoRwzwf3lKfPALMPrxTcQicBq_4yavFTf8-hTcJG-MeWWWS7xjUGT0AeuuK2qYTzfU7m5BJwQAqg0CC6Xtr0s5d2R03rUoN8_UBBIWu4iOP5072h8jvnnATIAo9bopEClU2DRb7CH1jumq/s1600/MSign.png&quot; height=&quot;257&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/5803421377814838700/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/11/windows-store-app-jenkins-error.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/5803421377814838700'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/5803421377814838700'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/11/windows-store-app-jenkins-error.html' title='Windows Store App Jenkins Error APPX0105、APPX0102、APPX0107'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoRwzwf3lKfPALMPrxTcQicBq_4yavFTf8-hTcJG-MeWWWS7xjUGT0AeuuK2qYTzfU7m5BJwQAqg0CC6Xtr0s5d2R03rUoN8_UBBIWu4iOP5072h8jvnnATIAo9bopEClU2DRb7CH1jumq/s72-c/MSign.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-1375817377728617120</id><published>2014-10-21T01:21:00.002+08:00</published><updated>2014-10-21T01:24:22.742+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><title type='text'>Code Snippet in Android Studio</title><content type='html'>在使用 Android Studio 撰寫程式時，很常輸入 inn 再加上 tab 來產生 if (obj != null) 的程式碼區段，這種程式碼片段是可以自行建立的，在 Android Studio 的 Preference / IDE Settings / Live Templates 中可以看到全部的 Code Snippets 與加入新的 Snippet。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
隨意點選一個，例如 others 中的 inst 可以看到這是一個 if instanceof 的程式碼片段，並且中間包含 3 個可編輯的變數及使用 $END$ 來令編輯完後輸入焦點停留在某處&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
if ($EXPR$ instanceof $TYPE$) {&lt;br /&gt;
&amp;nbsp; $TYPE$ $VAR1$ = ($TYPE$)$EXPR$;&lt;br /&gt;
&amp;nbsp; $END$&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
利用這些簡單的規則，我們就可以將平時一些常用到，篇幅又長的程式碼片段建立成 Live Template 來提高效率 (帥度)，以我自己為例，平時在開發上或撰寫試驗性的小專案時，我是不太喜歡寫 ListView + Adapter 的，因為重覆的程式碼非常的多，雖然可以使用 cmd+N 的方式 Gen 出需 Override 的 Function，但內容只有 return null 並沒有太大的幫助，所以我建立了一個自動產生 Adapter 的 Template 並取名為 newadpt 如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
public class $ADAPTER$ extends BaseAdapter {&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; class ViewHolder {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private Context context;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private LayoutInflater inflater;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private ArrayList&amp;lt;$ITEMTYPE$&amp;gt; $ITEMS$ = new ArrayList&amp;lt;$ITEMTYPE$&amp;gt;();&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public $ADAPTER$(Context context, ArrayList&amp;lt;$ITEMTYPE$&amp;gt; $ITEMS$) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.context = context;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.$ITEMS$ = $ITEMS$;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; inflater = LayoutInflater.from(context);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public View getView(int position, View convertView, ViewGroup parent) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ViewHolder viewHolder;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (convertView == null) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; viewHolder = new ViewHolder();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; convertView = inflater.inflate(R.layout.$LAYOUT$, parent, false);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; convertView.setTag(viewHolder);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; } else {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; viewHolder = (ViewHolder) convertView.getTag();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; $ITEMTYPE$ model = $ITEMS$.get(position);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; $END$&lt;br /&gt;
&amp;nbsp; &lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return convertView;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public int getCount() {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return ($ITEMS$ == null) ? 0 : $ITEMS$.size();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public $ITEMTYPE$ getItem(int position) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return $ITEMS$.get(position);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public long getItemId(int position) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return position;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
這裡可以看到我加入了 4 個變數，只要我輸入 newadpt 再按 tab 後，依序透過 tab 輸入 MemberListAdapter、Member、members、listview_item_member，就可以產生出以下程式碼片段，接下來我只要在被標為紅字的 Member 上按 option + Enter 再選 Create Class 就可以流暢 (帥氣) 的生出一個給 ListView 使用的 Adapter 如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
public class MemberListAdapter extends BaseAdapter {&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; class ViewHolder {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private Context context;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private LayoutInflater inflater;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private ArrayList&amp;lt;Member&amp;gt; members = new ArrayList&amp;lt;Member&amp;gt;();&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public MemberListAdapter(Context context, ArrayList&amp;lt;Member&amp;gt; members) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.context = context;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.members = members;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; inflater = LayoutInflater.from(context);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public View getView(int position, View convertView, ViewGroup parent) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ViewHolder viewHolder;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (convertView == null) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; viewHolder = new ViewHolder();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; convertView = inflater.inflate(R.layout.list_view_item_member, parent, false);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; convertView.setTag(viewHolder);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; } else {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; viewHolder = (ViewHolder) convertView.getTag();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Member model = members.get(position);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return convertView;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public int getCount() {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return (members == null) ? 0 : members.size();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public Member getItem(int position) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return members.get(position);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @Override&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public long getItemId(int position) {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return position;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAZB8VsuN68LN-0lfNPwqRtzzSk3A3dusJbGuWFUWlPh2AXCXRV5pdqimRBbkLBxGpGvbluXdBPOam8-xeZCQG5bYKPcfVX4TBaNXA2imR5tzSFvLfdPpTzkAz4NkrLhK2fSvbKEsu49jZ/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-10-21+%E4%B8%8A%E5%8D%881.18.58.png&quot; imageanchor=&quot;1&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAZB8VsuN68LN-0lfNPwqRtzzSk3A3dusJbGuWFUWlPh2AXCXRV5pdqimRBbkLBxGpGvbluXdBPOam8-xeZCQG5bYKPcfVX4TBaNXA2imR5tzSFvLfdPpTzkAz4NkrLhK2fSvbKEsu49jZ/s1600/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-10-21+%E4%B8%8A%E5%8D%881.18.58.png&quot; height=&quot;249&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
Visual C# 的版本可以參考這裡：&lt;a href=&quot;http://ascii-iicsa.blogspot.tw/2014/10/code-snippet-for-c.html&quot;&gt;開發團隊的基礎建設 Code Snippet for C#&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/1375817377728617120/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/10/code-snippet-in-android-studio.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/1375817377728617120'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/1375817377728617120'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/10/code-snippet-in-android-studio.html' title='Code Snippet in Android Studio'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAZB8VsuN68LN-0lfNPwqRtzzSk3A3dusJbGuWFUWlPh2AXCXRV5pdqimRBbkLBxGpGvbluXdBPOam8-xeZCQG5bYKPcfVX4TBaNXA2imR5tzSFvLfdPpTzkAz4NkrLhK2fSvbKEsu49jZ/s72-c/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-10-21+%E4%B8%8A%E5%8D%881.18.58.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-7862265541729963686</id><published>2014-10-19T00:59:00.000+08:00</published><updated>2014-10-19T00:59:59.234+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="c#"/><title type='text'>開發團隊的基礎建設 Code Snippet for C#</title><content type='html'>在 Visual Studio 中撰寫 C# 時，在編輯視窗打上 prop 加 2 個 tab 可以快速建立產生新 property 的程式碼片段，輸入 for 加 2 個 tab 也可以快速產生 for 迴圈的程式碼片段，這些是 Visual Studio 內建的 Code Snippet 功能，在安裝目錄下可以找到所有的 .snippet 檔案，也可以透過功能列的 Tools / Code Snippets Manager 查看各程式語言有哪些內建的片段，當然也可以自行新增，此篇即是記錄如何為 C# 建立 Code Snippet 以提高撰寫程式碼時的帥氣度。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
以我自己安裝的 Visual Studio 2013 為例，C# 的內鍵 Snippet 檔案放在&amp;nbsp;D:\Program Files (x86)\Microsoft Visual Studio 12.0\VC#\Snippets\1033\Visual C# 這個位置，我們隨意打開一個列常用的 for.snippet 可以發現內容是使用 XML 描述的如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&amp;gt;&lt;br /&gt;
&amp;lt;CodeSnippets &amp;nbsp;xmlns=&quot;http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet&quot;&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;CodeSnippet Format=&quot;1.0.0&quot;&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;Header&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Title&amp;gt;for&amp;lt;/Title&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Shortcut&amp;gt;for&amp;lt;/Shortcut&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Description&amp;gt;Code snippet for &#39;for&#39; loop&amp;lt;/Description&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Author&amp;gt;Microsoft Corporation&amp;lt;/Author&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;SnippetTypes&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;SnippetType&amp;gt;Expansion&amp;lt;/SnippetType&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;SnippetType&amp;gt;SurroundsWith&amp;lt;/SnippetType&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;/SnippetTypes&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;/Header&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;Snippet&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Declarations&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ID&amp;gt;index&amp;lt;/ID&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;Default&amp;gt;i&amp;lt;/Default&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ToolTip&amp;gt;Index&amp;lt;/ToolTip&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;/Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ID&amp;gt;max&amp;lt;/ID&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;Default&amp;gt;length&amp;lt;/Default&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ToolTip&amp;gt;Max length&amp;lt;/ToolTip&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;/Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;/Declarations&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Code Language=&quot;csharp&quot;&amp;gt;&amp;lt;![CDATA[for (int $index$ = 0; $index$ &amp;lt; $max$; $index$++)&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;{&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;$selected$ $end$&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;}]]&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;/Code&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;/Snippet&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;/CodeSnippet&amp;gt;&lt;br /&gt;
&amp;lt;/CodeSnippets&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
內容非常的簡單，即便不看文件，大致上可以歸納出幾重點，Shortcut 的部份定義程式碼片段的縮寫，Snippet 內的 Declarations 中有數個 Literal 定義程式碼片段的 &quot;變數&quot;，而變數可以設定是否開放編輯，以&amp;nbsp;Editable=&quot;false&quot; 屬性做控制。&lt;br /&gt;
&lt;br /&gt;
自行製作 Code Snippet 的方式很簡單，大致上你需要建立一個資料夾，裡面放著數個自行編寫的 .snippet 檔案，在功能表列的 Tools / Code Snippets Manager 視窗中選擇 C# 並且 Add 剛才建立的資料夾即可，舉例來說我產生了一個資料夾，並加入 param.snippet 與 newtable.snippet 兩個檔案如下，並且在 Code Snippets Manager 選擇這個資料夾即可，未來有新的 .snippet 檔只需要直接丟到這個資料夾即可使用，不必再操作 Visual Studio 做加入的流程，非常適合團隊共同維護一個 Git Repository 來貢獻程式碼片段。&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-EXFjrFWvZELGRL5_pzHEaIkHfObsVnwJ9mM4pWhteoFGFAMIRsaJu9TLm56-sQD2dKE4jOhXWF3l7F5KGNVWOnYwxSdmReFqRkV_5RBV9MgTGMP0SEV0VXWsh25iQidLqgqS8-yzlLKz/s1600/snippet.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-EXFjrFWvZELGRL5_pzHEaIkHfObsVnwJ9mM4pWhteoFGFAMIRsaJu9TLm56-sQD2dKE4jOhXWF3l7F5KGNVWOnYwxSdmReFqRkV_5RBV9MgTGMP0SEV0VXWsh25iQidLqgqS8-yzlLKz/s1600/snippet.png&quot; height=&quot;160&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
接下來講講我建立的這兩個 snippet 檔案，首先 params.snippet 這個需求的產生是在於我們常常使用 Dictionary&amp;lt;String, String&amp;gt; 這個物件來串接要丟到 REST API 的 Get 參數，所以寫了一個 snippet 來幫助我們快速產生以下片段，並且產生後用戶可以使用 tab 改變 param 及 value 兩個值&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
Dictionary&amp;lt;String, String&amp;gt; param = new Dictionary&amp;lt;String, String&amp;gt;();&lt;br /&gt;
param.Add(&quot;param&quot;, &quot;value&quot;);&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
這個 Snippet 檔的內容如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&amp;gt;&lt;br /&gt;
&amp;lt;CodeSnippets &amp;nbsp;xmlns=&quot;http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet&quot;&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;CodeSnippet Format=&quot;1.0.0&quot;&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;Header&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Title&amp;gt;params&amp;lt;/Title&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Shortcut&amp;gt;params&amp;lt;/Shortcut&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Description&amp;gt;Code snippet for Dictionary Params&amp;lt;/Description&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Author&amp;gt;Ascii&amp;lt;/Author&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;SnippetTypes&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;SnippetType&amp;gt;Expansion&amp;lt;/SnippetType&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;/SnippetTypes&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;/Header&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;Snippet&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Declarations&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ID&amp;gt;param&amp;lt;/ID&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;Default&amp;gt;param&amp;lt;/Default&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ToolTip&amp;gt;param key&amp;lt;/ToolTip&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;/Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ID&amp;gt;value&amp;lt;/ID&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;Default&amp;gt;value&amp;lt;/Default&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ToolTip&amp;gt;param value&amp;lt;/ToolTip&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;/Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;/Declarations&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Code Language=&quot;csharp&quot;&amp;gt;&amp;lt;![CDATA[Dictionary&amp;lt;String, String&amp;gt; param = new Dictionary&amp;lt;String, String&amp;gt;();&lt;br /&gt;
param.Add(&quot;$param$&quot;, &quot;$value$&quot;);]]&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;/Code&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;/Snippet&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;/CodeSnippet&amp;gt;&lt;br /&gt;
&amp;lt;/CodeSnippets&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
接下來 newtable.snippet 這個是在建立 ORM 使用的類別時，可以快速產生類別及基本的 attribute 與 primary key 欄位，並讓撰寫程式的開發者可以利用 tab 更改欄位、類別、屬性的名稱&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
[Table(&quot;member_table&quot;)]&lt;br /&gt;
public class MemberTable&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public MemberTable()&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; [PrimaryKey, Column(&quot;member_id&quot;)]&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public Integer MemberId { get; set; }&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
這個 newtable.snippet 的內容如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&amp;gt;&lt;br /&gt;
&amp;lt;CodeSnippets &amp;nbsp;xmlns=&quot;http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet&quot;&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;CodeSnippet Format=&quot;1.0.0&quot;&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;Header&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Title&amp;gt;newtable&amp;lt;/Title&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Shortcut&amp;gt;newtable&amp;lt;/Shortcut&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Description&amp;gt;Code snippet for Create a ORM Class&amp;lt;/Description&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Author&amp;gt;Ascii&amp;lt;/Author&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;SnippetTypes&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;SnippetType&amp;gt;Expansion&amp;lt;/SnippetType&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;/SnippetTypes&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;/Header&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;Snippet&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Declarations&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ID&amp;gt;table_name&amp;lt;/ID&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;Default&amp;gt;table_name&amp;lt;/Default&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ToolTip&amp;gt;Table Name&amp;lt;/ToolTip&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;/Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ID&amp;gt;class_name&amp;lt;/ID&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;Default&amp;gt;class_name&amp;lt;/Default&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ToolTip&amp;gt;ORM Class Name&amp;lt;/ToolTip&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;/Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ID&amp;gt;p_key_name&amp;lt;/ID&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;Default&amp;gt;p_key_name&amp;lt;/Default&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ToolTip&amp;gt;Primary Key table column&amp;lt;/ToolTip&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;/Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ID&amp;gt;p_key_property&amp;lt;/ID&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;Default&amp;gt;p_key_property&amp;lt;/Default&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;     &lt;/span&gt;&amp;lt;ToolTip&amp;gt;Primary Key Property&amp;lt;/ToolTip&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;    &lt;/span&gt;&amp;lt;/Literal&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;/Declarations&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;Code Language=&quot;csharp&quot;&amp;gt;&amp;lt;![CDATA[[Table(&quot;$table_name$&quot;)]&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public class $class_name$&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; public $class_name$()&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; [PrimaryKey, Column(&quot;$p_key_name$&quot;)]&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; public Integer $p_key_property$ { get; set; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }]]&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;   &lt;/span&gt;&amp;lt;/Code&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt;  &lt;/span&gt;&amp;lt;/Snippet&amp;gt;&lt;br /&gt;
&lt;span class=&quot;Apple-tab-span&quot; style=&quot;white-space: pre;&quot;&gt; &lt;/span&gt;&amp;lt;/CodeSnippet&amp;gt;&lt;br /&gt;
&amp;lt;/CodeSnippets&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
更詳細的資訊可以參考官方文件：&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/z41h7fat.aspx&quot;&gt;Visual C# Code Snippets&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/7862265541729963686/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/10/code-snippet-for-c.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/7862265541729963686'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/7862265541729963686'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/10/code-snippet-for-c.html' title='開發團隊的基礎建設 Code Snippet for C#'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-EXFjrFWvZELGRL5_pzHEaIkHfObsVnwJ9mM4pWhteoFGFAMIRsaJu9TLm56-sQD2dKE4jOhXWF3l7F5KGNVWOnYwxSdmReFqRkV_5RBV9MgTGMP0SEV0VXWsh25iQidLqgqS8-yzlLKz/s72-c/snippet.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-5959683548144668279</id><published>2014-10-14T01:54:00.002+08:00</published><updated>2014-10-14T01:54:53.237+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="store apps"/><category scheme="http://www.blogger.com/atom/ns#" term="windows phone"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>建立及更新 Live Tile 動態磚資訊</title><content type='html'>在上一篇筆記&amp;nbsp;&lt;a href=&quot;http://ascii-iicsa.blogspot.tw/2014/10/secondary-tiles.html&quot;&gt;基礎 Secondary Tiles 新增及操作&lt;/a&gt;&amp;nbsp;記錄了如何釘上基本的動態磚來做內容的入口時有提到，使用 NotificationsExtensions 可以比較方便的達到花樣更豐富的動態磚。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
NotificationsExtensions 本身是一個 &lt;a href=&quot;http://msdn.microsoft.com/zh-tw/library/vstudio/gg597391(v=vs.100).aspx&quot;&gt;Protable Classes Library&lt;/a&gt;&amp;nbsp;所開發的 Library，所以對於 Windows Phone 及 Windows Store 的支援性是一樣的，裡面大部份的 Function 在做的事是將傳入的圖片，字串等參數組成一段 XML 來建立或更新動態磚。&lt;br /&gt;
&lt;br /&gt;
在 &lt;a href=&quot;http://msdn.microsoft.com/zh-tw/library/windows/apps/xaml/Dn642158(v=win.10).aspx&quot;&gt;NotificationsExtensions 的官方文件&lt;/a&gt;中可以下載到包含 Source Code 的範例專案，打開專案中 TileContent.cs 這個檔案的最底部可以發現 TileContentFactory&amp;nbsp;是一個使用 Static Factory Pattern 開發的類別，其中有很多像&amp;nbsp;CreateTileSquare150x150Block 這樣子的靜態 Method，這些都是用來產生各式外型 Live Tile 的 Method，可以參考&amp;nbsp;&lt;a href=&quot;http://msdn.microsoft.com/en-us//library/windows/apps/hh761491?lc=1028&quot;&gt;The tile template catalog&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
在使用 NotificationsExtensions 更新 Live Tile 之前，首先當然要有已經釘到桌面上的 Tile，而查詢 Tile 是否已被釘上，可以使用列舉的方式，也可以直接使用 TileId 來查詢，方法如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
// 使用 foreach 從 tilelist 中找到已釘至桌面的合適 Tile&lt;br /&gt;
IReadOnlyList&amp;lt;SecondaryTile&amp;gt; tilelist = await Windows.UI.StartScreen.SecondaryTile.FindAllAsync();&lt;br /&gt;
&lt;br /&gt;
// 或直接查詢要 Update 的 Tile 是否存在&lt;br /&gt;
SecondaryTile.Exists(Constants.TILE_ID)&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
而之所以要介紹這個 Library，利用以下兩種方式來更新 Tile 就可以明顯看出好處了&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
const String TILE_ID = &quot;tile_id&quot;;&lt;br /&gt;
&lt;br /&gt;
// 使用 NotificationsExtensions 更新動態磚&lt;br /&gt;
if (SecondaryTile.Exists(TILE_ID))&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; // 更新文字&lt;br /&gt;
&amp;nbsp; &amp;nbsp; ITileWide310x150Text04 tileContent = TileContentFactory.CreateTileWide310x150Text04();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; tileContent.TextBodyWrap.Text = &quot;Sent to a secondary tile from NotificationsExtensions!&quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; ITileSquare150x150Text04 squareContent = TileContentFactory.CreateTileSquare150x150Text04();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; squareContent.TextBodyWrap.Text = &quot;Sent to a secondary tile from NotificationExtensions!&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; tileContent.Square150x150Content = squareContent;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; TileUpdateManager.CreateTileUpdaterForSecondaryTile(TILE_ID).Update(tileContent.CreateNotification());&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; // 更新數字&lt;br /&gt;
&amp;nbsp; &amp;nbsp; BadgeNumericNotificationContent badgeContent = new BadgeNumericNotificationContent(6);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; BadgeUpdateManager.CreateBadgeUpdaterForSecondaryTile(TILE_ID).Update(badgeContent.CreateNotification());&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 自行描述更新資訊&lt;br /&gt;
if (SecondaryTile.Exists(TILE_ID))&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; // 更新文字&lt;br /&gt;
&amp;nbsp; &amp;nbsp; string tileXmlString = &quot;&amp;lt;tile&amp;gt;&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;+ &quot;&amp;lt;visual version=&#39;2&#39;&amp;gt;&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;+ &quot;&amp;lt;binding template=&#39;TileWide310x150Text04&#39; fallback=&#39;TileWideText04&#39;&amp;gt;&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;+ &quot;&amp;lt;text id=&#39;1&#39;&amp;gt;Send to a secondary tile from strings&amp;lt;/text&amp;gt;&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;+ &quot;&amp;lt;/binding&amp;gt;&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;+ &quot;&amp;lt;binding template=&#39;TileSquare150x150Text04&#39; fallback=&#39;TileSquareText04&#39;&amp;gt;&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;+ &quot;&amp;lt;text id=&#39;1&#39;&amp;gt;Send to a secondary tile from strings&amp;lt;/text&amp;gt;&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;+ &quot;&amp;lt;/binding&amp;gt;&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;+ &quot;&amp;lt;/visual&amp;gt;&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;+ &quot;&amp;lt;/tile&amp;gt;&quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; XmlDocument tileDOM = new XmlDocument();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; tileDOM.LoadXml(tileXmlString);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; TileNotification tile = new TileNotification(tileDOM);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; TileUpdateManager.CreateTileUpdaterForSecondaryTile(TILE_ID).Update(tile);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; // 更新數字&lt;br /&gt;
&amp;nbsp; &amp;nbsp; string badgeXmlString = &quot;&amp;lt;badge value=&#39;9&#39;/&amp;gt;&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; XmlDocument badgeDOM = new XmlDocument();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; badgeDOM.LoadXml(badgeXmlString);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; BadgeNotification badge = new BadgeNotification(badgeDOM);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; BadgeUpdateManager.CreateBadgeUpdaterForSecondaryTile(TILE_ID).Update(badge);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/5959683548144668279/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/10/live-tile.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/5959683548144668279'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/5959683548144668279'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/10/live-tile.html' title='建立及更新 Live Tile 動態磚資訊'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-7991322536475904316</id><published>2014-10-08T00:38:00.001+08:00</published><updated>2014-10-08T00:53:09.490+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="store apps"/><category scheme="http://www.blogger.com/atom/ns#" term="windows phone"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>基礎 Secondary Tiles 新增及操作</title><content type='html'>動態磚 ( Live Tile ) 一直是從早期的 Metro Style 到現在 Windows 10 預覽版開放下載後，微軟很重視，且用來審核 App 品質的一項元件，在 App 安裝完成後會在開始畫面釘上應用程式的動態磚，這是應用程式的預設進入點，其他應用程式額外的動態磚，都叫作 Secondary Tile。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
建立 Live Tile 可以使用透過簡單的方式建立 SecondaryTile 物件，也可以和 Notification、Badge 一樣的，透過 NotificationExtensions Library 建立樣式較豐富的 Live Tile，但到底建立一個以上的 Live Tile 有什麼好處呢？&lt;br /&gt;&lt;br /&gt;其實大部份的目的是透過背景作業來改變動態磚上的資訊例如 Badge Content 或背景圖，達到讓用戶不必進到應用程式就可以快速取得各項簡短資訊。例如天氣的 App 除了應該程式 icon 之外，若提供用戶釘選台北市、宜蘭縣的 Live Tile 在首頁上，並且每 30 分鐘在背景將天氣圖及氣溫更新上去，都是對用戶來說很方便的功能，且 Tile 本身是可以帶參數的，用戶若是點選宜蘭縣的 Tile 在進到 App 後直接開啟宜蘭縣頁面的詳細旅遊天氣及未來一週天氣預測，這些都是很好的用戶體驗。&lt;br /&gt;
&lt;br /&gt;
以下示範如何建立最單純的靜態 Secondary Tile，並且在 App 類別內識別用戶是透過台北市還是宜蘭市的 Tile 開啟，這種 Tile 是沒有動態效果的，通常是讓用戶釘選各功能的進入點，我在 Tile 的參數欄位放了釘磚塊當時的時間做為參數的示範&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
public sealed partial class MainPage : WindowsLayoutSupportPage&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public const String TILE_ID_ELAND = &quot;tile_id_eland&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public const String TILE_ID_TAIPEI = &quot;tile_id_taipei&quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public MainPage()&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.InitializeComponent();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public static Rect GetElementRect(FrameworkElement element)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; GeneralTransform buttonTransform = element.TransformToVisual(null);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Point point = buttonTransform.TransformPoint(new Point());&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return new Rect(point, new Size(element.ActualWidth, element.ActualHeight));&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private async void OnElandButtonClick(object sender, RoutedEventArgs e)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SecondaryTile secondaryTile = new SecondaryTile(TILE_ID_ELAND,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;e-land&quot;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; DateTime.UtcNow.Ticks.ToString(),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; new Uri(&quot;ms-appx:///Assets/eland.png&quot;),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; TileSize.Square150x150);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 若是 Windows Phone 則不用設定底下這兩種尺吋的圖片 Uri&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.Wide310x150Logo = new Uri(&quot;ms-appx:///Assets/eland-w.png&quot;);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.Square310x310Logo = new Uri(&quot;ms-appx:///Assets/eland-f.png&quot;);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.ForegroundText = ForegroundText.Dark;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.Square30x30Logo = new Uri(&quot;ms-appx:///Assets/eland-s.png&quot;);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.ShowNameOnSquare150x150Logo = true;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // Windows 上建立動態磚時會跳出確認 Dialog，所以需指定從何處顯示 Dialog&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Rect rect = GetElementRect((FrameworkElement)sender);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Boolean success = await secondaryTile.RequestCreateForSelectionAsync(rect, Placement.Default);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 若是 Windows Phone 則是直接透過以下方式建立，不會跳確認的 Dialog 故不需指定位置&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; //await secondaryTile.RequestCreateAsync();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private async void OnTiapeiButtonClick(object sender, RoutedEventArgs e)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SecondaryTile secondaryTile = new SecondaryTile(TILE_ID_TAIPEI,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;taipei&quot;,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; DateTime.UtcNow.Ticks.ToString(),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; new Uri(&quot;ms-appx:///Assets/taipei.png&quot;),&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; TileSize.Square150x150);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 若是 Windows Phone 則不用設定底下這兩種尺吋的圖片 Uri&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.Wide310x150Logo = new Uri(&quot;ms-appx:///Assets/taipei-w.png&quot;);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.Square310x310Logo = new Uri(&quot;ms-appx:///Assets/taipei-f.png&quot;);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.ForegroundText = ForegroundText.Dark;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.Square30x30Logo = new Uri(&quot;ms-appx:///Assets/taipei-s.png&quot;);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; secondaryTile.VisualElements.ShowNameOnSquare150x150Logo = true;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // Windows 上建立動態磚時會跳出確認 Dialog，所以需指定從何處顯示 Dialog&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Rect rect = GetElementRect((FrameworkElement)sender);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Boolean success = await secondaryTile.RequestCreateForSelectionAsync(rect, Placement.Default);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
在按了這兩個按鍵並建立 taipei 和 e-land 的動態磚後，透過這兩個動態磚即可在 App 的 OnLaunched 中利用 LaunchActivatedEventArgs 參數識別動態磚建立時的 TileId 及參數值，例如&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
public sealed partial class App : Application&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; protected override void OnLaunched(LaunchActivatedEventArgs e)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 透過應用程式預設 Tile 啟動應用程式時，以下兩個參數值會為空字串&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Debug.WriteLine(e.Arguments); // 得到空字串，或像 635482961996820121 這樣的時間字串&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Debug.WriteLine(e.TileId); // 得到空字串，或 tile_id_taipei、tile_id_eland&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
刪除 Secondary Tile 的方式除了讓用戶在桌面上按右鍵 unpin 之外，也可以透過 SecondaryTile 物件的&amp;nbsp;RequestDeleteForSelectionAsync 方法來操作，僅需要提供 TileId 即可，簡單的範例如下&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
private async void OnUnpinSecondaryTileClick(object sender, RoutedEventArgs e)&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; if (SecondaryTile.Exists(TILE_ID_TAIPEI))&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SecondaryTile secondaryTile = new SecondaryTile(TILE_ID_TAIPEI);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Boolean isUnpinned = await secondaryTile.RequestDeleteForSelectionAsync(GetElementRect((FrameworkElement)sender), &amp;nbsp;Placement.Below);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/7991322536475904316/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/10/secondary-tiles.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/7991322536475904316'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/7991322536475904316'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/10/secondary-tiles.html' title='基礎 Secondary Tiles 新增及操作'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-4732290724575550334</id><published>2014-09-26T00:39:00.000+08:00</published><updated>2014-10-07T22:08:07.957+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="store apps"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>整合 Setting 與加入自定 Layout 的設定畫面</title><content type='html'>在 Windows 8 這個作業系統推出時，整合了搜尋、分享、設定三個入口到 Charms 上面，到了 Windows 8.1 時雖然 Search 的觀念有些調整，但 Setting 方面依然不變，且增加了方便開發者製作自定 Layout 設定頁的方法。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;在 Store Apps 上新增 Setting 的支援方式依然不變，但如果是撰寫 Universal Apps 時，就要特別注意 Charms 相關 API 在 Windows Phone 上面是不支援的，必須使用 #if WINDOWS_APP 將程式碼包起來，以下示範如何在 Shared Project 中為應用程式加入設定，我在 App.xaml.cs 中 override OnWindowsCreated 並加入 &lt;a href=&quot;http://msdn.microsoft.com/zh-tw/library/windows/apps/xaml/windows.ui.applicationsettings.settingspane.aspx&quot;&gt;SettingsPane&lt;/a&gt; 的支援，注意因為是寫在 Shared Project 中，所以大多數的程式碼都被包起來以避免編譯器回報 Error。&lt;br /&gt;
&lt;br /&gt;
此處我僅附上加入到 App class 內的程式碼，原先 App class 內的程式碼未列出來的皆不需修改&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
public sealed partial class App : Application&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public App()&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.InitializeComponent();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.Suspending += this.OnSuspending;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; protected override void OnWindowCreated(WindowCreatedEventArgs args)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; base.OnWindowCreated(args);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; #if WINDOWS_APP&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; #endif&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; #if WINDOWS_APP&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private void OnCommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; UICommandInvokedHandler handler = new UICommandInvokedHandler(OnSettingsCommand);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand command1 = new SettingsCommand(&quot;setting1&quot;, &quot;Setting 1&quot;, handler);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; args.Request.ApplicationCommands.Add(command1);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand command2 = new SettingsCommand(&quot;setting2&quot;, &quot;Setting 2&quot;, handler);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; args.Request.ApplicationCommands.Add(command2);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand command3 = new SettingsCommand(&quot;setting3&quot;, &quot;Setting 3&quot;, handler);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; args.Request.ApplicationCommands.Add(command3);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand command4 = new SettingsCommand(&quot;setting4&quot;, &quot;Setting 4&quot;, handler);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; args.Request.ApplicationCommands.Add(command4);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand command5 = new SettingsCommand(&quot;setting5&quot;, &quot;Setting 5&quot;, handler);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; args.Request.ApplicationCommands.Add(command5);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand command6 = new SettingsCommand(&quot;setting6&quot;, &quot;Setting 6&quot;, handler);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; args.Request.ApplicationCommands.Add(command6);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand command7 = new SettingsCommand(&quot;setting7&quot;, &quot;Setting 7&quot;, handler);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; args.Request.ApplicationCommands.Add(command7);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand command8 = new SettingsCommand(&quot;setting8&quot;, &quot;Setting 8&quot;, handler);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; args.Request.ApplicationCommands.Add(command8);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; try&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand command9 = new SettingsCommand(&quot;setting9&quot;, &quot;Setting 9&quot;, handler);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; args.Request.ApplicationCommands.Add(command9);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; catch (Exception)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private void OnSettingsCommand(IUICommand command)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SettingsCommand settingsCommand = (SettingsCommand)command;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Debug.WriteLine(String.Format(&quot;Id :: {0}, Label :: {1}&quot;, settingsCommand.Id, settingsCommand.Label));&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; #endif&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
以上程式碼可以注意一下，我在加入第 9 個 SettingsCommand 時使用 try cache 將指令包起來，這是因為 SettingsPane 在加入超過 8 個 SettingsCommand 時執行會發生 Exception，所以記得 SettingsPane 上只是放各設定細項的入口，不是所有細節。&lt;br /&gt;
&lt;br /&gt;
同時可以注意到 SettingsPane 上只能加入字串格式的 Command，如果想要像預設會加入的 Permission 一樣可以開啟內含豐富元件的 SettingsPane 時，得在 OnSettingsCommand Function 內自行將 SettingsPane 關掉並顯示一個寬度為 346 個 pixel 的 Control，這方面在 Store App 上製作並不如 Windows 8 時一樣需要自行撰寫全部的動作，可以使用官方所提供的&amp;nbsp;&lt;a href=&quot;http://msdn.microsoft.com/zh-tw/library/windows/apps/xaml/windows.ui.xaml.controls.settingsflyout.aspx&quot;&gt;SettingsFlyout class&lt;/a&gt;&amp;nbsp;來簡易的達成。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/4732290724575550334/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/09/setting-layout.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4732290724575550334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4732290724575550334'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/09/setting-layout.html' title='整合 Setting 與加入自定 Layout 的設定畫面'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-2975758654554195527</id><published>2014-09-22T01:44:00.000+08:00</published><updated>2014-09-22T01:48:20.130+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="windows mobile"/><category scheme="http://www.blogger.com/atom/ns#" term="windows phone"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>使用 NotificationsExtensions 建立 Toast Notification</title><content type='html'>現在應用程式的使用流程愈來愈傾向於盡量的少打擾使用者，Toast Notification 就是一種用來通知用戶事件發生又沒什麼干擾性的方法，建立 Toast Notification 的最基本方式是利用 XML 描述 Toast 的 Layout 並利用&amp;nbsp;ToastNotificationManager 來顯示，而 XML 的內容結構可以參考這裡：&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/windows/apps/br212853.aspx&quot;&gt;Tiles, toast, and badge schemas&lt;/a&gt;&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;
&lt;br /&gt;
因為撰寫組成 XML 的程式碼太陽春了，所以可以透過 NuGet 安裝 NotificationsExtensions 來幫助我們做這件事，這個 Library 提供了一些 Factory 讓開發者快速的產生 Toast、Tile、Badge，官方的文件可以參考這裡：&lt;a href=&quot;http://msdn.microsoft.com/library/windows/apps/xaml/Dn642158(v=win.10).aspx&quot;&gt;Using the NotificationsExtensions library in your code&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
在使用 NuGet 安裝 NotificationsExtensions 時若是使用搜尋需特別注意 Microsoft 的官方版本除了 NotificationsExtensions 之外，還會找到另一個叫 NotificationsExtensions.WinRT 的官方版本，這是給 Windows Runtime 框架的 Win8 App 使用的，內容有些差異會無法讓 Windows Phone Project 使用，若是要撰寫 Universal Apps 那裝 Created by Microsoft 的那個 NotificationsExtensions 就對了。&lt;br /&gt;
&lt;br /&gt;
以下撰寫一段簡單的例子來產生通知，這段 Code 在 Windows Store App 上與 Windows Phone Store App 上寫法是一模一樣的，但顯示的樣式是不同的，Windows Phone 上沒有包含圖片的功能 &lt;span style=&quot;color: #e06666;&quot;&gt;( 測試時，記得將 Package.appxmanifest 中的 Toast capable 設為 Yes )&lt;/span&gt;。&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
private void OnButtonClick(object sender, RoutedEventArgs e)&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; var toastContent = NotificationsExtensions.ToastContent.ToastContentFactory.CreateToastImageAndText01();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; toastContent.TextBodyWrap.Text = &quot;Lorem ipsum dolor sit amet&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; toastContent.Image.Src = &quot;http://aaa.bbb.ccc/Toast.png&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; toastContent.Image.Alt = &quot;A red rectangle&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; toastContent.Audio.Content = NotificationsExtensions.ToastContent.ToastAudioContent.LoopingAlarm;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; toastContent.Audio.Loop = true;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; toastContent.Duration = NotificationsExtensions.ToastContent.ToastDuration.Long;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; var toast = toastContent.CreateNotification();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; Windows.UI.Notifications.ToastNotificationManager.CreateToastNotifier().Show(toast);&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
上面的範例使用了 ToastImageAndText01 這種樣式，除了這個之外還有許多 Toast 的樣式，可以參考以前 Windows Runtime 的官方文件：&lt;a href=&quot;http://msdn.microsoft.com/library/windows/apps/windows.ui.notifications.toasttemplatetype&quot;&gt;ToastTemplateType enumeration&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
如果只需要撰寫 Windows Store Apps 也可以安裝 NotificationsExtensions.WinRT 函式庫，使方法大致上也是利用 Factory 來建立各種外型的 Toast 例如&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
/*&lt;br /&gt;
以下程式碼僅能在 Windows RT 中正常編譯&lt;br /&gt;
*/&lt;br /&gt;
private void OnButtonClick(object sender, RoutedEventArgs e)&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; IToastNotificationContent toastContent = null;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; IToastImageAndText02 templateContent = ToastContentFactory.CreateToastImageAndText02();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; templateContent.TextHeading.Text = &quot;Heading text&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; templateContent.TextBodyWrap.Text = &quot;Body text that wraps.&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; templateContent.Image.Src = &quot;http://aaa.bbb.ccc/toast.png&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; templateContent.Image.Alt = &quot;Alt&quot;;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; toastContent = templateContent;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; ToastNotification toast = toastContent.CreateNotification();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; ToastNotificationManager.CreateToastNotifier().Show(toast);&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/2975758654554195527/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/09/notificationsextensions-toast.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/2975758654554195527'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/2975758654554195527'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/09/notificationsextensions-toast.html' title='使用 NotificationsExtensions 建立 Toast Notification'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-4827139516143113735</id><published>2014-09-21T00:59:00.001+08:00</published><updated>2014-09-21T22:27:31.829+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="store apps"/><category scheme="http://www.blogger.com/atom/ns#" term="windows phone"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>利用 AudioEncodingProperties 產生 MediaStreamSource</title><content type='html'>曾經在 Silverlight for Windows Phone 上使用過 BackgroundAudioPlayer 的開發者大概會知道，這個播放器除了餵一般的 Uri 之外，若要播放串流就得實作該音檔格式的 MediaStreamSource 來供播放器讀取，而這個實作過程相當的辛苦，除了要讀音檔格式的 Spec 之外，背景播放的測試也是很耗時間的，幸好這個過程在撰寫 Store Apps 時不需再經歷一次。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
在 Store Apps 中的 MediaElement 也是同樣除了 Uri 之外還可以指定 MediaStreamSource 來播放自訂的串流，但是 MediaStreamSource 的 Constructor 可以指定&amp;nbsp;MediaStreamDescriptor，這就是用來描述音檔格式的方法，目前可以利用&amp;nbsp;&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/windows.media.mediaproperties.audioencodingproperties.aspx&quot;&gt;AudioEncodingProperties&lt;/a&gt;&amp;nbsp;建立&amp;nbsp;&amp;nbsp;AAC、AAC-ADTS、MP3、PCM、WMA 這幾種格式的串流，如果正好你需要播放這些格式，那麼 Store Apps 的框架很貼心的幫你節省了上千行程式碼的撰寫時間。&lt;br /&gt;
&lt;br /&gt;
其中 AAC 指的是 MP4 或 M4A 這種包裝的方式，與 ADTS 格式的 Frame 排列規則略有不同，有興趣的可以參考 &lt;a href=&quot;http://zh.wikipedia.org/zh-tw/%E9%80%B2%E9%9A%8E%E9%9F%B3%E8%A8%8A%E7%B7%A8%E7%A2%BC&quot;&gt;AAC 的 Wiki&lt;/a&gt;，有需要播放 AAC 串流的開發者別忘了看看副檔名確認是哪一種 AAC。&lt;br /&gt;
&lt;br /&gt;
以下我用很簡短的 Code 示範如何播放 MP3 的串流，串流來源是放在 Storage 底下的 Audio.mp3 這個檔案，首先在 XAML 中加入一個 MediaElement 元件及一顆播放按鍵。&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;Page&lt;br /&gt;
&amp;nbsp; &amp;nbsp; x:Class=&quot;App1.PlayerPage&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns:local=&quot;using:App1&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; mc:Ignorable=&quot;d&quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;lt;Grid Background=&quot;{ThemeResource ApplicationPageBackgroundThemeBrush}&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;MediaElement x:Name=&quot;Player&quot; AreTransportControlsEnabled=&quot;True&quot; AudioCategory=&quot;BackgroundCapableMedia&quot; /&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;Button Content=&quot;Play&quot; Click=&quot;OnPlayButtonClick&quot;/&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;lt;/Grid&amp;gt;&lt;br /&gt;
&amp;lt;/Page&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
接著在 PlayerPage.xaml.cs 中利用 MediaStreamSource 將 Audio.mp3 檔案讀出來播放，這裡為了提供連貫的閱讀性，將流程全部寫在 Page 內，但理想上將各種格式的 MediaStreamSource 各別封裝成獨立的 Class 將可以得到更簡潔的播放流程程式碼，例如建立 MP3MediaStreamSource 或 AACMediaStreamSource 之類的來使用。&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
public sealed partial class PlayerPage : Page&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private StorageFile inputMP3File;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private MediaStreamSource MSS = null;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private IRandomAccessStream mssStream;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private IInputStream inputStream;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private UInt64 byteOffset;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private TimeSpan timeOffset;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private const UInt32 sampleSize = 1152;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private TimeSpan sampleDuration = new TimeSpan(0, 0, 0, 0, 70);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public PlayerPage()&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.InitializeComponent();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private async void OnPlayButtonClick(object sender, RoutedEventArgs e)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; inputMP3File = await ApplicationData.Current.LocalFolder.GetFileAsync(&quot;temp0.mp3&quot;);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; byteOffset = 0;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; uint sampleRate = 44100;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; uint channelCount = 2;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; uint bitRate = 128000;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MusicProperties mp3FileProperties = await inputMP3File.Properties.GetMusicPropertiesAsync();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; TimeSpan songDuration = mp3FileProperties.Duration;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Debug.WriteLine(&quot;歌曲總長度 :: &quot; + songDuration.TotalSeconds);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AudioEncodingProperties audioProps = AudioEncodingProperties.CreateMp3(sampleRate, channelCount, bitRate);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AudioStreamDescriptor audioDescriptor = new AudioStreamDescriptor(audioProps);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MSS = new MediaStreamSource(audioDescriptor);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MSS.CanSeek = true;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MSS.MusicProperties.Title = mp3FileProperties.Title;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MSS.Duration = songDuration;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MSS.Starting += OnMSSStarting;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MSS.SampleRequested += OnMSSSampleRequested;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MSS.Closed += OnMSSClosed;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Player.SetMediaStreamSource(MSS);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; async void OnMSSStarting(Windows.Media.Core.MediaStreamSource sender, MediaStreamSourceStartingEventArgs args)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MediaStreamSourceStartingRequest request = args.Request;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if ((request.StartPosition != null) &amp;amp;&amp;amp; request.StartPosition.Value &amp;lt;= MSS.Duration)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; UInt64 sampleOffset = (UInt64)request.StartPosition.Value.Ticks / (UInt64)sampleDuration.Ticks;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; timeOffset = new TimeSpan((long)sampleOffset * sampleDuration.Ticks);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; byteOffset = sampleOffset * sampleSize;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (mssStream == null)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MediaStreamSourceStartingRequestDeferral deferal = request.GetDeferral();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; try&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; mssStream = await inputMP3File.OpenAsync(FileAccessMode.Read);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; request.SetActualStartPosition(timeOffset);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; deferal.Complete();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; catch (Exception)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MSS.NotifyError(MediaStreamSourceErrorStatus.FailedToOpenFile);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; deferal.Complete();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; else&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; request.SetActualStartPosition(timeOffset);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; async void OnMSSSampleRequested(Windows.Media.Core.MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (mssStream != null)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MediaStreamSourceSampleRequest request = args.Request;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (byteOffset + sampleSize &amp;lt;= mssStream.Size)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MediaStreamSourceSampleRequestDeferral deferal = request.GetDeferral();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; inputStream = mssStream.GetInputStreamAt(byteOffset);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MediaStreamSample sample = await MediaStreamSample.CreateFromStreamAsync(inputStream, sampleSize, timeOffset);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; sample.Duration = sampleDuration;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; sample.KeyFrame = true;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; byteOffset += sampleSize;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; timeOffset = timeOffset.Add(sampleDuration);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; request.Sample = sample;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; deferal.Complete();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; void OnMSSClosed(Windows.Media.Core.MediaStreamSource sender, MediaStreamSourceClosedEventArgs args)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (mssStream != null)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; mssStream.Dispose();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; mssStream = null;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; sender.SampleRequested -= OnMSSSampleRequested;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; sender.Starting -= OnMSSStarting;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; sender.Closed -= OnMSSClosed;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (sender == MSS)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; MSS = null;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/4827139516143113735/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/09/audioencodingproperties.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4827139516143113735'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/4827139516143113735'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/09/audioencodingproperties.html' title='利用 AudioEncodingProperties 產生 MediaStreamSource'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2305840094117072816.post-8646255293086367405</id><published>2014-09-19T01:21:00.000+08:00</published><updated>2014-09-19T01:21:50.194+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="store apps"/><category scheme="http://www.blogger.com/atom/ns#" term="windows runtime"/><title type='text'>Full View 與 Fill View 的 Layout 切換方法</title><content type='html'>在 Windows 8.1 上使用 Windows RT 開發的 App 時可以將畫面切為 Full 全螢幕、Fill 3/4 螢幕 與 Snap 320px 寬的尺寸，但在撰寫 Universal Apps 時會發現 Snap 的模式不見了，最窄也僅能讓 App 呈現寬 500px 的尺寸，在 Windows Store App 中移除了 Snap View 這種呈現方式，僅剩下三種，簡易的說明如下。&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;FullScreenLandscape：全螢幕&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;FullScreenPortrait：寬度為 500px ~ 螢幕的一半以下&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;color: #cc0000;&quot;&gt;Filled：寬度大於螢幕的一半 ~ 螢幕寬度 - 500px&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
大致上如果想在這三種不同的尺寸下指定不同的排版樣式，搭配 MVVM 是最節省作業系統資源及開發成本的方式，在 Windows RT 時期常會使用 LayoutAwarePage 來簡單達到效果，在 Store App 時方式差不多，但需要經過小部分改寫才能使用，搜尋了一下實在找不到官方對於 LayoutAwarePage 的介紹，可能暫時得開 Windows RT 的 Template 來參考，另外官方建議在將專案轉至 8.1 時將 LayoutAwarePage 移除的方式在這：&lt;a href=&quot;http://msdn.microsoft.com/library/windows/apps/dn263110.aspx&quot;&gt;API changes for Windows 8.1(XAML)&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
以下我將 LayoutAwarePage 對於 WindowsSizeChange 時切換 View 的方式集中起來讓 Windows Store App 也可以根據視窗尺寸改變排版方式，這裡注意我使用了 #if WINDOWS_APP 的方式將類別內容全部包起來了，因為我將這個類別放在 Universal Apps 的 Shared Project 內，而這個類別內有太多 Windows Phone SDK 不支援的 API。&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
public class WindowsLayoutSupportPage : Page&lt;br /&gt;
{&lt;br /&gt;
#if WINDOWS_APP&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private List&amp;lt;Control&amp;gt; _layoutAwareControls;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public WindowsLayoutSupportPage()&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Loaded += OnWindowsLayoutSupportPageLoaded;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Unloaded += OnWindowsLayoutSupportPageUnloaded;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; void OnWindowsLayoutSupportPageLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; StartLayoutUpdates(sender, e);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; protected void OnWindowsLayoutSupportPageUnloaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; StopLayoutUpdates(sender, e);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void StartLayoutUpdates(object sender, RoutedEventArgs e)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var control = sender as Control;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (control == null) return;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (this._layoutAwareControls == null)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SizeChanged += this.OnPageSizeChanged;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this._layoutAwareControls = new List&amp;lt;Control&amp;gt;();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this._layoutAwareControls.Add(control);&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; VisualStateManager.GoToState(control, DetermineVisualState(ApplicationView.Value), false);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void StopLayoutUpdates(object sender, RoutedEventArgs e)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var control = sender as Control;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (control == null || this._layoutAwareControls == null) return;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this._layoutAwareControls.Remove(control);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (this._layoutAwareControls.Count == 0)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this._layoutAwareControls = null;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; SizeChanged -= this.OnPageSizeChanged;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; protected string DetermineVisualState(ApplicationViewState viewState)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return viewState.ToString();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; public void InvalidateVisualState()&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (this._layoutAwareControls != null)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; string visualState = DetermineVisualState(ApplicationView.Value);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; foreach (var layoutAwareControl in this._layoutAwareControls)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; VisualStateManager.GoToState(layoutAwareControl, visualState, false);&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; private void OnPageSizeChanged(object sender, SizeChangedEventArgs e)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; {&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; InvalidateVisualState();&lt;br /&gt;
&amp;nbsp; &amp;nbsp; }&lt;br /&gt;
#endif&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;
有了上面這個類別後，我們將需要支援改變 View 的 Page 改為這個類別，即可在最上方所列出來的三種尺寸切換不同的容器，配合同樣的 DataContext 其實增加的負擔不會太大，以下這個例子我讓 MainPage 可以在 FullL、FullP、Fill 三種尺寸下各選擇不同的容器，僅需針對 XAML 做修改即可在改變尺寸時讓視窗內容在藍、綠、紅三個 Grid 做切換&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;
&amp;lt;local:WindowsLayoutSupportPage&lt;br /&gt;
&amp;nbsp; &amp;nbsp; x:Class=&quot;UAPractice.MainPage&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns:local=&quot;using:UAPractice&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; mc:Ignorable=&quot;d&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;lt;Grid&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;Grid x:Name=&quot;FullLandscapeView&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Background=&quot;#00AED8&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AutomationProperties.AutomationId=&quot;FullLandscapeView&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/Grid&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;Grid x:Name=&quot;FullPortraitView&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Background=&quot;#99FF99&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AutomationProperties.AutomationId=&quot;FullPortraitView&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Visibility=&quot;Collapsed&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/Grid&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;Grid x:Name=&quot;FillView&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Background=&quot;#FF9999&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AutomationProperties.AutomationId=&quot;FillView&quot;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Visibility=&quot;Collapsed&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/Grid&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;VisualStateManager.VisualStateGroups&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;VisualStateGroup&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;VisualState x:Name=&quot;FullScreenLandscape&quot;/&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;VisualState x:Name=&quot;Filled&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;Storyboard&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;ObjectAnimationUsingKeyFrames Storyboard.TargetName=&quot;FillView&quot; Storyboard.TargetProperty=&quot;Visibility&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;Visible&quot;/&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/ObjectAnimationUsingKeyFrames&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;ObjectAnimationUsingKeyFrames Storyboard.TargetName=&quot;FullLandscapeView&quot; Storyboard.TargetProperty=&quot;Visibility&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;Collapsed&quot;/&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/ObjectAnimationUsingKeyFrames&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/Storyboard&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/VisualState&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;VisualState x:Name=&quot;FullScreenPortrait&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;Storyboard&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;ObjectAnimationUsingKeyFrames Storyboard.TargetName=&quot;FullPortraitView&quot; Storyboard.TargetProperty=&quot;Visibility&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;Visible&quot;/&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/ObjectAnimationUsingKeyFrames&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;ObjectAnimationUsingKeyFrames Storyboard.TargetName=&quot;FullLandscapeView&quot; Storyboard.TargetProperty=&quot;Visibility&quot;&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;Collapsed&quot;/&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/ObjectAnimationUsingKeyFrames&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/Storyboard&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/VisualState&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/VisualStateGroup&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/VisualStateManager.VisualStateGroups&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;lt;/Grid&amp;gt;&lt;br /&gt;
&amp;nbsp; &amp;nbsp;&lt;br /&gt;
&amp;lt;/local:WindowsLayoutSupportPage&amp;gt;&lt;br /&gt;
&lt;/code&gt;
&lt;br /&gt;&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://ascii-iicsa.blogspot.com/feeds/8646255293086367405/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/09/full-view-fill-view-layout.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/8646255293086367405'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2305840094117072816/posts/default/8646255293086367405'/><link rel='alternate' type='text/html' href='http://ascii-iicsa.blogspot.com/2014/09/full-view-fill-view-layout.html' title='Full View 與 Fill View 的 Layout 切換方法'/><author><name>Ascii</name><uri>http://www.blogger.com/profile/15765801846531811052</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM-61GbB05eJXDvjU4NGXdFYd3PHJndTKCpaWI8H-a5wnfsIPVE1yG-x7aUEceAX2QMadqh8HNVVQaO_Kpuy6wHN6wg0bKzapH509hfBNQe7BmSqNl4jrasFAZO1sKQw/s220/Ascii_300.png'/></author><thr:total>0</thr:total></entry></feed>