-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
239 lines (128 loc) · 142 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>赵元杰的技术博客</title>
<link href="/atom.xml" rel="self"/>
<link href="http://qq157755587.github.io/"/>
<updated>2019-06-27T09:06:30.018Z</updated>
<id>http://qq157755587.github.io/</id>
<author>
<name>赵元杰</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>kotlin 开篇</title>
<link href="http://qq157755587.github.io/2019/06/27/kotlin-opening/"/>
<id>http://qq157755587.github.io/2019/06/27/kotlin-opening/</id>
<published>2019-06-27T08:50:47.000Z</published>
<updated>2019-06-27T09:06:30.018Z</updated>
<content type="html"><![CDATA[<h1 id="Kotlin-开篇"><a href="#Kotlin-开篇" class="headerlink" title="Kotlin 开篇"></a>Kotlin 开篇</h1><p>这篇文章是为「码上开学」写的,首发于<a href="https://kaixue.io/kotlin-overview/" target="_blank" rel="noopener">码上开学官网 </a>。</p><h2 id="引子"><a href="#引子" class="headerlink" title="引子"></a>引子</h2><blockquote><p>找到那把三叉戟,你便能号令整个海洋。 ——《海王》</p></blockquote><h2 id="为什么需要新语言"><a href="#为什么需要新语言" class="headerlink" title="为什么需要新语言"></a>为什么需要新语言</h2><p>Java是当今世界最流行的工业级语言,有着非常成熟的生态和广泛的开发群体。当初Android选择Java作为开发语言,也是为了吸引Java程序员这个世界上最大的开发群体。最早的一批Android程序员可能已经用Java写了近十年Android程序了,各种实践方法也比较成熟了。那么今天我们为什么还需要一门新语言呢?这就要从Java的发行历史说起了。</p><h2 id="Java历史"><a href="#Java历史" class="headerlink" title="Java历史"></a>Java历史</h2><table><thead><tr><th>Java版本</th><th>发布日期</th><th>重要功能</th></tr></thead><tbody><tr><td>JDK 1.0</td><td>Jan 1996</td><td>Initial release</td></tr><tr><td>JDK 1.1</td><td>Feb 1997</td><td>Reflection, JDBC, Inner Classes</td></tr><tr><td>J2SE 1.2</td><td>Dec 1998</td><td>Collection, JIT</td></tr><tr><td>J2SE 1.3</td><td>May 2000</td><td>HotSpot JVM, JNDI</td></tr><tr><td>J2SE 1.4</td><td>Feb 2002</td><td>assert, Regular, NIO</td></tr><tr><td>J2SE 5.0</td><td>Sep 2004</td><td>Generics, Annotations, Autoboxing/unboxing, Concurrency</td></tr><tr><td>Java SE 6</td><td>Dec 2006</td><td>JDBC 4.0, Java Compiler API, New GC</td></tr><tr><td>Java SE 7</td><td>July 2011</td><td>Strings in switch, Diamond operator, Resource management</td></tr><tr><td>Java SE 8</td><td>Mar 2014</td><td>Lambda, Functional interface, Optionals, New Date and Time API</td></tr><tr><td>Java SE 9</td><td>Sep 2017</td><td>Multi-gigabytes heaps, Java Module System</td></tr></tbody></table><p>其实从Java 5开始,Java就是一门很完备的工业级语言了,生态也非常成熟,各种框架层出不穷。但是后续的Java 6和Java 7只是在Java 5基础上添加了一些功能和语法糖,根本算不上大版本更新,我觉得版本号定为5.1,5.2可能更合适。如果你稍微了解过同期的C#,看看Lambda和LINQ,你大概也会对Java怒其不争吧。</p><p>直到2014年Java 8正式发布,才算是Java语言的一次大更新,加入了呼声很高的Lambda和stream。可是等等,Android是哪一年发布的?<a href="https://en.wikipedia.org/wiki/Android_version_history" target="_blank" rel="noopener">2008年</a>!所以Android是以Java 6来进行开发。虽然从Android Studio 3.0开始可以<a href="https://developer.android.com/studio/write/java8-support" target="_blank" rel="noopener">使用部分Java 8特性进行开发</a>了,但是非常好用的<a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html" target="_blank" rel="noopener">stream</a>只能在Android 7.0以上使用(即minSdkVersion = 24)。受困于Android版本碎片化,我们只能放弃。那么用Java 6写代码到底有什么问题呢?</p><h2 id="从一道算法题说起"><a href="#从一道算法题说起" class="headerlink" title="从一道算法题说起"></a>从一道算法题说起</h2><p>请听题:有一个英文小写单词列表List\<String>,要求将其按首字母分组(key为 ‘a’ - ‘z’),并且每个分组内的单词列表都是按升序排序,得到一个Map\<Character, List\<String>>。请尝试用10行以内Java 6.0代码完成。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">List<String> keywords = ...;</span><br><span class="line">Map<Character, List<String>> result = <span class="keyword">new</span> HashMap<>();</span><br><span class="line"><span class="keyword">for</span> (String k: keywords) {</span><br><span class="line"> <span class="keyword">char</span> firstChar = k.charAt(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (!result.contains(firstChar)) {</span><br><span class="line"> result.put(firstChar, <span class="keyword">new</span> ArrayList<String>());</span><br><span class="line"> }</span><br><span class="line"> result.get(firstChar).add(k);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">for</span> (List<String> list: result.values()) {</span><br><span class="line"> Collections.sort(list);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>实际上已经超过10行了。我们再看看业界标杆C#是怎么写的</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">List<<span class="keyword">string</span>> keywords = …;</span><br><span class="line"><span class="keyword">var</span> result = keywords</span><br><span class="line"> .GroupBy(k => k[<span class="number">0</span>])</span><br><span class="line"> .ToDictionary(</span><br><span class="line"> g => g.Key,</span><br><span class="line"> g => g.OrderBy(k => k).ToList());</span><br></pre></td></tr></table></figure><p>为了代码可读性,我添加了一些换行。如果你愿意的话,写成一行也行。可以明显看出,对比当今先进语言,Java 6已经无法让人愉快的写代码。用Java 6写代码时,我们脑子里想的不是要<strong>做什么</strong>,而是<strong>怎么做</strong>。</p><h2 id="Java平台"><a href="#Java平台" class="headerlink" title="Java平台"></a>Java平台</h2><p>我们平时说Java,其实包含了两个不同的概念:一是Java语言本身,二是Java虚拟机,即JVM。虽然上面吐槽了Java语言本身的历史包袱,但是JVM还是非常优秀的,它有非常多的优点:</p><ul><li>跨平台</li><li>自动垃圾回收</li><li>运行速度快(你没看错。虽然Java程序速度没有C/C++快,但是在所有编程语言中Java属于速度快的那一拨。大数据框架<a href="https://hadoop.apache.org/" target="_blank" rel="noopener">Hadoop</a>便是用Java开发,能顺利处理海量数据。)</li></ul><p>我们知道Java代码编译后会生成<a href="https://en.wikipedia.org/wiki/Java_bytecode" target="_blank" rel="noopener">字节码</a>,然后字节码在JVM中运行注意Android中的虚拟机并不是JVM,而是Dalvik/ART,但也是编译成字节码。那么有没有可能新<strong>创造</strong>一门语言,编译的时候也生成字节码,然后在JVM中运行呢?这样既可以摆脱Java的历史包袱,又能享受到JVM和成熟Java框架的各种好处!答案当然是肯定的!实际上Java平台已经衍生出<a href="https://www.scala-lang.org/" target="_blank" rel="noopener">Scala</a>、<a href="https://clojure.org/" target="_blank" rel="noopener">Clojure</a>、<a href="http://groovy-lang.org/" target="_blank" rel="noopener">Groovy</a>等比较流行的语言了。而今天我们要讲的<a href="http://kotlinlang.org" target="_blank" rel="noopener">Kotlin</a>则是Java平台中的新贵,出自大名鼎鼎的<a href="https://www.jetbrains.com/" target="_blank" rel="noopener">JetBrains</a>公司。打开他们的官网你就会发现,很多著名的IDE(比如<a href="https://www.jetbrains.com/idea/" target="_blank" rel="noopener">IntelliJ</a>、<a href="https://www.jetbrains.com/ruby/" target="_blank" rel="noopener">RubyMine</a>、<a href="https://www.jetbrains.com/webstorm/" target="_blank" rel="noopener">WebStorm</a>)都是出自这家公司。IntelliJ是当今最主流的Java IDE,JetBrains公司在开发IntelliJ的过程中积累的经验令Kotlin的诞生显得水到渠成。而Google在2017年的I/O大会上<a href="https://techcrunch.com/2017/05/17/google-makes-kotlin-a-first-class-language-for-writing-android-apps/" target="_blank" rel="noopener">宣布</a>Kotlin成为Android开发的<a href="https://developer.android.com/kotlin/" target="_blank" rel="noopener">官方编程语言</a>后,更是令Kotlin一夜之间成为最受瞩目的编程语言之一。那么我们来看看Kotlin会给我们带来哪些好处吧。</p><h2 id="Kotlin带来的好处"><a href="#Kotlin带来的好处" class="headerlink" title="Kotlin带来的好处"></a>Kotlin带来的好处</h2><h3 id="Data-Class"><a href="#Data-Class" class="headerlink" title="Data Class"></a>Data Class</h3><p>假设你在做一个金融交易系统,需要定义一个Class来表示每一笔支付,包含金额和币种。这里有一个简单的例子:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Purchase</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> String currency;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span> price; <span class="comment">//为了便于演示,这里将价格设为整数类型</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看起来不错。但是作为一个经验丰富的程序员,你一眼就发现了潜藏的问题。一个金融系统,一定是要保证每笔交易的正确性的,你肯定不希望object中的currency和price的值在某个模块里面被粗心的人修改了。所以你需要一个Immutable Class,即不可更改的类。于是你做了如下修改:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Purchase</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> String currency;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> price;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Purchase</span><span class="params">(String currency, <span class="keyword">int</span> price)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.currency = currency;</span><br><span class="line"> <span class="keyword">this</span>.price = price;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getCurrency</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="keyword">this</span>.currency; }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getPrice</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="keyword">this</span>.price; }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>你将currency和price定义为private field,通过构造方法来赋值,并且只暴露get方法。这样就不用担心数据被其他人误修改了。完美!可是等等,这就够了吗?熟读《Java编程思想》的你立刻想起,一个完备的Class还需要重写<code>equals()</code>、<code>hashCode()</code>和<code>toString()</code>方法!于是你又添加了一些代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Purchase</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object o)</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">hashCode</span><span class="params">()</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>{}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我就问你烦不烦:) 仅仅是表示金额和币种,怎么要写那么多代码?如果有十几个字段,那还不得上百行代码呀?!请看Kotlin中优雅的实现:</p><figure class="highlight kt"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">data</span> <span class="class"><span class="keyword">class</span> <span class="title">Purchase</span></span>(<span class="keyword">val</span> currency: String, <span class="keyword">val</span> price: int)</span><br></pre></td></tr></table></figure><p>没了。</p><p>一行代码搞定:)</p><p>关键字<code>val</code>会自动为currency和price创建不可更改的field,而<code>data</code>则会帮我们自动生成equals()、hashCode()和toString()方法!这还不是全部,看下面的代码:</p><figure class="highlight kt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> iPhone8 = Purchase(“CNY”, <span class="number">5888</span>)</span><br><span class="line"><span class="keyword">val</span> iPhoneX = iPhone8.copy(price = <span class="number">8888</span>)</span><br></pre></td></tr></table></figure><p>看到<code>copy</code>方法了吗?是不是巨好用?这也是data class为我们自动生成的,方便吧:)</p><h3 id="Extension-Functions"><a href="#Extension-Functions" class="headerlink" title="Extension Functions"></a>Extension Functions</h3><p>Kotlin中的<a href="http://kotlinlang.org/docs/reference/extensions.html" target="_blank" rel="noopener">Extension</a>类似于Objective-C中的<a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Category.html" target="_blank" rel="noopener">Category</a>,可以帮助我们扩展已有的Class,而无需继承这个Class,无论我们能不能访问源码。这对于系统Class以及一些第三方library中的Class特别有帮助。</p><p>随着项目规模的扩大,你的代码里肯定少不了一系列Utils类(比如<code>FileUtils.java</code>,<code>DateUtils.java</code>)来封装一些繁琐但常用的功能。比如在Android开发中,如果我们要动态的调整一个View的宽高,必须要先将dp转为px,所以我们会有这样一个方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ScreenUtil</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">dip2px</span><span class="params">(<span class="keyword">float</span> dipValue)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">int</span>) (dipValue * density + <span class="number">0.5f</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后这样使用:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">layoutParams.width = ScreenUtil.dip2px(<span class="number">16F</span>)</span><br></pre></td></tr></table></figure><p>这样看起来没什么不对,只是不太符合人的阅读习惯。看看用Extension能帮我们做些什么:</p><figure class="highlight kt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="built_in">Int</span>.<span class="title">toPx</span><span class="params">()</span></span>: <span class="built_in">Int</span> {</span><br><span class="line"> <span class="keyword">return</span> ScreenUtil.dip2px(<span class="keyword">this</span>.toFloat())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们给整数类型添加了一个Extension Function,叫做<code>toPx</code>,用来实现dp到px的转换。然后优雅的调用:</p><figure class="highlight kt"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">layoutParams.width = <span class="number">16</span>.toPx()</span><br></pre></td></tr></table></figure><p>可读性是不是好多了🙂。你可能会想,Extension一个一个自己写也挺麻烦的,有没有大神把常用的Extension Functions封装成一个library啊?有的,Google爸爸已经帮我们考虑到了😘。请参看<a href="https://developer.android.com/kotlin/ktx" target="_blank" rel="noopener">Android KTX</a>项目。这个项目是<a href="https://developer.android.com/jetpack/" target="_blank" rel="noopener">Android Jetpack</a>的一部分,而Jetpack也是我们接下来要分享的内容。</p><h3 id="Null-Safety"><a href="#Null-Safety" class="headerlink" title="Null Safety"></a>Null Safety</h3><p>这是一个值得吹上三天三夜的革新。在Quora上有这样一个问题:<a href="https://www.quora.com/Why-was-the-Null-Pointer-Exception-in-Java-called-a-billion-dollar-mistake" target="_blank" rel="noopener">为什么空指针异常被称为10亿美金的错误?</a>相信每一个Java程序员都对这个问题深有体会。看看我们为了避免程序崩溃,不得不写多么丑陋的代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (a != <span class="keyword">null</span> && a.b != <span class="keyword">null</span> && a.b.c != <span class="keyword">null</span>) {</span><br><span class="line"> println(a.b.c.toString());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样写有两个问题,一是代码很丑陋,二是本少爷很容易忘记做空指针检查啊😤!!!Java 8引入了<a href="https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html" target="_blank" rel="noopener">Optional</a>来解决这个问题,比自己检查空指针要稍微优雅一点,但仍然有许多不必要的代码包装。</p><p>Kotlin的类型系统从一开始就致力于避免空指针异常。我们在Kotlin中定义变量时可以指定它为可空类型(Nullable Type)和不可空类型(Non-Null Type)。</p><figure class="highlight kt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a: String = <span class="string">"abc"</span> <span class="comment">// 不可空</span></span><br><span class="line">a = <span class="literal">null</span> <span class="comment">// 编译报错</span></span><br><span class="line">print(a.length) <span class="comment">// 没问题</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> b: String? = <span class="string">"abc"</span> <span class="comment">// 可空</span></span><br><span class="line">b = <span class="literal">null</span> <span class="comment">// 没问题</span></span><br><span class="line">print(b.length) <span class="comment">// 编译报错</span></span><br><span class="line">print(b?.length) <span class="comment">// 没问题</span></span><br></pre></td></tr></table></figure><p>上例的a是Non-Null,而b是Nullable。他们的唯一区别是定义的时候b的<code>String</code>后面加了个问号<code>?</code>,代表它是Nullable的。只有Nullable类型可以赋值为null。调用Nullable对象的方法时,需要加一个问号,像<code>b?.length</code>。如果b为null,那么<code>print(b?.length)</code>这行代码不会被执行。这样设计有什么好处呢?回到我们上一个例子,如果我们将<code>a</code>,<code>a.b</code>,<code>a.b.c</code>都定义为Nullable,那么就可以这样写:</p><figure class="highlight kt"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">println(a?.b?.c?.toString())</span><br></pre></td></tr></table></figure><p>如果它们中间任意一个为null,那么 <code>a?.b?.c?.toString()</code> 这个 chain 就会返回 <strong>null</strong> 。是不是很强大很方便?理论上讲,只要我们的类型定义合理,那么90%的空指针异常都是能避免。为什么我不敢说100%?因为有时候我们看设计文档确定某个值绝对不可能为null,于是给它定义为Non-Null,结果程序运行时偏偏就传过来一个null……😂</p><h3 id="Courtines"><a href="#Courtines" class="headerlink" title="Courtines"></a>Courtines</h3><p>Android开发中多线程处理一直是一个难点,稍微不小心就容易出问题。你可能已经学习过<a href="https://github.com/ReactiveX/RxJava" target="_blank" rel="noopener">RxJava</a>,并在项目中成功使用RxJava来处理线程问题。这非常好。但是如果你的业务逻辑并没有复杂到必须用RxJava来解决,你应该看看Kotlin中的<a href="https://kotlinlang.org/docs/reference/coroutines.html" target="_blank" rel="noopener">Courtines</a>。Courtines通常翻译成_协程<em>,在<a href="https://www.lua.org/" target="_blank" rel="noopener">Lua</a>等程序语言中已经有着广泛的应用。它的概念稍微有些复杂,我们可以暂时认为它是一种</em>无需锁_并且<em>没有线程切换开销</em>的轻量级线程。</p><p>我们看一个例子,假设我需要从网络取回来一些数据,然后保存到数据库,那么传统的异步回调写法是这样的:</p><figure class="highlight kt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">networkRequest { result -></span><br><span class="line"> databaseSave(result) { rows -></span><br><span class="line"> <span class="comment">// Result saved</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>而用Courtines的写法是这样的:</p><figure class="highlight kt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> result = networkRequest()</span><br><span class="line">databaseSave(result)</span><br><span class="line"><span class="comment">// Result saved</span></span><br></pre></td></tr></table></figure><p>你可能已经发现了,这段代码根本不关心线程如何切换,只关心我到底要实现什么功能。实际上Courtines背后远比这要复杂,想要熟练使用需要经历一些学习曲线,但好在曲线并不算陡峭。我们后面会有专门文章来讲解如合使用Courtines。如果你已经激动的等不及了(和第一次知道Courtines时的我一样),可以先跟着Google Codelabs中的<a href="https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#0" target="_blank" rel="noopener">教程</a>来练练手。</p><h3 id="与Java的交互"><a href="#与Java的交互" class="headerlink" title="与Java的交互"></a>与Java的交互</h3><p>虽然前面我们讲到,Java平台上的语言都会编译成字节码,但这并不代表所有语言都能与Java无缝交互。比如<a href="http://lampwww.epfl.ch/~michelou/scala/using-scala-from-java.html" target="_blank" rel="noopener">Scala和Java的交互</a>就非常复杂。幸运的是,JetBrains从一开始就将与Java的交互性作为Kotlin的设计目标之一。无论是从Java调用Kotlin,还是从Kotlin调用Java,都非常自然,没有什么障碍。这意味着我们可以任意使用Java丰富的第三方库,也可以在现有的Java工程基础上用Kotlin添加新的功能。万一你遇到某些特殊情况,有一段逻辑用Kotlin搞不定(我写这篇文章时就遇到一个),可以把这段逻辑抽出来用Java写。有Java兜底,我们就能放心的为项目引入Kotlin了。</p><h3 id="无障碍升级"><a href="#无障碍升级" class="headerlink" title="无障碍升级"></a>无障碍升级</h3><p>我们前面讲到,要想在Android开发中使用Java 8中的stream,需要设定minSdkVersion = 24。如果将来要使用Java 9的新特性,恐怕又得等Android版本更新了。Kotlin则不同,你可以把它当作一个集成到项目中的第三方library,可以随时升级到最新版本!这意味着Kotlin将来推出的更多新特性都能应用到所有Android项目中!</p><h2 id="迁移到Kotlin会遇到什么问题"><a href="#迁移到Kotlin会遇到什么问题" class="headerlink" title="迁移到Kotlin会遇到什么问题"></a>迁移到Kotlin会遇到什么问题</h2><p>前面讲了Kotlin的这么多好处,但要知道世上没有十全十美的语言。就Kotlin而言,目前社区反映比较多的问题是可见性修饰符(Visibility Modifiers)。我们知道Java中有四种访问权限:<code>public</code>,<code>protected</code>,<code>private</code>和<code>package-private</code>。其中package-private是指在同一个包名下可见,在library开发中非常方便。而在Kotlin中没有了package-private,取而代之的是<code>internal</code>,即在同一个<em>模块(Module)</em>内可见。这样我们在设计library时必须对可见性控制有更周全的考虑。为Android开发library,不可避免的要重写系统方法。如果你用重写了一个Java中的<code>package-private</code>方法,那么不好意思,这个方法会变成<code>public</code>🤣,原本并不想暴露出来的方法暴露了……</p><p>如果你主要做应用开发,那么目前已经没有什么坑了。Google已经逐渐用Kotlin重写Android文档中的所有例子,Github上用Kotlin开发的项目也在飞快增长。社区方面,在StackOverflow做的<a href="https://insights.stackoverflow.com/survey/2018/" target="_blank" rel="noopener">2018年度调查</a>中,Kotlin更是一举登上最受欢迎语言榜第二名!就像前面讲的,万一有问题还有Java兜底。所以你唯一需要考虑的可能就是团队学习成本。好在Kotlin是一门非常简易、现代的语言,相信做这个决定并不困难。</p><h2 id="回到最开始"><a href="#回到最开始" class="headerlink" title="回到最开始"></a>回到最开始</h2><p>我想肯定有好事的人要问,最开始那个算法题用Kotlin怎么写呢?</p><figure class="highlight kt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> keywords = arrayOf(<span class="string">"apple"</span>, <span class="string">"app"</span>, <span class="string">"alpha"</span>, ...)</span><br><span class="line"><span class="keyword">val</span> result = keywords</span><br><span class="line"> .groupBy { it[<span class="number">0</span>] }</span><br><span class="line"> .mapValues { it.value.sorted() }</span><br></pre></td></tr></table></figure><p>你还在等什么?</p>]]></content>
<summary type="html">
<h1 id="Kotlin-开篇"><a href="#Kotlin-开篇" class="headerlink" title="Kotlin 开篇"></a>Kotlin 开篇</h1><p>这篇文章是为「码上开学」写的,首发于<a href="https://kaixue.
</summary>
<category term="Kotlin" scheme="http://qq157755587.github.io/categories/Kotlin/"/>
</entry>
<entry>
<title>Chrome Custom Tabs最佳实践</title>
<link href="http://qq157755587.github.io/2016/08/12/custom-tabs-best-practices/"/>
<id>http://qq157755587.github.io/2016/08/12/custom-tabs-best-practices/</id>
<published>2016-08-12T08:54:34.000Z</published>
<updated>2016-08-13T09:02:56.000Z</updated>
<content type="html"><![CDATA[<p>距离Google发布Chrome Custom Tabs已经一年,Twitter、Medium等国外App早已支持了这个功能,但遗憾的是国内App鲜有支持。这篇文章以官方<a href="https://developer.chrome.com/multidevice/android/customtabs" target="_blank" rel="noopener">开发文档</a>和<a href="https://github.com/GoogleChrome/custom-tabs-client" target="_blank" rel="noopener">示例源码</a>为基础,加上自己的理解,希望能帮助读者快速掌握Chrome Custom Tabs的用法。</p><h1 id="为什么要用Chrome-Custom-Tabs?"><a href="#为什么要用Chrome-Custom-Tabs?" class="headerlink" title="为什么要用Chrome Custom Tabs?"></a>为什么要用Chrome Custom Tabs?</h1><p>当App需要打开一个网站时,开发者面临两种选择:默认浏览器或WebView。这两种选择都有不足。从App跳转到浏览器是一个非常重的切换,并且浏览器无法自定义;而WebView无法与浏览器共享cookies等数据,并且需要开发者处理非常多的场景。</p><p>Chrome Custom Tabs提供了一种新的选择,既能在App和网页之间流畅切换,又能有多种自定义选项。其实它本质上是调用了Chrome中的一个Activity来打开网页,这样想就能理解这些优点了。能自定义的项目有:</p><ul><li><p>Toolbar颜色</p></li><li><p>进场和出场动画</p></li><li><p>Toolbar上的action button,menu item和bottom toolbar(通过RemoveView实现)</p></li></ul><p>Chrome Custom Tabs还提供预启动Chrome和预加载网页内容的功能,与传统方式相比加载速度有显著提升。</p><p><img src="https://developer.chrome.com/multidevice/images/customtab/performance.gif" alt="alt"></p><h1 id="什么时候用Chrome-Custom-Tabs,什么时候用WebView?"><a href="#什么时候用Chrome-Custom-Tabs,什么时候用WebView?" class="headerlink" title="什么时候用Chrome Custom Tabs,什么时候用WebView?"></a>什么时候用Chrome Custom Tabs,什么时候用WebView?</h1><p>如果Web页面是你自己的内容(比如淘宝商品页之于手机淘宝),那么WebView是最好的选择,因为你可能需要针对网页内容及用户操作做非常多的自定义。如果是跳到一个外部网站,比如在App中点了一个广告链接跳转到广告商的网站,那么建议使用Chrome Custom Tabs。</p><h1 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h1><p>用户的手机上需要安装Chrome <strong>45</strong>或以上版本,并且<strong>设为默认浏览器</strong>。考虑到Chrome在国内手机上的占有率,这确实是个问题……但如果你的APP不只是面对国内市场,那么以Google在海外市场的影响力,这完全不是问题。</p><p>肯定有人要问,如果手机上没有装Chrome,调用Chrome Custom Tabs会发生什么行为呢?我们查看<code>CustomTabsIntent.Builder</code>的<a href="https://android.googlesource.com/platform/frameworks/support/+/c502e63/customtabs/src/android/support/customtabs/CustomTabsIntent.java#164" target="_blank" rel="noopener">源码</a>可以发现,Builder的内部构造了一个action为<code>Intent.ACTION_VIEW</code>的Intent,所以答案是调用默认浏览器来打开URL。</p><p>这时我们可以发现Chrome Custom Tabs的原理:如果Chrome是默认浏览器,那么这个Intent自然就会唤起Chrome,然后Chrome会根据Intent的各个Extra来配置前面所讲的自定义项。这里有一个隐藏的好处:<strong>如果你的工作恰好是开发浏览器,那么也可以根据这些Extra信息来定制界面!</strong></p><h1 id="开发向导"><a href="#开发向导" class="headerlink" title="开发向导"></a>开发向导</h1><h2 id="快速上手"><a href="#快速上手" class="headerlink" title="快速上手"></a>快速上手</h2><p>首先在你的<em>build.gradle</em>文件中加入dependency</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">dependencies {</span><br><span class="line"> ...</span><br><span class="line"> compile <span class="string">'com.android.support:customtabs:24.1.1'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后写几行代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">String url = <span class="string">"https://www.google.com"</span>;</span><br><span class="line">CustomTabsIntent.Builder builder = <span class="keyword">new</span> CustomTabsIntent.Builder();</span><br><span class="line">CustomTabsIntent customTabsIntent = builder.build();</span><br><span class="line">customTabsIntent.launchUrl(<span class="keyword">this</span>, Uri.parse(url));</span><br></pre></td></tr></table></figure><p>就好了!不费吹灰之力~</p><p>注意launchUrl这个方法的第一个参数是个Activity。肯定有人会跳起来说,我要在ViewHolder里面处理点击跳转,根本拿不到Activity,只有Context肿么办?!!(其实这个人就是我)</p><p>那么我们看看launchUrl的源码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">launchUrl</span><span class="params">(Activity context, Uri url)</span> </span>{</span><br><span class="line"> intent.setData(url);</span><br><span class="line"> ActivityCompat.startActivity(context, intent, startAnimationBundle);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>原来是为了传入startAnimationBundle这个参数来实现自定义转场动画。那么我们自己处理一下就好了:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (context <span class="keyword">instanceof</span> Activity) {</span><br><span class="line"> customTabsIntent.launchUrl((Activity) context, uri);</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> Intent intent = customTabsIntent.intent;</span><br><span class="line"> intent.setData(uri);</span><br><span class="line"> <span class="keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {</span><br><span class="line"> context.startActivity(intent, customTabsIntent.startAnimationBundle);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> context.startActivity(intent);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="修改Toolbar颜色"><a href="#修改Toolbar颜色" class="headerlink" title="修改Toolbar颜色"></a>修改Toolbar颜色</h2><p>你一定希望Chrome Custom Tabs的Toolbar颜色与你自己APP的Toolbar颜色保持相同,看起来就像是在APP内打开网页一样。一行代码搞定:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">builder.setToolbarColor(colorInt);</span><br></pre></td></tr></table></figure><h2 id="添加action-button"><a href="#添加action-button" class="headerlink" title="添加action button"></a>添加action button</h2><p>你可能想要在Toolbar上加上action button,那么需要创建一个PendingIntent。下面的代码增加了一个发送邮件的action button:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Intent actionIntent = <span class="keyword">new</span> Intent(Intent.ACTION_SEND);</span><br><span class="line">actionIntent.setType(<span class="string">"*/*"</span>);</span><br><span class="line">actionIntent.putExtra(Intent.EXTRA_EMAIL, <span class="string">"[email protected]"</span>);</span><br><span class="line">actionIntent.putExtra(Intent.EXTRA_SUBJECT, <span class="string">"example"</span>);</span><br><span class="line">PendingIntent pi = PendingIntent.getActivity(<span class="keyword">this</span>, <span class="number">0</span>, actionIntent, <span class="number">0</span>);</span><br><span class="line">Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_share);<span class="comment">//注意在正式项目中不要在UI线程读取图片</span></span><br><span class="line">builder.setActionButton(icon, <span class="string">"send email"</span>, pi, <span class="keyword">true</span>);</span><br></pre></td></tr></table></figure><h2 id="添加menu-item"><a href="#添加menu-item" class="headerlink" title="添加menu item"></a>添加menu item</h2><p>Chrome Custom Tabs的menu默认包含了三个显示为图标的item(Forward, Page Info, Refresh)和两个文字item(Find in page, Open in Browser)。我们可以再添加最多5个文字item。同样需要创建PendingIntent:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Intent menuIntent = <span class="keyword">new</span> Intent();</span><br><span class="line">menuIntent.setClass(getApplicationContext(), SomeActivity.class);</span><br><span class="line">PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), <span class="number">0</span>, menuIntent, <span class="number">0</span>);</span><br><span class="line">builder.addMenuItem(<span class="string">"Menu entry 1"</span>, pi);</span><br></pre></td></tr></table></figure><h2 id="设置转场动画"><a href="#设置转场动画" class="headerlink" title="设置转场动画"></a>设置转场动画</h2><p>如果你的APP设置了转场动画,那么为了统一的用户体验,可以在Chrome Custom Tabs中设置同样的动画</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">builder.setStartAnimations(<span class="keyword">this</span>, R.anim.slide_in_right, R.anim.slide_out_left);</span><br><span class="line">builder.setExitAnimations(<span class="keyword">this</span>, R.anim.slide_in_left, R.anim.slide_out_right);</span><br></pre></td></tr></table></figure><h2 id="预启动-Warm-up-Chrome和预加载"><a href="#预启动-Warm-up-Chrome和预加载" class="headerlink" title="预启动(Warm up) Chrome和预加载"></a>预启动(Warm up) Chrome和预加载</h2><p>默认情况下,调用了<a href="http://developer.android.com/reference/android/support/customtabs/CustomTabsIntent.html#launchUrl" target="_blank" rel="noopener">CustomTabsIntent#launchUrl</a>方法之后,才会在后台启动(原文是spin up)Chrome,然后加载网页。这个过程会花费宝贵的时间,并影响到用户体验。要是能「秒开」网页那就爽了。Chrome Custom Tabs可以绑定Chrome的一个Service,绑定成功之后可以预启动Chrome,还可以让Chrome预加载一些网页(当然这是要消耗一些流量的)。大致的过程是这样的:</p><ul><li>通过<a href="http://developer.android.com/reference/android/support/customtabs/CustomTabsClient.html#bindCustomTabsService" target="_blank" rel="noopener">CustomTabsClient#bindCustomTabsService</a>方法绑定Service。绑定成功后会得到一个CustomTabsClient的instance。如果绑定失败,说明Chrome版本过低或者没有安装。</li><li>通过<a href="http://developer.android.com/reference/android/support/customtabs/CustomTabsClient.html#warmup" target="_blank" rel="noopener">CustomTabsClient#warmup</a>方法在后台启动Chrome。</li><li>通过<a href="http://developer.android.com/reference/android/support/customtabs/CustomTabsClient.html#newSession" target="_blank" rel="noopener">CustomTabsClient#newSession</a>方法创建一个新的session。这一步还可以传入一个<a href="http://developer.android.com/reference/android/support/customtabs/CustomTabsCallback.html" target="_blank" rel="noopener">CustomTabsCallback</a>参数用来得到session的状态,比如页面是否加载完成。</li><li>通过<a href="http://developer.android.com/reference/android/support/customtabs/CustomTabsSession.html#mayLaunchUrl" target="_blank" rel="noopener">CustomTabsSession#mayLaunchUrl</a>来告诉Chrome要预加载哪些网页。</li><li>当用户点击事件发生时,将前面那个session传入<a href="http://developer.android.com/reference/android/support/customtabs/CustomTabsIntent.Builder.html" target="_blank" rel="noopener">CustomTabsIntent.Builder</a>,然后用文章最开头的方法打开网页。</li></ul><p>下面是完成这个过程的简单代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainActivity</span> <span class="keyword">extends</span> <span class="title">Activity</span> <span class="keyword">implements</span> <span class="title">ServiceConnectionCallback</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> CustomTabsSession mSession;</span><br><span class="line"> <span class="keyword">private</span> CustomTabsClient mClient;</span><br><span class="line"> <span class="keyword">private</span> CustomTabsServiceConnection mConnection;</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">bindCustomTabsService</span><span class="params">()</span> </span>{</span><br><span class="line"> mConnection = <span class="keyword">new</span> ServiceConnection(<span class="keyword">this</span>);</span><br><span class="line"> CustomTabsClient.bindCustomTabsService(<span class="keyword">this</span>, <span class="string">"com.android.chrome"</span>, mConnection);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">warmup</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (mClient != <span class="keyword">null</span>) mClient.warmup(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">preLaunch</span><span class="params">(String url)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (mClent != <span class="keyword">null</span> && mSession == <span class="keyword">null</span>) {</span><br><span class="line"> mSession = mClient.newSession(<span class="keyword">new</span> CustomTabsCallback({</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNavigationEvent</span><span class="params">(<span class="keyword">int</span> navigationEvent, Bundle extras)</span> </span>{</span><br><span class="line"> <span class="comment">// 这里可以取到Session的状态</span></span><br><span class="line"> }</span><br><span class="line"> }));</span><br><span class="line"> }</span><br><span class="line"> mSession.mayLaunchUrl(Uri.parse(url), <span class="keyword">null</span>, <span class="keyword">null</span>);<span class="comment">//这里的第三个参数可以传入一些低优先级的Url,但不能保证会被预加载</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">launch</span><span class="params">(String url)</span> </span>{</span><br><span class="line"> CustomTabsIntent.Builder builder = <span class="keyword">new</span> CustomTabsIntent.Builder(mSession);</span><br><span class="line"> CustomTabsIntent customTabsIntent = builder.build();</span><br><span class="line"> customTabsIntent.launchUrl(<span class="keyword">this</span>, Uri.parse(url));</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onDestroy</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (mConnection != <span class="keyword">null</span>) {</span><br><span class="line"> unbindService(mConnection);</span><br><span class="line"> mClient = <span class="keyword">null</span>;</span><br><span class="line"> mSession = <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">super</span>.onDestroy();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/** ServiceConnectionCallback的回调方法 */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onServiceConnected</span><span class="params">(CustomTabsClient client)</span> </span>{</span><br><span class="line"> mClient = client;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onServiceDisconnected</span><span class="params">()</span> </span>{</span><br><span class="line"> mClient = <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="简单的预启动"><a href="#简单的预启动" class="headerlink" title="简单的预启动"></a>简单的预启动</h2><p>Android Support Library 24.0.0开始加入了<a href="https://developer.android.com/reference/android/support/customtabs/CustomTabsClient.html#connectAndInitialize" target="_blank" rel="noopener">CustomTabsClient#connectAndInitialize</a>方法来简化预启动代码。如果你无法预料到用户会打开哪个URL,或者出于省电的考虑不想预加载URL,那么可以在Activity的onStart中加入一行代码来预启动。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CustomTabsClient.connectAndInitialize(<span class="keyword">this</span>, <span class="string">"com.android.chrome"</span>);</span><br></pre></td></tr></table></figure><h1 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h1><h2 id="绑定Custom-Tabs的service并预启动"><a href="#绑定Custom-Tabs的service并预启动" class="headerlink" title="绑定Custom Tabs的service并预启动"></a>绑定Custom Tabs的service并预启动</h2><p>预启动Chrome可以帮助你节省高达<strong>700ms</strong>的宝贵时间!这几乎是可以划分卡与不卡的差别。启动过程是在后台以低优先级进行,所以<strong>不会对你的APP性能有负面影响</strong>。</p><h2 id="预加载网页内容"><a href="#预加载网页内容" class="headerlink" title="预加载网页内容"></a>预加载网页内容</h2><p>预加载网页后可以达到秒开的效果!所以如果你至少有50%的把握用户会打开某个URL,你应当调用<a href="http://developer.android.com/reference/android/support/customtabs/CustomTabsSession.html#mayLaunchUrl%28android.net.Uri,%20android.os.Bundle,%20java.util.List%3Candroid.os.Bundle%3E%29" target="_blank" rel="noopener">mayLaunchUrl()</a>方法。这个方法会提前下载并渲染网页内容,但不可避免的会有一点流量和电量的消耗。如果用户正在使用收费的数据流量,或者手机电量不足,那么这个方法不会生效。所以我们<strong>完全不用自己考虑性能优化</strong>。</p><h2 id="备选方案"><a href="#备选方案" class="headerlink" title="备选方案"></a>备选方案</h2><p>如果用户的手机上没有安装Chrome,那么打开默认浏览器可能并不是最好的用户体验。所以如果在bindService那一步失败了,无论是打开默认浏览器还是WebView,选择一个你认为最好的备选方案。</p><h2 id="referrer"><a href="#referrer" class="headerlink" title="referrer"></a>referrer</h2><p>很多网站都会统计自己的流量是从哪儿来的,所以最好告诉他们是你的帅气APP给他们带来了流量:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">intent.putExtra(Intent.EXTRA_REFERRER, </span><br><span class="line"> Uri.parse(Intent.URI_ANDROID_APP_SCHEME + <span class="string">"//"</span> + context.getPackageName()));</span><br></pre></td></tr></table></figure><h2 id="加入自定义动画"><a href="#加入自定义动画" class="headerlink" title="加入自定义动画"></a>加入自定义动画</h2><p>自定义的转场动画会让你的网页跳转更流畅。确保进场动画和出场动画是反向的,比如网页从右边进来,就从右边出去,这样能帮助用户理解跳转关系。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">builder.setStartAnimations(<span class="keyword">this</span>, R.anim.slide_in_right, R.anim.slide_out_left);</span><br><span class="line">builder.setExitAnimations(<span class="keyword">this</span>, R.anim.slide_in_left, R.anim.slide_out_right);</span><br></pre></td></tr></table></figure><h2 id="为Action-Button选个合适的图标"><a href="#为Action-Button选个合适的图标" class="headerlink" title="为Action Button选个合适的图标"></a>为Action Button选个合适的图标</h2><p>一个合适的图标能让用户快速的理解到APP的功能。但记住图标的最大尺寸是宽48dp高24dp。</p><h2 id="其他支持的浏览器"><a href="#其他支持的浏览器" class="headerlink" title="其他支持的浏览器"></a>其他支持的浏览器</h2><p>前面有讲到其他浏览器也有机会支持Custom Tabs的功能(虽然我还没发现有哪款已经支持了)。如果你检测到不止一个浏览器支持Custom Tabs,那么第一次调用时最好询问用户打算用哪个浏览器。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns a list of packages that support Custom Tabs.</span></span><br><span class="line"><span class="comment"> */</span> </span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ArrayList <span class="title">getCustomTabsPackages</span><span class="params">(Context context)</span> </span>{</span><br><span class="line"> PackageManager pm = context.getPackageManager();</span><br><span class="line"> <span class="comment">// Get default VIEW intent handler.</span></span><br><span class="line"> Intent activityIntent = <span class="keyword">new</span> Intent(Intent.ACTION_VIEW, Uri.parse(<span class="string">"http://www.example.com"</span>));</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Get all apps that can handle VIEW intents.</span></span><br><span class="line"> List resolvedActivityList = pm.queryIntentActivities(activityIntent, <span class="number">0</span>);</span><br><span class="line"> ArrayList packagesSupportingCustomTabs = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"> <span class="keyword">for</span> (ResolveInfo info : resolvedActivityList) {</span><br><span class="line"> Intent serviceIntent = <span class="keyword">new</span> Intent();</span><br><span class="line"> serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);</span><br><span class="line"> serviceIntent.setPackage(info.activityInfo.packageName);</span><br><span class="line"> <span class="comment">// Check if this package also resolves the Custom Tabs service.</span></span><br><span class="line"> <span class="keyword">if</span> (pm.resolveService(serviceIntent, <span class="number">0</span>) != <span class="keyword">null</span>) {</span><br><span class="line"> packagesSupportingCustomTabs.add(info);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> packagesSupportingCustomTabs;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="给用户选择权"><a href="#给用户选择权" class="headerlink" title="给用户选择权"></a>给用户选择权</h2><p>如果你的APP之前一直是用默认浏览器打开URL,后来才加入的Custom Tabs,那么老用户可能希望保留原来的习惯。可以考虑在设置里面增加一个选项,让用户自行选择。</p><h2 id="尽量让Native-APP处理URL"><a href="#尽量让Native-APP处理URL" class="headerlink" title="尽量让Native APP处理URL"></a>尽量让Native APP处理URL</h2><p>有些URL可以由Native APP处理。如果用户安装了Twitter的APP并且点击了Twitter的URL,他可能更期望用Twitter APP打开。所以在打开URL之前,检查看看手机里有没有其他APP可以处理这个URL。</p><h2 id="自定义Toolbar颜色"><a href="#自定义Toolbar颜色" class="headerlink" title="自定义Toolbar颜色"></a>自定义Toolbar颜色</h2><p>如果你希望用户觉得网页内容是你的APP的一部分,那么就将Toolbar颜色设为你的primaryColor。如果希望清除的表明网页内容与APP无关,那么选个不同的颜色吧。</p><h2 id="增加一个分享按钮"><a href="#增加一个分享按钮" class="headerlink" title="增加一个分享按钮"></a>增加一个分享按钮</h2><p>用户可能想要把URL分享给好友,但Custom Tabs默认并没有分享按钮,所以最好自己加个吧。</p><h2 id="自定义关闭按钮"><a href="#自定义关闭按钮" class="headerlink" title="自定义关闭按钮"></a>自定义关闭按钮</h2><p>Custom Tabs左上角的关闭按钮默认是一个叉叉。如果你希望用户感觉到网页内容是APP的一部分,那么最好把叉叉换成返回按钮。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">builder.setCloseButtonIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_arrow_back));</span><br></pre></td></tr></table></figure><h2 id="分清内部链接和外部链接"><a href="#分清内部链接和外部链接" class="headerlink" title="分清内部链接和外部链接"></a>分清内部链接和外部链接</h2><p>举个例子,如果用户在Twitter APP里点击了一个<code>http://twitter.com</code>开头的URL,那么应该在APP内部处理。Custom Tabs只应用来处理外部链接。</p><h2 id="处理连击"><a href="#处理连击" class="headerlink" title="处理连击"></a>处理连击</h2><p>如果你想在用户点击URL到打开Custom Tabs之间的这段时间做一点准备工作,确保不要超过100ms,否则用户可能会觉得APP没有反应而再次点击。</p><p>然而你懂的在Android上是无法完全避免卡顿的,所以当用户反复点击同一个URL时,你应该只将URL打开一次。</p><h1 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h1><p>如果你的APP是使用默认浏览器打开URL,那么身为一个合格的开发者,即使你的APP只在国内上架,也应该加入Custom Tabs支持。毕竟国内还是有一些会科学上网的用户使用Chrome的。更重要的是,<strong>我们这些开发者如果看到有国内APP使用了Custom Tabs,会欣慰的点个赞!</strong></p>]]></content>
<summary type="html">
<p>距离Google发布Chrome Custom Tabs已经一年,Twitter、Medium等国外App早已支持了这个功能,但遗憾的是国内App鲜有支持。这篇文章以官方<a href="https://developer.chrome.com/multidevice/an
</summary>
<category term="Android" scheme="http://qq157755587.github.io/categories/Android/"/>
<category term="chrome" scheme="http://qq157755587.github.io/tags/chrome/"/>
<category term="customtabs" scheme="http://qq157755587.github.io/tags/customtabs/"/>
</entry>
<entry>
<title>Kotlin在Android中的应用</title>
<link href="http://qq157755587.github.io/2015/11/14/kotlin-in-android/"/>
<id>http://qq157755587.github.io/2015/11/14/kotlin-in-android/</id>
<published>2015-11-14T05:13:52.000Z</published>
<updated>2015-11-14T07:27:22.000Z</updated>
<content type="html"><![CDATA[<p>最近公司新开发了一个新App,android程序员就我一个人,爱咋写咋写~~于是我尝试用Kotlin完成了这个App。开发过程中越来越感动……太好用了!!!解决了android开发中的好多痛点!</p><h1 id="dp-sp-px间的转换"><a href="#dp-sp-px间的转换" class="headerlink" title="dp, sp, px间的转换"></a>dp, sp, px间的转换</h1><p>传统的java写法通常是写个类似<code>Util.dpToPx(int value)</code>这样的方法。看看Kotlin是怎么通过<a href="https://kotlinlang.org/docs/reference/extensions.html" target="_blank" rel="noopener">Extension</a>来优雅的解决的:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="built_in">Int</span>.<span class="title">dpToPx</span><span class="params">()</span></span>: <span class="built_in">Int</span> {</span><br><span class="line"> <span class="keyword">if</span> (toInt() <span class="keyword">in</span> intArrayOf(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">this</span> * Global.density).toInt() <span class="comment">//这里的Gloabl.density是在应用启动时获取的</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后就可以这样写了:<code>params.topMargin = 16.dpTpPx()</code></p><p>是不是感动到哭😭</p><h1 id="设置View的宽高"><a href="#设置View的宽高" class="headerlink" title="设置View的宽高"></a>设置View的宽高</h1><p>如果用java来写通常也是会有个Util方法<code>ViewHelper.setSize(View view, int width, int height)</code>,我每次在ViewHolder中调用一堆这样的方法都觉得很蛋疼……我们的救世主来了:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> View.<span class="title">setSize</span><span class="params">(width: <span class="type">Int</span>, height: <span class="type">Int</span>)</span></span> {</span><br><span class="line"> <span class="keyword">val</span> params = layoutParams</span><br><span class="line"> params.width = width</span><br><span class="line"> params.height = height</span><br><span class="line"> layoutParams = params</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意这里的<code>layoutParams</code>是Kotlin将<code>setLayoutParams()</code>和<code>getLayoutParams()</code>自动转为了<a href="https://kotlinlang.org/docs/reference/properties.html" target="_blank" rel="noopener">Property</a>,写过C#的应该不会陌生。然后我们就可以愉快的写代码了~<code>yourView.setSize(100, 100)</code></p><h1 id="View的动画"><a href="#View的动画" class="headerlink" title="View的动画"></a>View的动画</h1><p>每次写Animation都是一个头两个大……这回我们不看java了,直接上Kotlin代码:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> View.<span class="title">animateTopMargin</span><span class="params">(valueFromInDP: <span class="type">Int</span>, valueToInDP: <span class="type">Int</span>, duration: <span class="type">Long</span> = <span class="number">300</span>)</span></span> {</span><br><span class="line"> <span class="keyword">val</span> animation = <span class="keyword">object</span>: Animation() {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">applyTransformation</span><span class="params">(interpolatedTime: <span class="type">Float</span>, t: <span class="type">Transformation</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">val</span> params = layoutParams <span class="keyword">as</span> ViewGroup.MarginLayoutParams</span><br><span class="line"> <span class="keyword">val</span> from = valueFromInDP.dpToPx()</span><br><span class="line"> <span class="keyword">val</span> to = valueToInDP.dpToPx()</span><br><span class="line"> params.topMargin = from + ((to - from) * interpolatedTime).toInt()</span><br><span class="line"> layoutParams = params</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> animation.duration = duration</span><br><span class="line"> startAnimation(animation)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>哇,duration还贴心的设了个默认值呢!然后就可以这样写了~<code>yourView.animateTopMargin(16, 32)</code>或者你想把duration设长一点<code>yourView.animateTopMargin(16, 32, 500)</code></p><h1 id="让Fresco更易用"><a href="#让Fresco更易用" class="headerlink" title="让Fresco更易用"></a>让Fresco更易用</h1><p><a href="http://github.com/facebook/fresco" target="_blank" rel="noopener">Fresco</a>是Facebook出品的网络图片加载库,实现了许多在android上非常有用的功能。比如<a href="http://frescolib.org/docs/requesting-multiple-images.html#_" target="_blank" rel="noopener">Multi-URI</a>功能可以优先显示低分辨率的图片,等高分辨率的图片下载完成后再进行替换。这个功能在移动设备上太太太有用了,就是写起来有点蛋疼:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Uri lowResUri, highResUri;</span><br><span class="line">DraweeController controller = Fresco.newDraweeControllerBuilder()</span><br><span class="line"> .setLowResImageRequest(ImageRequest.fromUri(lowResUri))</span><br><span class="line"> .setImageRequest(ImageRequest.fromUri(highResUri))</span><br><span class="line"> .setOldController(mSimpleDraweeView.getController())</span><br><span class="line"> .build();</span><br><span class="line">mSimpleDraweeView.setController(controller);</span><br></pre></td></tr></table></figure><p>于是我又祭出了神器Extension</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> SimpleDraweeView.<span class="title">setMultiUri</span><span class="params">(lowResUri: <span class="type">Uri</span>, highResUri: <span class="type">Uri</span>)</span></span> {</span><br><span class="line"> <span class="keyword">val</span> newController = Fresco.newDraweeControllerBuilder()</span><br><span class="line"> .setLowResImageRequest(ImageRequest.fromUri(lowResUri))</span><br><span class="line"> .setImageRequest(ImageRequest.fromUri(highResUri))</span><br><span class="line"> .setOldController(controller)</span><br><span class="line"> .build();</span><br><span class="line"> controller = newController</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后怎么使用我就不写了,大家都知道了:smiley:</p><h1 id="焕然一新的SharedPreference"><a href="#焕然一新的SharedPreference" class="headerlink" title="焕然一新的SharedPreference"></a>焕然一新的SharedPreference</h1><p>你看到这里可能觉得不过是扩展方法嘛,Obj-C也可以做到啊,没什么嘛。哈哈哈哈图样图森破!有请<a href="https://kotlinlang.org/docs/reference/delegated-properties.html" target="_blank" rel="noopener">Delegated Properties</a>!下面这段代码来源于<a href="http://google.com/+OmarMiatello" target="_blank" rel="noopener">Omar Miatello</a>在今年米兰的GDG DevFest的<a href="https://goo.gl/7Vhy0s" target="_blank" rel="noopener">演讲</a></p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AppPreferences</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> context: Context) {</span><br><span class="line"> <span class="keyword">val</span> preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)</span><br><span class="line"> <span class="keyword">var</span> userName <span class="keyword">by</span> PreferenceDelegates.string(defaultValue = context.getString(R.string.user_name))</span><br><span class="line"> <span class="keyword">var</span> password <span class="keyword">by</span> PreferenceDelegates.string()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">object</span> PreferenceDelegates {</span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="title">string</span><span class="params">(defaultValue: <span class="type">String</span>? = <span class="literal">null</span>)</span></span>: ReadWriteProperty<AppPreferences, String?> {</span><br><span class="line"> <span class="keyword">return</span> PrefString(defaultValue)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">PrefString</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> defaultValue: String?) : ReadWriteProperty<AppPreferences, String?> {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">getValue</span><span class="params">(thisRef: <span class="type">AppPreferences</span>, property: <span class="type">KProperty</span><*>)</span></span>: String? {</span><br><span class="line"> <span class="keyword">return</span> thisRef.preferences.getString(property.name, defaultValue)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">setValue</span><span class="params">(thisRef: <span class="type">AppPreferences</span>, property: <span class="type">KProperty</span><*>, value: <span class="type">String</span>?)</span></span> {</span><br><span class="line"> thisRef.preferences.edit().putString(property.name, value).apply()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Hmm,乍一看有点摸不着头脑呢……简单的讲,AppPreferences这个class里面有两个delegate property,分别是userName和password。他们的get和set方法分别对应了SharedPreference中的getString和setString方法。纳尼?这么神奇???接着往下看</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span>: <span class="type">Application</span></span>() {</span><br><span class="line"> <span class="keyword">val</span> pref <span class="keyword">by</span> lazy { AppPreferences(<span class="keyword">this</span>) }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onCreate()</span><br><span class="line"> <span class="comment">// 其他代码</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在Application中定义了<code>pref</code>,注意它被<code>by lazy</code>修饰了,这样它就只在第一次调用的时候初始化,是不是很方便?</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">BaseActivity</span>: <span class="type">AppCompatActivity</span></span>() {</span><br><span class="line"> <span class="keyword">val</span> app <span class="keyword">by</span> lazy { application <span class="keyword">as</span> App }</span><br><span class="line"> <span class="keyword">val</span> pref <span class="keyword">by</span> lazy { app.pref }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里我们在所有Activity的父类中引入了pref,即AppPreferences的实例。然后……就可以爽了~~</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyActivity</span>: <span class="type">BaseActivity</span></span>() {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">(savedInstanceState: <span class="type">Bundle</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState)</span><br><span class="line"> </span><br><span class="line"> pref.userName = <span class="string">"Who"</span></span><br><span class="line"> pref.password = <span class="string">"You"</span></span><br><span class="line"> Log.d(<span class="string">"userName"</span>, pref.userName)</span><br><span class="line"> Log.d(<span class="string">"password"</span>, pref.password)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看到没有,用起来简直和field一毛一样!再也不用写<code>pref.edit().putString("userName", "Who").apply()</code>了!!!老板再也不用担心我把SharedPreference的key写错了😂</p><h1 id="Lambdas"><a href="#Lambdas" class="headerlink" title="Lambdas"></a>Lambdas</h1><p>这个话题可就大了,一本书都讲不完,举个例子好了。<a href="https://github.com/ReactiveX/RxJava" target="_blank" rel="noopener">RxJava</a>现在很火,<a href="https://github.com/rengwuxian" target="_blank" rel="noopener">光头哥</a>写了一篇很好的<a href="http://gank.io/post/560e15be2dca930e00da1083" target="_blank" rel="noopener">文章</a>介绍了RxJava在android中的应用。抄一段代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">getUser(userId)</span><br><span class="line"> .doOnNext(<span class="keyword">new</span> Action1<User>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">call</span><span class="params">(User user)</span> </span>{</span><br><span class="line"> processUser(user);</span><br><span class="line"> })</span><br><span class="line"> .observeOn(AndroidSchedulers.mainThread())</span><br><span class="line"> .subscribe(<span class="keyword">new</span> Observer<User>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(User user)</span> </span>{</span><br><span class="line"> userView.setUser(user);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCompleted</span><span class="params">()</span> </span>{</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable error)</span> </span>{</span><br><span class="line"> <span class="comment">// Error handling</span></span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> });</span><br></pre></td></tr></table></figure><p>嗯,链式调用,很清晰,但用Kotlin还可以更简洁:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">getUser(userId)</span><br><span class="line"> .doOnNext{ user -> processUser(user) }</span><br><span class="line"> .observeOn(AndroidSchedulers.mainThread())</span><br><span class="line"> .subscribe({</span><br><span class="line"> user -> userView.setUser(user)</span><br><span class="line"> },{</span><br><span class="line"> error -> <span class="comment">// Error handling</span></span><br><span class="line"> })</span><br></pre></td></tr></table></figure><p>Done!收拾东西下班回家~</p><h1 id="风险"><a href="#风险" class="headerlink" title="风险"></a>风险</h1><p>我知道你在担心什么。Kotlin目前还是beta版,用于正式项目要评估风险。事实上在第一个beta版中就出现了在android 4.3设备上无法安装的bug,好在官方很快就修复了。怎么讲呢,这是需要项目组全体成员一起评估的问题。我之所以敢在实际项目中使用,一是因为项目比较简单,万一遇到不可解决的问题可以改用java写,反正Kotlin和java可以无缝混用;二是因为程序员就我一个人,胆子粗啊哈哈哈哈~</p><h1 id="感想"><a href="#感想" class="headerlink" title="感想"></a>感想</h1><p>整个项目做下来让我感到很愉悦,代码的优雅足以说服我冒一点点风险。开发过程中我常常在想,要是有一个包含各种扩展方法的library就好了,我就不用自己造小轮子了。但转念一下,我真的需要一个完整的library吗?那必然会像<a href="https://github.com/google/guava" target="_blank" rel="noopener">Guava</a>一样成为一个巨无霸,但实际用到的功能可能只有10%。在Kotlin的<a href="http://kotlinslackin.herokuapp.com/" target="_blank" rel="noopener">官方Slack</a>里<a href="https://github.com/jakewharton" target="_blank" rel="noopener">Jake Wharton</a>(没错就是那个大神)提了一个有趣的思路:在一个git里放了很多不同类别的扩展方法文件(比如ViewExt.kt,IntExt.kt),需要哪个文件就把它pull下来,而不是compile整个jar包。如果能用gradle实现就太方便了😆</p><h1 id="补充资料"><a href="#补充资料" class="headerlink" title="补充资料"></a>补充资料</h1><p><a href="http://antonioleiva.com/" target="_blank" rel="noopener">Antonio Leiva</a>写了一本书<a href="http://antonioleiva.com/kotlin-android-developers-book/" target="_blank" rel="noopener">Kotlin for Android Developers</a>,价格实惠干货满满,值得购买。配套的源码在<a href="https://github.com/antoniolg/Kotlin-for-Android-Developers" target="_blank" rel="noopener">https://github.com/antoniolg/Kotlin-for-Android-Developers</a></p>]]></content>
<summary type="html">
<p>最近公司新开发了一个新App,android程序员就我一个人,爱咋写咋写~~于是我尝试用Kotlin完成了这个App。开发过程中越来越感动……太好用了!!!解决了android开发中的好多痛点!</p>
<h1 id="dp-sp-px间的转换"><a href="#dp-
</summary>
<category term="Kotlin" scheme="http://qq157755587.github.io/categories/Kotlin/"/>
</entry>
<entry>
<title>快速自定义tableViewCell</title>
<link href="http://qq157755587.github.io/2015/08/19/custom-table-view-cell/"/>
<id>http://qq157755587.github.io/2015/08/19/custom-table-view-cell/</id>
<published>2015-08-19T12:47:01.000Z</published>
<updated>2015-08-19T13:11:43.000Z</updated>
<content type="html"><![CDATA[<p>最近因为工作需要开始学习iOS。在网上搜自动定义tableViewCell,出来的结果都是要新建xib文件和对应的class,然后在上面添加控件,再注册到tableViewController中。<br>这样做一来很繁琐,二来很容易在绑定outlet的时候抛异常(我就遇到了)。同事告诉我一个快捷的办法。</p><p>1、直接在StoryBoard里面将控件添加到Prototype Cell中。</p><p><img src="tableViewCell.png" alt="Image of tableViewCell"></p><p>2、新增一个Class(比如MyTableViewCell)继承UITableViewCell。</p><p>3、将Prototype Cell的Class设为MyTableViewCell。</p><p>4、按住<em>control</em>将控件拖到MyTableViewCell中,添加outlet。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">@property (nonatomic, strong) IBOutlet UILable *label;</span><br><span class="line">@property (nonatomic, strong) IBOutlet UIImageView *image;</span><br></pre></td></tr></table></figure></p><p>5、直接调用<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_IDENTITY forIndexPath:indexPath];</span><br></pre></td></tr></table></figure></p><p>搞定~</p>]]></content>
<summary type="html">
<p>最近因为工作需要开始学习iOS。在网上搜自动定义tableViewCell,出来的结果都是要新建xib文件和对应的class,然后在上面添加控件,再注册到tableViewController中。<br>这样做一来很繁琐,二来很容易在绑定outlet的时候抛异常(我就遇到了
</summary>
<category term="iOS" scheme="http://qq157755587.github.io/categories/iOS/"/>
<category term="tableViewCell" scheme="http://qq157755587.github.io/tags/tableViewCell/"/>
</entry>
<entry>
<title>Kotlin学习笔记(三)控制流程</title>
<link href="http://qq157755587.github.io/2015/08/08/learning-kotlin-3/"/>
<id>http://qq157755587.github.io/2015/08/08/learning-kotlin-3/</id>
<published>2015-08-08T06:40:18.000Z</published>
<updated>2015-08-08T08:06:59.000Z</updated>
<content type="html"><![CDATA[<h1 id="If-Expression"><a href="#If-Expression" class="headerlink" title="If Expression"></a>If Expression</h1><p>在Kotlin中,<strong>if</strong>是一个表达式,它有返回类型。因此Kotlin中不再需要三元表达式。</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> max = <span class="keyword">if</span> (a > b) a <span class="keyword">else</span> b</span><br></pre></td></tr></table></figure><p>if分支也可以是代码块(blocks),代码块的末尾是返回值。</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> max = <span class="keyword">if</span> (a > b) {</span><br><span class="line"> print(<span class="string">"Choose a"</span>)</span><br><span class="line"> a <span class="comment">// a会被赋值给max</span></span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> print(<span class="string">"Choose b"</span>)</span><br><span class="line"> b <span class="comment">// b会被赋值给max</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="When-Expression"><a href="#When-Expression" class="headerlink" title="When Expression"></a>When Expression</h1><p><strong>when</strong>替代了switch语句,简单的例子像这样:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">when</span> (x) {</span><br><span class="line"> <span class="number">1</span> -> print(<span class="string">"x == 1"</span>)</span><br><span class="line"> <span class="number">2</span> -> print(<span class="string">"x == 2"</span>)</span><br><span class="line"> <span class="keyword">else</span> -> {</span><br><span class="line"> print(<span class="string">"x is neither 1 or 2"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果多个分支使用同样的处理逻辑,那么可以用逗号将它们结合起来:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">when</span> (x) {</span><br><span class="line"> <span class="number">1</span>, <span class="number">2</span> -> print(<span class="string">"x == 1 or x == 2"</span>)</span><br><span class="line"> <span class="keyword">else</span> -> print(<span class="string">"otherwise"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>分支中除了使用常量(上例中的1、2),也可以使用函数(arbitrary expressions):</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">when</span> (x) {</span><br><span class="line"> parseInt(s) -> print(<span class="string">"s encodes x"</span>)</span><br><span class="line"> esle -> print(<span class="string">"s does not encode x"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>也可以用<strong>in</strong>或<strong>!in</strong>判断一个值是否在一个范围(range)或集合(collection)中:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">when</span> (x) {</span><br><span class="line"> <span class="keyword">in</span> <span class="number">1</span>...<span class="number">10</span> -> print(<span class="string">"x is in the range"</span>)</span><br><span class="line"> <span class="keyword">in</span> validNumbers -> print(<span class="string">"x is valid"</span>)</span><br><span class="line"> !<span class="keyword">in</span> <span class="number">10</span>...<span class="number">20</span> -> print(<span class="string">"x is outside the range"</span>)</span><br><span class="line"> <span class="keyword">else</span> -> print(<span class="string">"none of the above"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>还可以用<strong>is</strong>或<strong>!is</strong>来检查一个值是否是某个类型:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> hasPrefix = <span class="keyword">when</span>(x) {</span><br><span class="line"> <span class="keyword">is</span> String -> x.startsWith(<span class="string">"prefix"</span>) <span class="comment">// 注意这里s已经被自动转为String类型了</span></span><br><span class="line"> <span class="keyword">else</span> -> <span class="literal">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>when还可以用来取代<strong>if-else if</strong>。如果没有传参数,那么分支条件就是简单的boolean表达式,当条件为true时分支就会被执行:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">when</span> {</span><br><span class="line"> x.isOdd() -> print(<span class="string">"x is odd"</span>)</span><br><span class="line"> x.isEven() -> print(<span class="string">"x is even"</span>)</span><br><span class="line"> <span class="keyword">else</span> -> print(<span class="string">"x is funny"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="For-Loops"><a href="#For-Loops" class="headerlink" title="For Loops"></a>For Loops</h1><p>感觉没什么特别的,跟Java一样:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (item <span class="keyword">in</span> collection)</span><br><span class="line"> print(item)</span><br><span class="line"> </span><br><span class="line"><span class="keyword">for</span> (i <span class="keyword">in</span> array.indices)</span><br><span class="line"> print(array[i])</span><br></pre></td></tr></table></figure><h1 id="While-Loops"><a href="#While-Loops" class="headerlink" title="While Loops"></a>While Loops</h1><p>也没什么特别的:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (x > <span class="number">0</span>) {</span><br><span class="line"> x--</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">val</span> y = retrieveData()</span><br><span class="line">} <span class="keyword">while</span> (y != <span class="literal">null</span>) <span class="comment">// 注意虽然y是在大括号内部定义的,但这里仍然可以访问到</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="If-Expression"><a href="#If-Expression" class="headerlink" title="If Expression"></a>If Expression</h1><p>在Kotlin中,<strong>if</stron
</summary>
<category term="Kotlin" scheme="http://qq157755587.github.io/categories/Kotlin/"/>
<category term="if" scheme="http://qq157755587.github.io/tags/if/"/>
<category term="when" scheme="http://qq157755587.github.io/tags/when/"/>
<category term="for" scheme="http://qq157755587.github.io/tags/for/"/>
<category term="while" scheme="http://qq157755587.github.io/tags/while/"/>
</entry>
<entry>
<title>Kotlin学习笔记(二)基本类型</title>
<link href="http://qq157755587.github.io/2015/07/28/learning-kotlin-2/"/>
<id>http://qq157755587.github.io/2015/07/28/learning-kotlin-2/</id>
<published>2015-07-28T10:31:26.000Z</published>
<updated>2015-07-30T09:51:37.000Z</updated>
<content type="html"><![CDATA[<p>如果你学过Ruby,你一定知道“万物皆对象”。Kotlin中也一样。即便数字(Numbers)在Kotlin中是基本类型,也可以这样写:<code>123.toString()</code></p><p>下面我们来看一下Kotlin中的基本类型。</p><h1 id="Numbers"><a href="#Numbers" class="headerlink" title="Numbers"></a>Numbers</h1><p>Kotlin提供的数字类型和Java很像:</p><table><thead><tr><th style="text-align:center"><strong>Type</strong></th><th style="text-align:center"><strong>Bitwidth</strong></th></tr></thead><tbody><tr><td style="text-align:center">Double</td><td style="text-align:center">64</td></tr><tr><td style="text-align:center">Float</td><td style="text-align:center">32</td></tr><tr><td style="text-align:center">Long</td><td style="text-align:center">64</td></tr><tr><td style="text-align:center">Int</td><td style="text-align:center">32</td></tr><tr><td style="text-align:center">Short</td><td style="text-align:center">16</td></tr><tr><td style="text-align:center">Byte</td><td style="text-align:center">8</td></tr></tbody></table><p>值得注意的是,定义Long形要用大写L:<strong>123L</strong></p><p>而16进制和二进制则和Java一样:<strong>0x0F</strong> <strong>0b0101</strong></p><p>8进制则不支持。</p><p>浮点型和Java一样,类似<strong>123.45</strong>默认为Double类型,想要Float类型则应写成<strong>123.45f</strong></p><p>在Java平台中,Number是基本类型。如果需要一个可空的数字(比如Int?)或者泛型,数字就会被装箱(boxed):</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> a: <span class="built_in">Int</span> = <span class="number">10000</span></span><br><span class="line">print(a == a) <span class="comment">// Prints 'true'</span></span><br><span class="line">print(a === a) <span class="comment">// Prints 'true'</span></span><br><span class="line"><span class="keyword">val</span> boxedA: <span class="built_in">Int</span>? = a</span><br><span class="line"><span class="keyword">val</span> anotherBoxedA: <span class="built_in">Int</span>? = a</span><br><span class="line">print(boxedA == anotherBoxedA) <span class="comment">// Prints 'true'</span></span><br><span class="line">print(boxedA === anotherBoxedA) <span class="comment">// !!!Prints 'false'!!!</span></span><br></pre></td></tr></table></figure><p><code>==</code>相当于调用<code>equals</code>方法。而<code>===</code>相当于Java中的<code>==</code>,用来比较引用对象。</p><p>注意在官方的<a href="http://kotlinlang.org/docs/reference/basic-types.html#representation" target="_blank" rel="noopener">Reference</a>中用的是即将被deprecated的<code>identityEquals</code>,会得到错误的结果,详情参看 <a href="http://stackoverflow.com/questions/31666155/kotlins-identityequals-not-working-as-expected" target="_blank" rel="noopener">http://stackoverflow.com/questions/31666155/kotlins-identityequals-not-working-as-expected</a></p><p>Kotlin中,数字类型<strong>不能</strong>像Java一样隐式(implicitly)转换类型。</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> a:<span class="built_in">Int</span>? = <span class="number">1</span></span><br><span class="line"><span class="keyword">val</span> b:<span class="built_in">Long</span>? = a <span class="comment">// 这里会编译报错,Int不能转换成Long</span></span><br><span class="line">print(a == b) <span class="comment">// 这里会编译报错,Int和Long不能进行比较</span></span><br></pre></td></tr></table></figure><p>而所有的数字类型都有如下显示方法:</p><ul><li>toByte()</li><li>toShort()</li><li>toInt()</li><li>toLong()</li><li>toFloat()</li><li>toDouble()</li><li>toChar()</li></ul><p>写惯Java的人可能觉得这样会很不方便,但事情也没有那么糟糕。因为运算符可以帮我们隐式的转换数字类型:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> l = <span class="number">1</span>.toLong() + <span class="number">3</span> <span class="comment">// Long + Int => Long</span></span><br></pre></td></tr></table></figure><p>仅Int和Long可以进行位运算。运算符与Java不同:</p><ul><li>shl(bits) 有符号的左位移,在Java中是<code><<</code></li><li>shr(bits) 有符号的右位移,在Java中是<code>>></code></li><li>ushr(bits) 无符号的右位移,在Java中是<code>>>></code></li><li>and(bits) 与,在Java中是<code>&</code></li><li>or(bits) 或,在Java中是<code>|</code></li><li>xor(bits) 异或,在Java中是<code>^</code></li><li>inv() 非,在Java中是<code>~</code></li></ul><h1 id="Characters"><a href="#Characters" class="headerlink" title="Characters"></a>Characters</h1><p>Int不能直接转为Char,下面的代码会编译报错:</p><p><code>val a:Char = 1</code></p><p>但是Char可以通过<code>toInt()</code>方法转为Int。</p><p>同数字一样,如果需要一个可空类型(Char?),那么这个Char会被自动装箱(boxed)。</p><h1 id="Booleans"><a href="#Booleans" class="headerlink" title="Booleans"></a>Booleans</h1><p>没什么好说的,也是自动装箱的问题。</p><h1 id="Arrays"><a href="#Arrays" class="headerlink" title="Arrays"></a>Arrays</h1><p>Kotlin中的数组写法与Java区别很大,有专门的<code>Array</code>类来创建和操作数组。</p><p>如果我想创建一个[1, 2, 3]数组,可以这样写:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> a:Array<<span class="built_in">Int</span>> = arrayOf(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>) <span class="comment">// 注意这里的数字装箱了</span></span><br><span class="line">a[<span class="number">0</span>] = a[<span class="number">1</span>] + a[<span class="number">2</span>] <span class="comment">// 这里的操作与Java一样</span></span><br></pre></td></tr></table></figure><p>还可以像这样写:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Creates an Array<String> with values ["0", "1", "4", "9", "16"]</span></span><br><span class="line"><span class="keyword">val</span> asc = Array(<span class="number">5</span>, {i -> (i * i).toString()})</span><br></pre></td></tr></table></figure><p>如果想要一个空数组,可以使用<code>arrayOfNulls(size: Int)</code>方法。</p><p>如果不想装箱,Kotlin也提供一些原子类型(primitive type)的数组,如<code>IntArray</code>,<code>ShortArray</code>等。这些类型没有继承<code>Array</code>类型,但是拥有相同的方法。每个类型都有对应的工厂方法:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> x: IntArray = intArrayOf(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)</span><br><span class="line">x[<span class="number">0</span>] = x[<span class="number">1</span>] + x[<span class="number">2</span>]</span><br></pre></td></tr></table></figure><h1 id="Strings"><a href="#Strings" class="headerlink" title="Strings"></a>Strings</h1><p><code>s.[0]</code>相当于Java中的<code>s.charAt(0)</code></p><p>还可以使用iterator:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (c <span class="keyword">in</span> str) {</span><br><span class="line"> println(c)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Kotlin包含两种类型的字符串:与Java字符串相同的escaped strings和新的raw strings。escaped strings中的换行符是<code>\n</code>,而raw strings就厉害了。看这个例子:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> text = <span class="string">"""</span></span><br><span class="line"><span class="string"> for (c in "foo")</span></span><br><span class="line"><span class="string"> print(c)</span></span><br><span class="line"><span class="string">"""</span></span><br></pre></td></tr></table></figure><p>注意第二行和第三行都是字符串,它们被包含在三个引号中。</p><p>Kotlin中再也不用写繁琐的<code>String.format</code>了,只需要一个<code>$</code>:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> i = <span class="number">10</span></span><br><span class="line"><span class="keyword">val</span> s = <span class="string">"i = <span class="variable">$i</span>"</span> <span class="comment">// evaluates to "i = 10"</span></span><br></pre></td></tr></table></figure><p>甚至可以像这样写表达式:</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> s = <span class="string">"abc"</span></span><br><span class="line"><span class="keyword">val</span> str = <span class="string">"<span class="variable">$s</span>.length is <span class="subst">${s.length}</span>"</span> <span class="comment">// evaluates to "abc.length is 3"</span></span><br></pre></td></tr></table></figure><p><em>这篇是用<a href="http://typora.io/" target="_blank" rel="noopener">Typora</a>写的,确实很好用,而且是国人出品!</em></p>]]></content>
<summary type="html">
<p>如果你学过Ruby,你一定知道“万物皆对象”。Kotlin中也一样。即便数字(Numbers)在Kotlin中是基本类型,也可以这样写:<code>123.toString()</code></p>
<p>下面我们来看一下Kotlin中的基本类型。</p>
<h1 id="
</summary>
<category term="Kotlin" scheme="http://qq157755587.github.io/categories/Kotlin/"/>
<category term="number" scheme="http://qq157755587.github.io/tags/number/"/>
<category term="character" scheme="http://qq157755587.github.io/tags/character/"/>
<category term="boolean" scheme="http://qq157755587.github.io/tags/boolean/"/>
<category term="string" scheme="http://qq157755587.github.io/tags/string/"/>
<category term="array" scheme="http://qq157755587.github.io/tags/array/"/>
</entry>
<entry>
<title>Kotlin学习笔记(一)基本语法</title>
<link href="http://qq157755587.github.io/2015/07/24/learning-kotlin-1/"/>
<id>http://qq157755587.github.io/2015/07/24/learning-kotlin-1/</id>
<published>2015-07-24T10:37:27.000Z</published>
<updated>2015-07-25T12:01:07.000Z</updated>
<content type="html"><![CDATA[<p>先提个问题</p><h1 id="Android程序员为什么要学Kotlin?"><a href="#Android程序员为什么要学Kotlin?" class="headerlink" title="Android程序员为什么要学Kotlin?"></a>Android程序员为什么要学Kotlin?</h1><ol><li>Java不争气。在1.5时代Java或许还能和C#谈笑风生,而现在C#不知道比Java高明到哪里去了。虽然Java8中也加入了闭包等有用的特性,但Android又不支持……</li><li><a href="https://github.com/ReactiveX/RxJava" target="_blank" rel="noopener">RxJava</a>。想象这样一个需求:用户连续点击某个区域10次且每次间隔不超过500毫秒,则触发一个彩蛋。<br>实现这个需求很容易,但代码会很混乱。我们得自己控制计时器,保存时间戳,可能还会有令人讨厌的空接口。如果用RxJava来做,几行代码就能搞定。如果再配合Kotlin的函数式编程,简直不能更爽~<br>关于RxJava的使用,网上已经有很多了。我有空了可能也会写一些。</li><li>如果你尝试了<a href="https://developer.android.com/tools/data-binding/guide.html" target="_blank" rel="noopener">Android Databinding</a>,有没有发现External Libraries里面多了kotlin-runtime和kotlin-stdlib?<br>Google内部正是用Kotlin开发的Databinding。虽然短期内Kotlin还不太可能成为Android官方开发语言,但至少这是个好的迹象。</li></ol><h1 id="为什么是Kotlin?"><a href="#为什么是Kotlin?" class="headerlink" title="为什么是Kotlin?"></a>为什么是Kotlin?</h1><p>现在替代Java的选择有很多,Scala、Groovy都能写Android,甚至微软最新发布的Visual Studio都能进来插一脚,为什么要选<a href="http://kotlinlang.org/" target="_blank" rel="noopener">Kotlin</a>呢?<br>这又是另一个很大的话题了。请参看大神Jake Wharton的这篇<a href="https://docs.google.com/document/d/1ReS3ep-hjxWA8kZi0YqDbEhCqTt29hG8P44aA9W0DM8/edit" target="_blank" rel="noopener">文章</a>(要翻墙)。</p><p>那么进入正题。我的学习步骤基本是按照官方的<a href="http://kotlinlang.org/docs/reference/" target="_blank" rel="noopener">Reference</a>来,所以可能写着写着就成了翻译官方文档了……哈哈哈哈</p><h1 id="Function"><a href="#Function" class="headerlink" title="Function"></a>Function</h1><p>最开始学C的时候,我们会说为了实现XXX写个函数(Function),学Java就变成了写个方法(Method),现在Kotlin又变成函数了……<br>所以我后面提到Java的时候会说Method,提到Kotlin的时候会说Function,其实都是一个意思~</p><p>先看下Java中的写法<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sum</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> a + b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>然后是Kotlin<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">sum</span><span class="params">(a: <span class="type">Int</span>, b: <span class="type">Int</span>)</span></span>: <span class="built_in">Int</span> {</span><br><span class="line"> <span class="keyword">return</span> a + b</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>区别:</p><ol><li>用fun开头</li><li>数据类型写在后面</li><li>结尾不用写分号</li></ol><p>这语法让我想起了Action Script,写惯了Java再来写Kotlin总觉得有些别扭……</p><p>如果想简洁一点可以这样写在一行里,并且返回值类型可以是隐式(inferred)的。不过我个人不太喜欢这样写。</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">sum</span><span class="params">(a: <span class="type">Int</span>, b: <span class="type">Int</span>)</span></span> = a + b</span><br></pre></td></tr></table></figure><p>如果是public function就必须显式的标明返回类型</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="title">sum</span><span class="params">(a: <span class="type">Int</span>, b: <span class="type">Int</span>)</span></span>: <span class="built_in">Int</span> = a + b</span><br></pre></td></tr></table></figure><p>没有返回值则用<strong>Unit</strong>关键字</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">printSum</span><span class="params">(a: <span class="type">Int</span>, b: <span class="type">Int</span>)</span></span>: <span class="built_in">Unit</span> {</span><br><span class="line"> print(a + b)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>即使函数是public的,Unit也可以省略掉<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="title">printSum</span><span class="params">(a: <span class="type">Int</span>, b: <span class="type">Int</span>)</span></span> {</span><br><span class="line"> print(a + b)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h1 id="local-variables"><a href="#local-variables" class="headerlink" title="local variables"></a>local variables</h1><p>只读变量(read-only)用<strong>val</strong>定义<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> a: <span class="built_in">Int</span> = <span class="number">1</span></span><br><span class="line"><span class="keyword">val</span> b = <span class="number">1</span> <span class="comment">// 如果赋了初始值值,可以省略数据类型</span></span><br><span class="line"><span class="keyword">val</span> c: <span class="built_in">Int</span> <span class="comment">// 否则需要指定类型</span></span><br><span class="line">c = <span class="number">1</span></span><br></pre></td></tr></table></figure></p><p>可变变量(mutable)用<strong>var</strong>定义<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x = <span class="number">5</span></span><br><span class="line">x += <span class="number">1</span></span><br></pre></td></tr></table></figure></p><h1 id="string-templates"><a href="#string-templates" class="headerlink" title="string templates"></a>string templates</h1><p>用<strong>${}</strong>包住变量就行了,这比Java中的String.valueof方便多了<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> a = <span class="number">1</span></span><br><span class="line">print(<span class="string">"Value of a: <span class="subst">${a}</span>"</span>}</span><br></pre></td></tr></table></figure></p><h1 id="conditional-expressions"><a href="#conditional-expressions" class="headerlink" title="conditional expressions"></a>conditional expressions</h1><p>看起来有点像三元表达式,但可读性更强<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">max</span><span class="params">(a: <span class="type">Int</span>, b: <span class="type">Int</span>)</span></span> = <span class="keyword">if</span> (a > b) a <span class="keyword">else</span> b</span><br></pre></td></tr></table></figure></p><h1 id="nullable-values"><a href="#nullable-values" class="headerlink" title="nullable values"></a>nullable values</h1><p>写java的同学一定受够了NullPointerException吧。Kotlin与Java很大不同的一点就是显示的标明了value是否可能为空。<br>如果可能为空,则用<strong>?</strong>注明<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x: <span class="built_in">Int</span>?</span><br></pre></td></tr></table></figure></p><p>先知道有这么回事就行了,后面的章节会详细说明。</p><h1 id="type-checks-amp-automatic-casts"><a href="#type-checks-amp-automatic-casts" class="headerlink" title="type checks & automatic casts"></a>type checks & automatic casts</h1><p>和C#一样,用<strong>is</strong>操作符来判断变量的类型<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">getStringLength</span><span class="params">(obj: <span class="type">Any</span>)</span></span>: <span class="built_in">Int</span>? {</span><br><span class="line"> <span class="keyword">if</span> (obj <span class="keyword">is</span> String) {</span><br><span class="line"> <span class="keyword">return</span> obj.length <span class="comment">// 注意这里obj已经自动转成String类型了,所以可以调用length方法</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span> <span class="comment">// 这里obj依然是Any类型</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h1 id="loop"><a href="#loop" class="headerlink" title="loop"></a>loop</h1><p>和Java相比是把冒号改成了<strong>in</strong><br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">(args: <span class="type">Array</span><<span class="type">String</span>>)</span></span> {</span><br><span class="line"> <span class="keyword">for</span> (arg <span class="keyword">in</span> args)</span><br><span class="line"> print(arg)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h1 id="while-loop"><a href="#while-loop" class="headerlink" title="while loop"></a>while loop</h1><p>和Java基本一样<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">(args: <span class="type">Array</span><<span class="type">String</span>>)</span></span> { </span><br><span class="line"> <span class="keyword">var</span> i = <span class="number">0</span></span><br><span class="line"> <span class="keyword">while</span> (i < args.size())</span><br><span class="line"> print(args[i++]) </span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h1 id="when-expression"><a href="#when-expression" class="headerlink" title="when expression"></a>when expression</h1><p><strong>when</strong>可以类比Java中的<strong>switch case</strong>,但是更加强大,可以接受各种操作符<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">cases</span><span class="params">(obj: <span class="type">Any</span>)</span></span> { </span><br><span class="line"> <span class="keyword">when</span> (obj) {</span><br><span class="line"> <span class="number">1</span> -> print(<span class="string">"One"</span>)</span><br><span class="line"> <span class="string">"Hello"</span> -> print(<span class="string">"Greeting"</span>)</span><br><span class="line"> <span class="keyword">is</span> <span class="built_in">Long</span> -> print(<span class="string">"Long"</span>)</span><br><span class="line"> !<span class="keyword">is</span> String -> print(<span class="string">"Not a string"</span>)</span><br><span class="line"> <span class="keyword">else</span> -> print(<span class="string">"Unknow"</span>)</span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h1 id="ranges"><a href="#ranges" class="headerlink" title="ranges"></a>ranges</h1><p><strong>in</strong>操作符太好用了,我就不贴对应的Java代码了,因为想想就觉得麻烦……<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (x <span class="keyword">in</span> <span class="number">1</span>..y-<span class="number">1</span>) </span><br><span class="line"> print(<span class="string">"OK"</span>)</span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> (x !<span class="keyword">in</span> <span class="number">0</span>..array.lastIndex) </span><br><span class="line"> print(<span class="string">"Out"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (x <span class="keyword">in</span> <span class="number">1</span>..<span class="number">5</span>) </span><br><span class="line"> print(x)</span><br></pre></td></tr></table></figure></p><h1 id="collections"><a href="#collections" class="headerlink" title="collections"></a>collections</h1><p>collections配合in使用很方便<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (text <span class="keyword">in</span> names) <span class="comment">// 这里调用了names.contains(text) </span></span><br><span class="line"> print(<span class="string">"Yes"</span>)</span><br></pre></td></tr></table></figure></p><p>如果配合函数式编程和Lambdas表达式就不能更爽……<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">names filter { it.startsWith(<span class="string">"A"</span>) } </span><br><span class="line"> sortBy { it } </span><br><span class="line"> map { it.toUpperCase() } </span><br><span class="line"> forEach { print(it) }</span><br></pre></td></tr></table></figure></p><p>注意这里函数前面没有小圆点(<strong>.</strong>),这个大概是特有的简写,后面再慢慢学吧~</p>]]></content>
<summary type="html">
<p>先提个问题</p>
<h1 id="Android程序员为什么要学Kotlin?"><a href="#Android程序员为什么要学Kotlin?" class="headerlink" title="Android程序员为什么要学Kotlin?"></a>Android
</summary>
<category term="Kotlin" scheme="http://qq157755587.github.io/categories/Kotlin/"/>
<category term="syntax" scheme="http://qq157755587.github.io/tags/syntax/"/>
</entry>
<entry>
<title>在Android中将文本转为曲线</title>
<link href="http://qq157755587.github.io/2015/07/18/android-convert-text-to-curves/"/>
<id>http://qq157755587.github.io/2015/07/18/android-convert-text-to-curves/</id>
<published>2015-07-18T08:09:27.000Z</published>
<updated>2015-07-20T10:24:14.000Z</updated>
<content type="html"><![CDATA[<p>最近在Android平台上生成PDF文件时,碰到了内嵌字体的问题。大部分打印厂的机器是不支持PDF内嵌字体的,<br>所以要想将文字按指定的字体印刷出来,只能<a href="http://baike.baidu.com/subview/8597409/8551998.htm" target="_blank" rel="noopener">转曲</a>。</p><h1 id="转曲"><a href="#转曲" class="headerlink" title="转曲"></a>转曲</h1><p>转曲的意思就是把文字转成曲线线段,可以想象成把文字的轮廓勾勒出来。<br>Android中的<a href="http://developer.android.com/reference/android/graphics/Paint.html#getTextPath(java.lang.String, int, int, float, float, android.graphics.Path" target="_blank" rel="noopener">Paint.getTextPath</a><br>方法可以获得String的Path,而<a href="http://developer.android.com/reference/android/graphics/PathMeasure.html" target="_blank" rel="noopener">PathMeasure</a>可以获得Path中的关键点。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Paint paint = <span class="keyword">new</span> Paint(Paint.ANTI_ALIAS_FLAG);</span><br><span class="line">paint.setTextSize(yourTextSize);</span><br><span class="line">Path fontPath = <span class="keyword">new</span> Path();</span><br><span class="line">paint.getTextPath(yourText, <span class="number">0</span>, yourText.length(), <span class="number">0f</span>, paint.getFontSpacing(), fontPath);</span><br><span class="line">PathMeasure pathMeasure = <span class="keyword">new</span> PathMeasure(fontPath, <span class="keyword">false</span>);</span><br><span class="line"><span class="keyword">float</span>[] pos = <span class="keyword">new</span> <span class="keyword">float</span>[<span class="number">2</span>]; <span class="comment">// pos用来存放每一个关键点</span></span><br><span class="line"><span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">float</span> distance = <span class="number">0f</span>;</span><br><span class="line"> <span class="keyword">while</span> (distance < pathMeasure.getLength()) {</span><br><span class="line"> pathMeasure.getPosTan(distance, pos, <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 在这里将pos设置到你自己的曲线里</span></span><br><span class="line"> <span class="comment">// 我使用PDFjet生成PDF,那么这里就将pos保存到com.pdfjet.Point中</span></span><br><span class="line"> distance += <span class="number">0.1f</span>; <span class="comment">// 0.1f是每个关键点的间隔。间隔越大轮廓越粗糙,反之越细腻。如果设的过小会影响性能。</span></span><br><span class="line"> }</span><br><span class="line">} <span class="keyword">while</span> (pathMeasure.nextContour());<span class="comment">// nextContour会跳到下一段曲线</span></span><br></pre></td></tr></table></figure><h1 id="计算曲线嵌套层数"><a href="#计算曲线嵌套层数" class="headerlink" title="计算曲线嵌套层数"></a>计算曲线嵌套层数</h1><p>得到文字曲线后还不能直接印刷,因为我们只是得到了文字的轮廓,还得往轮廓里填色。<br>当然不能所有轮廓都填黑色,这样文字会变成一坨一坨的黑色……<br>我们需要计算每条曲线被多少条曲线全包围(注意我们现在得到的曲线全都是头尾相连的)。<br>如果被偶数条曲线(包含0条)包围,则填黑色;如果被奇数条曲线包围,则填白色(底色)。<br>Android或Java本身不提供这样的方法,还好已经有人帮我们研究出方法了,详细参看<a href="http://alienryderflex.com/polygon/。" target="_blank" rel="noopener">http://alienryderflex.com/polygon/。</a></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Polygon</span> </span>{</span><br><span class="line"> <span class="comment">// Polygon coodinates.</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">float</span>[] polyY, polyX;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Number of sides in the polygon.</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> polySides;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Default constructor.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> px Polygon y coods.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> py Polygon x coods.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> ps Polygon sides count.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Polygon</span><span class="params">( <span class="keyword">float</span>[] px, <span class="keyword">float</span>[] py, <span class="keyword">int</span> ps )</span> </span>{</span><br><span class="line"> polyX = px;</span><br><span class="line"> polyY = py;</span><br><span class="line"> polySides = ps;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Checks if the Polygon contains a point.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> "http://alienryderflex.com/polygon/"</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> x Point horizontal pos.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> y Point vertical pos.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> Point is in Poly flag.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">contains</span><span class="params">( <span class="keyword">float</span> x, <span class="keyword">float</span> y )</span> </span>{</span><br><span class="line"> <span class="keyword">boolean</span> oddTransitions = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">for</span>( <span class="keyword">int</span> i = <span class="number">0</span>, j = polySides -<span class="number">1</span>; i < polySides; j = i++ ) {</span><br><span class="line"> <span class="keyword">if</span>((Float.compare(polyY[i], y) < <span class="number">0</span> && Float.compare(polyY[j], y) >= <span class="number">0</span> ) || ( Float.compare(polyY[j], y) < <span class="number">0</span> && Float.compare(polyY[i], y) >= <span class="number">0</span>)){</span><br><span class="line"> <span class="keyword">if</span>(Float.compare(polyX[i] + ( y - polyY[i] ) / ( polyY[j] - polyY[i] ) * ( polyX[j] - polyX[i] ), x) < <span class="number">0</span> ) {</span><br><span class="line"> oddTransitions = !oddTransitions;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> oddTransitions;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Polygon的构造方法前两个参数填多边形(可以将曲线理解为多边形)横坐标和纵坐标的数组,第三个参数填多边形的边数,即坐标的个数。<br>contains方法即可检查某个点是否在这个多边形中。<br>那么思路就很简单了:如果曲线A的任一关键点不在曲线B中,B就没有包围A。<br>代码就不贴了。</p><h1 id="填色"><a href="#填色" class="headerlink" title="填色"></a>填色</h1><p>如上所述,如果被偶数条曲线包围,填黑色;被奇数条曲线包围,填白色。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> nestingNum = surroundedNumber(pathPointsList, pathPoints);</span><br><span class="line"><span class="keyword">if</span> (nestingNum % <span class="number">2</span> == <span class="number">0</span>) {</span><br><span class="line"> paint.setColor(Color.BLACK);</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> paint.setColor(Color.WHITE);</span><br><span class="line">}</span><br><span class="line">canvas.drawPath(path, paint);</span><br></pre></td></tr></table></figure><p>大功告成~</p><p>详细代码请参考<a href="https://gist.github.com/qq157755587/32e927e77e259cd84631" target="_blank" rel="noopener">https://gist.github.com/qq157755587/32e927e77e259cd84631</a></p>]]></content>
<summary type="html">
<p>最近在Android平台上生成PDF文件时,碰到了内嵌字体的问题。大部分打印厂的机器是不支持PDF内嵌字体的,<br>所以要想将文字按指定的字体印刷出来,只能<a href="http://baike.baidu.com/subview/8597409/8551998.ht
</summary>
<category term="Android" scheme="http://qq157755587.github.io/categories/Android/"/>
<category term="PDF" scheme="http://qq157755587.github.io/tags/PDF/"/>
<category term="Paint" scheme="http://qq157755587.github.io/tags/Paint/"/>
<category term="Path" scheme="http://qq157755587.github.io/tags/Path/"/>
</entry>
</feed>