Android 自定义搜索框

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bit_kaki/article/details/74995986

  现在市场上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) {

}

  第二就是如果需要写输入每个字的联想搜索,同样可以写一个接口在TextWatcheronTextChanged方法里,然后联系词库,展示出来。

  最后就是在我们的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();
        }
    });
}

     至此,大功告成。


没有更多推荐了,返回首页