android - 根据 Material 设计指南实现 SearchView

标签 android material-design searchview material-components-android

我一直在寻找根据 material design guidelines. 在 Activity 工具栏(操作栏)中实现搜索 View 的方法。

单击搜索图标时,整个工具栏会显示动画效果,只有带有白色背景的搜索 EditText 和建议出现在主视图中,而不是下拉菜单。

以下是指南的屏幕截图: enter image description here

这是来自 Gmail 收件箱实现的 gif: enter image description here

我一直在寻找代码示例和教程,但到目前为止我没有成功。我该怎么做?

最佳答案

我尝试了几个 Material SearchView 库,但没有一个像支持库中的那样好,所以我决定重新设计它,经过大量工作,我对结果很满意:

enter image description here

你可以这样做:

1) 将 SearchView 项添加到您的菜单中

<item
    android:id="@+id/m_search"
    android:icon="@drawable/ic_action_search"
    android:title="@string/search_title"
    app:actionLayout="@layout/search_view_layout"
    app:showAsAction="ifRoom|collapseActionView" />

请注意,我声明的是 actionLayout 而不是 actionViewClass,我认为这是将 SearchView 主题与 Toolbar 主题分开设置的唯一方法。

search_view_layout.xml:

<android.support.v7.widget.SearchView
    android:id="@+id/search_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/SearchViewTheme" />

2) 将自定义 SearchView 主题添加到您的样式中,同时在您的 Toolbar 主题中声明 SearchView 主题:

<style name="SearchViewTheme" parent="Widget.AppCompat.SearchView.ActionBar">
    <item name="layout">@layout/toolbar_search_view</item>
    <item name="commitIcon">@drawable/ic_search_commit</item>
    <item name="colorControlNormal">@color/material_light_active_icon</item>
    <item name="colorControlHighlight">@color/material_ripple_light</item>
    <item name="autoCompleteTextViewStyle">@style/AutoCompleteTextViewStyle</item>
    <item name="suggestionRowLayout">@layout/search_view_suggestion_row</item>
    <item name="android:maxWidth">9999dp</item>
</style>

<style name="AutoCompleteTextViewStyle" parent="Widget.AppCompat.Light.AutoCompleteTextView">
    <item name="android:popupBackground">@drawable/search_suggestions_bg</item>
    <item name="android:popupElevation">0dp</item>
</style>

<style name="ToolbarTheme" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
    <item name="searchViewStyle">@style/SearchViewTheme</item>
</style>

toolbar_search_view.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/search_bar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:paddingEnd="8dp">

<!-- This is actually used for the badge icon *or* the badge label (or neither) -->
<TextView
    android:id="@+id/search_badge"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_marginBottom="2dp"
    android:drawablePadding="0dp"
    android:gravity="center_vertical"
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="?android:attr/textColorPrimary"
    android:visibility="gone" />

<ImageView
    android:id="@+id/search_button"
    style="?attr/actionButtonStyle"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:contentDescription="@string/abc_searchview_description_search"
    android:focusable="true" />

<LinearLayout
    android:id="@+id/search_edit_frame"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:layoutDirection="locale"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/search_mag_icon"
        style="@style/RtlOverlay.Widget.AppCompat.SearchView.MagIcon"
        android:layout_width="@dimen/abc_dropdownitem_icon_width"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:scaleType="centerInside"
        android:visibility="gone" />

    <!-- Inner layout contains the app icon, button(s) and EditText -->
    <LinearLayout
        android:id="@+id/search_plate"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:orientation="horizontal">

        <view
            android:id="@+id/search_src_text"
            class="android.support.v7.widget.SearchView$SearchAutoComplete"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical"
            android:layout_marginEnd="@dimen/item_list_horizontal_margin"
            android:layout_marginStart="@dimen/item_list_horizontal_margin"
            android:layout_weight="1"
            android:background="@null"
            android:dropDownAnchor="@id/anchor_dropdown"
            android:dropDownHeight="wrap_content"
            android:dropDownHorizontalOffset="0dp"
            android:dropDownVerticalOffset="0dp"
            android:ellipsize="end"
            android:imeOptions="actionSearch"
            android:inputType="text|textAutoComplete|textNoSuggestions"
            android:maxLines="1"
            android:paddingEnd="8dp"
            android:textColor="@android:color/black"
            android:textColorHint="@color/material_light_hint_text"
            android:textSize="20sp" />

        <ImageView
            android:id="@+id/search_close_btn"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:contentDescription="@string/abc_searchview_description_clear"
            android:focusable="true"
            android:paddingEnd="8dp"
            android:paddingStart="8dp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/submit_area"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/search_go_btn"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:contentDescription="@string/abc_searchview_description_submit"
            android:focusable="true"
            android:paddingEnd="8dp"
            android:paddingStart="8dp"
            android:visibility="gone" />

        <ImageView
            android:id="@+id/search_voice_btn"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:contentDescription="@string/abc_searchview_description_voice"
            android:focusable="true"
            android:paddingEnd="8dp"
            android:paddingStart="8dp"
            android:visibility="gone" />
    </LinearLayout>
</LinearLayout>

请注意,我在工具栏 View 下添加了 anchor 下拉 View ,因此建议将获得全屏宽度。

<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/appBar"
    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:collapseIcon="@drawable/ic_search_collapse"
    app:popupTheme="@style/AppTheme.PopupOverlay"
    app:theme="@style/ToolbarTheme" />

<View
    android:id="@+id/anchor_dropdown"
    android:layout_width="match_parent"
    android:layout_height="0dp" />

</android.support.design.widget.AppBarLayout>

search_view_suggestion_row.xml:

(如果您想在建议之间设置分隔符,请更改建议分隔符的可见性):

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="58dp"
    android:theme="@style/Theme.AppCompat.DayNight">

<!-- Icons come first in the layout, since their placement doesn't depend on
     the placement of the text views. -->
<ImageView
    android:id="@android:id/icon1"
    style="@style/RtlOverlay.Widget.AppCompat.Search.DropDown.Icon1"
    android:layout_width="56dp"
    android:layout_height="56dp"
    android:layout_alignParentBottom="true"
    android:layout_alignParentTop="true"
    android:scaleType="centerInside"
    android:visibility="invisible" />

<ImageView
    android:id="@+id/edit_query"
    style="@style/RtlOverlay.Widget.AppCompat.Search.DropDown.Query"
    android:layout_width="56dp"
    android:layout_height="56dp"
    android:layout_alignParentBottom="true"
    android:layout_alignParentTop="true"
    android:background="?attr/selectableItemBackground"
    android:scaleType="centerInside"
    android:visibility="gone" />

<ImageView
    android:id="@id/android:icon2"
    style="@style/RtlOverlay.Widget.AppCompat.Search.DropDown.Icon2"
    android:layout_width="56dp"
    android:layout_height="56dp"
    android:layout_alignParentBottom="true"
    android:layout_alignParentTop="true"
    android:layout_alignWithParentIfMissing="true"
    android:scaleType="centerInside"
    android:visibility="gone" />

<!-- The subtitle comes before the title, since the height of the title depends on whether the
     subtitle is visible or gone. -->
<TextView
    android:id="@android:id/text2"
    style="?android:attr/dropDownItemStyle"
    android:layout_width="match_parent"
    android:layout_height="29dp"
    android:layout_alignParentBottom="true"
    android:layout_alignWithParentIfMissing="true"
    android:gravity="top"
    android:maxLines="1"
    android:paddingBottom="4dp"
    android:textColor="?android:textColorSecondary"
    android:textSize="12sp"
    android:visibility="gone" />

<!-- The title is placed above the subtitle, if there is one. If there is no
     subtitle, it fills the parent. -->
<TextView
    android:id="@android:id/text1"
    style="?android:attr/dropDownItemStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@android:id/text2"
    android:layout_centerVertical="true"
    android:ellipsize="end"
    android:maxLines="1"
    android:scrollHorizontally="false"
    android:textColor="?android:textColorPrimary"
    android:textSize="16sp" />

<View
    android:id="@+id/suggestion_divider"
    android:layout_width="match_parent"
    android:layout_height="0.5dp"
    android:layout_alignParentBottom="true"
    android:layout_alignStart="@android:id/text1"
    android:layout_marginStart="8dp"
    android:background="@color/divider_color"
    android:visibility="gone" />

建议背景和提交图标是定制的,我使用的其余图标可以在以下位置找到:https://material.io/icons/

ic_search_commit.xml:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:autoMirrored="true"
    android:viewportHeight="24.0"
    android:viewportWidth="24.0">
    <path
        android:fillColor="@color/active_icon_color"
        android:pathData="m18.364,16.95l-8.605,-8.605l7.905,-0l-0.007,-2.001l-11.314,0l0,11.314l1.994,-0l0.007,-7.898l8.605,8.605l1.414,-1.414z" />

search_suggestions_bg.xml:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
    <shape android:shape="rectangle">
        <padding android:top="0.5dp" />
        <stroke
            android:width="0.5dp"
            android:color="@color/divider_color" />
    </shape>
</item>
<item>
    <shape android:shape="rectangle">
        <solid android:color="@color/cards_and_dialogs_color" />
    </shape>
</item>
</layer-list>

在您的 colors.xml 中添加以下值(仅当您使用 DayNight 主题时才添加值-night):

值/colors.xml

<color name="material_light_primary_text">#DE000000</color>
<color name="material_light_hint_text">#61000000</color>
<color name="material_light_active_icon">#8A000000</color>
<color name="material_ripple_light">#1F000000</color>
<color name="divider_color">#1F000000</color>
<color name="active_icon_color">#8A000000</color>
<color name="cards_and_dialogs_color">@android:color/white</color>
<color name="quantum_grey_600">#757575</color>

values-night/colors.xml:

<color name="divider_color">#1FFFFFFF</color>
<color name="active_icon_color">@android:color/white</color>
<color name="cards_and_dialogs_color">#424242</color>

3) 最后一部分,让魔法发生在代码中:

在您想要的 Activity 中设置和初始化 SearchView

private MenuItem mSearchItem;
private Toolbar mToolbar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    mToolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(mToolbar);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);

    mSearchItem = menu.findItem(R.id.m_search);

    MenuItemCompat.setOnActionExpandListener(mSearchItem, new MenuItemCompat.OnActionExpandListener() {
        @Override
        public boolean onMenuItemActionCollapse(MenuItem item) {
            // Called when SearchView is collapsing
            if (mSearchItem.isActionViewExpanded()) {
                animateSearchToolbar(1, false, false);
            }
            return true;
        }

        @Override
        public boolean onMenuItemActionExpand(MenuItem item) {
            // Called when SearchView is expanding
            animateSearchToolbar(1, true, true);
            return true;
        }
    });

    return true;
}

public void animateSearchToolbar(int numberOfMenuIcon, boolean containsOverflow, boolean show) {

    mToolbar.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white));
    mDrawerLayout.setStatusBarBackgroundColor(ContextCompat.getColor(this, R.color.quantum_grey_600));

    if (show) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            int width = mToolbar.getWidth() -
                    (containsOverflow ? getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_overflow_material) : 0) -
                    ((getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_material) * numberOfMenuIcon) / 2);
            Animator createCircularReveal = ViewAnimationUtils.createCircularReveal(mToolbar,
                    isRtl(getResources()) ? mToolbar.getWidth() - width : width, mToolbar.getHeight() / 2, 0.0f, (float) width);
            createCircularReveal.setDuration(250);
            createCircularReveal.start();
        } else {
            TranslateAnimation translateAnimation = new TranslateAnimation(0.0f, 0.0f, (float) (-mToolbar.getHeight()), 0.0f);
            translateAnimation.setDuration(220);
            mToolbar.clearAnimation();
            mToolbar.startAnimation(translateAnimation);
        }
    } else {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            int width = mToolbar.getWidth() -
                    (containsOverflow ? getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_overflow_material) : 0) -
                    ((getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_material) * numberOfMenuIcon) / 2);
            Animator createCircularReveal = ViewAnimationUtils.createCircularReveal(mToolbar,
                    isRtl(getResources()) ? mToolbar.getWidth() - width : width, mToolbar.getHeight() / 2, (float) width, 0.0f);
            createCircularReveal.setDuration(250);
            createCircularReveal.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mToolbar.setBackgroundColor(getThemeColor(MainActivity.this, R.attr.colorPrimary));
                    mDrawerLayout.setStatusBarBackgroundColor(getThemeColor(MainActivity.this, R.attr.colorPrimaryDark));
                }
            });
            createCircularReveal.start();
        } else {
            AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
            Animation translateAnimation = new TranslateAnimation(0.0f, 0.0f, 0.0f, (float) (-mToolbar.getHeight()));
            AnimationSet animationSet = new AnimationSet(true);
            animationSet.addAnimation(alphaAnimation);
            animationSet.addAnimation(translateAnimation);
            animationSet.setDuration(220);
            animationSet.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    mToolbar.setBackgroundColor(getThemeColor(MainActivity.this, R.attr.colorPrimary));
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
            mToolbar.startAnimation(animationSet);
        }
        mDrawerLayout.setStatusBarBackgroundColor(getThemeColor(MainActivity.this, R.attr.colorPrimaryDark));
    }
}

private boolean isRtl(Resources resources) {
    return resources.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}

private static int getThemeColor(Context context, int id) {
    Resources.Theme theme = context.getTheme();
    TypedArray a = theme.obtainStyledAttributes(new int[]{id});
    int result = a.getColor(0, 0);
    a.recycle();
    return result;
}

关于代码的几点注意事项:

1) 动画会根据你设置的菜单项数量调整它的起点,如果工具栏有溢出图标,它会自动检测布局是 LTR 还是 RTL。

2) 我正在使用抽屉导航 Activity ,所以我将 StatusBar 颜色设置为 mDrawerLayout,如果您使用常规 Activity ,您可以这样设置 StatusBar 颜色:

getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.quantum_grey_600));

3) 循环显示动画仅适用于 KitKat 及以上版本。

关于android - 根据 Material 设计指南实现 SearchView,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30369246/

相关文章:

java - GitHub 和 Android studio 没有 gradle.build

android - 如何在android studio中启用dex编译器D8(Next-generation Dex Compiler)

android - Material 日期选择器选择的日期圈不在中心,如何使其适合中心?

android - 没有焦点的搜索 View 图标化

android - 在搜索 View 中更改 x 标记图标

android - 更改搜索 View 中的默认搜索图标位置

android - 单击时会启动多个 Activity

android - Play 核心应用内评论 API 未显示评论 Activity

android - 后退按钮未显示在 CollapsingToolbarLayout 中

android - AppCompatDialog : Impossible to change Text Color