现在市场上Android软件流行的搜索框,普遍来说都是点击之后进入一个新的页面,在新的页面里展示出历史搜索、热门搜索,输入字以后显示联想关键词,点击这些词或者搜索按钮时候再进行搜索。然而在平板上,横屏展示时候再用这样的方式就很糟糕,加上我们产品搜索库的底层为地图,搜索内容的内容也多是数字内容,于是联想关键词也没有太大意义。于是最后设计出来的搜索框就是这样一个需求:
1.搜索框在不同模块的提示搜索关键字不同;
2.搜索框尽量简洁(因为要覆盖在地图上);
3.搜索框得有个语音搜索功能。
于是在满足这样的需求下,最后做出来的效果如下所示:
这个搜索框是挺简单的,不过判断一个功能的好坏的标准并不是简单与复杂,而是是否满足需求,既然做了,那就将它记录总结下吧。
首先是布局文件,我们的搜索框主要有两个界面,初始化时候是:
输入内容了以后是:
差别在于多了个取消的按钮,以及将语音的图片替换成搜索两个字。
所以我们的布局文件最后的设计为:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rl_search"
android:layout_width="420dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@drawable/shape_plus_minus"
android:gravity="center_horizontal">
<RelativeLayout
android:id="@+id/rl_mapsearcher"
android:layout_width="420dp"
android:layout_height="50dp"
android:background="@drawable/shape_plus_minus">
<RelativeLayout
android:id="@+id/rl_mapsearcher_btn"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp">
<ImageButton
android:id="@+id/ib_voice"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/mapsearcher_voice"
android:clickable="true"
android:contentDescription="@string/voice"
android:visibility="visible" />
<TextView
android:id="@+id/tv_search"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:clickable="true"
android:gravity="center"
android:text="@string/search"
android:textColor="@color/black"
android:textSize="20sp"
android:visibility="gone" />
</RelativeLayout>
<ImageView
android:id="@+id/iv_line"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/rl_mapsearcher_btn"
android:src="@drawable/mapsearcher_line" />
<ImageButton
android:id="@+id/ib_cancle"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@id/iv_line"
android:background="@drawable/mapsearcher_cancle"
android:clickable="true"
android:visibility="gone" />
<EditText
android:id="@+id/et_input"
android:layout_width="260dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toRightOf="@+id/iv_search"
android:background="@android:color/transparent"
android:hint="请输入关键字"
android:paddingBottom="5dp"
android:paddingTop="5dp"
android:textColor="@android:color/black"
android:textSize="20sp" />
<ImageView
android:id="@+id/iv_search"
android:layout_width="30dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:src="@drawable/mapsearcher_icon" />
</RelativeLayout>
</RelativeLayout>
接下来是写相关接口。我们搜索框设计的功能有:
1.搜索框在不同模块的提示搜索关键字不同;
2.普通搜索;
3.语音搜索。
为了满足功能1,我们不同模块需要能获取到这个EditView控件,给他赋值;为了满足功能2,我们需要获取到EditView控件里的值,并有个搜索响应事件;为了满足功能3,我们需要有个语音搜索响应事件。此外,还考虑了个取消搜索的响应事件。于是就接口如下所示:
public interface ISearcher {
void setImageButtonVoiceClickLisenter(onImageButtonVoiceClickLisenter lisenter);
void setImageButtonCancelClickLisenter(onImageButtonCancelClickLisenter lisenter);
void setonTextViewSearchClickLisenter(onTextViewSearchClickLisenter lisenter);
String getSearchCondition();
EditText getEt_input();
interface onImageButtonVoiceClickLisenter {
void onClick(EditText input, ImageView voice, View view);
}
interface onImageButtonCancelClickLisenter {
void onClick(EditText input, ImageView cancle, View view);
}
interface onTextViewSearchClickLisenter {
void onClick(EditText input, TextView search, View view);
}
}
有了接口,接下来就是完善代码了,代码里采用了ButterKnife框架,如下所示:
public class SearchView extends RelativeLayout implements ISearcher{
private Context context;
private TextWatcher textWatcher;
private ISearcher.onImageButtonVoiceClickLisenter onImageButtonVoiceClickLisenter;
private ISearcher.onImageButtonCancelClickLisenter onImageButtonCancelClickLisenter;
private ISearcher.onTextViewSearchClickLisenter onTextViewSearchClickLisenter;
@BindView(R.id.et_input)
EditText et_input;
@BindView(R.id.ib_voice)
ImageButton ib_voice;
@BindView(R.id.ib_cancle)
ImageButton ib_cancle;
@BindView(R.id.tv_search)
TextView tv_search;
@BindView(R.id.rl_mapsearcher)
RelativeLayout rl_mapsearcher;
View view;
@BindView(R.id.rl_search)
RelativeLayout rl_search;
public SearchView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
view= LayoutInflater.from(context).inflate(R.layout.view_search,this);
ButterKnife.bind(this, view);
this.context = context;
initAttrs(attrs);
initVariables();
}
public SearchView(Context context, AttributeSet attrs) {
super(context, attrs);
view= LayoutInflater.from(context).inflate(R.layout.view_search,this);
ButterKnife.bind(this, view);
this.context = context;
initAttrs(attrs);
initVariables();
}
/**
* ================================= interface Override start=========================
**/
@Override
public String getSearchCondition() {
return et_input.getText().toString().trim();
}
@Override
public EditText getEt_input() {
return et_input;
}
@Override
public void setImageButtonVoiceClickLisenter(onImageButtonVoiceClickLisenter lisenter) {
this.onImageButtonVoiceClickLisenter = lisenter;
}
@Override
public void setImageButtonCancelClickLisenter(onImageButtonCancelClickLisenter lisenter) {
this.onImageButtonCancelClickLisenter = lisenter;
}
@Override
public void setonTextViewSearchClickLisenter(onTextViewSearchClickLisenter lisenter) {
this.onTextViewSearchClickLisenter = lisenter;
}
/**================================= interface Override end=========================**/
/**
* ================================= private method start=========================
**/
private void initAttrs(AttributeSet attrs) {
if (attrs == null) {
return;
}
rl_mapsearcher.getBackground().setAlpha(100);
rl_search.getBackground().setAlpha(100);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Searcher);
int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int index = a.getIndex(i);
switch (index) {
case R.styleable.Searcher_searcher_edit_text:
et_input.setText(a.getString(index));
break;
case R.styleable.Searcher_searcher_edit_textColor:
et_input.setTextColor(a.getColor(index, Color.BLACK));
break;
case R.styleable.Searcher_searcheer_edit_hintcolor:
et_input.setHintTextColor(a.getColor(index, Color.BLACK));
break;
case R.styleable.Searcher_searcheer_edit_hinttextr:
et_input.setHint(a.getString(index));
break;
case R.styleable.Searcher_searcher_edit_textSize:
et_input.setTextSize(TypedValue.COMPLEX_UNIT_PX, a.getDimension(index, 20));
break;
case R.styleable.Searcher_searcher_ht_src:
ib_voice.setImageDrawable(a.getDrawable(index));
break;
case R.styleable.Searcher_searcher_background:
rl_mapsearcher.setBackgroundDrawable(a.getDrawable(index));
break;
case R.styleable.Searcher_searcher_bt_src:
ib_cancle.setImageDrawable(a.getDrawable(index));
break;
default:
break;
}
}
}
private void initVariables() {
textWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void afterTextChanged(Editable s) {
int length = s.length();
if (length > 0 && et_input.isFocused()) {
ib_voice.setVisibility(View.GONE);
ib_cancle.setVisibility(View.VISIBLE);
tv_search.setVisibility(View.VISIBLE);
} else {
ib_cancle.setVisibility(View.GONE);
tv_search.setVisibility(View.GONE);
ib_voice.setVisibility(View.VISIBLE);
}
}
};
}
/**================================= private method end=========================**/
/**
* ================================= click listener start=========================
**/
@OnFocusChange(R.id.et_input)
public void onEditTextFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
rl_mapsearcher.setSelected(true);
et_input.setHint("");
if (et_input.getText().length() > 0) {
ib_cancle.setVisibility(View.VISIBLE);
tv_search.setVisibility(View.VISIBLE);
ib_voice.setVisibility(View.GONE);
} else {
et_input.setSelected(false);
ib_cancle.setVisibility(View.GONE);
tv_search.setVisibility(View.GONE);
ib_voice.setVisibility(View.VISIBLE);
}
} else {
et_input.setHint("请输入关键字");
}
}
@OnTextChanged(R.id.et_input)
public void onEditTextTextChanged() {
if (this.textWatcher != null) {
this.et_input.addTextChangedListener(this.textWatcher);
}
}
@OnClick(R.id.ib_voice)
public void onImageButtonVoiceClick() {
if (onImageButtonVoiceClickLisenter != null) {
this.onImageButtonVoiceClickLisenter.onClick(et_input, ib_voice, this);
}
}
@OnClick(R.id.ib_cancle)
public void onImageButtonCancelClick() {
et_input.setText("");
et_input.clearFocus();
if (onImageButtonCancelClickLisenter != null) {
this.onImageButtonCancelClickLisenter.onClick(et_input, ib_cancle, this);
}
}
@OnClick(R.id.tv_search)
public void onTextViewSearchClick() {
if (onTextViewSearchClickLisenter != null) {
this.onTextViewSearchClickLisenter.onClick(et_input, tv_search, this);
}
}
/**================================= click listener end=========================**/
}
这里有两个可以注意的地方,第一是可以在OnTextChange里根据传入参数直接重写TextWatcher的三个监听方法,参照:
@OnTextChanged(value = R.id.nameEditText, callback = OnTextChanged.Callback.BEFORE_TEXT_CHANGED)
void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@OnTextChanged(value = R.id.nameEditText, callback = OnTextChanged.Callback.TEXT_CHANGED)
void onTextChanged(CharSequence s, int start, int before, int count) {
}
@OnTextChanged(value = R.id.nameEditText, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED)
void afterTextChanged(Editable s) {
}
第二就是如果需要写输入每个字的联想搜索,同样可以写一个接口在TextWatcher的onTextChanged方法里,然后联系词库,展示出来。
最后就是在我们的Activity里调用自定义搜索框:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3 );
searchView=(SearchView)findViewById(R.id.searchview);
//设置语音按钮点击事件监听
searchView.setImageButtonVoiceClickLisenter(new ISearcher.
onImageButtonVoiceClickLisenter() {
@Override
public void onClick(EditText input, ImageView voice, View view) {
speakStartWaitingStatus();
}
});
//设置搜索按钮点击事件监听
searchView.setonTextViewSearchClickLisenter(new ISearcher.
onTextViewSearchClickLisenter() {
@Override
public void onClick(EditText input, TextView search, View view) {
searchStartWaitingStatus();
}
});
}
至此,大功告成。