Android自定义View——自定义搜索框(SearchView) 非常实用的控件

    xiaoxiao2021-04-04  40

    好多东西写起来太麻烦了,而且我在最开始用的也不是自己写的,所以找了一个非常棒的测试了一下.  转载的

    在 Android开发中,当系统数据项比较多时,常常会在app添加搜索功能,方便用户能快速获得需要的数据。搜索栏对于我们并不陌生,在许多app都能见到它,比如豌豆荚

    在某些情况下,我们希望我们的自动补全信息可以不只是纯文本,还可以像豌豆荚这样,能显示相应的图片和其他数据信息,因此Android给我们提供的AutoCompleteTextView往往就不够用,在大多情况下我们都需要自己去实现搜索框。

    分析

    根据上面这张图,简单分析一下自定义搜索框的结构与功能,有  1. 搜索界面大致由三部门组成,如图:输入框+(自动补全)提示框+结果列表。  2. 提示框的数据与输入框输入的文本是实时联动的,而结果列表只有在每次进行搜索操作时才会更新数据  3. 输入框的UI应是动态的,即UI随着输入的文本的改变而改变,如:在未输入文本时,清除按钮应该是隐藏的;只有当框中有文本时才会显示。  4. 软键盘也应该是动态的,如完成搜索时应自动隐藏。  5. 选择提示框的选项会自动补全输入框,且自动进行搜索  6. (external)有热门搜索推荐/记录搜索记录的功能——热门搜索推荐列表只在刚要进行搜索的时候弹出,即未输入文本时,可供用户选择。

    根据上面的分析,我们认为一个搜索框应该包含输入框和提示框两个部分。搜索框可以设置一个回调监听接口,当需要进行搜索操作时,调用监听者的search()方法,从而实现具体的搜索操作以及结果列表的数据联动。

    演示Demo

    注意:  1. 这里,博主图方便没有模拟太多数据,而且提示框和热搜列表也都只是使用String类型的数据,各位看官们可以根据自身需要去设置item_layout和相应的adapter。  2. 由于个人习惯,博主在这个demo中使用了通用适配器,所以生成和设置adapter的代码比较简略,看官们可以根据传统的ViewHolder模式打造自己的adapter。或者学习一下通用适配器的打造。可以参考这里(鸿神博客Again)学习一下通用适配器的打造,在我的源码里面也有对应的源码。

    实现

    好了,说了那么多,开始来看代码吧

    先看SearchView的布局文件 search_layout.xml

    [html] view plain copy print ? <?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"                android:background="#eee"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:orientation="vertical">        <LinearLayout          android:background="#eb4f38"          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:orientation="horizontal">              <FrameLayout                android:layout_weight="1"              android:layout_width="0dp"              android:layout_height="wrap_content">                <EditText                  android:id="@+id/search_et_input"                  android:layout_gravity="center_vertical"                  android:layout_margin="10dp"                  android:drawableLeft="@drawable/search_icon"                  android:drawablePadding="5dp"                  android:layout_width="match_parent"                  android:layout_height="wrap_content"                  android:background="@drawable/search_edittext_shape"                  android:textSize="16sp"                  android:imeOptions="actionSearch"                  android:inputType="text"                  android:hint="请输入关键字"/>                <ImageView                  android:visibility="gone"                  android:layout_marginRight="20dp"                  android:src="@drawable/iv_delete_bg"                  android:id="@+id/search_iv_delete"                  android:layout_gravity="right|center_vertical"                  android:layout_width="wrap_content"                  android:layout_height="wrap_content"/>          </FrameLayout>            <Button              android:id="@+id/search_btn_back"              android:layout_marginRight="10dp"              android:layout_marginTop="10dp"              android:layout_marginBottom="10dp"              android:layout_gravity="center_vertical"              android:background="@drawable/btn_search_bg"              android:layout_width="@dimen/btn_width"              android:layout_height="@dimen/btn_height"              android:text="返回"              android:textColor="@color/color_white"/>      </LinearLayout>        <ListView          android:visibility="gone"          android:id="@+id/search_lv_tips"          android:background="@drawable/lv_search_tips_bg"          android:layout_marginLeft="20dp"          android:layout_marginRight="20dp"          android:layout_marginBottom="10dp"          android:layout_width="match_parent"          android:layout_height="200dp">      </ListView>  </LinearLayout>   <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:background="#eee" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:background="#eb4f38" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <FrameLayout android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content"> <EditText android:id="@+id/search_et_input" android:layout_gravity="center_vertical" android:layout_margin="10dp" android:drawableLeft="@drawable/search_icon" android:drawablePadding="5dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/search_edittext_shape" android:textSize="16sp" android:imeOptions="actionSearch" android:inputType="text" android:hint="请输入关键字"/> <ImageView android:visibility="gone" android:layout_marginRight="20dp" android:src="@drawable/iv_delete_bg" android:id="@+id/search_iv_delete" android:layout_gravity="right|center_vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </FrameLayout> <Button android:id="@+id/search_btn_back" android:layout_marginRight="10dp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:layout_gravity="center_vertical" android:background="@drawable/btn_search_bg" android:layout_width="@dimen/btn_width" android:layout_height="@dimen/btn_height" android:text="返回" android:textColor="@color/color_white"/> </LinearLayout> <ListView android:visibility="gone" android:id="@+id/search_lv_tips" android:background="@drawable/lv_search_tips_bg" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_marginBottom="10dp" android:layout_width="match_parent" android:layout_height="200dp"> </ListView> </LinearLayout>

    注意:demo中颜色什么的都直接用的rgb 值去设置,在实际开发时,需要把它们都统一管理到values目录下 。

    比较简单,需要注意的是EditText的这个属性

    android:imeOptions="actionSearch"  就是把Enter键设置为Search键,并把点击Enter键的动作设为actionSearch,这样既可在代码中监听何时按下search键

    没什么说的,bg属性可以直接看看源码。接下来看模拟的bean类,这里直接就叫Bean.Java

    [java] view plain copy print ? public class Bean {        private int iconId;      private String title;      private String content;      private String comments;        public Bean(int iconId, String title, String content, String comments) {          this.iconId = iconId;          this.title = title;          this.content = content;          this.comments = comments;      }        public int getIconId() {          return iconId;      }        public void setIconId(int iconId) {          this.iconId = iconId;      }        public String getTitle() {          return title;      }        public void setTitle(String title) {          this.title = title;      }        public String getContent() {          return content;      }        public void setContent(String content) {          this.content = content;      }        public String getComments() {          return comments;      }        public void setComments(String comments) {          this.comments = comments;      }  }   public class Bean { private int iconId; private String title; private String content; private String comments; public Bean(int iconId, String title, String content, String comments) { this.iconId = iconId; this.title = title; this.content = content; this.comments = comments; } public int getIconId() { return iconId; } public void setIconId(int iconId) { this.iconId = iconId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getComments() { return comments; } public void setComments(String comments) { this.comments = comments; } }

    接着看主角SearchView.java

    [java] view plain copy print ? public class SearchView extends LinearLayout implements View.OnClickListener {        /**      * 输入框      */      private EditText etInput;        /**      * 删除键      */      private ImageView ivDelete;        /**      * 返回按钮      */      private Button btnBack;        /**      * 上下文对象      */      private Context mContext;        /**      * 弹出列表      */      private ListView lvTips;        /**      * 提示adapter (推荐adapter)      */      private ArrayAdapter<String> mHintAdapter;        /**      * 自动补全adapter 只显示名字      */      private ArrayAdapter<String> mAutoCompleteAdapter;        /**      * 搜索回调接口      */      private SearchViewListener mListener;        /**      * 设置搜索回调接口      *      * @param listener 监听者      */      public void setSearchViewListener(SearchViewListener listener) {          mListener = listener;      }        public SearchView(Context context, AttributeSet attrs) {          super(context, attrs);          mContext = context;          LayoutInflater.from(context).inflate(R.layout.search_layout, this);          initViews();      }        private void initViews() {          etInput = (EditText) findViewById(R.id.search_et_input);          ivDelete = (ImageView) findViewById(R.id.search_iv_delete);          btnBack = (Button) findViewById(R.id.search_btn_back);          lvTips = (ListView) findViewById(R.id.search_lv_tips);            lvTips.setOnItemClickListener(new AdapterView.OnItemClickListener() {              @Override              public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {                  //set edit text                  String text = lvTips.getAdapter().getItem(i).toString();                  etInput.setText(text);                  etInput.setSelection(text.length());                  //hint list view gone and result list view show                  lvTips.setVisibility(View.GONE);                  notifyStartSearching(text);              }          });            ivDelete.setOnClickListener(this);          btnBack.setOnClickListener(this);            etInput.addTextChangedListener(new EditChangedListener());          etInput.setOnClickListener(this);          etInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {              @Override              public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {                  if (actionId == EditorInfo.IME_ACTION_SEARCH) {                      lvTips.setVisibility(GONE);                      notifyStartSearching(etInput.getText().toString());                  }                  return true;              }          });      }        /**      * 通知监听者 进行搜索操作      * @param text      */      private void notifyStartSearching(String text){          if (mListener != null) {              mListener.onSearch(etInput.getText().toString());          }          //隐藏软键盘          InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);          imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);      }        /**      * 设置热搜版提示 adapter      */      public void setTipsHintAdapter(ArrayAdapter<String> adapter) {          this.mHintAdapter = adapter;          if (lvTips.getAdapter() == null) {              lvTips.setAdapter(mHintAdapter);          }      }        /**      * 设置自动补全adapter      */      public void setAutoCompleteAdapter(ArrayAdapter<String> adapter) {          this.mAutoCompleteAdapter = adapter;      }        private class EditChangedListener implements TextWatcher {          @Override          public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {            }            @Override          public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {              if (!"".equals(charSequence.toString())) {                  ivDelete.setVisibility(VISIBLE);                  lvTips.setVisibility(VISIBLE);                  if (mAutoCompleteAdapter != null && lvTips.getAdapter() != mAutoCompleteAdapter) {                      lvTips.setAdapter(mAutoCompleteAdapter);                  }                  //更新autoComplete数据                  if (mListener != null) {                      mListener.onRefreshAutoComplete(charSequence + "");                  }              } else {                  ivDelete.setVisibility(GONE);                  if (mHintAdapter != null) {                      lvTips.setAdapter(mHintAdapter);                  }                  lvTips.setVisibility(GONE);              }            }            @Override          public void afterTextChanged(Editable editable) {          }      }        @Override      public void onClick(View view) {          switch (view.getId()) {              case R.id.search_et_input:                  lvTips.setVisibility(VISIBLE);                  break;              case R.id.search_iv_delete:                  etInput.setText("");                  ivDelete.setVisibility(GONE);                  break;              case R.id.search_btn_back:                  ((Activity) mContext).finish();                  break;          }      }        /**      * search view回调方法      */      public interface SearchViewListener {            /**          * 更新自动补全内容          *          * @param text 传入补全后的文本          */          void onRefreshAutoComplete(String text);            /**          * 开始搜索          *          * @param text 传入输入框的文本          */          void onSearch(String text);    //        /**  //         * 提示列表项点击时回调方法 (提示/自动补全)  //         */  //        void onTipsItemClick(String text);      }    }   public class SearchView extends LinearLayout implements View.OnClickListener { /** * 输入框 */ private EditText etInput; /** * 删除键 */ private ImageView ivDelete; /** * 返回按钮 */ private Button btnBack; /** * 上下文对象 */ private Context mContext; /** * 弹出列表 */ private ListView lvTips; /** * 提示adapter (推荐adapter) */ private ArrayAdapter<String> mHintAdapter; /** * 自动补全adapter 只显示名字 */ private ArrayAdapter<String> mAutoCompleteAdapter; /** * 搜索回调接口 */ private SearchViewListener mListener; /** * 设置搜索回调接口 * * @param listener 监听者 */ public void setSearchViewListener(SearchViewListener listener) { mListener = listener; } public SearchView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; LayoutInflater.from(context).inflate(R.layout.search_layout, this); initViews(); } private void initViews() { etInput = (EditText) findViewById(R.id.search_et_input); ivDelete = (ImageView) findViewById(R.id.search_iv_delete); btnBack = (Button) findViewById(R.id.search_btn_back); lvTips = (ListView) findViewById(R.id.search_lv_tips); lvTips.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { //set edit text String text = lvTips.getAdapter().getItem(i).toString(); etInput.setText(text); etInput.setSelection(text.length()); //hint list view gone and result list view show lvTips.setVisibility(View.GONE); notifyStartSearching(text); } }); ivDelete.setOnClickListener(this); btnBack.setOnClickListener(this); etInput.addTextChangedListener(new EditChangedListener()); etInput.setOnClickListener(this); etInput.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { if (actionId == EditorInfo.IME_ACTION_SEARCH) { lvTips.setVisibility(GONE); notifyStartSearching(etInput.getText().toString()); } return true; } }); } /** * 通知监听者 进行搜索操作 * @param text */ private void notifyStartSearching(String text){ if (mListener != null) { mListener.onSearch(etInput.getText().toString()); } //隐藏软键盘 InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); } /** * 设置热搜版提示 adapter */ public void setTipsHintAdapter(ArrayAdapter<String> adapter) { this.mHintAdapter = adapter; if (lvTips.getAdapter() == null) { lvTips.setAdapter(mHintAdapter); } } /** * 设置自动补全adapter */ public void setAutoCompleteAdapter(ArrayAdapter<String> adapter) { this.mAutoCompleteAdapter = adapter; } private class EditChangedListener implements TextWatcher { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { if (!"".equals(charSequence.toString())) { ivDelete.setVisibility(VISIBLE); lvTips.setVisibility(VISIBLE); if (mAutoCompleteAdapter != null && lvTips.getAdapter() != mAutoCompleteAdapter) { lvTips.setAdapter(mAutoCompleteAdapter); } //更新autoComplete数据 if (mListener != null) { mListener.onRefreshAutoComplete(charSequence + ""); } } else { ivDelete.setVisibility(GONE); if (mHintAdapter != null) { lvTips.setAdapter(mHintAdapter); } lvTips.setVisibility(GONE); } } @Override public void afterTextChanged(Editable editable) { } } @Override public void onClick(View view) { switch (view.getId()) { case R.id.search_et_input: lvTips.setVisibility(VISIBLE); break; case R.id.search_iv_delete: etInput.setText(""); ivDelete.setVisibility(GONE); break; case R.id.search_btn_back: ((Activity) mContext).finish(); break; } } /** * search view回调方法 */ public interface SearchViewListener { /** * 更新自动补全内容 * * @param text 传入补全后的文本 */ void onRefreshAutoComplete(String text); /** * 开始搜索 * * @param text 传入输入框的文本 */ void onSearch(String text); // /** // * 提示列表项点击时回调方法 (提示/自动补全) // */ // void onTipsItemClick(String text); } }

    搜索框主要包含两个结构:输入栏+弹出框(自动补全或热门搜素推荐)。

    代码不多,实现很简单,主要是需要给EditText(输入框)设置点击监听和文本改变监听,有以下几点:  1. 当输入框没有文本时,点击输入框,显示热门搜索列表框。  2. 当输入框有文本时,点击输入框,应显示自动补全列表框。  3. 当输入框的文本发生改变时,需要更新自动补全列表框的数据。由于这些数据应该是在外部(调用者)中获得的,所以可以通过接口回调的形式,当需要更新时,通知监听者更新数据。  4. 当输入框的文本从空”“变换到非空时,即有字符时,界面应显示自动补全框,隐藏热门搜索框。  5. 当输入框的文本从非空变为空时,系统应隐藏自动补全框和热门搜索框。  6. 需要监听是否按下search键(enter),按下时通知监听者执行search操作

    结合以上6点和在上文分析过的内容,就能很轻松地实现该view。

    之后来看看搜索界面的布局文activity_main.xml

    [html] view plain copy print ? <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"                xmlns:tools="http://schemas.android.com/tools"                android:layout_width="match_parent"                android:layout_height="match_parent"                tools:context=".MainActivity"                android:orientation="vertical">        <com.yetwish.customsearchdemo.activity.widge.SearchView          android:id="@+id/main_search_layout"          android:layout_width="match_parent"          android:layout_height="wrap_content">      </com.yetwish.customsearchdemo.activity.widge.SearchView>        <ListView          android:visibility="gone"          android:id="@+id/main_lv_search_results"          android:layout_width="match_parent"          android:layout_height="wrap_content">        </ListView>  </LinearLayout>   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical"> <com.yetwish.customsearchdemo.activity.widge.SearchView android:id="@+id/main_search_layout" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.yetwish.customsearchdemo.activity.widge.SearchView> <ListView android:visibility="gone" android:id="@+id/main_lv_search_results" android:layout_width="match_parent" android:layout_height="wrap_content"> </ListView> </LinearLayout>

    就是一个SearchView加上一个结果列表,这些我们在上文都分析过了,所以也没什么好说的。布局可根据自身需求去自定义。

    最后就是搜索界面调用该view  MainActiviy.java

    [java] view plain copy print ? public class MainActivity extends Activity implements SearchView.SearchViewListener {        /**      * 搜索结果列表view      */      private ListView lvResults;        /**      * 搜索view      */      private SearchView searchView;          /**      * 热搜框列表adapter      */      private ArrayAdapter<String> hintAdapter;        /**      * 自动补全列表adapter      */      private ArrayAdapter<String> autoCompleteAdapter;        /**      * 搜索结果列表adapter      */      private SearchAdapter resultAdapter;        /**      * 数据库数据,总数据      */      private List<Bean> dbData;        /**      * 热搜版数据      */      private List<String> hintData;        /**      * 搜索过程中自动补全数据      */      private List<String> autoCompleteData;        /**      * 搜索结果的数据      */      private List<Bean> resultData;        /**      * 默认提示框显示项的个数      */      private static int DEFAULT_HINT_SIZE = 4;        /**      * 提示框显示项的个数      */      private static int hintSize = DEFAULT_HINT_SIZE;        /**      * 设置提示框显示项的个数      *      * @param hintSize 提示框显示个数      */      public static void setHintSize(int hintSize) {          MainActivity.hintSize = hintSize;      }          @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          requestWindowFeature(Window.FEATURE_NO_TITLE);          setContentView(R.layout.activity_main);          initData();          initViews();      }        /**      * 初始化视图      */      private void initViews() {          lvResults = (ListView) findViewById(R.id.main_lv_search_results);          searchView = (SearchView) findViewById(R.id.main_search_layout);          //设置监听          searchView.setSearchViewListener(this);          //设置adapter          searchView.setTipsHintAdapter(hintAdapter);          searchView.setAutoCompleteAdapter(autoCompleteAdapter);            lvResults.setOnItemClickListener(new AdapterView.OnItemClickListener() {              @Override              public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {                  Toast.makeText(MainActivity.this, position + "", Toast.LENGTH_SHORT).show();              }          });      }        /**      * 初始化数据      */      private void initData() {          //从数据库获取数据          getDbData();          //初始化热搜版数据          getHintData();          //初始化自动补全数据          getAutoCompleteData(null);          //初始化搜索结果数据          getResultData(null);      }        /**      * 获取db 数据      */      private void getDbData() {          int size = 100;          dbData = new ArrayList<>(size);          for (int i = 0; i < size; i++) {              dbData.add(new Bean(R.drawable.icon, "android开发必备技能" + (i + 1), "Android自定义view——自定义搜索view", i * 20 + 2 + ""));          }      }        /**      * 获取热搜版data 和adapter      */      private void getHintData() {          hintData = new ArrayList<>(hintSize);          for (int i = 1; i <= hintSize; i++) {              hintData.add("热搜版" + i + ":Android自定义View");          }          hintAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, hintData);      }        /**      * 获取自动补全data 和adapter      */      private void getAutoCompleteData(String text) {          if (autoCompleteData == null) {              //初始化              autoCompleteData = new ArrayList<>(hintSize);          } else {              // 根据text 获取auto data              autoCompleteData.clear();              for (int i = 0, count = 0; i < dbData.size()                      && count < hintSize; i++) {                  if (dbData.get(i).getTitle().contains(text.trim())) {                      autoCompleteData.add(dbData.get(i).getTitle());                      count++;                  }              }          }          if (autoCompleteAdapter == null) {              autoCompleteAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, autoCompleteData);          } else {              autoCompleteAdapter.notifyDataSetChanged();          }      }        /**      * 获取搜索结果data和adapter      */      private void getResultData(String text) {          if (resultData == null) {              // 初始化              resultData = new ArrayList<>();          } else {              resultData.clear();              for (int i = 0; i < dbData.size(); i++) {                  if (dbData.get(i).getTitle().contains(text.trim())) {                      resultData.add(dbData.get(i));                  }              }          }          if (resultAdapter == null) {              resultAdapter = new SearchAdapter(this, resultData, R.layout.item_bean_list);          } else {              resultAdapter.notifyDataSetChanged();          }      }        /**      * 当搜索框 文本改变时 触发的回调 ,更新自动补全数据      * @param text      */      @Override      public void onRefreshAutoComplete(String text) {          //更新数据          getAutoCompleteData(text);      }        /**      * 点击搜索键时edit text触发的回调      *      * @param text      */      @Override      public void onSearch(String text) {          //更新result数据          getResultData(text);          lvResults.setVisibility(View.VISIBLE);          //第一次获取结果 还未配置适配器          if (lvResults.getAdapter() == null) {              //获取搜索数据 设置适配器              lvResults.setAdapter(resultAdapter);          } else {              //更新搜索数据              resultAdapter.notifyDataSetChanged();          }          Toast.makeText(this"完成搜素", Toast.LENGTH_SHORT).show();      }    }   public class MainActivity extends Activity implements SearchView.SearchViewListener { /** * 搜索结果列表view */ private ListView lvResults; /** * 搜索view */ private SearchView searchView; /** * 热搜框列表adapter */ private ArrayAdapter<String> hintAdapter; /** * 自动补全列表adapter */ private ArrayAdapter<String> autoCompleteAdapter; /** * 搜索结果列表adapter */ private SearchAdapter resultAdapter; /** * 数据库数据,总数据 */ private List<Bean> dbData; /** * 热搜版数据 */ private List<String> hintData; /** * 搜索过程中自动补全数据 */ private List<String> autoCompleteData; /** * 搜索结果的数据 */ private List<Bean> resultData; /** * 默认提示框显示项的个数 */ private static int DEFAULT_HINT_SIZE = 4; /** * 提示框显示项的个数 */ private static int hintSize = DEFAULT_HINT_SIZE; /** * 设置提示框显示项的个数 * * @param hintSize 提示框显示个数 */ public static void setHintSize(int hintSize) { MainActivity.hintSize = hintSize; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); initData(); initViews(); } /** * 初始化视图 */ private void initViews() { lvResults = (ListView) findViewById(R.id.main_lv_search_results); searchView = (SearchView) findViewById(R.id.main_search_layout); //设置监听 searchView.setSearchViewListener(this); //设置adapter searchView.setTipsHintAdapter(hintAdapter); searchView.setAutoCompleteAdapter(autoCompleteAdapter); lvResults.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) { Toast.makeText(MainActivity.this, position + "", Toast.LENGTH_SHORT).show(); } }); } /** * 初始化数据 */ private void initData() { //从数据库获取数据 getDbData(); //初始化热搜版数据 getHintData(); //初始化自动补全数据 getAutoCompleteData(null); //初始化搜索结果数据 getResultData(null); } /** * 获取db 数据 */ private void getDbData() { int size = 100; dbData = new ArrayList<>(size); for (int i = 0; i < size; i++) { dbData.add(new Bean(R.drawable.icon, "android开发必备技能" + (i + 1), "Android自定义view——自定义搜索view", i * 20 + 2 + "")); } } /** * 获取热搜版data 和adapter */ private void getHintData() { hintData = new ArrayList<>(hintSize); for (int i = 1; i <= hintSize; i++) { hintData.add("热搜版" + i + ":Android自定义View"); } hintAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, hintData); } /** * 获取自动补全data 和adapter */ private void getAutoCompleteData(String text) { if (autoCompleteData == null) { //初始化 autoCompleteData = new ArrayList<>(hintSize); } else { // 根据text 获取auto data autoCompleteData.clear(); for (int i = 0, count = 0; i < dbData.size() && count < hintSize; i++) { if (dbData.get(i).getTitle().contains(text.trim())) { autoCompleteData.add(dbData.get(i).getTitle()); count++; } } } if (autoCompleteAdapter == null) { autoCompleteAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, autoCompleteData); } else { autoCompleteAdapter.notifyDataSetChanged(); } } /** * 获取搜索结果data和adapter */ private void getResultData(String text) { if (resultData == null) { // 初始化 resultData = new ArrayList<>(); } else { resultData.clear(); for (int i = 0; i < dbData.size(); i++) { if (dbData.get(i).getTitle().contains(text.trim())) { resultData.add(dbData.get(i)); } } } if (resultAdapter == null) { resultAdapter = new SearchAdapter(this, resultData, R.layout.item_bean_list); } else { resultAdapter.notifyDataSetChanged(); } } /** * 当搜索框 文本改变时 触发的回调 ,更新自动补全数据 * @param text */ @Override public void onRefreshAutoComplete(String text) { //更新数据 getAutoCompleteData(text); } /** * 点击搜索键时edit text触发的回调 * * @param text */ @Override public void onSearch(String text) { //更新result数据 getResultData(text); lvResults.setVisibility(View.VISIBLE); //第一次获取结果 还未配置适配器 if (lvResults.getAdapter() == null) { //获取搜索数据 设置适配器 lvResults.setAdapter(resultAdapter); } else { //更新搜索数据 resultAdapter.notifyDataSetChanged(); } Toast.makeText(this, "完成搜素", Toast.LENGTH_SHORT).show(); } }

    使用SearchView比较简单,只要给SearchView设置onSearchViewListener监听接口,实现对应的方法,并给SearchView传入热搜版和自动补全的adapter既可。

    这里使用的匹配算法比较简单,也没有考虑多个搜索词的情况,(这些之后都可以再完善),主要实现就是在总数据中匹配每个Bean的Title是否包含搜索词,包含则表示该数据匹配,否则不匹配。然后将所有匹配的Bean显示到结果列表中。

    考虑到实际开发中,数据量十分庞大,可以只把结果集的一部分(如前10个)显示出来,上拉到底的时候再加载之后的记录,也就是可以加入上拉加载的机制,使app性能更优化。

    自动补全匹配也是采用相同的算法。算法都比较简单,当然也可以弄得复杂点,比如根据“ ”(空格)去分割输入文本,再逐个考虑单个搜索词的匹配项,把匹配次数从多到少排列出结果集等等。这里不细说。

    这里有一个问题是进入该搜索界面时需要加载所有的数据项到内存,当数据项很多时,是否会占用大量的内存?如果是应该如何避免?是采用只加载一部分数据的形式,还是直接使用搜索词到数据库中查询更优?还请各位看官大神们给出宝贵的意见~

    好了,自定义搜索框到这就打造完成啦,是不是感觉简单过头了。

    各位看官如果有任何问题可评论或者发邮件跟我联系yetwish@gmail.com

    囧~忘记贴代码了,代码放在github上,各位看官直接download即可  链接:https://github.com/yetwish/CustomSearchView

    转载请注明原文地址: https://ju.6miu.com/read-666088.html

    最新回复(0)