美文网首页
2019-05-31 Android仿iOS版本饿了吗搜索框动画

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

作者: 馒Care | 来源:发表于2019-05-31 09:35 被阅读0次

    先看看效果图吧


    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看起来比较简单,实践的过程还是浪费了不少时间。

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/rqwitctx.html