先看看效果图吧
原理性的结构不阐述,主要就是层级移动跟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看起来比较简单,实践的过程还是浪费了不少时间。