EDIT MODE

モバイルアプリ開発に関することを書いています

エンジニア向けの Sketch3 入門を DevDays と potatotips で発表しました

少し前に、仕事で Android アプリのデザインリニューアルを行う際、Sketch3 を使ってデザイナーさんと UI デザインを共有する機会がありました。

そのことがきっかけとなり、自分でも Sketch3 を使えるようになりたいなと思い、ちょっとずつ練習していった内容などを、タイトルにある2つのイベントで発表させて頂きました。

Stack Overflow DevDays

みんな大好き Stack Overflow が、日本で初めて主催したイベントです。 個人開発者向けとしては珍しい平日開催のイベントでした。

イベントページでは最終的に149名の申し込みがあったようですが、(数えていませんが)当日はその半分くらいな印象でした。 ちょっとギリギリまでイベントの内容も分からなかったので、予定に組み込むのが難しかったのかなと、個人的には思います。

イベント全体の振り返りは、以下のブログが詳しいので、ここでは自分の発表について触れるだけにします。

発表資料

15分の発表枠を頂いていたのですが、Sketch3 の良さが伝わるようデモをメインで行いました。 Shapeを組み合わせて、シンプルなアイコンなら割と簡単に出来るということと、画像の書き出しがとても便利だということをデモしました。

ちなみにこのとき手順メモを手元で見るために、モニタの設定をミラーリングにしないでプロジェクタを見つつデモしたのですが、ものすごくやりづらかったです。デモはやっぱりミラーリングにして、手元を見ながらやるべきですね...。

potatotips #22

AndroidiOS の Tips 共有にフォーカスした勉強会です。 毎月1回のペースで、いろいろな企業が主催する形で開催されています。今回はメルカリさん主催で行われました。 大人気のイベントで、発表枠もオーディエンス枠も抽選を勝ち抜かないと参加出来ません。

この勉強会は、参加枠に「ブログまとめ枠」という形式があり、イベントについてまとめる専任者がいらっしゃるので、こちらも全体については、以下が詳しいのでそちらをご覧ください。

発表資料

前述の DevDays と日程が近く、せっかくなので DevDays での発表内容に、もっとエンジニア向けの内容を追加して資料を再構築したのが、この資料です。 本当はプラグインについても触れたいなと思ったのですが、そもそも DevDays に参加したという potatotips 参加者が少なかったら、Sketch3 についても改めて説明を入れたかったので、プラグインに触れるのは止めました。

ということで、実際に発表の冒頭で DevDays に参加した人に挙手してもらったら1名のみでしたので、プラグインに触れなくても時間ギリギリの発表になってしまいました。

まとめ

Mac OS 専用ソフトではあるものの、モバイルやWebの仕事でデザイナーさんと一緒に作業をする機会があれば、Sketch3 は絶対にエンジニアも使うべきです。

それ以外でもプライベートで何か作っているのであれば、Sketch3 は購入を検討するに値する価値があると思います。 出来れば OSS ライセンス版みたいのが、あると嬉しいんですけどね。

最後に、僕もまだまだ Sketch3 使い始めたばかりなので、ノウハウに飢えてます。 もし Sketch3 の勉強会情報とか知ってる方は、@androhi にメンションいただけると泣いて喜びます。

IntelliJ Plugin 勉強会を開催しました

Twitter で Plugin 開発の辛みなどを書いていたら、@konifar さんや @shiraj_i さんと話が合い、勢いで勉強会を企画することになったのですが、先日無事に開催することが出来ました。(残念ながら @konifar さんは当日参加出来なかったのですが...)

イベントページは、こんな感じでした。 IntelliJ Plugin 勉強会

会場は 株式会社Gunosy さんに提供頂き、おいしいエスプレッソとクリームどら焼きで楽しく過ごせました。

f:id:androhi:20150813230542p:plain

当日の人数は全部で7人で、自己紹介しつつ Plugin 開発の経験を確認したところ、以下のような内訳でした。

  • 経験有り(かつ Plugin Repository への公開も有り):3人
  • 少し経験有り:1人
  • 未経験(もしくは始めたばかり):3人

思ったよりきれいに分かれたので、みんなが「知りたいこと」を挙げては誰かが答えるという感じで、かなりゆるく進めていきました。

そのときのやり取りは、GitHub Pages にまとめて公開しています。

いくつか盛り上がったポイントがあったのですが、個人的なハイライトは Plugin のデバッグ実行に Android Studio が使える方法が分かった点です。本当にこれを知ることが出来て良かった!

今回の参加者にサムライズムの @yusuke さんがいらっしゃって、誰かが「 AndroidStudio でデバッグ出来たらいいのに」と言ったら、さらっと「たぶん出来るよ」と。さすがプロだと思いました。

そしてステッカーまで頂くことができました。

f:id:androhi:20150813232959p:plain

いろいろと知見を共有することが出来たし、楽しい時間を作る事が出来たので、また来月もどこかで第2回を開催したいなと思っています。興味持っていただけた方は、ぜひ参加してみてください。

最後に。

Plugin 開発もライブラリ開発と同じように自分で欲しい機能を作ることができるし、それが誰かの役に立つかもしれないという点で、とても楽しいです。

技術的には Java Swing がベースなので、割と敷居は低いと思います。ただドキュメントを始めとした情報量が少ないのが玉にキズかなと思います。

今回の勉強会を通して、そのあたりを和らげるような勉強会になっていくといいなと思いました。

ToolWindow タイプの Intellij Plugin 作成の Tips

初めて Intellij Plugin を作ってみました。

どんな Plugin を作ったかは、前の記事に書いたので興味あるかたは読んでみてください。 また作成した Plugin のコードGitHub で公開しています。

Intellij Plugin を作ってみて感じこと

ちょっとググると分かりますが、言語を限定しなくても関連する情報が少ないです。

後述するブログやサイトと、その中で紹介されてるブログやサイトでほぼめぼしい情報が底をつきます。

あとはもう、OSS として公開されてる Intellij Plugin のコードを参考にするか、Intellij IDEA CE のコードを参考にするしかありませんでした。

とはいえ、メインの技術は Java Swing という枯れた技術なので、そこは情報量豊富でした。 そのため調べながら作るとしたら、以下のような組み合わせを繰り返す感じになると思います。

  • Plugin 作成の流れや基本 >> 公式サイトや Plugin 作成について書かれたブログを見る
  • UI まわりの基本 >> Swing 関連のサイトやブログを見る
  • ピンポイントでの実装方法 >> OSS の Plugin のコードを読む
  • 上記でも解決できない問題 >> Intellij IDEA CE のコードを読む

Plugin のタイプ

AndroidStudio や Intellij IDEA などを実際に使ってる人には説明不要かと思いますが、Plugin をざっくり3タイプに分けるとこんな感じだと思ってます。

  1. ほぼ UI を持たないサポート系
  2. 特定のタイミングで機能するダイアログ系
  3. 常駐?して機能するツール

1 は CodeGenerator とか Editor 系の Plugin を指してます。

2 はそのまんまですが Importer 系とか FileGenerator とかいろいろありますね。

3 は ProjectTree とか BuildTool 系で、いわゆる画面の左端や右端、もしくは下部に表示/非表示を切り替えられるタイプの Plugin です。

ToolWindow の作成

Plugin プロジェクトの作成方法などは、後述するサイトなどで詳しく紹介されているのでここでは触れません。 まずは ToolWindow を表示するために必要な手順を書いていこうと思います。

1. ToolWindow にコンテンツをセットするクラスを作る

以下のように ToolWindow に表示する UI をセットするためのクラスを追加します。ポイントは、ToolWindowFactory インターフェースを実装する点です。

public class AndroidDrawableViewerToolWindowFactory implements ToolWindowFactory {

    @Override
    public void createToolWindowContent(Project project, ToolWindow toolWindow) {
        JLabel label = new JLabel("Hello World");

        final ContentManager contentManager = toolWindow.getContentManager();
        final Content content = contentManager.getFactory().createContent(label, null, false);
        contentManager.addContent(content);
    }
}

createContent(JComponent, String, boolean) メソッドで、第一引数に Swing の UIConponent をセットでき、主にそれが ToolWindow 内に表示されます。

2. plugin.xml に ToolWindow を生成する宣言を記述する

作成する Plugin の初期化時に、1 で作成した処理が動くように宣言する必要があります。 記述するのは、 <extension> タグの中です。

  <extensions defaultExtensionNs="com.intellij">
    <!-- Add your extensions here -->
    <toolWindow anchor="right" canCloseContents="true" id="DrawableViewer"
                factoryClass="com.androhi.androiddrawableviewer.AndroidDrawableViewerToolWindowFactory" />
  </extensions>

各属性について詳しくは公式サイトの ここ に書かれていますが、補足すると factoryClass の指定は完全修飾名(パッケージ名込み)で書かないとダメでした。

3. ToolWindow を表示する

1 と 2 を作成して実行すると、1 で createContent(JComponent, String, boolean) メソッドに渡した JComponent(ここだと"Hello World"と表示するラベル)が表示される ToolWindow を表示できると思います。

ToolWindow の表示切り替えは、メニューから行う方法と ToolButtons で行う方法があります。

メニューでの切り替え

View > ToolWindowsにPlugin名

f:id:androhi:20150723231446p:plain

ToolButtons での切り替え

View > Tool Buttons がオンの状態で plugin.xml で指定した anchor の場所のボタン(ここではWindow右端)

f:id:androhi:20150723231510p:plain

ToolWindow に ToolBar を設置する

下図のように ToolWindow 内には、何かしらのボタン郡があるものが多いと思います。この領域は ToolBar と呼ばれるものらしく、コンテンツ部分とは独立して配置されるようです。

f:id:androhi:20150723233841p:plain

このパターンの ToolWindow には、SimpleToolWindowPanel という JPanel のサブクラスを使うのが一般的らしいです。(主に Intellij CE のコードを見て知りました。)

public class DrawableViewer extends SimpleToolWindowPanel {

    public DrawableViewer(final Project project) {
        super(true, true);
        this.project = project;

        setToolbar(createToolbarPanel());
        setContent(createContentPanel());
    }

    private JComponent createToolbarPanel() {
        final DefaultActionGroup actionGroup = new DefaultActionGroup();
        actionGroup.add(new AnAction());
        final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar("AndroidDrawableViewer", actionGroup, true);
        return actionToolbar.getComponent();
    }

    private JScrollPane createContentPanel() {
        String projectPath = project.getBasePath();
        ...
    }
    ....
}

上のコードで、setToolBar(JComponent) メソッドで ToolBar 領域の UI を、setContent(JComponent) メソッドで Content 領域の UI をそれぞれセットする仕組みになっています。

setContent() の方は、割といろいろな JComponent がセットできますが、setToolBar() の方は AnAction クラスもしくはサブクラス(要はアイコンボタン)のグループを内包した ActionToolbar の Component をセットするのがセオリーなようです。

ここでは見やすいように new Action() をセットしていますが、実際には固有のアイコンや処理を定義するので、AnAction のサブクラスのインスタンスをセットします。そのあたりは、GitHubのコード で全体を見ていただいた方が分かり易いかもしれません。

ToolWindow の表示更新

これがかなり悩みました。

以下に書いた内容も正しいか自信がありません。もっとよい方法をご存知の方は、ぜひ教えてください。

やりたかったことは、Action があったときに ToolWindow のコンテンツを新しいデータを反映した後、再描画するという内容です。

デコンパイルした Intellij 側のコード見たりいろいろと試したのですが再描画できず、結局コンテンツを再生成してセットし直すことで、期待した動きになりました。

以下は先程の ToolBar のボタンをクリックしたときのアクションクラスです。

public class EditTargetResDirAction extends AnAction {

    public EditTargetResDirAction() {
        super("Edit directory", "Edit target resource directory", AllIcons.General.Settings);
    }

    @Override
    public void actionPerformed(AnActionEvent anActionEvent) {
        Project project = anActionEvent.getProject();
        ...
        final VirtualFile file = FileChooser.chooseFile(descriptor, project, selectDir);
        if (file != null) {
            ...
            resetContent(project);
        }
    }

    private void resetContent(Project project) {
        DrawableViewer drawableViewer = new DrawableViewer(project);
        ContentManager contentManager = ToolWindowManager.getInstance(project)
                .getToolWindow(DrawableViewer.TOOL_WINDOW_ID).getContentManager();
        Content content = contentManager.getFactory().createContent(drawableViewer, null, false);

        contentManager.removeAllContents(true);
        contentManager.addContent(content);
    }

getToolWindow(String) メソッドに plugin.xml に記述した ToolWindow の ID を渡すと同じ ToolWindow が取得できるので、それに対して再生成した JComponent インスタンス(=drawableViewer)をセットし直しています。 セットするときは、Add するのでその前に古い方は Remove しています。

まとめ

ToolWindow まわりは Swing では無く Intellij の仕組みなので、特に公開されてる情報が少なく途中で心が折れかけました。

でも ToolWindow で実現する Plugin は、そのコンテンツを見ながらコードを書けたりするので、いろいろと楽しい Plugin が作れるんじゃないかなーと感じました。

加えて Intellij IDEA 14 から実装されたデコンパイル機能が、ものすごく重宝しました。これのおかげで公開まで辿りつけた気がします。

そしてめでたく JetBrains Plugin Repository に公開されたので、もし興味ある方は感想などいただけると嬉しいです。

AndroidDrawableViewer f:id:androhi:20150723230925p:plain

参考にしたサイト

Android Drawable Viewer という Intellij Plugin を作ってます

Pluginを作り始めた経緯など

Androidアプリ開発をしていて、今のプロジェクトのdrawable配下の画像を一覧で確認したいなと思い、そういったIntellij IDEA Pluginを探したのですが見つからなかったので自作することにしました。

プロジェクトも比較的大きなものだと、いろいろな画像ファイルをアプリにバンドルします。特にアイコン関連が多くなりがちです。 さらにAndroidでは様々なディスプレイ解像度に対応するため、同じ画像を2パターン、3パターン、あるいはそれ以上必要になってくるかと思います。

そのあたりの管理うんぬんの前に、画像のプレビュー/ファイル名/対応する解像度など、全体を把握したいことが多々ありました。今作っているのは、それらを解決するためのPluginです。

どんなPluginか?

Androidプロジェクトのdrawableフォルダに入っている画像ファイルの情報を、リスト化するPluginです。まだbeta版ですが、ソースGitHubに上げています。

実行サンプルは、こんな感じです。

f:id:androhi:20150716024808p:plain

今後追加したい機能

まだまだ機能やUIは改善すべきとこが多く、継続してアップデートしたいと思っています。 直近で検討している改善点は、以下のようなものです。

  • リソースディレクトリの設定
  • 画像の詳細情報(サイズや色)の表示
  • 任意のdrawableフォルダの絞り込み表示

このあたりが実装できたら、Jetbrains Plugin Repositoryにも公開したいなと考えています。

Plugin作成で参考にしたサイト

Intellij IDEAを触ったことがあれば、実装自体はそれほど難しくない気がします。個人的にはSwingのUI Componentsを初めて触ったので、その使い方に四苦八苦してます。

以下、メインで参考にさせて頂いたサイトなどです。執筆者や開発者の方々、ありがとうございます。

JRebel for Android betaを試してみました

JRebel for Android とは?

ZeroTurnaround社のプロダクトの一つで、Androidアプリ開発を爆速にしようとするツールのようです。キャッチコピーは、Live Android Developmentです。

今はまだベータ版で、招待制になっています。

導入方法

注意

invitationを受け取ったメールアドレスでactivateしないと、このツールは使えません。 使ってみたい場合は、この公式サイトからbeta版を申し込みましょう。

1. Pluginのインストール

IntelliJ IDEA pluginとして提供されているので、AndroidStudioのPreference > Pluginからインストール出来ます。今回は限定的な公開なので、専用のリポジトリを指定してそこからインストールしました。

AndroidStudioを再起動すると、Activateするためのダイアログが表示され、招待されたメールアドレスを入力すると以下のようなダイアログが出て、インストールが完了しました。

f:id:androhi:20150618020812p:plain

2. Run with JRebel for Androidを実行

とりあえずJRebel for Androidが追加したRunボタンがあるから、それを実行しろとのことなので、適当なプロジェクトを開きました。確かにそのようなボタンが増えてました。ロケットですね。

f:id:androhi:20150618020827p:plain

最初にこのRun with JRebel for Androidを実行すると、何かイニシャライズしてるのか分かりませんが、通常のGradleでのビルドよりちょっと時間がかかりました。

初回のビルドが終わると、自動で以下のようにbuild.gradleにpluginの記述が追記され、専用のコンソールがオープンされました。

apply plugin: 'com.zeroturnaround.jrebel.android'

f:id:androhi:20150618020846p:plain

使い方

1. Run with JRebel for Android

準備として、必ず最初にRun with JRebel for Androidで実機にアプリを、インストール&起動させる必要があるようです。そうするとJRebel for Androidのコンソールに、"Application started and ready for use." と出ます。

同じく実機の方にも、"JRebel for Android started" のToastが表示されました。

f:id:androhi:20150618020905p:plain

2. Make Module

1の状態を保ったまま、ソースコードなりリソースなり修正します。修正が終わったら、Build > Make Module 'app' を実行します。ショートカットだと shift+command+F9です。

しばらくするとアプリの表示が更新され、"Reloaded classes" とまたもやToastが表示されました。

f:id:androhi:20150618020924p:plain

感想

試した環境はこんな感じです。

  • MacBookAir Mid 2011
  • AndroidStudio 1.3 Preview3
  • Xperia Z1f
  • サンプルコード程度のかなり小さいプロジェクト

5回ほど更新してみたところ、だいたい10秒前後で実機に反映されました。 確かにこれなら実機で爆速開発が出来そうな気がしました。特にリソースの変更をすぐに実機で確認したいとか、よくあるケースなので神ツールになりそうな感じがします。

ちなみに、実機側でも "Service connected" とToastが出るので、何かServiceが動いてるようです。が、どれがJRebelのかは分かりませんでした。

Design Support Library v22.2.0について Part 3

Part 1ではTextInputLayoutとFloatingActionButtonについて、Part 2ではSnackbarとCoordinatorLayoutについて調べたことを、それぞれ書きました。

今回は、TabLayoutについて書いていきます。

TabLayoutの概要

リファレンスを確認すると、TabLayoutクラスはHorizontalScrollViewクラスを継承していました。 API Level 20まで使われていたActionBar.Tabなどとは、関連がないようです。 Tabの生成にはTabLayout.TabクラスnewTab()メソッドを使います。

MaterialDesignにおけるTabの使い方は、デザインガイドラインで細かく指定されています。 このクラスの見た目や振る舞いは、ガイドラインに沿った形で実装されているようです。

基本的な使い方

とりあえずTabを画面に配置するために、各Tabのコンテンツは一旦無視して、以下のように実装してみました。

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

    <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

</RelativeLayout>
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.addTab(tabLayout.newTab().setText("tab 1"));
        tabLayout.addTab(tabLayout.newTab().setText("tab 2"));
        tabLayout.addTab(tabLayout.newTab().setText("tab 3"));
    }
}

f:id:androhi:20150617014729p:plain

やっていることは、newTab()メソッドでタブを生成してsetText()メソッドでタブのタイトルを設定しているだけですが、デフォルトでも以下のような点が実現出来てました。

  • タブのタイトルが自動でAllCaps
  • タブのインジケーターがアニメーションする
  • カレントでないタブのタイトル色が薄くなる

またsetIcon()メソッドでアイコンをタブに表示することも出来ます。アイコンサイズは24dpです。

tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.ic_event_grey_600_24dp));
tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.ic_devices_grey_600_24dp));
tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.ic_directions_subway_grey_600_24dp));

f:id:androhi:20150617014754p:plain

ただし、setText()setIcon()を両方使うと横並びに表示されてしまい、ガイドラインTabs with icons and textとは異なってしまいました。この場合は、setCustomView()メソッドガイドラインに沿うよう、補うしかなさそうです。

f:id:androhi:20150617014814p:plain

ModeとGravity

ガイドラインを見ると、Tabには以下のような2つのModeと2種類の見た目について、記述されています。

  • Mode : Fixed / Scrollable
  • Gravity : Center / Fill

それぞれsetTabMode()メソッドsetTabGravity()メソッドで指定出来ました。

1. Fixed & Center

f:id:androhi:20150617014835p:plain

2. Fixed & Fill

f:id:androhi:20150617014858p:plain

3. Scrollable & Fill

f:id:androhi:20150617014921p:plain

4. Scrollable & Center

f:id:androhi:20150617014936p:plain

ViewPagerとの併用

リファレンスにも書かれているように、TabLayoutクラスはViewPagerと併用するためのインターフェースやメソッドが用意されています。 個人的には、これがTabLayoutクラスを使う醍醐味だと感じました。

セットする方法は2パターン用意されていて、簡単に言うとマニュアル方式とオートマチック方式です。 マニュアル方式だと、以下の3ステップで設定します。

  1. TabLayout#setTabsFromPagerAdapter(PagerAdapter)メソッドでPagerAdapterをTabLayoutにセットする
  2. TabLayout.TabLayoutOnPageChangeListenerインターフェースでTabの切り替えイベントをViewPagerにリンクする
  3. TabLayout.ViewPagerOnTabSelectedListenerインターフェースでViewPagerの切り替えイベントをTabLayoutにリンクする

オートマチック方式では、上記3ステップと同等の処理を1つのメソッド(TabLayout#setupWithViewPager(ViewPager))の内部で、全てやってくれるそうです。

ちなみにどちらの方式も、タブタイトルは内部でPagerAdapter#getPageTitle(int)メソッドから取ってきたものを、セットするとのことです。

これらを踏まえてコードで書くと、以下のようになりました。

<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=".MainActivity">

    <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />
    
    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tabs"
        />

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

    <TextView
        android:id="@+id/page_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="32sp"
        />

</RelativeLayout>
public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
        ViewPager viewPager = (ViewPager) findViewById(R.id.pager);

        FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                return TestFragment.newInstance(position + 1);
            }

            @Override
            public CharSequence getPageTitle(int position) {
                return "tab " + (position + 1);
            }

            @Override
            public int getCount() {
                return 3;
            }
        };

        viewPager.setAdapter(adapter);
        viewPager.addOnPageChangeListener(this);

        //オートマチック方式: これだけで両方syncする
        tabLayout.setupWithViewPager(viewPager);

        //マニュアル方式: これでViewPagerのPositionとTabのPositionをsyncさせるらしい
        //tabLayout.setTabsFromPagerAdapter(adapter);
        //viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
        //tabLayout.setOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager));

    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        Log.d("MainActivity", "onPageSelected() position="+position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }

    public static class TestFragment extends Fragment {

        public TestFragment() {
        }

        public static TestFragment newInstance(int page) {
            Bundle args = new Bundle();
            args.putInt("page", page);
            TestFragment fragment = new TestFragment();
            fragment.setArguments(args);
            return fragment;
        }

        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            int page = getArguments().getInt("page", 0);
            View view = inflater.inflate(R.layout.fragment_test, container, false);
            ((TextView) view.findViewById(R.id.page_text)).setText("Page " + page);
            return view;
        }
    }
}

f:id:androhi:20150617015000p:plain

もしタブタイトルにアイコンを設定したい場合は、getPageTitle()でnullなどを返してTabLayoutにViewPagerをセットした後で、setIcon()を使えば良さそうです。

先程のコードを、以下のように書き換えました。(変更がない箇所は省略しています)

...
@Override
public CharSequence getPageTitle(int position) {
    return null;
}

...
        
//オートマチック方式: これだけで両方syncする
tabLayout.setupWithViewPager(viewPager);

//アイコンセット
tabLayout.getTabAt(0).setIcon(R.drawable.ic_devices_grey_600_24dp);
tabLayout.getTabAt(1).setIcon(R.drawable.ic_directions_subway_grey_600_24dp);
tabLayout.getTabAt(2).setIcon(R.drawable.ic_event_grey_600_24dp);
...

f:id:androhi:20150617015019p:plain

ブランドカラーの設定

以下のようにブランドカラーを設定する場合は、ほぼToolbarと同じ設定の仕方でした。

DarkかLiteかは親のテーマを引き継ぎますし、背景色はandroid:background=?attr/colorPrimaryの記述が必要です。 インジケーターには特に何も指定しなくてもcolorAccentが適用されるようです。

もしタブタイトルのテキスト色を変えたい場合は、setTabTextColors(ColorStateList)メソッドsetTabTextColors(int, int)メソッドを使って変更出来ます。

<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
    <item name="colorPrimary">#3f51b5</item>
    <item name="colorPrimaryDark">#303f9f</item>
    <item name="colorAccent">#ff4081</item>
</style>
<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=".MainActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        />

    <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/toolbar"
        android:background="?attr/colorPrimary"
        />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tabs"
        />

</RelativeLayout>

f:id:androhi:20150617015046p:plain

まとめ

TabLayoutいいクラスですね。タブの実装が、かなり楽になりそうだなと思いました。 特にViewPagerと使うことを十分に考慮してあるのが、とても助かります。

ただタブタイトルを自動でgetPageTitle()からセット出来たりするとこまでやってくれるのであれば、いっそTabPagerViewクラスとか作ってしまってsetTabEnable(boolean)メソッドとかでタブが生えたり消えたり出来たら良かったなーとも思いました。

とはいえ、ほとんど場合は今まで通りViewPagerを生成して、TabLayout#setupWithViewPager(PagerAdapter)を呼べば済みそうなので、本当に便利なクラスだと思います。

世界に羽ばたく!! Product Manager Night に参加してきました

TL;DR

以下、イベントページからの抜粋です。

シリコンバレー界隈では「Engineering BackgroundをもったProductManagerが重要だ」 と言われEngineerのNext StepとしてのProduct Managerが定着してきているように思います。 日本においてもそういったProduct Managerが増えてきてるのではないでしょうか?

エンジニアからProductManagerになるといいこと多いし、すごくやりがいのある職種だよという話しが盛り沢山のイベントでした。

概要

※ 資料は近日公開らしいです(たぶんイベントページに掲載されるはず)

今回勉強になったこと

4名の方が発表されたのですが、それぞれ経験則に基づいたProductManager論をまとめていて、どなたの発表もとても面白かったです。 資料は公開されるそうなので、細かい部分はそれを見たほうが参考になると思うので、この記事では個人的に勉強になったことをまとめようと思います。

ProductManagerの役割について

発表者の共通的な意見として

  • とにかくProductに関わるもの(チーム/開発/ビジネス)は全てやる
  • それらを成功に導く

といったことが、大枠での役割とのことです。 ただし、所属企業の規模や事業内容、または個人のスキルセットなどで変わってくるので、一概にこれというものも無いようです。

とはいえ、みなさん口を揃えておっしゃってたのが、「誰よりもプロダクトを理解し、プロダクトを愛して、それを周りに伝えること」が最も重要な役割だということです。

エンジニアがなるProductManagerになるメリット

エンジニアリングをバックグラウンドに持っていることによって技術的な判断が加味されて、いろいろな決断の精度が上がったり、開発工程にも積極的にコミット出来るという強みが生まれること。 という感じでした。

あとは純粋に「エンジニアは技術が分かるリーダーを好むよね」という話しも。

エンジニアだったが故のデメリット

まとめると、自分基準になってしまうことに尽きるようです。 これはスケジュールや技術的な判断など、どうしても自分の経験から導き出してしまう弊害があるようです。

例えば、この開発は(俺だったら)3日くらいかな、こういう要件の実装は(俺だったら)ちょっと難しそう、などといった無意識な「俺だったら」が出てしまうんだそうです。

ProductManagerってどうなの?

  • とにかくやること多くて忙しい
  • 市場やサービスを作ってる感のやりがいは半端ない
  • エンジニアこそなるべき

具体的には、チームマネジメントもプロダクトのマネジメントも、全てが技術的な理解が欠かせないので、そういったことに長けてるエンジニアがProductManagerになることのメリット多いとのことです。

まとめ

ちょうど先日メルカリさんの記事にも、似たような話しが出てきて気になっていたので、個人的にもいろいろ考えていきたいなと思えるようになりました。

エンジニアのキャリアアップって、いろんな方向性があって本当に難しいと思います。その中の一つの道としてProductManagerについて勉強できたので、このイベントに参加してよかったです。

登壇者の方、主催のスマートニュースの方には、よいイベントを開いて下さって本当に感謝しています。ありがとうございました! 特にエンジニアからProductManagerにキャリアアップされた方の生の声が聞けたことで、具体的なお話がとても参考になりました。