マテリアルデザインな検索バーを作る


Google PlayやInboxなどで使われている、Toolbarの上に重なるように表示される検索バーの実装です。

# サンプル

https://github.com/nakaken0629/MaterialSearchViewSample

シンプルなのアクティビティの場合

|通常|検索バーを開く|
|---+------------|
|![basic1.png](https://qiita-image-store.s3.amazonaws.com/0/75307/0a2f7ef1-64f9-9351-d51c-0e2b7588996d.png "basic1.png")|![basic2.png](https://qiita-image-store.s3.amazonaws.com/0/75307/3f3df51a-dcf5-6043-fd4c-e343a056767b.png "basic2.png")|

タブを含むアクティビティの場合

|通常|検索バーを開く|
|---+------------|
|![tab1.png](https://qiita-image-store.s3.amazonaws.com/0/75307/c5891825-26d0-77f5-4cda-10f1892613ca.png "tab1.png")|![tab2.png](https://qiita-image-store.s3.amazonaws.com/0/75307/31a304d6-c4b9-c50d-4de4-78b2de42c79b.png "tab2.png")|

# 実装方法

MaterialSearchViewライブラリーを利用しました。

https://github.com/MiguelCatalan/MaterialSearchView

## usage

上記のgithubのREADMEに書いてある通りですが、もう一度ここで転記しておきます。

**gradleファイルに、依存関係を追記する**

```json:build.gradle
dependencies {
    compile 'com.miguelcatalan:materialsearchview:1.4.0'
}
```

**MaterialSearchViewをレイアウト内のToolbarと同じ位置に追加する**

```xml:activity_xxx.xml
<!— Must be last for right layering display —>
<FrameLayout
    android:id="@+id/toolbar_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

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

    <com.miguelcatalan.materialsearchview.MaterialSearchView
        android:id="@+id/search_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</FrameLayout>
```

**メニューファイルにメニューのアイテムを追加する**

```xml:menu_xxx.xml
<item
    android:id="@+id/action_search"
    android:icon="@drawable/ic_action_action_search"
    android:orderInCategory="100"
    android:title="@string/abc_search_hint"
    app:showAsAction="always" />
```

**onCreateOptionsMenuメソッドに定義を追加する**

```java:ActivityXxx.java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);

    MenuItem item = menu.findItem(R.id.action_search);
    searchView.setMenuItem(item);

    return true;
}
```

**リスナーを定義する**

```java:ActivitXxx.java
searchView = (MaterialSearchView) findViewById(R.id.search_view);
searchView.setOnQueryTextListener(new MaterialSearchView.OnQueryTextListener() {
    @Override
    public boolean onQueryTextSubmit(String query) {
        //Do some magic
        return false;
    }

    @Override
    public boolean onQueryTextChange(String newText) {
        //Do some magic
        return false;
    }
});

searchView.setOnSearchViewListener(new MaterialSearchView.SearchViewListener() {
    @Override
    public void onSearchViewShown() {
        //Do some magic
    }

    @Override
    public void onSearchViewClosed() {
        //Do some magic
    }
});
```

## ライブラリーのドキュメントに書いてなくて、はまったところ

MaterialSearchViewがうまくアクションバーに重なるように表示されるには、

```
FrameLayout
+-- Toolbar
+-- MaterialSearchView
```

というように、FrameLayout中にToolbar → MaterialSearchViewという順にViewが定義されないといけない。しかし、最近のAndroidStudioが生成するEmptyActivityとTabActivityはいずれも一筋縄ではいかなかった。

### EmptyActivityの場合

AndroidStudioが作成するプロジェクトでは、アクティビティのスタイルのデフォルトが `Theme.AppCompat.Light.DarkActionBar` になっている。これには、すでにtoolbarが含まれているため、上記の手順のレイアウトを定義している時のtoolbarと被ってしまう。 `NoActionBar` を使えばいいのだが、その場合Toolbarの色やスタイルを自分で定義しなければならない。もし、デフォルトのスタイルのままでMaterialSearchViewを使いたい場合は、Toolbarが含まれているViewGroupを探し出して、コードでMaterialSearchViewをインスタンス化して追加すると良い。

```java:BasicActivity.java
public class BasicActivity extends AppCompatActivity {
    private MaterialSearchView searchView;

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

        searchView = new MaterialSearchView(this);
        searchView.setLayoutParams(new ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT, 
            ViewGroup.LayoutParams.WRAP_CONTENT
        ));

        ViewGroup viewGroup = (ViewGroup) findViewById(R.id.activity_main).getParent();
        while (viewGroup != null) {
            if (viewGroup instanceof ActionBarOverlayLayout) {
                viewGroup = (ViewGroup) viewGroup.getParent();
                viewGroup.addView(searchView);
                break;
            }
            viewGroup = (ViewGroup) viewGroup.getParent();
        }
        // 略
    }
    // 略
}
```

### TabActivityの場合

TabActivityの場合は、AndroidStudioのウィザートが `NoActionBar` を使うようにコードを生成してくれる。ただし、Toolbarと同じ階層にタブ用のビューがあるので、FrameLayoutになっていない。そのため、Toolbarの部分をFrameLayoutでさらに囲み、その中にMaterialSearchViewを入れると良い。

```xml:activity_tab.xml
<!-- 略 -->
    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/appbar_padding_top"
        android:theme="@style/AppTheme.AppBarOverlay">

        <!-- ToolbarをFrameLayoutで囲み、MaterialSearchViewを追加 -->
        <FrameLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|enterAlways"
                app:popupTheme="@style/AppTheme.PopupOverlay">

            </android.support.v7.widget.Toolbar>

            <com.miguelcatalan.materialsearchview.MaterialSearchView
                android:id="@+id/search_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </FrameLayout>

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

    </android.support.design.widget.AppBarLayout>
<!-- 略 -->
```

# TODO

- 検索バーで検索が行われた後の処理は書いていない。
- タブごとに検索バーの表示を制御する処理は書いていない。