Android
Kotlin
QiitaAPI
AndroidStudio
ハンズオン

Kotlinで書くAndroidアプリハンズオン

使用するAPI

今回はQiitaAPiを題材とします。(ドキュメント)

登録不要で使えて、例えば、 https://qiita.com//api/v2/items で、投稿を取得できます。

[{
    rendered_body: " <h1> <span id="
    プロローグ " class="
    fragment "></span><a href="# % E3 % 83 % 97 % E3 % 83 % AD % E3 % 83 % AD % E3 % 83 % BC % E3 % 82 % B0 "><i class="
    fa fa - link "></i></a>プロローグ</h1> <p><a href="
    https: //camo.qiitausercontent.com/6c659e011f46abdb3a08bf3bac0a5d82418cbac3/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f31356433383165642d343436312d633235392d633831312d3062373163393133646364652e706e67" target="_blank" rel="nofollow noopener"><img src="https://camo.qiitausercontent.com/6c659e011f46abdb3a08bf3bac0a5d82418cbac3/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f31356433383165642d343436312d633235392d633831312d3062373163393133646364652e706e67" alt="20171109214610.png" data-canonical-src="https://qiita-image-store.s3.amazonaws.com/0/76843/15d381ed-4461-c259-c811-0b71c913dcde.png"></a></p> <p>参照: <a href="http://r-dimension.xsrv.jp/classes_j/sine_cosine/" class="autolink" rel="nofollow noopener" target="_blank">http://r-dimension.xsrv.jp/classes_j/sine_cosine/</a></p> <p><strong>うげ〜。いやだこの図。。</strong>はい、こんにちは。。。<br> こちらの図見るとオエ〜ってなりますよね。僕はなります。<br> なのでお口直しに、小松菜奈さんの画像貼っときますわ。</p> <p><a href="https://camo.qiitausercontent.com/1ce1b5e53bb6405ccc06ed7fe0ce189cad70e816/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f64383133376461362d656337362d633435322d663637392d6266306130646566343164642e6a706567" target="_blank" rel="nofollow noopener"><img src="https://camo.qiitausercontent.com/1ce1b5e53bb6405ccc06ed7fe0ce189cad70e816/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f64383133376461362d656337362d633435322d663637392d6266306130646566343164642e6a706567" width="200" data-canonical-src="https://qiita-image-store.s3.amazonaws.com/0/76843/d8137da6-ec76-c452-f679-bf0a0def41dd.jpeg"></a></p> <p>最近ネバヤン(バンド)に胸熱で。。お別れの歌のMVやばい。<br> <a href="https://www.youtube.com/watch?v=ZFI-Hqeu_Ag" class="autolink" rel="nofollow noopener" target="_blank">https://www.youtube.com/watch?v=ZFI-Hqeu_Ag</a></p> <h1> <span id="本題" class="fragment"></span><a href="#%E6%9C%AC%E9%A1%8C"><i class="fa fa-link"></i></a>本題</h1> <p><strong>円運動とかゆらゆら上下運動とかってsin()とかcos()が必要じゃないですか?<br> メディアアート系のプログラミングって。oFとかprocessingとか、Unityも?。</strong></p> <p>なのでなるべく優し〜く入門勉強してきたいと思います。</p> <p>参考サイト<br> <a href="http://yoppa.org/ma2_10/1739.html" class="autolink" rel="nofollow noopener" target="_blank">http://yoppa.org/ma2_10/1739.html</a><br> <a href="http://r-dimension.xsrv.jp/classes_j/sine_cosine/" class="autolink" rel="nofollow noopener" target="_blank">http://r-dimension.xsrv.jp/classes_j/sine_cosine/</a></p> <h1> <span id="入門します" class="fragment"></span><a href="#%E5%85%A5%E9%96%80%E3%81%97%E3%81%BE%E3%81%99"><i class="fa fa-link"></i></a>入門します!</h1> <blockquote> <p>さて、プログラムによって図形を描く場合に避けて通れないのが三角関数(サイン、コサイン)です。<br> 円などに代表される曲線を描く際によく使われます。<br> タンジェントは使用頻度が低いので、ここでは省略します。</p> </blockquote> <p>だそうです。。。</p> <p>まず先ほどの図。<br> 大丈夫だから、ちょっとみてみてください。</p> <p><a href="https://camo.qiitausercontent.com/6c659e011f46abdb3a08bf3bac0a5d82418cbac3/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f31356433383165642d343436312d633235392d633831312d3062373163393133646364652e706e67" target="_blank" rel="nofollow noopener"><img src="https://camo.qiitausercontent.com/6c659e011f46abdb3a08bf3bac0a5d82418cbac3/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f31356433383165642d343436312d633235392d633831312d3062373163393133646364652e706e67" alt="20171109214610.png" data-canonical-src="https://qiita-image-store.s3.amazonaws.com/0/76843/15d381ed-4461-c259-c811-0b71c913dcde.png"></a></p> <p>これを勇気を出して、単純に読み解くと、</p> <h2> <span id="r--円の半径" class="fragment"></span><a href="#r--%E5%86%86%E3%81%AE%E5%8D%8A%E5%BE%84"><i class="fa fa-link"></i></a>R → 円の半径</h2> <h2> <span id="x--y-円上の座標" class="fragment"></span><a href="#x--y-%E5%86%86%E4%B8%8A%E3%81%AE%E5%BA%A7%E6%A8%99"><i class="fa fa-link"></i></a>(x , y) →円上の座標</h2> <h2> <span id="θ--角度" class="fragment"></span><a href="#%CE%B8--%E8%A7%92%E5%BA%A6"><i class="fa fa-link"></i></a>θ → 角度</h2> <p>らしいです。</p> <p><a href="https://camo.qiitausercontent.com/67043a3190bfd14f3ed76443dc06bafe7c52a117/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f31343937306263642d303762632d656361322d306138632d3730363130393662323235372e6a706567" target="_blank" rel="nofollow noopener"><img src="https://camo.qiitausercontent.com/67043a3190bfd14f3ed76443dc06bafe7c52a117/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f31343937306263642d303762632d656361322d306138632d3730363130393662323235372e6a706567" alt="20171109214219.jpg" data-canonical-src="https://qiita-image-store.s3.amazonaws.com/0/76843/14970bcd-07bc-eca2-0a8c-7061096b2257.jpeg"></a></p> <p>雰囲気がある。いい。<br> 溺れるナイフは漫画最高だったけど映画最悪だったなー笑</p> <p>そして(x,y)の座標の求め方は、</p> <h2> <span id="x--r--cosθ" class="fragment"></span><a href="#x--r--cos%CE%B8"><i class="fa fa-link"></i></a>x = R * cos(θ)</h2> <h2> <span id="y--r--sinθ" class="fragment"></span><a href="#y--r--sin%CE%B8"><i class="fa fa-link"></i></a>y = R * sin(θ)</h2> <p>でOKだそうです。お、意外といけるのでは?<br> ではこの式を使ってoFで円を描いてみます。<br> まぁofDrawCircle()で描けるのですが、、、勉強に。</p> <p>できたぞ〜!!!!!<br> <a href="https://camo.qiitausercontent.com/af4ce1ca438fff2abb9967032271b63454ecd513/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f36316235636230612d346234642d326533322d353264342d3938303938663566303362622e706e67" target="_blank" rel="nofollow noopener"><img src="https://camo.qiitausercontent.com/af4ce1ca438fff2abb9967032271b63454ecd513/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f36316235636230612d346234642d326533322d353264342d3938303938663566303362622e706e67" alt="スクリーンショット 2017-11-09 22.37.08.png" data-canonical-src="https://qiita-image-store.s3.amazonaws.com/0/76843/61b5cb0a-4b4d-2e32-52d4-98098f5f03bb.png"></a></p> <p>動画<br> <a href="https://www.youtube.com/watch?v=JTVPKTFheS8" class="autolink" rel="nofollow noopener" target="_blank">https://www.youtube.com/watch?v=JTVPKTFheS8</a></p> <p>コード</p> <div class="code-frame" data-lang="cpp"> <div class="code-lang"><span class="bold">ofApp.cpp</span></div> <div class="highlight"><pre><span></span> <span class="cp">#include</span> <span class="cpf">"ofApp.h"</span><span class="cp"></span> <span class="kt">float</span> <span class="n">phase</span><span class="p">;</span> <span class="c1">// 段階の意味</span> <span class="k">const</span> <span class="kt">float</span> <span class="n">R</span> <span class="o">=</span> <span class="mi">200</span><span class="p">;</span> <span class="c1">// 円の半径</span> <span class="c1">//--------------------------------------------------------------</span> <span class="kt">void</span> <span class="n">ofApp</span><span class="o">::</span><span class="n">setup</span><span class="p">(){</span> <span class="n">ofSetFrameRate</span><span class="p">(</span><span class="mi">60</span><span class="p">);</span> <span class="n">ofSetBackgroundAuto</span><span class="p">(</span><span class="nb">false</span><span class="p">);</span> <span class="c1">// 毎回背景を塗りつぶしてリセットしない!</span> <span class="n">ofBackground</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">);</span> <span class="p">}</span> <span class="c1">//--------------------------------------------------------------</span> <span class="kt">void</span> <span class="n">ofApp</span><span class="o">::</span><span class="n">update</span><span class="p">(){</span> <span class="c1">// 段階を更新</span> <span class="n">phase</span> <span class="o">+=</span> <span class="mf">0.01</span><span class="p">;</span> <span class="p">}</span> <span class="c1">//--------------------------------------------------------------</span> <span class="kt">void</span> <span class="n">ofApp</span><span class="o">::</span><span class="n">draw</span><span class="p">(){</span> <span class="c1">//原点を画面の中心点に</span> <span class="n">ofTranslate</span><span class="p">(</span><span class="n">ofGetWidth</span><span class="p">()</span><span class="o">/</span><span class="mi">2</span><span class="p">,</span> <span class="n">ofGetHeight</span><span class="p">()</span><span class="o">/</span><span class="mi">2</span><span class="p">);</span> <span class="c1">//点の座標を三角関数で計算するぞ x = R * cos(θ) y = R * sin(θ)</span> <span class="n">ofVec2f</span> <span class="n">pos</span><span class="p">;</span> <span class="c1">// 円の位置</span> <span class="n">pos</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">R</span> <span class="o">*</span> <span class="n">cos</span><span class="p">(</span><span class="n">phase</span><span class="p">);</span> <span class="n">pos</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">R</span> <span class="o">*</span> <span class="n">sin</span><span class="p">(</span><span class="n">phase</span><span class="p">);</span> <span class="c1">//色を黄色に</span> <span class="n">ofSetColor</span><span class="p">(</span><span class="mi">256</span><span class="p">,</span><span class="mi">256</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">// 4ポイントの円を描画</span> <span class="n">ofDrawCircle</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span> <span class="p">}</span> </pre></div> </div> <p><strong>どうやら 先ほどの公式のcos(θ)のθにあたる変数phase(段階)をupdateで増やしていくのがキモみたいっすね。</strong></p> <p>これに時間経過とか入れてやればいいのかな<br> そんくらいの理解でいいかな。。</p> <h1> <span id="浅くてすんません" class="fragment"></span><a href="#%E6%B5%85%E3%81%8F%E3%81%A6%E3%81%99%E3%82%93%E3%81%BE%E3%81%9B%E3%82%93"><i class="fa fa-link"></i></a>浅くてすんません。。</h1> <p>次は、</p> <h2> <span id="y--r--sinθ-1" class="fragment"></span><a href="#y--r--sin%CE%B8-1"><i class="fa fa-link"></i></a>y = R * sin(θ)</h2> <p>の公式を取得したので上下ゆらゆらやってみます!!</p> <p>おっできた</p> <p><a href="https://camo.qiitausercontent.com/8a931fa630600b58c095bbc10aae74e23045e908/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f64373435653365632d633033382d303038662d653062322d6430366564386234336330642e706e67" target="_blank" rel="nofollow noopener"><img src="https://camo.qiitausercontent.com/8a931fa630600b58c095bbc10aae74e23045e908/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37363834332f64373435653365632d633033382d303038662d653062322d6430366564386234336330642e706e67" alt="スクリーンショット 2017-11-09 22.43.45.png" data-canonical-src="https://qiita-image-store.s3.amazonaws.com/0/76843/d745e3ec-c038-008f-e0b2-d06ed8b43c0d.png"></a></p> <p>動画<br> <a href="https://www.youtube.com/watch?v=pQptfrukBRE" class="autolink" rel="nofollow noopener" target="_blank">https://www.youtube.com/watch?v=pQptfrukBRE</a></p> <p><strong>コードはこちら。今回はRがゆらゆらさせる範囲で、変数phase(段階)の増える値がゆらゆらする速度になってるんですね。</strong></p> <div class="code-frame" data-lang="cpp"> <div class="code-lang"><span class="bold">ofApp.cpp</span></div> <div class="highlight"><pre><span></span> <span class="kt">float</span> <span class="n">phase</span><span class="p">;</span> <span class="c1">// 段階の意味</span> <span class="k">const</span> <span class="kt">float</span> <span class="n">R</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span> <span class="c1">//ゆらゆらさせる範囲</span> <span class="c1">//--------------------------------------------------------------</span> <span class="kt">void</span> <span class="n">ofApp</span><span class="o">::</span><span class="n">setup</span><span class="p">(){</span> <span class="n">ofSetFrameRate</span><span class="p">(</span><span class="mi">60</span><span class="p">);</span> <span class="n">ofBackground</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">);</span> <span class="p">}</span> <span class="c1">//--------------------------------------------------------------</span> <span class="kt">void</span> <span class="n">ofApp</span><span class="o">::</span><span class="n">update</span><span class="p">(){</span> <span class="c1">// 段階を更新</span> <span class="n">phase</span> <span class="o">+=</span> <span class="mf">0.01</span><span class="p">;</span> <span class="p">}</span> <span class="c1">//--------------------------------------------------------------</span> <span class="kt">void</span> <span class="n">ofApp</span><span class="o">::</span><span class="n">draw</span><span class="p">(){</span> <span class="c1">//原点を画面の中心点に</span> <span class="n">ofTranslate</span><span class="p">(</span><span class="n">ofGetWidth</span><span class="p">()</span><span class="o">/</span><span class="mi">2</span><span class="p">,</span> <span class="n">ofGetHeight</span><span class="p">()</span><span class="o">/</span><span class="mi">2</span><span class="p">);</span> <span class="n">ofVec2f</span> <span class="n">pos</span><span class="p">;</span> <span class="n">pos</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// 左右は運動なし</span> <span class="n">pos</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">R</span> <span class="o">*</span> <span class="n">sin</span><span class="p">(</span><span class="n">phase</span><span class="p">);</span> <span class="c1">// 上下ゆらゆら</span> <span class="c1">//色を黄色に</span> <span class="n">ofSetColor</span><span class="p">(</span><span class="mi">256</span><span class="p">,</span><span class="mi">256</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">// 30ポイントの円を描画</span> <span class="n">ofDrawCircle</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="mi">30</span><span class="p">);</span> <span class="p">}</span> </pre></div> </div> <h2> <span id="x--r--cosθ-1" class="fragment"></span><a href="#x--r--cos%CE%B8-1"><i class="fa fa-link"></i></a>x = R * cos(θ)</h2> <p>を使えば左右ゆらゆらも表現できると思います^^。<br> ちょっとは三角関数と仲良くなれた気がします!<br> 今回で基礎をかじった気がするから、sin波も学んでみたいです。</p> <p>完。</p> <p>本家のブログはこちら 超雑多です。w🍺<br> ホンキートンク・スーダラブルース<br> <a href="http://www.sudara-bluse.tokyo/entry/openframeworks_5" class="autolink" rel="nofollow noopener" target="_blank">http://www.sudara-bluse.tokyo/entry/openframeworks_5</a></p> ",
        body: " #プロローグ ![20171109214610.png](https://qiita-image-store.s3.amazonaws.com/0/76843/15d381ed-4461-c259-c811-0b71c913dcde.png) 参照: http://r-dimension.xsrv.jp/classes_j/sine_cosine/ **うげ〜。いやだこの図。。**はい、こんにちは。。。 こちらの図見るとオエ〜ってなりますよね。僕はなります。 なのでお口直しに、小松菜奈さんの画像貼っときますわ。 <img src="
    https: //qiita-image-store.s3.amazonaws.com/0/76843/d8137da6-ec76-c452-f679-bf0a0def41dd.jpeg" width="200"> 最近ネバヤン(バンド)に胸熱で。。お別れの歌のMVやばい。 https://www.youtube.com/watch?v=ZFI-Hqeu_Ag #本題 **円運動とかゆらゆら上下運動とかってsin()とかcos()が必要じゃないですか? メディアアート系のプログラミングって。oFとかprocessingとか、Unityも?。** なのでなるべく優し〜く入門勉強してきたいと思います。 参考サイト http://yoppa.org/ma2_10/1739.html http://r-dimension.xsrv.jp/classes_j/sine_cosine/ #入門します! >さて、プログラムによって図形を描く場合に避けて通れないのが三角関数(サイン、コサイン)です。 円などに代表される曲線を描く際によく使われます。 タンジェントは使用頻度が低いので、ここでは省略します。 だそうです。。。 まず先ほどの図。 大丈夫だから、ちょっとみてみてください。 ![20171109214610.png](https://qiita-image-store.s3.amazonaws.com/0/76843/15d381ed-4461-c259-c811-0b71c913dcde.png) これを勇気を出して、単純に読み解くと、 ##R → 円の半径 ##(x , y) →円上の座標 ##θ → 角度 らしいです。 ![20171109214219.jpg](https://qiita-image-store.s3.amazonaws.com/0/76843/14970bcd-07bc-eca2-0a8c-7061096b2257.jpeg) 雰囲気がある。いい。 溺れるナイフは漫画最高だったけど映画最悪だったなー笑 そして(x,y)の座標の求め方は、 ## x = R * cos(θ) ## y = R * sin(θ) でOKだそうです。お、意外といけるのでは? ではこの式を使ってoFで円を描いてみます。 まぁofDrawCircle()で描けるのですが、、、勉強に。 できたぞ〜!!!!! ![スクリーンショット 2017-11-09 22.37.08.png](https://qiita-image-store.s3.amazonaws.com/0/76843/61b5cb0a-4b4d-2e32-52d4-98098f5f03bb.png) 動画 https://www.youtube.com/watch?v=JTVPKTFheS8 コード ```cpp:ofApp.cpp #include "ofApp.h" float phase; // 段階の意味 const float R = 200; // 円の半径 //-------------------------------------------------------------- void ofApp::setup(){ ofSetFrameRate(60); ofSetBackgroundAuto(false); // 毎回背景を塗りつぶしてリセットしない! ofBackground(255, 255, 255); } //-------------------------------------------------------------- void ofApp::update(){ // 段階を更新 phase += 0.01; } //-------------------------------------------------------------- void ofApp::draw(){ //原点を画面の中心点に ofTranslate(ofGetWidth()/2, ofGetHeight()/2); //点の座標を三角関数で計算するぞ x = R * cos(θ) y = R * sin(θ) ofVec2f pos; // 円の位置 pos.x = R * cos(phase); pos.y = R * sin(phase); //色を黄色に ofSetColor(256,256, 0); // 4ポイントの円を描画 ofDrawCircle(pos.x, pos.y, 4); } ``` **どうやら 先ほどの公式のcos(θ)のθにあたる変数phase(段階)をupdateで増やしていくのがキモみたいっすね。** これに時間経過とか入れてやればいいのかな そんくらいの理解でいいかな。。 #浅くてすんません。。 次は、 ## y = R * sin(θ) の公式を取得したので上下ゆらゆらやってみます!! おっできた ![スクリーンショット 2017-11-09 22.43.45.png](https://qiita-image-store.s3.amazonaws.com/0/76843/d745e3ec-c038-008f-e0b2-d06ed8b43c0d.png) 動画 https://www.youtube.com/watch?v=pQptfrukBRE **コードはこちら。今回はRがゆらゆらさせる範囲で、変数phase(段階)の増える値がゆらゆらする速度になってるんですね。** ```cpp:ofApp.cpp float phase; // 段階の意味 const float R = 100; //ゆらゆらさせる範囲 //-------------------------------------------------------------- void ofApp::setup(){ ofSetFrameRate(60); ofBackground(255, 255, 255); } //-------------------------------------------------------------- void ofApp::update(){ // 段階を更新 phase += 0.01; } //-------------------------------------------------------------- void ofApp::draw(){ //原点を画面の中心点に ofTranslate(ofGetWidth()/2, ofGetHeight()/2); ofVec2f pos; pos.x = 0; // 左右は運動なし pos.y = R * sin(phase); // 上下ゆらゆら //色を黄色に ofSetColor(256,256, 0); // 30ポイントの円を描画 ofDrawCircle(pos.x, pos.y, 30); } ``` ## x = R * cos(θ) を使えば左右ゆらゆらも表現できると思います^^。 ちょっとは三角関数と仲良くなれた気がします! 今回で基礎をかじった気がするから、sin波も学んでみたいです。 完。 本家のブログはこちら 超雑多です。w🍺 ホンキートンク・スーダラブルース http://www.sudara-bluse.tokyo/entry/openframeworks_5 ",
        coediting: false,
    comments_count: 0,
    created_at: "2017-11-09T22:50:51+09:00",
    group: null,
    id: "fe412a67b64e793d138b",
    likes_count: 0,
    private: false,
    reactions_count: 0,
    tags: [{
            name: "processing",
            versions: []
        },
        {
            name: "openFrameworks",
            versions: []
        }
    ],
    title: "【openFrameworks 初心者冒険記5】逃げまくってた...三角関数の扉を再ノック。sin() cos() サインコサイン....。メディアアートで避けては通れない?最初の壁",
    updated_at: "2017-11-09T22:50:51+09:00",
    url: "http://qiita.com/39_isao/items/fe412a67b64e793d138b",
    user: {
        description: "鮭とメロンパンが好物な奥田民生になりたいボーイです。 最近はてなブログ始めました http://sudara-bluse.hatenablog.com/",
        facebook_id: "",
        followees_count: 52,
        followers_count: 44,
        github_login_name: null,
        id: "39_isao",
        items_count: 103,
        linkedin_id: "",
        location: "",
        name: "",
        organization: "",
        permanent_id: 76843,
        profile_image_url: "https://qiita-image-store.s3.amazonaws.com/0/76843/profile-images/1508030106",
        twitter_screen_name: "sudara_bluse",
        website_url: "http://sudara-bluse.hatenablog.com/"
    }
}, 
{,,,}
]

今回は、以下のデータを使ってAndroidアプリを作ってみます。

  • 投稿タイトル
  • ユーザアイコン
  • ユーザ名
  • URL

完成したアプリ

Kapture 2017-11-13 at 10.54.36.gif

プロジェクトを作成する

AndroidStudioを開きます

スクリーンショット 2017-11-05 11.53.48.png

新しいプロジェクトを作成

スクリーンショット 2017-11-05 11.54.40.png

スクリーンショット 2017-11-05 11.55.04.png

スクリーンショット 2017-11-05 11.55.16.png

使うライブラリをインストールする

build.gradleに設定を書く

build.gradle (app)を開いて、dependenciesの中に以下を追加します。

// net work
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

// image
compile 'com.squareup.picasso:picasso:2.5.2'

ファイル全体

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.2"
    defaultConfig {
        applicationId "com.example.tominaga.qiita_client_d3"
        minSdkVersion 21
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:26.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

    // net work
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

    // image
    compile 'com.squareup.picasso:picasso:2.5.2'
}

「Sync Now」 をクリック
スクリーンショット 2017-11-09 23.01.28.png

通信周りのクラスを作成する

Retrofitのクライアントクラスを作成

スクリーンショット 2017-11-05 12.16.04.png

スクリーンショット 2017-11-10 8.47.00.png

interface QiitaClient {
    @GET("/api/v2/items")
    fun items(@Query("query") query: String? = null,
              @Query("page") page: Int = 1,
              @Query("per_page") perPage: Int = 50): Call<List<Item>>

    companion object {
        fun create(): QiitaClient {
            val retrofit = retrofit2.Retrofit.Builder()
                    .baseUrl("http://qiita.com")
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
            return retrofit.create(QiitaClient::class.java)
        }
    }
}

Gsonのモデルクラスを作成

スクリーンショット 2017-11-10 8.48.31.png

data class Item(
        val id: String?,
        val title: String?,
        val body: String?,
        val url: String?,
        val comments_count: Int?,
        val likes_count: Int?,
        val created_at: String?,
        val user: User?
)

スクリーンショット 2017-11-10 8.48.42.png

data class User(
        val description: String?,
        val facebook_id: String?,
        val followees_count: Int,
        val followers_count: Int,
        val github_login_name: String?,
        val id: String,
        val items_count: Int,
        val linkedin_id: String?,
        val location: String?,
        val name: String?,
        val organization: String?,
        val permanent_id: Int?,
        val profile_image_url: String?,
        val twitter_screen_name: String?,
        val website_url: String?
)

一覧画面を作る

一覧画面に必要なもの準備

ファイル作成

スクリーンショット 2017-11-05 12.28.30.png

スクリーンショット 2017-11-05 12.28.52.png

スクリーンショット 2017-11-05 12.29.23.png

インターネットを使う権限を設定する

AndroidManifest.xmlを開いて、manifest要素の中に以下を追加します。これを書かないと、このアプリからインターネットを使うことができません。

<uses-permission android:name="android.permission.INTERNET" />

一覧画面の設定

AndroidManifest.xmlを開いて、activity要素のandroid:label属性を以下のように変更します。

<activity
    android:name=".ListActivity"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

最終的なAndroidManifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.tominaga.qiita_client_d3">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".ListActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

一覧画面のLayoutを書く

ActivityのLayout

activity_list.xmlを開いて、以下のように書きます。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ListActivity">

    <EditText android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:hint="Keyword"
        android:inputType="text"/>

    <ListView android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true"
        android:layout_below="@id/edit_text"/>

</RelativeLayout>

一覧画面の行のLayoutを作成

スクリーンショット 2017-11-09 23.12.49.png

スクリーンショット 2017-11-09 23.13.04.png

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginRight="10dp"/>

    <TextView android:id="@+id/item_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_toRightOf="@id/image_view"
        android:ellipsize="end"
        android:lines="1"
        android:textSize="20sp"
        tools:text="item"/>

    <TextView android:id="@+id/user_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_toRightOf="@id/image_view"
        android:ellipsize="end"
        android:lines="1"
        tools:text="user"/>

</RelativeLayout>

再度、activity_list.xmlを開いて、ListView要素の中に以下を追記すると、プレビューの一覧画面に先ほど作った行が表示されるようになります。

    <ListView android:id="@+id/list_view"
              ...
              tools:listitem="@layout/list_item"/>

一覧画面を表示する処理を書く

行を表示する処理を書く

ListActivity を開いて、ListActivityの中に以下を追加します。importの追加を聞かれたらOKしてください。Adapterは、ListViewのように、保持している大量のデータの中から一部だけを表示するViewを使うときに、Viewとデータの橋渡しをします。

private inner class ListAdapter(context: Context, resource: Int) : ArrayAdapter<Item>(context, resource) {

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var convertView = convertView
        if (convertView == null) {
            // 再利用可能なViewがない場合は作る
            convertView = layoutInflater.inflate(R.layout.list_item, null)
        }

        val imageView = convertView?.findViewById<ImageView>(R.id.image_view) as ImageView
        val itemTitleView = convertView.findViewById<TextView>(R.id.item_title) as TextView
        val userNameView = convertView.findViewById<TextView>(R.id.user_name) as TextView

        imageView.setImageBitmap(null) // 残ってる画像を消す(再利用された時)

        // 表示する行番号のデータを取り出す
        val result = getItem(position)

        Picasso.with(context).load(result.user?.profile_image_url).into(imageView)
        itemTitleView.text = result.title
        userNameView.text = result.user?.name

        return convertView
    }
}

さらに ListActivityに以下を記述

lateinit private var mAdapter: ListAdapter

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)

    mAdapter = ListAdapter(this, R.layout.list_item)

    val listView = findViewById<ListView>(R.id.list_view) as ListView
    listView.adapter = mAdapter
}

検索処理を記述する

ListActivityを開いて、ListActivityの中に以下を追加します。importの追加を聞かれたらOKしてください。View.OnKeyListenerは、キーボードのキーを押されたりしたときに呼び出されるところです。Enterキーが上がったときに検索処理を実行し、実行結果をAdapterに詰める(詰めたら画面は随時更新される)処理を書いています

private inner class OnKeyListener : View.OnKeyListener {

    override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean {
        if (keyEvent.action != KeyEvent.ACTION_UP || keyCode != KeyEvent.KEYCODE_ENTER) {
            return false
        }

        val editText = view as EditText
        // キーボードを閉じる
        val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(editText.windowToken, 0)

        var text = editText.text.toString()
        try {
            // url encode 例. スピッツ > %83X%83s%83b%83c
            text = URLEncoder.encode(text, "UTF-8")
        } catch (e: UnsupportedEncodingException) {
            Log.e("", e.toString(), e)
            return true
        }

        if (!TextUtils.isEmpty(text)) {
            val request = QiitaClient.create().items(text)
            Log.d("", request.request().url().toString())
            val item = object : Callback<List<Item>> {
                override fun onResponse(call: Call<List<Item>>?, response: Response<List<Item>>?) {
                    mAdapter.clear()
                    response?.body()?.forEach { mAdapter.add(it) }
                }

                override fun onFailure(call: Call<List<Item>>?, t: Throwable?) {
                }
            }
            request.enqueue(item)
        }
        return true
    }
}

さらに ListActivityに以下を記述

override fun onCreate(savedInstanceState: Bundle?) {
    // 省略...

    val editText = findViewById<EditText>(R.id.edit_text) as EditText
    editText.setOnKeyListener(OnKeyListener())
}

結果このようになっているはずです

import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.*
import com.squareup.picasso.Picasso
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.UnsupportedEncodingException
import java.net.URLEncoder


package com.example.tominaga.qiita_client_d3

import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.*
import com.squareup.picasso.Picasso
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.UnsupportedEncodingException
import java.net.URLEncoder


class ListActivity : Activity() {

    lateinit private var mAdapter: ListAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_list)

        mAdapter = ListAdapter(this, R.layout.list_item)

        val listView = findViewById<ListView>(R.id.list_view) as ListView
        listView.adapter = mAdapter

        val editText = findViewById<EditText>(R.id.edit_text) as EditText
        editText.setOnKeyListener(OnKeyListener())
    }

    private inner class ListAdapter(context: Context, resource: Int) : ArrayAdapter<Item>(context, resource) {

        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
            var convertView = convertView
            if (convertView == null) {
                // 再利用可能なViewがない場合は作る
                convertView = layoutInflater.inflate(R.layout.list_item, null)
            }

            val imageView = convertView?.findViewById<ImageView>(R.id.image_view) as ImageView
            val itemTitleView = convertView.findViewById<TextView>(R.id.item_title) as TextView
            val userNameView = convertView.findViewById<TextView>(R.id.user_name) as TextView

            imageView.setImageBitmap(null) // 残ってる画像を消す(再利用された時)

            // 表示する行番号のデータを取り出す
            val result = getItem(position)

            Picasso.with(context).load(result.user?.profile_image_url).into(imageView)
            itemTitleView.text = result.title
            userNameView.text = result.user?.name

            return convertView
        }
    }

    private inner class OnKeyListener : View.OnKeyListener {

        override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean {
            if (keyEvent.action != KeyEvent.ACTION_UP || keyCode != KeyEvent.KEYCODE_ENTER) {
                return false
            }

            val editText = view as EditText
            // キーボードを閉じる
            val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            imm.hideSoftInputFromWindow(editText.windowToken, 0)

            var text = editText.text.toString()
            try {
                // url encode
                text = URLEncoder.encode(text, "UTF-8")
            } catch (e: UnsupportedEncodingException) {
                Log.e("", e.toString(), e)
                return true
            }

            if (!TextUtils.isEmpty(text)) {
                val request = QiitaClient.create().items(text)
                Log.d("", request.request().url().toString())
                val item = object : Callback<List<Item>> {
                    override fun onResponse(call: Call<List<Item>>?, response: Response<List<Item>>?) {
                        mAdapter.clear()
                        response?.body()?.forEach { mAdapter.add(it) }
                    }

                    override fun onFailure(call: Call<List<Item>>?, t: Throwable?) {
                    }
                }
                request.enqueue(item)
            }
            return true
        }
    }
}

実行してみましょう

詳細画面に遷移してWebViewで閲覧する

詳細画面の準備

ルートパッケージ上で右クリックして、New > Activity > Blank Activity。

スクリーンショット 2017-11-09 23.27.02.png

スクリーンショット 2017-11-09 23.27.32.png

詳細画面のLayout XMLを書く

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DetailActivity">

    <WebView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

一覧画面から詳細画面に遷移する処理を書く

private inner class OnItemClickListener : AdapterView.OnItemClickListener {
    override fun onItemClick(adapterView: AdapterView<*>?, view: View?, position: Int, id: Long) {
        val intent = Intent(this@ListActivity, DetailActivity::class.java)
        // タップされた行番号のデータを取り出す
        val result = mAdapter.getItem(position)
        intent.putExtra("url", result.url)
        startActivity(intent)
    }
}

さらに、onCreate(Bundle)の中に

listView.onItemClickListener = OnItemClickListener()

を追加します。先ほど作ったOnItemClickListenerをListViewに設定してます。

詳細画面で投稿を閲覧する処理を書く

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.webkit.WebView
import android.webkit.WebViewClient

class DetailActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_detail)
        val webView = findViewById<WebView>(R.id.web_view)
        val url = intent.getStringExtra("url")

        // リンクをタップしたときに標準ブラウザを起動させない
        webView .webViewClient = WebViewClient()

        // 最初に投稿を表示
        webView .loadUrl(url)

        // jacascriptを許可する
        webView .settings.javaScriptEnabled = true
    }
}

実行してみましょう

お疲れさまでした。

最後に

完成はこちらにあります。https://github.com/decoch/qiita-client-for-d3

iTunes APIを使ったAndroidアプリハンズオンを参考にKotlinに書き換えさせて頂きました。
ありがとうございました。