Android多重搜寻,例如传送,搜寻联络人

时间:2020-02-23 14:29:04  来源:igfitidea点击:

在本教程中,我们将讨论并实现搜索功能,该功能将在下拉菜单中显示匹配的结果,并允许根据搜索到的字符串过滤ListView结果。
这种界面通常出现在"食物交付"应用程序中,这些应用程序有很多可供选择的选项。
用户可以基于某个标签/类别进行搜索以快速找到他们想要的结果。

在本教程的最后,您将能够提出一个与下面给出的应用程序相似的工作应用程序。

Android多重搜寻

对于上述应用程序,我们将不使用SearchView。
相反,我们将使用CardView中包装的EditText。
弹出的建议列表下拉列表将是RecyclerView。

代码

下面给出了" activity_main.xml"布局。

<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:card_view="https://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#FFFFFF">

  <android.support.v7.widget.Toolbar
      android:id="@+id/toolbar"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="#212121"
      android:minHeight="?attr/actionBarSize">

      <TextView
          android:id="@+id/toolbar_title"
          style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="center"
          android:text="@string/app_name"
          android:textColor="#FFF" 

  </android.support.v7.widget.Toolbar>

  <RelativeLayout
      android:id="@+id/view_search"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#50000000"
      android:clickable="true"
      android:visibility="invisible">

      <ProgressBar
          android:id="@+id/marker_progress"
          style="?android:attr/progressBarStyle"
          android:layout_width="50dp"
          android:layout_height="50dp"
          android:layout_centerInParent="true"
          android:indeterminate="true"
          android:visibility="gone" 
  </RelativeLayout>

  <ListView
      android:id="@+id/listContainer"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#fff"
      android:clipToPadding="false"
      android:divider="#fff"
      android:paddingTop="56dp"
      android:visibility="gone" 

  <android.support.v7.widget.CardView
      android:id="@+id/card_search"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="4dp"
      android:visibility="invisible"
      card_view:cardCornerRadius="2dp">

      <RelativeLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content">

          <LinearLayout
              android:id="@+id/linearLayout_search"
              android:layout_width="match_parent"
              android:layout_height="48dp">

              <ImageView
                  android:id="@+id/image_search_back"
                  android:layout_width="48dp"
                  android:layout_height="48dp"
                  android:background="?android:attr/selectableItemBackground"
                  android:clickable="true"
                  android:padding="12dp"
                  android:src="@mipmap/ic_arrow_back" 

              <EditText
                  android:id="@+id/edit_text_search"
                  android:layout_width="0dp"
                  android:layout_height="match_parent"
                  android:layout_weight="1"
                  android:background="#fff"
                  android:focusable="true"
                  android:gravity="center_vertical"
                  android:hint="@string/search_restaurants_and_cuisines"
                  android:imeOptions="actionSearch"
                  android:inputType="textCapWords"
                  android:maxLines="1"
                  android:paddingLeft="12dp"
                  android:paddingRight="8dp" 

          </LinearLayout>

          <View
              android:id="@+id/line_divider"
              android:layout_width="match_parent"
              android:layout_height=".5dp"
              android:layout_below="@+id/linearLayout_search"
              android:background="#eee" 

          <android.support.v7.widget.RecyclerView
              android:id="@+id/recyclerView"
              android:layout_width="match_parent"
              android:layout_height="250dp"
              android:layout_below="@+id/line_divider"
              android:divider="#FFFFFF" 
      </RelativeLayout>
  </android.support.v7.widget.CardView>

  <TextView
      android:id="@+id/txtNoResultsFound"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerInParent="true"
      android:gravity="center"
      android:padding="@dimen/activity_horizontal_margin"
      android:text="@string/no_results_found" 

  <ListView
      android:id="@+id/listView"
      android:layout_width="wrap_content"
      android:layout_height="match_parent"
      android:layout_below="@+id/toolbar"
      android:layout_marginBottom="@dimen/corners_small_value"
      android:layout_marginLeft="@dimen/corners_small_value"
      android:layout_marginRight="@dimen/corners_small_value">

  </ListView>

  <View
      android:id="@+id/toolbar_shadow"
      android:layout_width="match_parent"
      android:layout_height="4dp"
      android:layout_below="@+id/toolbar"
      android:background="@drawable/toolbar_shadow" 
</RelativeLayout>

在上面的代码中,CardView是一个Custom SearchView UI,左侧带有一个后退按钮。
ListView将显示适配器中的所有餐厅。

如下所示,在Model.java文件中定义了餐厅和美食类型的数据源。

package com.theitroad.efficientsearch;

import java.util.List;

public class Model {

  public String name;
  public String id;
  public List cuisines;
  public boolean isCuisine;
  public int numberOfCuisine;

  public Model(String id, String name, List cuisines, boolean isCuisine, int numberOfCuisine) {

      this.name = name;
      this.id = id;
      this.cuisines = cuisines;
      this.isCuisine = isCuisine;
      this.numberOfCuisine = numberOfCuisine;
  }
}

区分餐厅和美食的参数是isCuisine,其类型为Boolean。

MainActivity.java在下面给出。

package com.theitroad.efficientsearch;

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.CardView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MainActivity extends AppCompatActivity implements CuisineSearchAdapter.ItemListener {

  Toolbar toolbar;
  private ImageView image_search_back;
  private RelativeLayout view_search;
  private EditText edit_text_search;
  private CardView card_search;
  private View line_divider, toolbar_shadow;
  RecyclerView recyclerView;
  ListView listView;
  String text = "";
  List modelsList, filterModels;
  List cuisinesModels;

  ListViewAdapter listViewAdapter;
  CuisineSearchAdapter cuisineSearchAdapter;
  ShowSearchView showSearchView;

  boolean editTextChangedFromClick = false;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      showSearchView = new ShowSearchView();

      toolbar = (Toolbar) findViewById(R.id.toolbar);
      toolbar.inflateMenu(R.menu.menu_main);
      image_search_back = (ImageView) findViewById(R.id.image_search_back);
      view_search = (RelativeLayout) findViewById(R.id.view_search);
      edit_text_search = (EditText) findViewById(R.id.edit_text_search);
      card_search = (CardView) findViewById(R.id.card_search);
      line_divider = findViewById(R.id.line_divider);
      toolbar_shadow = findViewById(R.id.toolbar_shadow);
      recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
      listView = (ListView) findViewById(R.id.listView);

      TextView no_results = (TextView) findViewById(R.id.txtNoResultsFound);
      listView.setEmptyView(no_results);
      populateRestaurantsAndCuisines();

      image_search_back.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              text = "";

              showSearchView.handleToolBar(MainActivity.this, card_search, toolbar, view_search, recyclerView, edit_text_search, line_divider);
              toolbar_shadow.setVisibility(View.VISIBLE);
              listViewAdapter = new ListViewAdapter(modelsList);

              listView.setAdapter(listViewAdapter);
          }
      });

      edit_text_search.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) {
              text = s.toString();

              if (editTextChangedFromClick) {
                  editTextChangedFromClick = false;

                  if (recyclerView.getVisibility() == View.VISIBLE)
                      recyclerView.setVisibility(View.GONE);
                  
              } else {
                  
                  if (recyclerView.getVisibility() != View.VISIBLE)
                      recyclerView.setVisibility(View.VISIBLE);
                  
                  if (s.toString().length() > 0) {
                      performFiltering(filterModels);
                  } else {
                      CuisineSearchAdapter cuisineSearchAdapter = new CuisineSearchAdapter(cuisinesModels, modelsList, MainActivity.this, MainActivity.this, text);
                      cuisineSearchAdapter.notifyDataSetChanged();
                      recyclerView.setAdapter(cuisineSearchAdapter);
                      
                      listViewAdapter = new ListViewAdapter(modelsList);
                      listView.setAdapter(listViewAdapter);
                  }
              }
              
          }

          @Override
          public void afterTextChanged(Editable s) {

          }
      });

      edit_text_search.setOnEditorActionListener(new TextView.OnEditorActionListener() {
          @Override
          public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
              if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                  //Your piece of code on keyboard search click
                  recyclerView.setVisibility(View.GONE);
                  ((InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(view_search.getWindowToken(), 0);

                  listViewAdapter.getFilter().filter(v.getText().toString());

                  return true;
              }
              return false;
          }
      });

      toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
              int menuItem = item.getItemId();
              switch (menuItem) {
                  case R.id.action_search:
                      recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
                      cuisineSearchAdapter = new CuisineSearchAdapter(cuisinesModels, filterModels, MainActivity.this, MainActivity.this, text);
                      recyclerView.setAdapter(cuisineSearchAdapter);
                      showSearchView.handleToolBar(MainActivity.this, card_search, toolbar, view_search, recyclerView, edit_text_search, line_divider);
                      break;
                  default:
                      break;
              }
              return false;
          }
      });

  }

  private void populateRestaurantsAndCuisines() {
      modelsList = new ArrayList();
      cuisinesModels = new ArrayList();

      List cuisinesList = new ArrayList();

      for (int i = 0; i < 7; i++)
          cuisinesList.add("Cafes");
      for (int i = 0; i < 4; i++)
          cuisinesList.add("Burgers");
      for (int i = 0; i < 4; i++)
          cuisinesList.add("Bars");

      modelsList.add(new Model("1", "McDonalds", new ArrayList(Arrays.asList("Cafes", "Burgers")), false, -1));
      modelsList.add(new Model("2", "KFC", new ArrayList(Arrays.asList("Cafes", "Burgers")), false, -1));
      modelsList.add(new Model("3", "Burger King", new ArrayList(Arrays.asList("Cafes", "Burgers")), false, -1));
      modelsList.add(new Model("4", "Subway", new ArrayList(Arrays.asList("Burgers")), false, -1));
      modelsList.add(new Model("5", "Cafe Coffee Day", new ArrayList(Arrays.asList("Cafes")), false, -1));
      modelsList.add(new Model("6", "Costa", new ArrayList(Arrays.asList("Cafes")), false, -1));
      modelsList.add(new Model("7", "Coffee Beans", new ArrayList(Arrays.asList("Cafes")), false, -1));
      modelsList.add(new Model("8", "Starbucks", new ArrayList(Arrays.asList("Cafes")), false, -1));
      modelsList.add(new Model("9", "Blues", new ArrayList(Arrays.asList("Bars")), false, -1));
      modelsList.add(new Model("10", "Hard Rock Cafe", new ArrayList(Arrays.asList("Bars", "Cafe")), false, -1));
      modelsList.add(new Model("11", "The Backyard Underground", new ArrayList(Arrays.asList("Bars")), false, -1));
      modelsList.add(new Model("12", "Downtown", new ArrayList(Arrays.asList("Bars")), false, -1));

      Map cuisineMap = new HashMap();
      for (String cuisine : cuisinesList) {
          Integer n = cuisineMap.get(cuisine);
          n = (n == null) ? 1 : ++n;
          cuisineMap.put(cuisine, n);

      }

      for (Map.Entry entry : cuisineMap.entrySet()) {
          Model model = new Model("", entry.getKey(), null, true, entry.getValue());
          modelsList.add(model);
          cuisinesModels.add(model);
      }

      filterModels = new ArrayList(modelsList);

      initialiseAdapters();

  }

  private void initialiseAdapters() {
      listViewAdapter = new ListViewAdapter(filterModels);
      listView.setAdapter(listViewAdapter);
  }

  @Override
  public void onItemClick(Model model) {

      editTextChangedFromClick = true;

      if (model.isCuisine) {
          edit_text_search.setText(model.name);
          listViewAdapter.getFilter().filter(model.name);

      } else {

          edit_text_search.setText(model.name);
          showSearchView.handleToolBar(MainActivity.this, card_search, toolbar, view_search, recyclerView, edit_text_search, line_divider);
          Toast.makeText(getApplicationContext(), model.name + " was selected.", Toast.LENGTH_LONG).show();
      }

  }

  public void performFiltering(List filteredSuggestions) {

      filteredSuggestions.clear();
      for (Model model : modelsList) {
          if (model.name.toLowerCase().contains(text.toLowerCase())) {
              filteredSuggestions.add(model);
          }
      }

      CuisineSearchAdapter cuisineSearchAdapter = new CuisineSearchAdapter(cuisinesModels, filteredSuggestions, MainActivity.this, MainActivity.this, text);
      cuisineSearchAdapter.notifyDataSetChanged();
      recyclerView.setAdapter(cuisineSearchAdapter);

  }
}

工具列布局是从" menu_main.xml"文件中设置的,如下所示。

<menu
  xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  xmlns:tools="https://schemas.android.com/tools"
  tools:context=".MainActivity">
  <item
      android:id="@+id/action_search"
      android:icon="@mipmap/ic_action_search"
      android:orderInCategory="100"
      android:title="Search"
      app:showAsAction="always"
</menu>

在详细分析MainActivity.java类之前,让我们看一下带有过滤器的ListViewAdapter的代码。
我们已经在此处实施的某些功能。

下面给出了ListViewAdapter.java类的代码。

package com.theitroad.efficientsearch;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;

public class ListViewAdapter extends BaseAdapter implements Filterable {

  private List modelList;
  private List mStringFilterList;
  private ValueFilter valueFilter;

  private class ViewHolder {
      TextView vendorName;
  }

  public ListViewAdapter(List modelList) {
      this.modelList = modelList;
      mStringFilterList = modelList;
  }

  @Override
  public int getCount() {

      if (modelList != null)
          return modelList.size();
      else
          return 0;
  }

  @Override
  public Model getItem(int position) {

      return modelList.get(position);
  }

  @Override
  public long getItemId(int position) {
      Model object = getItem(position);

      if (object.isCuisine) {
          return -1;
      } else
          return Integer.parseInt(object.id);
  }

  @Override
  public View getView(int position, View convertView, final ViewGroup parent) {

      ViewHolder holder = null;
      Model vendorModel = getItem(position);

      if (vendorModel.isCuisine) {
          return LayoutInflater.from(parent.getContext()).inflate(R.layout.row_null, null);

      } else {
          if (convertView == null) {
              convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_row_model, parent, false);
              holder = new ViewHolder();
              holder.vendorName = ((TextView) convertView.findViewById(R.id.txt_vendor_name));
              convertView.setTag(holder);

          } else {

              if (holder == null) {
                  convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_row_model, parent, false);
                  holder = new ViewHolder();
                  holder.vendorName = ((TextView) convertView.findViewById(R.id.txt_vendor_name));
                  convertView.setTag(holder);
              } else
                  holder = (ViewHolder) convertView.getTag();
          }

          holder.vendorName.setText(vendorModel.name);
          return convertView;
      }
  }

  @Override
  public Filter getFilter() {
      if (valueFilter == null) {
          valueFilter = new ValueFilter();
      }
      return valueFilter;
  }

  private class ValueFilter extends Filter {
      @Override
      protected FilterResults performFiltering(CharSequence constraint) {
          FilterResults results = new FilterResults();

          if (constraint != null && constraint.length() > 0) {
              List filterList = new ArrayList();

              for (int i = 0; i < mStringFilterList.size(); i++) {
                  if (!mStringFilterList.get(i).isCuisine) {

                      if (mStringFilterList.get(i).cuisines.contains(constraint)) {
                          Model model = new Model(mStringFilterList.get(i).id, mStringFilterList.get(i).name, mStringFilterList.get(i).cuisines, false, -1);
                          filterList.add(model);
                      }
                  }
              }

              if (filterList.size() == 0) {

                  for (int i = 0; i < mStringFilterList.size(); i++) {
                      if ((mStringFilterList.get(i).name.toUpperCase())
                              .contains(constraint.toString().toUpperCase()) && !mStringFilterList.get(i).isCuisine) {

                          Model model = new Model(mStringFilterList.get(i).id, mStringFilterList.get(i).name, mStringFilterList.get(i).cuisines, false, -1);
                          if (!model.isCuisine)
                              filterList.add(model);
                      }
                  }
              }

              results.count = filterList.size();
              results.values = filterList;
          } else {

              results.count = mStringFilterList.size();
              results.values = mStringFilterList;
          }
          return results;
      }

      @Override
      protected void publishResults(CharSequence constraint,
                                    FilterResults results) {

          modelList = (List) results.values;

          notifyDataSetChanged();
      }
  }
}

在上面的类中,我们检查每个Model实例上的ʻisCuisine参数。 如果是真的,我们添加一个高度为0的空行。 其他list_row_model.xml`已添加。
下面给出了每种布局的xml代码。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">
</LinearLayout>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:id="@+id/rl_car"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:padding="@dimen/activity_vertical_margin">

  <TextView
      android:id="@+id/txt_vendor_name"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:paddingBottom="5dp"
      android:textColor="#212121"
      android:textSize="20sp"
      android:textStyle="bold" 

</RelativeLayout>

单击工具列中的搜索图标,将使我们的自定义搜索UI膨胀,并在下面填充RecyclerView。
为此,在ShowSearchView类的实例上调用handleToolbar方法。
下面给出了" ShowSearchView.java"的代码。

package com.theitroad.efficientsearch;

import android.animation.Animator;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;

public class ShowSearchView {

  public static void handleToolBar(final Context context, final CardView search, Toolbar toolbarMain, final View view, final RecyclerView recyclerView, final EditText editText, final View line_divider) {
      final Animation fade_in = AnimationUtils.loadAnimation(context.getApplicationContext(), android.R.anim.fade_in);
      final Animation fade_out = AnimationUtils.loadAnimation(context.getApplicationContext(), android.R.anim.fade_out);
      if (search.getVisibility() == View.VISIBLE) {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
              final Animator animatorHide = ViewAnimationUtils.createCircularReveal(search,
                      search.getWidth() - (int) convertDpToPixel(56, context),
                      (int) convertDpToPixel(23, context),
                      (float) Math.hypot(search.getWidth(), search.getHeight()),
                      0);
              animatorHide.addListener(new Animator.AnimatorListener() {
                  @Override
                  public void onAnimationStart(Animator animation) {

                  }

                  @Override
                  public void onAnimationEnd(Animator animation) {
                      view.startAnimation(fade_out);
                      view.setVisibility(View.INVISIBLE);
                      search.setVisibility(View.GONE);
                      ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(view.getWindowToken(), 0);

                      recyclerView.setVisibility(View.GONE);
                  }

                  @Override
                  public void onAnimationCancel(Animator animation) {

                  }

                  @Override
                  public void onAnimationRepeat(Animator animation) {

                  }
              });
              animatorHide.setDuration(300);
              animatorHide.start();
          } else {
              ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(view.getWindowToken(), 0);
              view.startAnimation(fade_out);
              view.setVisibility(View.INVISIBLE);
              search.setVisibility(View.GONE);
          }
          editText.setText("");
          toolbarMain.getMenu().clear();
          toolbarMain.inflateMenu(R.menu.menu_main);
          search.setEnabled(false);
      } else {
          toolbarMain.getMenu().clear();
          toolbarMain.setNavigationIcon(null);
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
              final Animator animator = ViewAnimationUtils.createCircularReveal(search,
                      search.getWidth() - (int) convertDpToPixel(56, context),
                      (int) convertDpToPixel(23, context),
                      0,
                      (float) Math.hypot(search.getWidth(), search.getHeight()));
              animator.addListener(new Animator.AnimatorListener() {
                  @Override
                  public void onAnimationStart(Animator animation) {
                  }

                  @Override
                  public void onAnimationEnd(Animator animation) {
                      view.setVisibility(View.VISIBLE);
                      view.startAnimation(fade_in);
                      ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
                  }

                  @Override
                  public void onAnimationCancel(Animator animation) {

                  }

                  @Override
                  public void onAnimationRepeat(Animator animation) {

                  }
              });
              search.setVisibility(View.VISIBLE);
              if (search.getVisibility() == View.VISIBLE) {
                  animator.setDuration(300);
                  animator.start();
                  search.setEnabled(true);
              }
              fade_in.setAnimationListener(new Animation.AnimationListener() {
                  @Override
                  public void onAnimationStart(Animation animation) {

                  }

                  @Override
                  public void onAnimationEnd(Animation animation) {

                      editText.requestFocus();
                      recyclerView.setVisibility(View.VISIBLE);
                  }

                  @Override
                  public void onAnimationRepeat(Animation animation) {

                  }
              });
          } else {
              search.setVisibility(View.VISIBLE);
              search.setEnabled(true);
              recyclerView.setVisibility(View.VISIBLE);
              editText.requestFocus();
              ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);

          }
      }
  }

  public static float convertDpToPixel(float dp, Context context) {
      Resources resources = context.getResources();
      DisplayMetrics metrics = resources.getDisplayMetrics();
      return dp * (metrics.densityDpi/160f);
  }
}

handleToolbar方法根据分别单击的搜索图标/后退箭头来显示/隐藏自定义UI。
该视图以圆形显示动画的形式进行动画进/出。

RecyclerView充满了美食和餐厅。
尽管仅显示美食,直到用户键入任何内容。

RecyclerView的适配器在类" CuisinesSearchAdapter.java"中定义,如下所示。

package com.theitroad.efficientsearch;

import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.text.Normalizer;
import java.util.List;
import java.util.Locale;

public class CuisineSearchAdapter extends RecyclerView.Adapter {

  Context mContext;
  ItemListener mListener;
  String prefix = "";
  private List allVendors, cuisines;

  public CuisineSearchAdapter(List cuisines, List vendorModels, Context context, ItemListener itemListener, String text) {
      this.allVendors = vendorModels;
      this.cuisines = cuisines;
      this.mContext = context;
      this.mListener = itemListener;
      prefix = text;
  }

  public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

      ImageView icon;
      TextView textView;
      View parent;
      Model vendorModel;

      MyViewHolder(View itemView) {
          super(itemView);

          this.parent = itemView.findViewById(R.id.parentView);
          this.textView = (TextView) itemView.findViewById(R.id.textView);
          this.icon = (ImageView) itemView.findViewById(R.id.imageView);
          itemView.setOnClickListener(this);
      }

      void setData(Model model, MyViewHolder holder) {

          textView = holder.textView;
          icon = holder.icon;
          this.vendorModel = model;

          if (prefix.length() > 0) {

              if (model.isCuisine) {
                  textView.setText(highlight(prefix, model.name + " (" + model.numberOfCuisine + ")"));
                  icon.setImageResource(R.mipmap.ic_local_offer);
              } else {
                  textView.setText(highlight(prefix, model.name));
                  icon.setImageResource(R.mipmap.ic_local_dining);
              }

          } else {

              if (model.isCuisine) {
                  textView.setText(model.name + " (" + model.numberOfCuisine + ")");
                  icon.setImageResource(R.mipmap.ic_local_offer);
              }
          }

      }

      @Override
      public void onClick(View view) {

          if (mListener != null) {
              mListener.onItemClick(vendorModel);
          }

      }
  }

  @Override
  public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

      final View view = LayoutInflater.from(parent.getContext())
              .inflate(R.layout.recyclerview_list_row, parent, false);

      MyViewHolder myViewHolder = new MyViewHolder(view);
      return myViewHolder;

  }

  @Override
  public void onBindViewHolder(final MyViewHolder holder, final int listPosition) {

      if (prefix.length() > 0)
          holder.setData(allVendors.get(holder.getAdapterPosition()), holder);
      else
          holder.setData(cuisines.get(holder.getAdapterPosition()), holder);
  }

  @Override
  public int getItemCount() {

      if (prefix.length() > 0)
          return allVendors.size();
      else
          return cuisines.size();

  }

  private static CharSequence highlight(String search, String originalText) {
      //ignore case and accents
      //the same thing should have been done for the search text
      String normalizedText = Normalizer
              .normalize(originalText, Normalizer.Form.NFD)
              .replaceAll("\p{InCombiningDiacriticalMarks}+", "")
              .toLowerCase(Locale.ENGLISH);

      int start = normalizedText.indexOf(search.toLowerCase(Locale.ENGLISH));
      if (start = 0) {
              int spanStart = Math.min(start, originalText.length());
              int spanEnd = Math.min(start + search.length(),
                      originalText.length());

              highlighted.setSpan(new ForegroundColorSpan(Color.BLUE),
                      spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

              start = normalizedText.indexOf(search, spanEnd);
          }

          return highlighted;
      }
  }

  public interface ItemListener {
      void onItemClick(Model model);
  }

}

RecyclerView每行的布局在下面的xml代码中定义。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:id="@+id/parentView"
  android:background="#FFF"
  android:layout_height="wrap_content">

  <ImageView
      android:layout_width="24dp"
      android:layout_height="24dp"
      android:layout_gravity="center_vertical"
      android:layout_marginLeft="16dp"
      android:layout_marginRight="16dp"
      android:id="@+id/imageView"
      android:tint="@color/text_color"

  <TextView
      android:id="@+id/textView"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:padding="16dp"
      android:textColor="@color/text_color"
      android:textSize="14sp"

</LinearLayout>
  • 当用户输入文本时,RecyclerView数据将从MainActivity.java类中的performFiltering方法中过滤掉。
    然后,突出显示RecyclerView行中与键入的文本匹配的子字符串。

  • 单击美食会过滤列表,以显示所有以该美食为类型的餐馆。