如何制作覆盖整个 ActionBar/Toolbar 的 SearchView

How to make SearchView that covers the entire ActionBar/Toolbar

如何创建这个?

如果您尝试使用工具栏并且您已经膨胀了菜单项,您很快就会发现您的工具栏布局的宽度会缩小,因此它不会覆盖菜单项。

唯一想到的是扩充布局并将其添加到 Window 作为位于 ActionBar 顶部的叠加层。请记住,如果您不需要在 Toolbar/ActionBar 中膨胀 MenuItems,这不是问题,因为这样您就可以占据整个宽度。

所以我采用了将我的视图添加为叠加层的方法。

/**
 * A Material themed search view that should be overlayed on the Toolbar.
 */
public class SearchView extends FrameLayout implements View.onClickListener {
    // The search input
    private final EditText mSearchEditText;
    // The X button to clear the search
    private final View mClearSearch;

    // Listeners
    private onSearchListener mOnSearchListener;
    private onSearchCancelListener;

    /**
     * Interface definition for a callback to be invoked when a search is submitted.
     */
    public interface onSearchListener {
        void onSearch(String query);
    }

    /**
     * Interface definition for a callback to be invoked when a search is canceled.
     */
    public interface onSearchCancelListener {
        onCancelSearch();
    }

    public SearchView(final Context context) {
        this(context, null);
    }

    public SearchView(final Context context, final AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public SearchView(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);

        final LayoutInflater factory = LayoutInflater.from(context);
        factory.inflate(R.layout.toolbar_searchview, this);

        mSearchEditText = (EditText)findViewById(R.id.search_field);
        // Show/Hide the "X" button as text is entered/erased
        mSearchEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                toggleClearSearchButton(s);
            }

            @Override
            public void afterTextChanged(Editable s) {}
        });
        // Add listeners for "Enter" or "Search" key events
        mSearchEditText.setOnKeyListener(new OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                // Allow "Enter" key on hardware keyboard or "Search" key on soft-input to submit
                if ((event.getAction() == KeyEvent.ACTION_UP) &&
                    (keyCode == KeyEvent.KEYCODE_ENTER ||
                            keyCode == KeyEvent.KEYCODE_SEARCH)) {
                    final String query = getSearchQuery();
                    // Do nothing if query is empty
                    if (!TextUtils.isEmpty(query) && mOnSearchListener != null) {
                        mOnSearchListener.onSearch(query);
                    }
                    return true;
                }
                return false;
            }
        });

        findViewById(R.id.cancel_search).setOnClickListener(this);
        mClearSearch = findViewById(R.id.clear_search);
        mClearSearch.setOnClickListener(this);

        // Hide this view until we're ready to show it
        setVisibility(View.GONE);
        setBackgroundColor(Color.WHITE);
    }

    /**
     * Register a callback to be invoked which a search is submitted.
     *
     * @param l the callback that will run
     */
    public void setOnSearchListener(final onSearchListener l) {
        mOnSearchListener = l;
    }

    /**
     * Register a callback to be invoked which a search is canceled.
     *
     * @param l the callback that will run
     */
    public void setOnSearchCancelListener(final onSearchCancelListener l) {
        mOnSearchCancelListener = l;
    }

    /**
     * Sets the search query.
     *
     * @param query the query
     */
    public void setSearchQuery(final String query) {
        mSearchEditText.setText(query);
        toggleClearSearchButton(query);
    }

    /**
     * Returns the search query or null if none entered.
     */
    public String getSearchQuery() {
        return mSearchEditText.getText() != null ? mSearchEditText.getText().toString() : null;
    }

    /**
     * Returns {@code true} if the search view is visible, {@code false} otherwise.
     */
    public boolean isSearchViewVisible() {
        return getVisibility() == View.VISIBLE;
    }

    // Show the SearchView
    public void display() {
        if (isSearchViewVisible()) return;

        setVisibility(View.VISIBLE);
        setAlpha(0f);
        animate().alpha(1f)
            .setDuration(200);
    }

    // Hide the SearchView
    public void hide() {
        if (!isSearchViewVisible()) return;

        clearSearch();

        animate().alpha(0f)
            .setDuration(200);
        // Delay hiding view until animation is complete
        postDelayed(new Runnable() {
            @Override
            public void run() {
                setVisibility(View.GONE);
            }
        }, 200);
    }

    // LayoutParams for this overlay
    public static WindowManager.LayoutParams getSearchViewLayoutParams(final Activity activity) {
        final Rect rect = new Rect();
        final Window window = activity.getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rect);
        final int statusBarHeight = rect.top;

        final TypedArray actionBarSize = activity.getTheme().obtainStyledAttributes(
            new int[] {R.attr.actionBarSize});
        final int actionBarHeight = actionBarSize.getDimensionPixelSize(0, 0);
        actionBarSize.recycle();

        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
            rect.right /* This ensures we don't go under the navigation bar in landscape */,
            actionBarHeight,
            WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
            /* Allow touches to views outside of overlay */
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
            PixelFormat.TRANSLUCENT);

        params.gravity = Gravity.TOP | Gravity.START;
        params.x = 0;
        params.y = statusBarHeight;
        return params;
    }

    private void toggleClearSearchButton(final CharSequence query) {
        mClearSearch.setVisibility(!TextUtils.isEmpty(query) ? View.VISIBLE : View.INVISIBLE);
    }

    private void clearSearch() {
        mSearchEditText.setText("");
        mClearSearch.setVisibility(View.INVISIBLE);
    }

    private void onCancelSearch() {
        // Default is to hide this view unless an onSearchCancelListener is available
        if (mOnSearchCancelListener != null) {
            mOnSearchCancelListener.onCancelSearch();
        } else {
            hide();
        }
    }

    @Override
    public boolean dispatchKeyEvent(@NotNull final KeyEvent event) {
        // Handle "Back" key presses
        if (event.getAction() == KeyEvent.ACTION_UP &&
            event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            onCancelSearch();
            return true;
        }
        return super.dispatchKeyEvent(event);
    }

    @Override
    public void onClick(View v) {
        final int id = v.getId();
        switch (id) {
            case R.id.cancel_search:
                onCancelSearch();
                break;
            case R.id.clear_search:
                clearSearch();
                break;
        }
    }
}

布局如下

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

    <android.support.v7.internal.widget.TintImageView
        android:id="@+id/cancel_search"
        android:layout_width="56dp"
        android:layout_height="match_parent"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:contentDescription="@string/search"
        android:scaleType="center"
        android:src="@drawable/abc_ic_ab_back_mtrl_am_alpha" />

    <EditText
        android:id="@+id/search_actionbar_query_text"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@android:color/transparent"
        android:gravity="center_vertical"
        android:hint="@string/search_hint"
        android:imeOptions="actionSearch|flagNoExtractUi"
        android:inputType="text|textNoSuggestions"
        android:textColor="@color/search_text_color"
        android:textColorHint="@color/search_hint_text_color"
        android:singleLine="true"
        android:textSize="16sp" />

    <android.support.v7.internal.widget.TintImageView
        android:id="@+id/clear_search"
        android:layout_width="56dp"
        android:layout_height="match_parent"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:scaleType="center"
        android:src="@drawable/abc_ic_clear_mtrl_alpha"
        android:visibility="invisible" />

</LinearLayout>

这是我们在 Activity

中的实现
public class SearchActivity extends AppCompatActivity implements SearchView.onSearchListener, View.onClickListener {
    private static final String BUNDLE_KEY_SEARCH_SHOWN = "key_search_shown";
    private static final String BUNDLE_KEY_SEARCH_QUERY = "key_search_query";

    // Keep track of whether the SearchView has been added to the window
    private boolean mSearchViewAdded = false;
    private SearchView mSearchView;
    private Toolbar mToolbar;
    private WindowManager mWindowManager;

    // Bundle passed in to onCreate()
    private Bundle mSavedInstanceState;

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        mSavedInstanceState = icicle;

        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

        // This is just an example so you would need to tie this to your own button. This is here so you can show/hide the SearchView.
        findViewById(R.id.sample_btn).setOnClickListener(this);

        // You should have added a Toolbar View to your layout already so reference it here by your id you assigned it
        mToolbar = (Toolbar)findViewById(R.id.toolbar);
        if (mToolbar != null) {
            mSearchView = new SearchView(this);
            mSearchView.setOnSearchListener(this);

            setSupportActionBar(mToolbar);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mToolbar != null) {
            // Delay adding SearchView until Toolbar has finished loading
            mToolbar.post(new Runnable() {
                @Override
                public void run() {
                    if (!mSearchViewAdded && mWindowManager != null) {
                        mWindowManager.addView(mSearchView,
           SearchView.getSearchViewLayoutParams(SearchActivity.this));
                        mSearchViewAdded = true;

                        if (mSavedInstanceState != null) {
                            mSearchView.setSearchQuery(
                                mSavedInstanceState.getString(BUNDLE_KEY_SEARCH_QUERY));

                            if (mSavedInstanceState.getBoolean(BUNDLE_KEY_SEARCH_SHOWN)) {
                                mSearchView.display();
                            }
                        }
                    }
                }
            });
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        if (mSearchViewAdded) {
            try {
                mWindowManager.removeView(mSearchView);
            } catch (IllegalArgumentException e) {
            // Have seen this happen on occasion during orientation change.
            }
            mSearchViewAdded = false;
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (mSearchView != null) {
            outState.putBoolean(BUNDLE_KEY_SEARCH_SHOWN, mSearchView.isSearchViewVisible());
            outState.putString(BUNDLE_KEY_SEARCH_QUERY, mSearchView.getSearchQuery());
        }
    }

    @Override
    public void onClick(View v) {
        final int id = v.getId();
        switch (id) {
            case R.id.sample_btn:
                if (mSearchView.isSearchVisible()) {
                    mSearchView.hide();
                } else {
                    mSearchView.display();
                }
                break;
        }
    }

    @Override
    public void onSearch(final String query) {
        // Handle search here
    }
}

使用最新版本的appCompat。您想要的默认情况下会在 appCompat-v7:"22.1.1" 中发生。在菜单布局中使用 android.support.v7.widget.SearchView。

您的菜单文件应如下所示:

<item android:id="@+id/action_search"
    android:icon="@drawable/search"
    android:title="Search"
    app:showAsAction="always"
    app:actionViewClass="android.support.v7.widget.SearchView"
    android:iconifiedByDefault="true"/>

SearchView 的所有其他代码将与以前相同。