现在市场上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(); } }); }
至此,大功告成。