Android多重搜寻,例如传送,搜寻联络人
在本教程中,我们将讨论并实现搜索功能,该功能将在下拉菜单中显示匹配的结果,并允许根据搜索到的字符串过滤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行中与键入的文本匹配的子字符串。单击美食会过滤列表,以显示所有以该美食为类型的餐馆。

