アナログ金木犀

つれづれなるまままにつれづれする

一人旅してきた

9/28(木),29(金)に一泊二日の一人旅してきた。

場所は平湯温泉深山桜庵という場所。

写真をとる習慣が無いためか、写真はほとんど撮ってない。

唯一撮ったのが下記(本当にどうでもいい写真)だけで、肝心の宿や食事や部屋自体は全く撮るのを忘れていた。

今回は初めての一人旅だった。

疲れがどうにも取れずに思い切って有給をとり自然に囲まれた場所で温泉に浸かりたいなと思ったのがきっかけだった。

ご飯はすごい美味しかったし、温泉は最高だった。

季節的にまだ少し暑いから温泉は微妙かな?と思ったが、行った時にはちょうど気温が低くなり始めた時期になっていた。露天風呂では浸かっている部分は温かく、浸かってない部分はすこし寒いという自分の中では永遠に風呂に入っていられる条件が揃っていた。

温泉を浸かったあとは、湯上り処で無料配布している牛乳 / コーヒー牛乳を飲むこともできる。

夜ご飯は 17時半からと20時からのどちらかを先着で選べる。僕は20時からの方を選んだが、もし行く人がいれば17時半の方をオススメしようと思う。 なぜなら22時から23時までの間に夜鳴きそばというラーメンを無料配布しているからだ。食い意地がはってる自分でも20時に満腹度120%になる食事を食べた2時間後にラーメンを食べる食欲はなかった。ラーメンを食べるために17時半を選ぶべきだった。

ラーメンを食べるミッションをクリアできなかった以外は、大満足大満喫な一人旅行だった。

その初めての一人旅を体験して思ったことがあるので書き記しておこうと思う。

昔、「自分探し」という言葉があまり好きじゃなかった。というのも、〈今の自分をただただ認めたくない人の逃げの行為〉だと考えていて、その行為に嫌悪感を抱いていたんだと思う。最近の僕は「自分探し?いいんじゃない?楽しんできなっせ」くらいには嫌悪感は薄れてたけども、まぁ少なからず根っこには残ってた。

それが、一人旅をしてすこし考えが変わった。

一人で旅をすると、社会の雑念とかしがらみとか全くなしの場所で本当に一人になるので、全意識を自分に(半ば強制的に)向けられるということに気づいた。

最近特になのだが、自分の将来像をなかなかイメージすることができてなかった。 「自分は何をしたいんだっけ?」という問いに対して、無意識的にしがらみとか本来どうでもいいことが入ってきてたみたいで回答を出すのに変な矛盾を解決しなければならない状態になってしまっていたんだと思う。一人旅ではそういう雑念が剥がれて、クリアに考えられた。再確認できた。あぁ、やっぱりそういうことだよね、みたいな。

その回答みたいなのが出てきたときに「あ、これが『自分探し』か!」と一人で変に納得した。

そんな旅だった。

一人旅も良かったが、今度は彼女と行こうと思う。

【日記】DroidKaigiおもしろかった

DroidKaigiがおもしろかった。

発表の内容

まず発表してきたのでそのスライドをここに。

感想とか裏話とかを時系列に箇条書きで

前日まで

  • DroidKaigiの週にいろいろな締め切りが集中してしまっていて、資料作りにとれる時間が少なくて正直かなり焦ってた…
    • 枠組みはできていたけどこれくらいやれば大丈夫かなっていうレベルまで前日になるまで持っていくことができなかった
  • ScalaMatsuri や try!Swift などにも参加していて、体力的にも来ていた
  • スピーカーズディナーでアイコンは知ってるけど顔は知らないスピーカーやスタッフの方とお知り合いになれて、おかげで当日ちょっとやりやすかった
  • 30分枠の人たちみんなスライドが100枚以上行きましたとか、200枚近く行きましたとか言っていて、俺60-70くらいしかないんだけど大丈夫なんか?って心配になったりした

1日目

  • 1日目朝早く起きれたので(自分は2日目なんだけど)家でリハをしてみてた。2,3回やったら力尽きて寝てしまい遅刻した
  • リハ的に早く話し過ぎたら18分くらい、ゆっくり話せば25分くらいで終わるなぁという体感を得た。ゆっくり話そうと誓う
  • 会場に来てみたら、自分の発表場所 が Room3 という一番でかい場所ということを知って青ざめた
    • 自分はエモい系で技術的な話の本筋とは離れるので、小さい部屋で聞きに来てくれた人としっぽりと、なんならみんなで日本酒でも飲みながらやりたいな、くらいに考えていた
    • 加えて、自分の時間帯はむしろ自分が聞きたい発表ばかりある激選区 (しらじさんのツイートからもそう感じていたのは自分だけじゃなかったように思う)

  • そわそわしてきて心配になってきたので、弊社のたざわさんや Quipper のこにふぁーさんやだるま に資料見てもらって意見もらったりして、精神安定につとめてた
  • 1日目が終わって、こにふぁーさんとだるまと池袋まで行って飲んで、やすべえ行って、スパラクーアに泊まるなど
    • スパラクーアで深夜にDroidKaigiアプリにコミットしてた
    • こにふぁーさんとは自分がスタートアップで働いていた頃からの付き合いだけど、一緒に同じコードを触るって機会がなかったので感慨深い
    • 翌日296PRとのことだったので、あと4つやればよかったと後悔した

2日目

  • 緊張して吐きそうだった
  • 発表が始まってからは時間を忘れてしまって、ちょっと足早になってしまったようで終わったら21分くらいだった
  • 質問をいっぱいしてくれたので早く切り上げて終了みたいなことにならなくて助かった
  • 発表中に吐かなかった
  • 終わってから Twitterで反響見るまでちょっと怖かった
  • 見てみた感じ、少しでも役に立っていそうな、または共感してくれてそうなツイートばかりで胸を撫で下ろした
  • 会場に、発表の中に出てきた新卒の子がいたらしい (後で知った
  • アフターパーティーで前からアイコンは知ってるけどリアルに知らない人と話せてよかった
    • 「アイコンと感じが違いすぎてわかんねぇよ」ともれなく言われたので、アイコン変えたい
    • たしかに、写真の頃から10キロくらい痩せたし、メガネも1年以上はかけてない (だが裸眼)
  • アフターパーティー後につけ麺食って帰りました

反省

  • 話がやっぱり上手くなれない
  • 英語圏の人が見てもわかるような資料にすればよかった (思ったけど、時間が取れなかった
  • 声が小さかったかもしれない…? (動画を見て確認する

まとめ

勉強になることがたくさんあった。そして、なによりめちゃくちゃ楽しかった。

lottieが大変よろしいものだった

仕事でかっこいいインタラクションを実装することになって、canvasでごりごり頑張るかなぁとも思ったのだけど、少し前に話題になったlottieを使ってみることにした。

結果、レベルの高いアニメーションをすこぶる簡単に1時間もかからず実装することができた

実際に導入してみるところまで +α を説明してみる。

lottieとは

詳細は こちら から見ることができる。知らない人に三行で説明してみると

After Effectsで作ったアニメーションを

jsonに変換して

それをそのまま読み込ませてアプリに組み込める

以上。

Android, iOS, Native Reactに対応しているみたい。

例えば下記はAfter Effectsで Lottie のサンプルを動かしているところ。

これを、jsonに変換してそのまま lottieのライブラリを入れてjsonを読み込ませるだけで下記のようになる。

手順

導入から、実際にアプリに組み込むところまでの流れをざっと説明します。

After Effectsにbodymovinを導入する

https://github.com/bodymovin/bodymovinAfter Effectsに導入する

  1. http://aescripts.com/learn/zxp-installer/ でZXPInstallerをダウンロード
  2. ZXPInstallerを開こうとして「ZXPInstallerは壊れているため開けません。"ゴミ箱"に入れる必要があります。」と言われたら、 xattr -rc ZXPInstaller とすると開けるようになる
  3. bodymovinのZXPファイルをダウンロード
  4. ZXPInstallerに先のZXPファイルをドラッグ&ドロップ
  5. After Effectsを起動して、メニュー>AfterEffects CC>環境設定>一般設定 の「スクリプトによるファイルへの書き込みとネットワークへのアクセスを許可」を有効

jsonの作り方

  1. メニュー>ウィンドウ>拡張機能>Bodymovin
  2. 書き出したいコンポジションを選択(左列の円をクリック)して、選択した同じ行の右端の「…」をクリックして保存先を設定
  3. Renderをクリック

f:id:kgmyshin:20170219141622p:plain

これで指定した保存先にjsonが保存されます。

アプリに導入(Android)

https://github.com/airbnb/lottie-android を見てその通りにするだけ。

build.gradleに下記を追加。

dependencies {  
  compile 'com.airbnb.android:lottie:1.0.3'
}

そして、先ほどのjsonをassetsに配置して、

    animationView.setAnimation("data.json");
    animationView.loop(false);
    animationView.playAnimation();

とするだけ。自動でアニメーションを流したままとかであれば下記でも可。

<com.airbnb.lottie.LottieAnimationView
        android:id="@+id/animation_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:lottie_fileName="data.json"
        app:lottie_loop="true"
        app:lottie_autoPlay="true" />

以上で下記のようなクオリティ高いアニメーションを実装できる。

bodymovin入れてしまえば、あとはもう画像をImageViewにセットするレベルで簡単に高クオリティのアニメーションを実装できる。はい最高。

使う前の自分が疑問に思ってたところ

ちゃんと透過できます?

できました。下記はLottieのサンプルを動かしている様子。

サイズ調整とかどういう感じなん?

LottieAnimationView は実質ImageViewです。画像がセットされたImageViewと思って scaleType やらで調整できます。

はまりどころ?

うごかない?

下記のようにコンポジションの中に別のコンポジションがネストされているとき、そのネストされたコンポジションがアニメーションされないという事象にぶつかった。

f:id:kgmyshin:20170219142758p:plain

ネストを解消することでうまく動いた。bodymovinの吐き出すdemo htmlではしっかり動いていたので lottie側が対応していない or bugのよう。

セットしたアニメーションのクリアはどうする?

アニメーションのセットは下記コードでできる。

    animationView.setAnimation("data.json");

描画されているものをクリアするにはどうしたらいんだろう? animationView.clearAnimation(); かな?と思ったけど、これは違う。

先にも少し触れたが LottieAnimationView は実質ImageView。

setAnimation 時、選択されたjsonファイルはLottieDrawable に変換され setImageDrawable(lottieDrawable) が呼ばれる。

そのため、描画されているものをクリアするには setImageDrawable(null) で実現出来る。

感想

Cooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooool!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

RecyclerView.ViewHolderの細かい話

受肉したノロいと化し(ノロになりました)、療養 & パンデミックを防ぐために今週はずっと家に引きこもってます、釘宮です。

おかげで今年一楽しみにしていた柗亭新年会にも行けず、ATI発揮してconnpassページ作ったりもろもろ頑張ってたkyobashi.dexにも参加できずで、なるほどこれが2017年かと噛みしめております。いっそ1月中に今年の不運全てを出しきりたい。

体調の方は、だいぶ動けるようになりちょっと買い物でもと外に出てみたら「うん、死にそう」と思うくらいには回復しております。 家でずっと座ってプログラミングする分には特に支障はありません。

ということで、動けない腹いせに今更RecyclerViewについての細かい記事を。

「 Activityでは createIntent を」だったり、「FragmentではnewInstanceを」みたいな話に近いです。

よく見かけるコード

RecyclerView.AdapteronCreateViewHolderでよく以下のコードを見かけます。

public class MyAdapter extends RecyclerView.Adapter<HogeViewHolder> {
    private LayoutInfalter infalater;
    :
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         return new HogeViewHolder(inflater.inflate(R.layout.hoge, parent, false)); // ★
    }
    :
}

viewtypeが複数ある場合はこんな感じですかね。

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private LayoutInfalter infalater;
    :
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
             case VIEW_TYPE_HOGE: 
                 return new HogeViewHolder(inflater.inflate(R.layout.hoge, parent, false));// ★
             case VIEW_TYPE_FUGA: 
                 return new FugaViewHolder(inflater.inflate(R.layout.fuga, parent, false));// ★
             :
         }
    }
    :
}

何が良くないと思ったか

別段悪いコードじゃないと思います。

が、ただ大抵の場合において HogeViewHolderに対応するlayoutは R.layout.hoge と決まっています。そしてどのlayoutを使うかを知るべきなのは HogeViewHolder のみでいいはず 。上のコードではそのことを Adapter が知ってしまっています。

この一点においてだけちょっといただけないなと思うんです。

改善

下記のようにstatic ファクトリメソッドからのみ作るようにして、その中でレイアウトの指定をしてあげれば、すっきりします。

public class HogeViewHolder extends RecyclerView.ViewHolder {
    public static HogeViewHolder create(
            LayoutInflater inflater,
            ViewGroup parent,
            boolean attachToRoot
    ) {
        return new HogeViewHolder(inflater.inflate(R.layout.hoge, parent, attachToRoot));
    }

    private HogeViewHolder(View itemView) {
        super(itemView);
    }
}
public class MyAdapter extends RecyclerView.Adapter<HogeViewHolder> {
    private LayoutInfalter infalater;
    :
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         return HogeViewHolder.create(inflater, parent, false); // ★
    }
    :
}

viewtypeが複数ある場合。

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private LayoutInfalter infalater;
    :
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
             case VIEW_TYPE_HOGE: 
                 return HogeViewHolder.create(inflater, parent, false); // ★
             case VIEW_TYPE_FUGA: 
                 return FugaViewHolder.create(inflater, parent, false); // ★
             :
         }
    }
    :
}

もちろん、同じ ViewHolder で複数のレイアウトを使うという場合や (自分はあまりそういう使い方をしたことないですが)、DataBinding + generics で解決したりするような場合はこの限りじゃないです。

以上です。些細な話で申し訳ないです。上記のように変えたところでビジネス的価値がどれだけ上がるかどうかはわかりませんが、 new HogeViewHolder(inflater.inflate(R.layout.fuga, parent, false)); みたいなレイアウトの指定ミスはなくなりますね。

(ちなみに、 kyobashi.dexで話そうと思ってた内容はこれじゃないです。それはまた別の機会に...)

WakaTimeで前日のプログラミング時間の集計をGASを使ってslackに通知する

こんにちは。

年末年始に 『さくら荘のペットな彼女』を見て、主人公空太の以下の名言にいまだあてられております、kgmyshinです。

やばいよなあ、本気になるって。

帰ってきて、椎名が連載が決まったって聞いたとき、自分が否定されているような気がした。

心がくじけそうだった。本気だったから。後悔からも、悔しさからも、逃げも隠れも出来なかった。

でも、だから簡単なんだ。やるしかない!

この気持ちをぬぐうにはやるしかない! ダメでもダメでもやるしかない!

そのため、それなりにやる気はあったのですが、以下のツイートが回ってきてハッとしました。

自分は一日に何時間プログラミングをして、何時間本を読んで(インプットに費やして)、何時間アウトプットに費やしているのかということが無性に気になりました。

その中の 何時間プログラミングをして に関しては、年始にWakaTimeを導入することによってうまく集計できてました。

ただ、それでも毎日WakaTimeを見に行くのはしんどい。ということで、今回botを作ってslackに通知することにしました

WakaTimeにはslackインテグレーションが初めから用意されてますが、有料プランでのみ使用できます。ただAPIは無料ユーザーでも使えます。

Google Apps Scripts

はじめはhubotあたりで作ってHerokuやBluemixとかにあげる感じかなぁと思ってたのですが、どうやらGASで無料かつcron的なこともできるようなので下記らへんを参考に触ってみることにしました。

実装

まず WakaTime APIから前日の結果を取得して

  var date = new Date();
  date.setDate(date.getDate() - 1);
  var dateStr = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
  var apiKey = PropertiesService.getScriptProperties().getProperty("WAKATIME_API_KEY");
  var url = "https://wakatime.com/api/v1/users/@kgmyshin/durations?api_key=" + apiKey + "&date=" + dateStr;
  var urlFetchOption = {
    'method': 'get',
    'contentType' : 'application/json; charset=utf-8',
    'muteHttpExceptions' : true
  };
  var response = UrlFetchApp.fetch(url, urlFetchOption);
  var body = JSON.parse(response.getContentText());
  : 

前日の総プログラミング時間を算出して

  : 
  var body = JSON.parse(response.getContentText());
  
  var totalDuration = 0;
  for each(var item in body.data) {
    totalDuration = totalDuration + item.duration;
  }
  :

Slackにポストする。

  :
  var message = dateStr + "のプログラミングに費やした時間は " + Math.floor((totalDuration / 3600)) + "時間" + Math.floor(((totalDuration % 3600) / 60)) + "分 でした。"
  
  var token = PropertiesService.getScriptProperties().getProperty('SLACK_ACCESS_TOKEN');
  var slackApp = SlackApp.create(token);
  slackApp.postMessage("#general", message, {username: "えま", icon_url: "http://i.imgur.com/TdUStmi.png"});

下記がコードの全容です。(25行)

function postSlackMessage() {
  var date = new Date();
  date.setDate(date.getDate() - 1);
  var dateStr = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
  var apiKey = PropertiesService.getScriptProperties().getProperty("WAKATIME_API_KEY");
  var url = "https://wakatime.com/api/v1/users/@kgmyshin/durations?api_key=" + apiKey + "&date=" + dateStr;
  var urlFetchOption = {
    'method': 'get',
    'contentType' : 'application/json; charset=utf-8',
    'muteHttpExceptions' : true
  };
  var response = UrlFetchApp.fetch(url, urlFetchOption);
  var body = JSON.parse(response.getContentText());
  
  var totalDuration = 0;
  for each(var item in body.data) {
    totalDuration = totalDuration + item.duration;
  }
  
  var message = dateStr + "のプログラミングに費やした時間は " + Math.floor((totalDuration / 3600)) + "時間" + Math.floor(((totalDuration % 3600) / 60)) + "分 でした。"
  
  var token = PropertiesService.getScriptProperties().getProperty('SLACK_ACCESS_TOKEN');
  var slackApp = SlackApp.create(token);
  slackApp.postMessage("#general", message, {username: "えま", icon_url: "http://i.imgur.com/TdUStmi.png"});
}

これで下記のようにSlackに通知してくれます。

f:id:kgmyshin:20170111153448p:plain

Triggerの設定

Resources -> All your triggers を選択し、下記のように設定することで、毎日深夜1時に送信してくれるようになります。

f:id:kgmyshin:20170111154140p:plain

所感

楽。