Android の AppWidget で Snackbar を使う

# 概要
Android の AppWidget で [Snackbar](https://material.io/guidelines/components/snackbars-toasts.html#snackbars-toasts-specs) を使う方法について書きます。

## What is "Snackbar"?
Toast に似た Android のポップアップ機能です。画面下からせり出し、一定時間が経過したら画面下へと引っ込みます。

## 特徴
- 色を変えられる
- ユーザに追加のアクションを提示できる
- 下の方に出るので画面描画を阻害しない
- Toast と同じようにすぐ消えるので、重要な通知ではなく確認程度に使う

----

# 実装
では早速使ってみましょう。

## 依存の追加
app/build.gradle に Design Support library の依存を追記して、sync してください。

```groovy:app/build.gradle
dependencies {
    def supportLibVersion = '25.3.1'
    compile 'com.android.support:appcompat-v7:' + supportLibVersion
    compile 'com.android.support:support-v4:'   + supportLibVersion
    compile 'com.android.support:design:'       + supportLibVersion
}
```

## メッセージを表示
ごく簡単なものは下記のコードで表示できます。見たところ、Context が View になっているほかは Toast とほぼ同じように書くことができます。

```java:メッセージを表示
Snackbar.make(view, "Text", Snackbar.LENGTH_SHORT).show();
```

## 色を変える
Snackbar は色を変えることが可能です。その場合、一旦オブジェクトを保持して、getView() メソッドで View を取得して、```setBackgroundColor``` で任意の色を設定します。もちろん透過させることも可能です。

```java:色の変更
final Snackbar snackbar = Snackbar.make(view, "Text", Snackbar.LENGTH_SHORT);
snackbar.getView().setBackgroundColor(color);
```

## Action
Snackbar はユーザに追加アクションを提示することが可能です。ラベルの Text と、押された時の Action を定義した Listener を setAction で渡します。

```java:追加アクションの設定
snackbar.setAction("Next!", new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                Toast.makeText(v.getContext(), "Why?", Toast.LENGTH_SHORT).show();
            }
});
```

## 注意
- AppCompatActivity でないと使えない……レガシーアプリで TabActivity を継承しているような場合は諦める必要がある
- View を必要とする……View を持たない箇所、特に AppWidget では使えない

----
# AppWidget で Snackbar を使う
前述の通り、View を直接操作できない AppWidget では Snackbar を使うことができません。AppWidget 主体のアプリで、せっかくの機能を使えないのは残念なので、何とかならないかと考えました。
正攻法ではないですが、1つのアイデアとしてダミーの透明な Activity を表示し、そこの View を使って Snackbar を表示するというやり方を考えてみました。

## サンプルコード
[GitHub リポジトリ](https://github.com/toastkidjp/SnackbarExample/tree/qiita) にアップしてあります。

## サンプル GIF
![snackbar.gif](https://qiita-image-store.s3.amazonaws.com/0/102004/43c4c0f6-f005-3e92-2d9d-a23e79338e4f.gif)

AppWidget の各色をタップすると、その色の Snackbar が表示され、"Next!" と書かれたラベルをタップすると "Why?" という Toast を表示します。新旧共演です。

## 実装
[DummyActivity](https://github.com/toastkidjp/SnackbarExample/blob/qiita/app/src/main/java/jp/toastkid/snackbarexample/DummyActivity.java)

ポイントになるのは下記の4点です。

1. AppCompatActivity を継承
2. 透明テーマを使用
3. onCreate メソッドでの ```overridePendingTransition(0, 0);``` で、 Activity 起動時の Transition を無効化
4. StatusBar の透明化(APIレベル21以降)
5. 自動で finish する Executor タスクの登録
6. finish メソッドでの```overridePendingTransition(0, 0);``` で、 Activity 終了時の Transition を無効化

下記に詳述します。

### 1. AppCompatActivity を継承
Snackbar は AppCompatActivity でないと利用できないので、そうしてください。

### 2. 透明テーマを使用
下記の透明テーマを定義し、

```xml:app/src/main/res/values/styles.xml
<resources>

    <!-- ...... -->

    <style name="DummyTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:activityOpenExitAnimation">@null</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowAnimationStyle">@android:style/Animation</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>

</resources>
```

AndroidManifest.xml で設定してください。

```xml:app/src/main/AndroidManifest.xml
        <activity
            android:name=".DummyActivity"
            android:label="@string/app_name"
            android:theme="@style/DummyTheme"
            >
```

### 4. StatusBar の透明化(APIレベル21以降)
Window クラスの setStatusBarColor で上のバーを透明に、下のバーを Snackbar と同じ色に設定します。

```java:透明化
    private void setStatusBarTransparent() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = getWindow();
            window.setStatusBarColor(Color.TRANSPARENT);
            window.setNavigationBarColor(mColor);
        }
    }
```

### 5. 自動で finish する Executor タスクの登録
そのままだと透明な Activity が残ってしまって邪魔なので、非同期のタスクで DummyActivity を ```finish()``` させる必要があります。今回は Executors.newSingleThreadExecutor でシンプルにこんな風にしてみました。

```java:自動で_finish_する_Executor_タスクの登録
    private void executeFinisher() {
        final ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                finish();
            }
        });
        executorService.shutdown();
    }
```

----
# どうでもいいこと
Snackbar というのは軽食堂や移動アイスクリーム店を指すそうです。snickers ではないみたいです。

----
# 終わりに
AppWidget で Snackbar を使う方法について述べました。まともな方法ではなく、今後OSの制限で使えなくなる可能性もあるので、良い子は真似しないでください。

- Snackbar はよりデザイン性に優れユーザフレンドリなポップアップ
- ダミーの Activity を用意すれば AppWidget からでも Snackbar を使える

----
# リンク
- [サンプルコードの GitHub リポジトリ](https://github.com/toastkidjp/SnackbarExample/tree/qiita)