2019-05-31 Android仿iOS版本饿了吗搜索框动画

先看看效果图吧


ftms2-bll9o.gif

原理性的结构不阐述,主要就是层级移动跟View大小变化,以及推动的效果
主要代码块如下:


import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.TextView;

import androidx.appcompat.view.ViewPropertyAnimatorCompatSet;
import androidx.constraintlayout.widget.ConstraintLayout;

import com.jakewharton.rxbinding2.view.RxView;
import com.vv.life.mvvmhabit.utils.StringUtils;
import com.vv.life.mvvmhabit.utils.ToastUtils;
import com.vv.life.widget.R;

import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;

import io.reactivex.functions.Consumer;


/**
 * Desc:功能搜索Bar
 * <p>
 * Date: 2019-05-29
 * Copyright: Copyright (c) 2010-2019
 * Company:
 * Updater:
 * Update Time:
 * Update Comments:
 *
 * @author: [lianyagang]
 */
public class CommonSearchView extends FrameLayout implements Filter.FilterListener {
    public static final int REQUEST_VOICE = 9999;
    public static final double SCALE_VALUE = 1.08;

    /**
     * 默认是否显示左边返回键或者右边cancel按钮
     */
    private boolean showIvBack = false;

    private int backMargin = dp2px(45);
    private int rightMargin = dp2px(16);
    private MenuItem mMenuItem;
    private boolean mIsSearchOpen = false;
    private int mAnimationDuration;
    private boolean mClearingFocus;


    private ConstraintLayout mSearchLayout;
    private ConstraintLayout mSearchParentLayout;
    private EditText mSearchSrcTextView;
    public ImageView backBtn;
    private ImageView mEmptyBtn;
    public TextView cancelBtn;
    private CharSequence mOldQueryText;
    private CharSequence mUserQuery;

    private OnQueryTextListener mOnQueryChangeListener;
    private SearchViewListener mSearchViewListener;

    private ListAdapter mAdapter;

    private SavedState mSavedState;
    private boolean submit = false;

    private boolean ellipsize = false;

    private boolean allowVoiceSearch;
    private Drawable suggestionIcon;
    private int initialWidth;
    private Context mContext;


    /**
     * 最大 防止 快速切换变形
     */
    private int maxWidth;

    /**
     * 不要动画,默认是要的
     */
    private boolean noNeedAni = false;

    private ViewPropertyAnimatorCompatSet animatorSet;
    private ViewPropertyAnimator animatorCompat;


    public CommonSearchView(Context context) {
        this(context, null);
    }

    public CommonSearchView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CommonSearchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs);

        mContext = context;
        initiateView();
        initStyle(attrs, defStyleAttr);
        initSearchStatus();
    }

    public void initSearchStatus() {
        ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) mSearchLayout.getLayoutParams();
        if (showIvBack) {
            cancelBtn.setAlpha(0f);
            layoutParams.rightMargin = rightMargin;
            layoutParams.leftMargin = backBtn.getLayoutParams().width + dp2px(8);
        } else {
            mSearchSrcTextView.requestFocus();
            //这里需要注意不要调用ImageView的setAlpha,要使用View的setAlpha方法
            backBtn.setAlpha(0f);
            ConstraintLayout.LayoutParams cancelBtnLayoutParams = (ConstraintLayout.LayoutParams) cancelBtn.getLayoutParams();
            cancelBtnLayoutParams.rightMargin = rightMargin;
            cancelBtn.setLayoutParams(cancelBtnLayoutParams);
            layoutParams.rightMargin = cancelBtn.getLayoutParams().width + ((ConstraintLayout.LayoutParams) cancelBtn.getLayoutParams()).rightMargin * 2;
            layoutParams.leftMargin = rightMargin;
        }
        mSearchLayout.setLayoutParams(layoutParams);

        mSearchLayout.post(new Runnable() {
            @Override
            public void run() {

                int initWidth = mSearchSrcTextView.getWidth();
                int offset = (int) (initWidth - initWidth / SCALE_VALUE);
                maxWidth = initWidth + offset;

            }
        });

    }

    private void initStyle(AttributeSet attrs, int defStyleAttr) {
        TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.CommonSearchView);

        if (a != null) {
            if (a.hasValue(R.styleable.CommonSearchView_bdSearchBackground)) {
                setBackgroundColor(a.getColor(R.styleable.CommonSearchView_bdSearchBackground, 0));
            }
            if (a.hasValue(R.styleable.CommonSearchView_android_layout_height)) {
                setSearchParentHeight((int) a.getDimension(R.styleable.CommonSearchView_android_layout_height, R.dimen.dp40));
            }
            if (a.hasValue(R.styleable.CommonSearchView_bdSearchCancelColor)) {
                setCancelTextColor(a.getColor(R.styleable.CommonSearchView_bdSearchCancelColor, 0));
            }

            if (a.hasValue(R.styleable.CommonSearchView_bdSearchStatus)) {
                setSearchStatus(a.getBoolean(R.styleable.CommonSearchView_bdSearchStatus, true));
            }
            if (a.hasValue(R.styleable.CommonSearchView_android_textColor)) {
                setTextColor(a.getColor(R.styleable.CommonSearchView_android_textColor, 0));
            }

            if (a.hasValue(R.styleable.CommonSearchView_android_textColorHint)) {
                setHintTextColor(a.getColor(R.styleable.CommonSearchView_android_textColorHint, 0));
            }

            if (a.hasValue(R.styleable.CommonSearchView_android_hint)) {
                setHint(a.getString(R.styleable.CommonSearchView_android_hint));
            }

            if (a.hasValue(R.styleable.CommonSearchView_bdSearchVoiceIcon)) {
            }

            if (a.hasValue(R.styleable.CommonSearchView_bdSearchCloseIcon)) {
                setCloseIcon(a.getDrawable(R.styleable.CommonSearchView_bdSearchCloseIcon));
            }

            if (a.hasValue(R.styleable.CommonSearchView_bdSearchBackIcon)) {
                setBackIcon(a.getDrawable(R.styleable.CommonSearchView_bdSearchBackIcon));
            }

            if (a.hasValue(R.styleable.CommonSearchView_bdSearchSuggestionBackground)) {
            }

            if (a.hasValue(R.styleable.CommonSearchView_bdSearchSuggestionIcon)) {
                setSuggestionIcon(a.getDrawable(R.styleable.CommonSearchView_bdSearchSuggestionIcon));
            }

            if (a.hasValue(R.styleable.CommonSearchView_android_inputType)) {
                setInputType(a.getInt(R.styleable.CommonSearchView_android_inputType, EditorInfo.TYPE_NULL));
            }

            if (a.hasValue(R.styleable.CommonSearchView_bdSearchNeedAni)) {
                noNeedAni = !a.getBoolean(R.styleable.CommonSearchView_bdSearchNeedAni, true);
            }

            a.recycle();
        }
    }

    private void initiateView() {
        LayoutInflater.from(mContext).inflate(R.layout.widget_search_view, this, true);
        mSearchLayout = findViewById(R.id.search_layout);
        mSearchParentLayout = findViewById(R.id.search_parent);
        mSearchSrcTextView = findViewById(R.id.common_search_layout);
        backBtn = findViewById(R.id.iv_menu);
        mEmptyBtn = findViewById(R.id.clear);
        cancelBtn = findViewById(R.id.tv_right_text);
        mEmptyBtn.setOnClickListener(mOnClickListener);
        cancelBtn.setOnClickListener(mOnClickListener);
        //1秒钟内只允许点击1次
        RxView.clicks(backBtn)
                .throttleFirst(2, TimeUnit.SECONDS)
                .subscribe(object -> {
                    if (mSearchViewListener != null) {
                        mSearchViewListener.onSearchViewBack(backBtn);
                    }
                });
        allowVoiceSearch = false;
        initSearchView();
        setAnimationDuration(400);
    }

    private void initSearchView() {

        mSearchSrcTextView.setOnEditorActionListener((v, actionId, event) -> {
            onSubmitQuery();
            return true;
        });

        mSearchSrcTextView.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) {
                mUserQuery = s;
                startFilter(s);
                CommonSearchView.this.onTextChanged(s);
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

        mSearchSrcTextView.setOnFocusChangeListener((v, hasFocus) -> {
            if (hasFocus) {
                mIsSearchOpen = true;
                if (!TextUtils.isEmpty(mSearchSrcTextView.getText().toString())) {
                    mEmptyBtn.setVisibility(VISIBLE);
                }
                showKeyboard();
                if (backBtn.getAlpha() == 1) {
                    //左移动
                    startMoveAnimator(true);
                }
            }
        });
    }

    /**
     * Desc:开始动画
     * <p>
     * Author: [lianyagang]
     * Date: 2019-05-30
     *
     * @param isLeft 是否左移
     *               true 左移
     *               false 右移
     */
    public void startMoveAnimator(final boolean isLeft) {
        if (noNeedAni) {
            startMoveNoAni(isLeft);
            return;
        }

        if (mSearchViewListener != null) {
            if (isLeft) {
                mSearchViewListener.onSearchViewShown();
                mIsSearchOpen = true;
            } else {
                mSearchViewListener.onSearchViewClosed();
                mIsSearchOpen = false;
            }
        }

        backMargin = backBtn.getLayoutParams().width;
        //插值器,这里选取,开始向前甩和结束向后甩
        initialWidth = mSearchSrcTextView.getWidth();
        final double targetWidth = isLeft ? initialWidth - initialWidth / SCALE_VALUE : initialWidth * SCALE_VALUE - initialWidth;
        AccelerateDecelerateInterpolator accelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();

        backBtn.animate().setDuration(500).translationX(isLeft ? -backMargin : 0).start();
        cancelBtn.animate().setDuration(500).translationX(showIvBack ? (isLeft ? -rightMargin : 0) : (isLeft ? -0 : rightMargin)).start();

        mSearchLayout.animate().setDuration(500)
                .translationX(showIvBack ? (isLeft ? -backMargin : 0) : (isLeft ? -0 : backMargin)).setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float animatedValue = (float) valueAnimator.getAnimatedValue();
                backBtn.setAlpha(isLeft ? (1 - animatedValue) : animatedValue);
                cancelBtn.setAlpha(isLeft ? animatedValue : (1 - animatedValue));
                double v = isLeft ? initialWidth - animatedValue * targetWidth : initialWidth + animatedValue * targetWidth;
                if (v > maxWidth) {
                    v = maxWidth;
                }
                mSearchLayout.getLayoutParams().width = (int) v;
                mSearchLayout.requestLayout();
            }
        }).setInterpolator(accelerateDecelerateInterpolator);
    }


    /**
     * Desc:不需要动画的展示效果, 可以在动画之前取反,当成恢复上次的使用
     * Author: [lianyagang]
     * Date: 2019/11/19
     */
    public void startMoveNoAni(final boolean isLeft) {
        if (mSearchViewListener != null) {
            if (isLeft) {
                mSearchViewListener.onSearchViewShown();
                mIsSearchOpen = true;
            } else {
                mSearchViewListener.onSearchViewClosed();
                mIsSearchOpen = false;
            }
        }

        backMargin = backBtn.getLayoutParams().width;
        //插值器,这里选取,开始向前甩和结束向后甩
        initialWidth = mSearchSrcTextView.getWidth();
        final double targetWidth = isLeft ? initialWidth - initialWidth / SCALE_VALUE : initialWidth * SCALE_VALUE - initialWidth;
        AccelerateDecelerateInterpolator accelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();

        double v = isLeft ? initialWidth - 1 * targetWidth : initialWidth + 1 * targetWidth;
        //重新校正
        // int backMargin = dp2px(45);
        //左移,恢复初始在右边
        if (isLeft) {
            backBtn.setX(-backMargin);
            backBtn.setAlpha(0F);
            cancelBtn.setAlpha(1F);
            mSearchLayout.setX(0);

            mSearchLayout.getLayoutParams().width = (int) v;
            mSearchLayout.requestLayout();

        } else {
            //右移,恢复初始在左边
            backBtn.setX(0);
            backBtn.setAlpha(1F);
            cancelBtn.setAlpha(0F);

            mSearchLayout.getLayoutParams().width = (int) v;
            mSearchLayout.requestLayout();

        }

        int offset1 = dp2px(4);
        int offset2 = dp2px(16);
        mSearchLayout.setX(showIvBack ? (isLeft ? -backMargin : 0) : (isLeft ? offset1 : backMargin + offset2));
        backBtn.setX(isLeft ? -backMargin : 0);

    }


    public void startMoveAnimator2(final boolean isLeft) {

        if (mSearchViewListener != null) {
            if (isLeft) {
                mSearchViewListener.onSearchViewShown();
                mIsSearchOpen = true;
            } else {
                mSearchViewListener.onSearchViewClosed();
                mIsSearchOpen = false;
            }
        }

        int margin45 = dp2px(45);
        //插值器,这里选取,开始向前甩和结束向后甩
        initialWidth = mSearchSrcTextView.getWidth();
        final double targetWidth = isLeft ? initialWidth - initialWidth / SCALE_VALUE : initialWidth * SCALE_VALUE - initialWidth;

        backBtn.setX(isLeft ? -margin45 : 0);
        cancelBtn.setX(showIvBack ? (isLeft ? -rightMargin : 0) : (isLeft ? -0 : rightMargin));
        mSearchLayout.setX(showIvBack ? (isLeft ? -margin45 : 0) : (isLeft ? -0 : margin45));

        backBtn.setImageAlpha(isLeft ? 0 : 1);
        cancelBtn.setAlpha(isLeft ? 1 : 0);

        double v = isLeft ? initialWidth - 1 * targetWidth : initialWidth + 1 * targetWidth;
        if (v > maxWidth) {
            v = maxWidth;
        }
        mSearchLayout.getLayoutParams().width = (int) v;
        mSearchLayout.requestLayout();
    }

    private void startFilter(CharSequence s) {
        if (mAdapter != null && mAdapter instanceof Filterable) {
            ((Filterable) mAdapter).getFilter().filter(s, this);
        }
    }

    private final OnClickListener mOnClickListener = new OnClickListener() {

        @Override
        public void onClick(View v) {
            if (v == backBtn) {
                onBackClick(v);
            } else if (v == mEmptyBtn) {
                mSearchSrcTextView.setText(null);
            } else if (v == cancelBtn) {
                if (!showIvBack && TextUtils.isEmpty(mSearchSrcTextView.getText().toString())) {
                    if (mSearchViewListener != null) {
                        mSearchViewListener.onSearchViewClosed();
                        mIsSearchOpen = false;
                    }
                } else {
                    if (showIvBack) {
                        clearFocus();
                    } else {
                        resetFocus();
                    }
                    startMoveAnimator(false);
                }

            }
        }
    };


    private void onTextChanged(CharSequence newText) {
        CharSequence text = mSearchSrcTextView.getText();
        mUserQuery = text;
        boolean hasText = !TextUtils.isEmpty(text);
        if (hasText) {
            mEmptyBtn.setVisibility(VISIBLE);
        } else {
            mEmptyBtn.setVisibility(GONE);
        }

        if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) {
            mOnQueryChangeListener.onQueryTextChange(newText.toString());
        }
        mOldQueryText = newText.toString();
    }

    public void onSubmitQuery() {
        CharSequence query = mSearchSrcTextView.getText();
        if (query != null && TextUtils.getTrimmedLength(query) > 0) {
            if (!showIvBack) {
                startMoveAnimator(false);
            }
            mOnQueryChangeListener.onQueryTextSubmit(query.toString());
            resetFocus();
        } else {
            ToastUtils.showShort(StringUtils.getStringResource(R.string.common_input_notify));
        }
    }


    public void hideKeyboard(View view) {
        InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

    public void showKeyboard() {
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1 && mSearchSrcTextView.hasFocus()) {
            mSearchSrcTextView.clearFocus();
        }
        mSearchSrcTextView.requestFocus();
        InputMethodManager imm = (InputMethodManager) mSearchSrcTextView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.showSoftInput(mSearchSrcTextView, 0);
    }

    //Public Attributes

    @Override
    public void setBackground(Drawable background) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            mSearchSrcTextView.setBackground(background);
        } else {
            mSearchSrcTextView.setBackgroundDrawable(background);
        }
    }

    @Override
    public void setBackgroundColor(int color) {
        mSearchParentLayout.setBackgroundColor(color);
    }

    public void setSearchParentHeight(int height) {
        mSearchParentLayout.getLayoutParams().height = height;
        mSearchParentLayout.requestLayout();
    }

    public void setCancelTextColor(int color) {
        cancelBtn.setTextColor(color);
    }

    public void setTextColor(int color) {
        mSearchSrcTextView.setTextColor(color);
    }

    public void setHintTextColor(int color) {
        mSearchSrcTextView.setHintTextColor(color);
    }

    public void setHint(CharSequence hint) {
        mSearchSrcTextView.setHint(hint);
    }

    public void setCloseIcon(Drawable drawable) {
        mEmptyBtn.setImageDrawable(drawable);
    }

    public void setBackIcon(Drawable drawable) {
        backBtn.setImageDrawable(drawable);
    }

    public void setSuggestionIcon(Drawable drawable) {
        suggestionIcon = drawable;
    }

    public void setInputType(int inputType) {
        mSearchSrcTextView.setInputType(inputType);
    }

    public void setSearchStatus(boolean leftStatus) {
        this.showIvBack = leftStatus;
    }


    public void setCursorDrawable(int drawable) {
        try {
            // https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564
            Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
            f.setAccessible(true);
            f.set(mSearchSrcTextView, drawable);
        } catch (Exception ex) {
            Log.e("MaterialSearchView", ex.toString());
        }
    }

    public void setVoiceSearch(boolean voiceSearch) {
        allowVoiceSearch = voiceSearch;
    }


    /**
     * Calling this will set the query to search text box. if submit is true, it'll submit the query.
     *
     * @param query
     * @param submit
     */
    public void setQuery(CharSequence query, boolean submit) {
        mSearchSrcTextView.setText(query);
        if (query != null) {
            mSearchSrcTextView.setSelection(mSearchSrcTextView.length());
            mUserQuery = query;
        }
        if (submit && !TextUtils.isEmpty(query)) {
            onSubmitQuery();
        }
    }


    /**
     * Call this method and pass the menu item so this class can handle click events for the Menu Item.
     *
     * @param menuItem
     */
    public void setMenuItem(MenuItem menuItem) {
        this.mMenuItem = menuItem;
        mMenuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                showSearch();
                return true;
            }
        });
    }

    /**
     * Return true if search is open
     *
     * @return
     */
    public boolean isSearchOpen() {
        return mIsSearchOpen;
    }

    /**
     * Sets animation duration. ONLY FOR PRE-LOLLIPOP!!
     *
     * @param duration duration of the animation
     */
    public void setAnimationDuration(int duration) {
        mAnimationDuration = duration;
    }

    /**
     * Open Search View. This will animate the showing of the view.
     */
    public void showSearch() {
        showSearch(true);
    }

    /**
     * Open Search View. If animate is true, Animate the showing of the view.
     *
     * @param animate true for animate
     */
    public void showSearch(boolean animate) {
        if (isSearchOpen()) {
            return;
        }

        //Request Focus
        mSearchSrcTextView.setText(null);
        mSearchSrcTextView.requestFocus();

        if (animate) {
            setVisibleWithAnimation();

        } else {
            mSearchLayout.setVisibility(VISIBLE);
            if (mSearchViewListener != null) {
                mSearchViewListener.onSearchViewShown();
            }
        }
        mIsSearchOpen = true;
    }

    private void setVisibleWithAnimation() {
    }

    private void onBackClick(View view) {
        //1秒钟内只允许点击1次
        RxView.clicks(view)
                .throttleFirst(1, TimeUnit.SECONDS)
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept(Object object) throws Exception {
                        if (mSearchViewListener != null) {
                            mSearchViewListener.onSearchViewBack(view);
                        }
                    }
                });
    }

    /**
     * Close search view.
     */
    public void closeSearch() {
        if (!isSearchOpen()) {
            return;
        }

        mSearchSrcTextView.setText(null);
        clearFocus();
        if (mSearchViewListener != null) {
            mSearchViewListener.onSearchViewClosed();
        }
        mIsSearchOpen = false;

    }

    /**
     * Set this listener to listen to Query Change events.
     *
     * @param listener
     */
    public void setOnQueryTextListener(OnQueryTextListener listener) {
        mOnQueryChangeListener = listener;
    }

    /**
     * Set this listener to listen to Search View open and close events
     *
     * @param listener
     */
    public void setOnSearchViewListener(SearchViewListener listener) {
        mSearchViewListener = listener;
    }

    /**
     * Ellipsize suggestions longer than one line.
     *
     * @param ellipsize
     */
    public void setEllipsize(boolean ellipsize) {
        this.ellipsize = ellipsize;
    }

    @Override
    public void onFilterComplete(int count) {
        if (count > 0) {
//            showSuggestions();
        } else {
//            dismissSuggestions();
        }
    }

    @Override
    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        // Don't accept focus if in the middle of clearing focus
        if (mClearingFocus) {
            return false;
        }
        // Check if SearchView is focusable.
        if (!isFocusable()) {
            return false;
        }
        return mSearchSrcTextView.requestFocus(direction, previouslyFocusedRect);
    }

    @Override
    public void clearFocus() {
        mClearingFocus = true;
        hideKeyboard(this);
        super.clearFocus();
        mSearchSrcTextView.setText(null);
        mSearchSrcTextView.clearFocus();
        mClearingFocus = false;
        mEmptyBtn.setVisibility(GONE);
    }

    public void resetFocus() {
        mClearingFocus = true;
        hideKeyboard(this);
        mSearchSrcTextView.clearFocus();
        mClearingFocus = false;
        mEmptyBtn.setVisibility(GONE);
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();

        mSavedState = new SavedState(superState);
        mSavedState.query = mUserQuery != null ? mUserQuery.toString() : null;
        mSavedState.isSearchOpen = this.mIsSearchOpen;

        return mSavedState;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        mSavedState = (SavedState) state;

        if (mSavedState.isSearchOpen) {
            showSearch(false);
            setQuery(mSavedState.query, false);
        }

        super.onRestoreInstanceState(mSavedState.getSuperState());
    }

    static class SavedState extends BaseSavedState {
        String query;
        boolean isSearchOpen;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            this.query = in.readString();
            this.isSearchOpen = in.readInt() == 1;
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeString(query);
            out.writeInt(isSearchOpen ? 1 : 0);
        }

        //required field that makes Parcelables from a Parcel
        public static final Creator<SavedState> CREATOR =
                new Creator<SavedState>() {
                    @Override
                    public SavedState createFromParcel(Parcel in) {
                        return new SavedState(in);
                    }

                    @Override
                    public SavedState[] newArray(int size) {
                        return new SavedState[size];
                    }
                };
    }

    public interface OnQueryTextListener {

        /**
         * Called when the user submits the query. This could be due to a key press on the
         * keyboard or due to pressing a submit button.
         * The listener can override the standard behavior by returning true
         * to indicate that it has handled the submit request. Otherwise return false to
         * let the SearchView handle the submission by launching any associated intent.
         *
         * @param query the query text that is to be submitted
         * @return true if the query has been handled by the listener, false to let the
         * SearchView perform the default action.
         */
        boolean onQueryTextSubmit(String query);

        /**
         * Called when the query text is changed by the user.
         *
         * @param newText the new content of the query text field.
         * @return false if the SearchView should perform the default action of showing any
         * suggestions if available, true if the action was handled by the listener.
         */
        boolean onQueryTextChange(String newText);
    }

    public interface SearchViewListener {
        /**
         * Desc:search
         * Author: [lianyagang]
         * Date: 2020/1/10
         */
        void onSearchViewShown();

        /**
         * Desc:search close
         * Author: [lianyagang]
         * Date: 2020/1/10
         */
        void onSearchViewClosed();

        /**
         * Desc:search back
         * Author: [lianyagang]
         * Date: 2020/1/10
         *
         * @param view 点击的view
         */
        void onSearchViewBack(View view);
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     *
     * @param dpValue 虚拟像素
     * @return 像素
     */
    public static int dp2px(float dpValue) {
        return (int) (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density);
    }

}

相关的资源文件声明如下:

<declare-styleable name="CommonSearchView">
        <attr name="bdSearchBackground" format="color" />
        <attr name="bdSearchVoiceIcon" format="integer" />
        <attr name="bdSearchCloseIcon" format="integer" />
        <attr name="bdSearchBackIcon" format="integer" />
        <attr name="bdSearchCancelColor" format="reference|color" />
        <attr name="android:layout_height" format="dimension" />
        <attr name="bdSearchSuggestionIcon" format="integer" />
        <attr name="bdSearchStatus" format="boolean">
            <enum name="SHOW_LEFT" value="1" />
            <enum name="HIDE_LEFT" value="0" />
        </attr>
        <attr name="bdSearchSuggestionBackground" format="integer" />
        <attr name="android:hint" />
        <attr name="android:textColor" />
        <attr name="android:textSize" />
        <attr name="android:textColorHint" />
        <attr name="android:inputType" />
        <attr name="bdQueryTextChanged" format="reference" />
        <attr name="bdSearchNeedAni" format="boolean" />
    </declare-styleable>

布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/search_parent"
    android:layout_width="match_parent"
    android:layout_height="44dp"
    android:focusable="true"
    android:focusableInTouchMode="true">

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/iv_menu"
        android:layout_width="@dimen/dp45"
        android:layout_height="0dp"
        android:src="@drawable/ic_back"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_right_text"
        android:layout_width="@dimen/dp45"
        android:layout_height="0dp"
        android:gravity="center"
        android:text="@string/common_search_cancel"
        android:textColor="@color/black"
        android:textSize="@dimen/sp14"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/search_layout"
        android:layout_width="0dp"
        android:layout_height="@dimen/dp32"
        android:layout_gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/common_search_layout"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:background="@drawable/common_search_border"
            android:drawableLeft="@drawable/left_search"
            android:drawablePadding="@dimen/dp6"
            android:gravity="left|center"
            android:hint="@string/search_hint"
            android:imeOptions="actionSearch"
            android:maxLines="1"
            android:paddingStart="@dimen/dp6"
            android:paddingEnd="@dimen/dp6"
            android:singleLine="true"
            android:textColorHint="@color/search_layover_bg"
            android:textSize="@dimen/sp14"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/clear"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_search_clear"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintRight_toRightOf="@+id/common_search_layout"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

使用方式如下:

   //这里需要注意,引入的资源需要正确,否则无法正常读取到配置的属性
    <com.xxx.CommonSearchView
                    android:id="@+id/common_search"
                    android:layout_width="match_parent"
                    android:layout_height="480dp"
                    CommonSearchView:searchBackground="@color/blue"
                   CommonSearchView:searchCancelColor="@color/white"
                    CommonSearchView:searchStatus="SHOW_LEFT" />

MaterialSearchView:searchStatus 这个属性封装了,默认是显示左边的返回按钮,还是显示右边的Cancel按钮

好了整个View看起来比较简单,实践的过程还是浪费了不少时间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容