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


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

サンプル

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

通常 検索バーを開く

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

通常 検索バーを開く

実装方法

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

usage

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

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

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

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

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>

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

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メソッドに定義を追加する

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;
}

リスナーを定義する

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をインスタンス化して追加すると良い。

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を入れると良い。

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

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