I have a listview and want to search text from it. I have done it successfully but now I want to search the item and highlight the searched text in the listview. This is my filter function in the ListViewAdapter:
public void filter(String charText) {
charText = charText.toLowerCase(Locale.getDefault());
worldpopulationlist.clear();
if (charText.length() == 0) {
worldpopulationlist.addAll(arraylist);
}
else
{
for (WorldPopulation wp : arraylist)
{
// Find charText in wp
int startPos = wp.getCountry().toLowerCase(
Locale.getDefault()).indexOf(charText.toLowerCase(Locale.getDefault()));
int endPos = startPos + charText.length();
if (startPos != -1)
{
Spannable spannable = new SpannableString(wp.getCountry());
ColorStateList blueColor = new ColorStateList(new int[][] { new int[] {}}, new int[] { Color.BLUE });
TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, blueColor, null);
spannable.setSpan(highlightSpan, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// countryTextView.setText(spannable);
worldpopulationlist.add(wp);
}
}
}
notifyDataSetChanged();
}
I have googled it and I know that Spannable is used for this purpose but its not working. Please help me and tell me if you need any other related code.
EDIT:
The tutorial I followed was from here. I used the same code with a few minor changes. I just want to highlight the searched text in the list view (just one item e.g. country in this case).
Well, I downloaded the sample project and finally came with the following. Adapt the code to your needs.
In your filter method, store the string used to perform the filter:
// Filter Class
public void filter(String searchString) {
this.searchString = searchString;
...
// Filtering stuff as normal.
}
You must declare a member string to store it:
public class ListViewAdapter extends BaseAdapter {
...
String searchString = "";
...
And, in getView you highlight the search term:
public View getView(final int position, View view, ViewGroup parent) {
...
// Set the results into TextViews
WorldPopulation item = worldpopulationlist.get(position);
holder.rank.setText(item.getRank());
holder.country.setText(item.getCountry());
holder.population.setText(item.getPopulation());
// Find charText in wp
String country = item.getCountry().toLowerCase(Locale.getDefault());
if (country.contains(searchString)) {
Log.e("test", country + " contains: " + searchString);
int startPos = country.indexOf(searchString);
int endPos = startPos + searchString.length();
Spannable spanText = Spannable.Factory.getInstance().newSpannable(holder.country.getText()); // <- EDITED: Use the original string, as `country` has been converted to lowercase.
spanText.setSpan(new ForegroundColorSpan(Color.RED), startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
holder.country.setText(spanText, TextView.BufferType.SPANNABLE);
}
...
}
Hope it helps.
You can do like this..
public class ListViewAdapter extends BaseAdapter {
// Declare Variables
Context mContext;
LayoutInflater inflater;
String searchstring="";
private List<WorldPopulation> worldpopulationlist = null;
private ArrayList<WorldPopulation> arraylist;
public ListViewAdapter(Context context, List<WorldPopulation> worldpopulationlist) {
mContext = context;
this.worldpopulationlist = worldpopulationlist;
inflater = LayoutInflater.from(mContext);
this.arraylist = new ArrayList<WorldPopulation>();
this.arraylist.addAll(worldpopulationlist);
}
public class ViewHolder {
TextView rank;
}
#Override
public int getCount() {
return worldpopulationlist.size();
}
#Override
public WorldPopulation getItem(int position) {
return worldpopulationlist.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
public View getView(final int position, View view, ViewGroup parent) {
final ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
view = inflater.inflate(R.layout.listview_item, null);
// Locate the TextViews in listview_item.xml
holder.rank = (TextView) view.findViewById(R.id.ranklabel);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
// Set the results into TextViews
String faqsearchstr=worldpopulationlist.get(position).getRank().toLowerCase(Locale.getDefault());
if (faqsearchstr.contains(searchstring)) {
Log.e("test", faqsearchstr + " contains: " + searchstring);
System.out.println("if search text"+faqsearchstr);
int startPos = faqsearchstr.indexOf(searchstring);
int endPos = startPos + searchstring.length();
Spannable spanText = Spannable.Factory.getInstance().newSpannable(worldpopulationlist.get(position).getRank()); // <- EDITED: Use the original string, as `country` has been converted to lowercase.
spanText.setSpan(new ForegroundColorSpan(Color.GREEN), startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
holder.rank.setText(spanText, TextView.BufferType.SPANNABLE);
}
else
{
System.out.println("else search text"+faqsearchstr);
holder.rank.setText(worldpopulationlist.get(position).getRank());
}
// Listen for ListView Item Click
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
// Send single item click data to SingleItemView Class
Intent intent = new Intent(mContext, QuestionActivity.class);
// Pass all data rank
// intent.putExtra("rank",(worldpopulationlist.get(position).getRank()));
intent.putExtra("QuestionsIntent",((worldpopulationlist.get(position).getFaqpopulatedData())));
System.out.println("questionsss.."+(worldpopulationlist.get(position).getFaqpopulatedData()));
// Start SingleItemView Class
mContext.startActivity(intent);
}
});
return view;
}
// Filter Class
public void filter(String charText) {
this.searchstring=charText;
charText = charText.toLowerCase(Locale.getDefault());
worldpopulationlist.clear();
if (charText.length() == 0) {
System.out.println("inside filter if");
worldpopulationlist.addAll(arraylist);
}
else
{
for (WorldPopulation wp : arraylist)
{
if (wp.getRank().toLowerCase(Locale.getDefault()).contains(charText))
{
System.out.println("inside filter else");
worldpopulationlist.add(wp);
}
}
}
notifyDataSetChanged();
}
}
Use following code to highlight searched text from search/edit text
input is filtered name and mTextview is your text view which you want to highlight use this method after setting value to text view.
private void highlightString(String input, TextView mTextView) {
SpannableString spannableString = new SpannableString(mTextView.getText());
ForegroundColorSpan[] backgroundSpans = spannableString.getSpans(0, spannableString.length(), ForegroundColorSpan.class);
for (ForegroundColorSpan span : backgroundSpans) {
spannableString.removeSpan(span);
}
int indexOfKeyword = spannableString.toString().indexOf(input);
while (indexOfKeyword > 0) {
spannableString.setSpan(new ForegroundColorSpan(Color.GREEN), indexOfKeyword, indexOfKeyword + input.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
indexOfKeyword = spannableString.toString().indexOf(input, indexOfKeyword + input.length());
}
mTextView.setText(spannableString);
}
Related
I have created a searchview, when i type a word and press ENTER from the keyboard, the results show on a listview in 3 or 4 seconds. so i want to insert a progres spinner till the result populated. And Also I want to try to highlight the search word in the result in list view. I use "SpannableString highlightKeyword" but unable to highlight the result search word, I have tried many ways followed by several websites, but nothing happend. I Couldn't figure out where the mistake. Here are My codes:
mainactivity.java :
public class MainActivity extends AppCompatActivity implements SearchView.OnQueryTextListener {
// Declare Variables
ListView list;
ListViewAdapter adapter;
SearchView editsearch;
String[] animalNameList;
ArrayList<AnimalNames> arraylist = new ArrayList<AnimalNames>();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Resources res = getResources();
String[] animalNameList = res.getStringArray(R.array.animalNameList);
// Locate the ListView in listview_main.xml
list = (ListView) findViewById(R.id.listview);
for (int i = 0; i < animalNameList.length; i++) {
AnimalNames animalNames = new AnimalNames(animalNameList[i]);
// Binds all strings into an array
arraylist.add(animalNames);
}
// Pass results to ListViewAdapter Class
adapter = new ListViewAdapter(this, arraylist);
// Binds the Adapter to the ListView
list.setAdapter(adapter);
// Locate the EditText in listview_main.xml
editsearch = (SearchView) findViewById(R.id.search);
editsearch.setOnQueryTextListener(this);
}
#Override
public boolean onQueryTextSubmit(String newText) {
String text = newText;
adapter.filter(text);
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
return false;
}
===== ListviewAdapter.java:
public class ListViewAdapter extends BaseAdapter {
// Declare Variables
Context mContext;
LayoutInflater inflater;
private List<AnimalNames> animalNamesList = null;
private ArrayList<AnimalNames> arraylist;
public ListViewAdapter(Context context, List<AnimalNames> animalNamesList) {
mContext = context;
this.animalNamesList = animalNamesList;
inflater = LayoutInflater.from(mContext);
this.arraylist = new ArrayList<AnimalNames>();
this.arraylist.addAll(animalNamesList);
}
public class ViewHolder {
TextView name;
}
#Override
public int getCount() {
return animalNamesList.size();
}
#Override
public AnimalNames getItem(int position) {
return animalNamesList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
public View getView(final int position, View view, ViewGroup parent) {
final ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
view = inflater.inflate(R.layout.listview_item, null);
// Locate the TextViews in listview_item.xml
holder.name = (TextView) view.findViewById(R.id.name);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
// Set the results into TextViews
holder.name.setText(animalNamesList.get(position).getAnimalName());
return view;
}
// Filter Class
public void filter(String charText) {
charText = charText.toLowerCase(Locale.getDefault());
animalNamesList.clear();
if (charText.length() == 0) {
animalNamesList.addAll(arraylist);
} else {
for (AnimalNames wp : arraylist) {
if (wp.getAnimalName().toLowerCase(Locale.getDefault()).contains(charText)) {
animalNamesList.add(wp);
}
}
}
notifyDataSetChanged();
}
////Here My problem starts :
public static SpannableString highlightKeyword(CharSequence text, Pattern p, int fgcolor, int bgcolor) {
SpannableString ss = new SpannableString(text);
ColorStateList blueColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{Color.BLUE});
int start = 0;
int end;
Matcher m = p.matcher(text);
while (m.find(start)) {
start = m.start();
end = m.end();
BackgroundColorSpan bgspan = new BackgroundColorSpan(bgcolor);
ss.setSpan(bgspan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ForegroundColorSpan fgspan = new ForegroundColorSpan(fgcolor);
ss.setSpan(fgspan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
start = end;
}
return ss;
}
////But even did not the result word highlighted.
Thanks in Advance.
Alhamdulillah, At last I found my answer on google. Highligh in custom adapter within getview() method. As follows :
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
TextView text;
if (convertView == null) {
view = mInflater.inflate(R.layout.list_item, null);
} else {
view = convertView;
}
try {
if (mFieldId == 0) {
// If no custom field is assigned, assume the whole resource is a TextView
text = (TextView) view;
} else {
// Otherwise, find the TextView field within the layout
text = (TextView) view.findViewById(mFieldId);
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
throw new IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e);
}
// HIGHLIGHT...
String fullText = getItem(position);
if (mSearchText != null && !mSearchText.isEmpty()) {
int startPos = fullText.toLowerCase(Locale.US).indexOf(mSearchText.toLowerCase(Locale.US));
int endPos = startPos + mSearchText.length();
if (startPos != -1) {
Spannable spannable = new SpannableString(fullText);
ColorStateList blueColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{Color.BLUE});
TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, blueColor, null);
spannable.setSpan(highlightSpan, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setText(spannable);
} else {
text.setText(fullText);
}
} else {
text.setText(fullText);
}
return view;
}
I'm trying to make an application that will show details of a ListView row on click. I added a filter and three EditTexts for search options.
I if I use all options for searching , my app shows one member three times, that's why I commented out two of the conditions in the filter() method.
But the main problem is while I'm searching for a member it searches well but when clicking on the search result it seems the position of the ListView row changed. For example, if I found three search results the first one is on position 0, second on position 1 , and third one on position 2.
The Listview click event works fine without searching.
I need the old position like it was before filtering to send it via an Intent to another Activity that will show different data for each member.
ListViewAdapter.java class
`
public class ListViewAdapter extends BaseAdapter {
// Declare Variables
Context mContext;
LayoutInflater inflater;
private List<WorldPopulation> worldpopulationlist = null;
private ArrayList<WorldPopulation> arraylist;
public ListViewAdapter(Context context, List<WorldPopulation> worldpopulationlist) {
mContext = context;
this.worldpopulationlist = worldpopulationlist;
inflater = LayoutInflater.from(mContext);
this.arraylist = new ArrayList<WorldPopulation>();
this.arraylist.addAll(worldpopulationlist);
}
public class ViewHolder {
TextView rank;
TextView country;
TextView population;
ImageView flag;
}
#Override
public int getCount() {
return worldpopulationlist.size();
}
#Override
public WorldPopulation getItem(int position) {
return worldpopulationlist.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
public View getView(final int position, View view, final ViewGroup parent) {
final ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
view = inflater.inflate(R.layout.listview_item, null);
// Locate the TextViews in listview_item.xml
holder.rank = (TextView) view.findViewById(R.id.rank);
holder.country = (TextView) view.findViewById(R.id.country);
holder.population = (TextView) view.findViewById(R.id.population);
// Locate the ImageView in listview_item.xml
holder.flag = (ImageView) view.findViewById(R.id.flag);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
// Set the results into TextViews
holder.rank.setText(worldpopulationlist.get(position).getRank());
holder.country.setText(worldpopulationlist.get(position)
.getCountry());
holder.population.setText(worldpopulationlist.get(position)
.getPopulation());
// Set the results into ImageView
holder.flag.setImageResource(worldpopulationlist.get(position)
.getFlag());
// Listen for ListView Item Click
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
Intent summaryIntent = new Intent(mContext,
SecondActivity.class);
Bundle b = new Bundle();
b.putInt("Integer", position);
summaryIntent.putExtras(b);
mContext.startActivity(summaryIntent);
Toast.makeText(mContext, "Position:" + position,
Toast.LENGTH_SHORT).show();
Intent intent = new Intent(mContext, SecondActivity.class);
// Send single item click data to SingleItemView Class
// depending on "position", for example
// Pass all data rank
// intent.putExtra("rank",
// (worldpopulationlist.get(position).getRank()));
// ...likewise pass all other data ...
mContext.startActivity(intent);
}
});
return view;
}
// Filter Class
public void filter(String charText) {
charText = charText.toLowerCase(Locale.getDefault());
worldpopulationlist.clear();
if (charText.length() == 0) {
worldpopulationlist.addAll(arraylist);
} else {
for (WorldPopulation wp : arraylist) {
if (wp.getCountry().toLowerCase(Locale.getDefault())
.contains(charText) ) {
worldpopulationlist.add(wp);
}
if(wp.getRank().toLowerCase(Locale.getDefault())
.contains(charText)) {
worldpopulationlist.add(wp);
}
if (wp.getPopulation().toLowerCase(Locale.getDefault())
.contains(charText)) {
worldpopulationlist.add(wp);
}
}
}
notifyDataSetChanged();
}
}
In order to get the "old" position of an item in the onCLick() method, use
arraylist.indexOf( getItem(position) )
Change your filter method like this to avoid multiple adding of the same item:
// Filter Class
public void filter(String charText) {
charText = charText.toLowerCase(Locale.getDefault());
worldpopulationlist.clear();
if (charText.length() == 0) {
worldpopulationlist.addAll(arraylist);
}
else {
boolean addToList;
for (WorldPopulation wp : arraylist) {
addToList = false;
if (wp.getCountry().toLowerCase(Locale.getDefault())
.contains(charText) ) {
addToList = true;
}
if(wp.getRank().toLowerCase(Locale.getDefault())
.contains(charText)) {
addToList = true;
}
if (wp.getPopulation().toLowerCase(Locale.getDefault())
.contains(charText)) {
addToList = true;
}
// now add the item to your list if it matches the search criteria
if (addToList) {
worldpopulationlist.add(wp);
}
}
notifyDataSetChanged();
}
}
I am trying to do a search such that all the "visible" search letters should be highlighted. I tried using spannable but that didn't do the trick, maybe I wasnt doing it right? based on this: Highlight searched text in ListView items
How do i get to highlight the visible text? here's my filter :
private LayoutInflater mInflater;
private ValueFilter valueFilter;
public MySimpleArrayAdapter(Activity context) {
this.context = context;
mInflater = LayoutInflater.from(context);
}
private class ValueFilter extends Filter {
//Invoked in a worker thread to filter the data according to the constraint.
#Override
protected synchronized FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint != null && constraint.length() > 0) {
ArrayList<Integer> filterList = new ArrayList<>();
int iCnt = listItemsHolder.Names.size();
for (int i = 0; i < iCnt; i++) {
if(listItemsHolder.Types.get(i).toString().indexOf("HEADER_")>-1){
continue;
}
if (listItemsHolder.Names.get(i).matches(getRegEx(constraint))||(listItemsHolder.Names.get(i).toLowerCase().contains(constraint.toString().toLowerCase()))) {
if(filterList.contains(i))
continue;
filterList.add(i);
}
}
results.count = filterList.size();
results.values = filterList;
}else {
String prefixString = getRegEx(constraint);
mSearchText = prefixString;
results.count = listItemsHolder.Names.size();
ArrayList<Integer> tList = new ArrayList<>();
for(int i=0;i<results.count;i++){
tList.add(i);
}
results.values = tList;
}
return results;
}
//Invoked in the UI thread to publish the filtering results in the user interface.
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
ArrayList<Integer> resultsList = (ArrayList<Integer>)results.values;
if(resultsList != null) {
m_filterList = resultsList;
}
notifyDataSetChanged();
}
}
public String getRegEx(CharSequence elements){
String result = "(?i).*";
for(String element : elements.toString().split("\\s")){
result += element + ".*";
}
result += ".*";
return result;
}
Thanks in advance!
Here's my getview
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
ViewHolder holder;
if(filtering && m_filterList != null && m_filterList.size() > position)
position = m_filterList.get(position);
if (rowView == null) {
holder = new ViewHolder();
mInflater = context.getLayoutInflater();
rowView = mInflater.inflate(R.layout.rowlayout, null);
// configure view holder
holder.text = (TextView) rowView.findViewById(R.id.label);
holder.text.setTextColor(Color.WHITE);
holder.text.setSingleLine();
holder.text.setTextSize(15);
holder.text.setEllipsize(TextUtils.TruncateAt.END);
holder.text.setPadding(2, 2, 6, 2);
Typeface label = Typeface.createFromAsset(holder.text.getContext().getAssets(),
"fonts/arial-bold.ttf");
holder.text.setTypeface(label);
holder.image = (ImageView) rowView.findViewById(R.id.icon);
holder.image.setPadding(6, 4, 0, 4);
holder.image.getLayoutParams().height = (int) getResources().getDimension(R.dimen.icon_width_height);
holder.image.getLayoutParams().width = (int) getResources().getDimension(R.dimen.icon_width_height);
rowView.setBackgroundResource(R.drawable.row_border);
rowView.setPadding(2, 2, 6, 2);
rowView.setTag(holder);
}else {
// fill data
holder = (ViewHolder) rowView.getTag();
}
String id = listItemsHolder.getid(position);
String name = listItemsHolder.getName(position);
holder.image.setVisibility(View.VISIBLE);
if (name != null) {
holder.text.setText(listItemsHolder.getName(position));
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) holder.text.getLayoutParams();
params.leftMargin = 20;
}else{
holder.text.setText(id);
}
String fullText = listItemsHolder.getName(position);
// highlight search text
if (mSearchText != null && !mSearchText.isEmpty()) {
int startPos = fullText.toLowerCase(Locale.US).indexOf(mSearchText.toLowerCase(Locale.US));
int endPos = startPos + mSearchText.length();
if (startPos != -1) {
Spannable spannable = new SpannableString(fullText);
ColorStateList blueColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{Color.BLUE});
TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, blueColor, null);
spannable.setSpan(highlightSpan, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
holder.text.setText(spannable);
} else {
holder.text.setText(fullText);
}
} else {
holder.text.setText(fullText);
}
return rowView;
}
Let's assume you have create a custom adapter, then you can refer to the following code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
TextView text;
if (convertView == null) {
view = mInflater.inflate(mResource, parent, false);
} else {
view = convertView;
}
try {
if (mFieldId == 0) {
// If no custom field is assigned, assume the whole resource is a TextView
text = (TextView) view;
} else {
// Otherwise, find the TextView field within the layout
text = (TextView) view.findViewById(mFieldId);
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
throw new IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e);
}
String item = getItem(position);
text.setText(item);
String fullText = getItem(position);
// highlight search text
if (mSearchText != null && !mSearchText.isEmpty()) {
int startPos = fullText.toLowerCase(Locale.US).indexOf(mSearchText.toLowerCase(Locale.US));
int endPos = startPos + mSearchText.length();
if (startPos != -1) {
Spannable spannable = new SpannableString(fullText);
ColorStateList blueColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{Color.BLUE});
TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, blueColor, null);
spannable.setSpan(highlightSpan, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setText(spannable);
} else {
text.setText(fullText);
}
} else {
text.setText(fullText);
}
return view;
}
The mSearchText will be updated at the following inside performFiltering of ArrayFilter class.
String prefixString = prefix.toString().toLowerCase();
mSearchText = prefixString;
You can find more details in my sample code here or my GitHub (with lastest update).
Here is the screenshot
In your filter method, store the string used to perform the filter:
// Filter Class
public void filter(String searchString) {
this.searchString = searchString;
...
// Filtering stuff as normal.
}
You must declare a member string to store it:
public class ListViewAdapter extends BaseAdapter {
...
String searchString = "";
...
And, in getView you highlight the search term:
public View getView(final int position, View view, ViewGroup parent) {
...
// Set the results into TextViews
WorldPopulation item = worldpopulationlist.get(position);
holder.rank.setText(item.getRank());
holder.country.setText(item.getCountry());
holder.population.setText(item.getPopulation());
// Find charText in wp
String country = item.getCountry().toLowerCase(Locale.getDefault());
if (country.contains(searchString)) {
Log.e("test", country + " contains: " + searchString);
int startPos = country.indexOf(searchString);
int endPos = startPos + searchString.length();
Spannable spanText = Spannable.Factory.getInstance().newSpannable(holder.country.getText()); // <- EDITED: Use the original string, as `country` has been converted to lowercase.
spanText.setSpan(new ForegroundColorSpan(Color.RED), startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
holder.country.setText(spanText, TextView.BufferType.SPANNABLE);
}
...
}
Hope it helps.
Hi on your adapter class ,make a spanneble text and set it to your textview, the below code you can use for reference.
if ("text contains filter value".toLowerCase().contains("filter".toLowerCase())) {
Spannable spanText = Spannable.Factory.getInstance().newSpannable("text contains filter value".toLowerCase());
Matcher matcher = Pattern.compile("filter".toLowerCase())
.matcher("text contains filter value".toLowerCase());
while (matcher.find()) {
spanText.setSpan(new ForegroundColorSpan(Color.RED), matcher.start(),
matcher.start() + "filter".length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
yourTextView.setText(spanText);
}
This is only demo for highlight text, you can implement your self by calling
highlight(searchText, originalText) in filter,
public class MainActivity extends AppCompatActivity {
EditText editText;
TextView text;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.editText);
text = (TextView) findViewById(R.id.textView1);
editText.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.setText(highlight(editText.getText().toString(), text.getText().toString()));
}
#Override
public void afterTextChanged(Editable s) {
}
});
}
public static CharSequence highlight(String search, String originalText) {
String normalizedText = Normalizer.normalize(originalText, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "").toLowerCase();
int start = normalizedText.indexOf(search);
if (start <= 0) {
return originalText;
} else {
Spannable highlighted = new SpannableString(originalText);
while (start > 0) {
int spanStart = Math.min(start, originalText.length());
int spanEnd = Math.min(start + search.length(), originalText.length());
highlighted.setSpan(new BackgroundColorSpan(Color.YELLOW), spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
start = normalizedText.indexOf(search, spanEnd);
}
return highlighted;
}
}
}
Put this code before setting text in getview
Spannable wordtoSpan = new SpannableString("Your_text_in_getviews");
wordtoSpan.setSpan(new ForegroundColorSpan(Color.RED), 0, edtFilter
.getText().toString().length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
txt_contact.setText(wordtoSpan);
It can be done in a bit simpler way:
Define custom adapter:
class HighlightAutoCompleteAdapter(context: Context, resource: Int, private val textResId: Int, items: List<String>) :
ArrayAdapter<String>(context, resource, textResId, items) {
private val inflater = LayoutInflater.from(context)
var queryText = ""
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: inflater.inflate(textResId, parent, false)
val textView: TextView = view.findViewById(android.R.id.text1) as TextView
val fullText = getItem(position) as String
// highlight search text
val highlight: Spannable = SpannableString(fullText)
if (queryText.isNotEmpty()) {
val startPos: Int = fullText.toLowerCase(Locale.US).indexOf(queryText.toLowerCase(Locale.US))
val endPos: Int = startPos + queryText.length
if (startPos != -1) {
highlight.setSpan(StyleSpan(BOLD),
startPos,
endPos,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
textView.text = highlight
return view
}
}
Create the adapter and listen to text changes to keep the adapter updated:
val searchEditText: AutoCompleteTextView = view.findViewById(R.id.search_edit_text)
val arrayAdapter = HighlightAutoCompleteAdapter(requireContext(), 0, R.layout.search_complete_item, autoCompletionList)
searchEditText.setAdapter(arrayAdapter)
searchEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
arrayAdapter.queryText = s?.toString() ?: ""
}
override fun afterTextChanged(s: Editable?) {}
})
So I have a custom adapter, which I tries to implement a filter search where user key search item from edittext. And the filtering works just fine. However, in my list, I do also implement checkbox.
Let's say I have a list
Bar
Tar
Foo
Kay
Default list would get the position correctly, so no issue here.
The issue starts when I search for say a and the list will becomes.
Bar
Tar
Kay
And if I check on Kay after search, it returns me Foo instead.
And the following is my code for my adapter and filter, what is wrong?
public class MyMediaAdapter extends ArrayAdapter<Media> implements Filterable {
private List<Media> list;
private final Activity context;
private Filter mediaFilter;
private List<Media> origMediaList;
public MyMediaAdapter(Activity context, List<Media> list) {
super(context, R.layout.media_view, list);
this.context = context;
this.list = list;
this.origMediaList = list;
}
public int getCount() {
return list.size();
}
public Media getItem(int position) {
return list.get(position);
}
public long getItemId(int position) {
return list.get(position).hashCode();
}
private class ViewHolder {
protected TextView fName, fSub, fDuration, fSize;
protected CheckBox checkbox;
// protected CheckBox checkbox1;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
// Moved here to ensure the checkbox is persistent
ViewHolder viewHolder = new ViewHolder();
if (convertView == null) {
LayoutInflater inflator = context.getLayoutInflater();
view = inflator.inflate(R.layout.media_view, null);
// Moved out of the if-else to solve the problem
// view being recycled each time it scrolls
// final ViewHolder viewHolder = new ViewHolder();
viewHolder.fName = (TextView) view.findViewById(R.id.tvfname);
viewHolder.fSub = (TextView) view.findViewById(R.id.tvsub);
viewHolder.fDuration = (TextView) view.findViewById(R.id.tvduration);
viewHolder.fSize = (TextView) view.findViewById(R.id.tvsize);
viewHolder.checkbox = (CheckBox) view.findViewById(R.id.check);
view.setTag(viewHolder);
// Moved out of the if-else to solve the problem
// view being recycled each time it scrolls
// viewHolder.checkbox.setTag(list.get(position));
} else {
view = convertView;
// Moved out of the if-else to solve the problem
// view being recycled each time it scrolls
// ((ViewHolder) view.getTag()).checkbox.setTag(list.get(position));
viewHolder = (ViewHolder) view.getTag();
}
// Moved here to ensure the checkbox is persistent
viewHolder.checkbox.setId(position);
viewHolder.checkbox.setTag(list.get(position));
ViewHolder holder = (ViewHolder) view.getTag();
holder.fName.setText(list.get(position).getName());
holder.fSub.setText(list.get(position).getPath());
// Converting duration from String to Long
long milli = Long.valueOf(list.get(position).getDuration());
// Put it in % min, % sec format to display
holder.fDuration.setText(util.readableTime(milli));
// Convert data size from String to Long
long datasize = Long.valueOf(list.get(position).getData());
// Put in human readable format
holder.fSize.setText(util.readableFileSize(datasize));
holder.checkbox.setChecked(list.get(position).isSelected());
// viewHolder.checkbox.setId(position);
viewHolder.checkbox.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
CheckBox cb = (CheckBox) v;
int id = cb.getId();
if (selection[id]) {
cb.setChecked(false);
selection[id] = false;
list.get(id).setSelected(false);
} else {
cb.setChecked(true);
selection[id] = true;
list.get(id).setSelected(true);
}
}
});
// Implement SelectAll/DeselectAll feature
final CheckBox checkbox1 = (CheckBox) findViewById(R.id.cb_selectall);
checkbox1.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton button, boolean checked) {
// TODO Auto-generated method stub
if (checked) {
checkbox1.setText("Click to Deselect All");
for (int i = 0; i < list.size(); i++) {
selection[i] = true;
list.get(i).setSelected(true);
}
// Called to notify checkbox changes so the view gets updated immediately
notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "All files are selected", Toast.LENGTH_LONG).show();
} else {
checkbox1.setText("Click to Select All");
for (int i = 0; i < list.size(); i++) {
selection[i] = false;
list.get(i).setSelected(false);
}
notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "All files are deselected", Toast.LENGTH_LONG).show();
}
}
});
return view;
}
public void resetData() {
list = origMediaList;
}
#Override
public Filter getFilter() {
if (mediaFilter == null)
mediaFilter = new mediaFilter();
return mediaFilter;
}
private class mediaFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
// We implement here the filter logic
if (constraint == null || constraint.length() == 0) {
// No filter implemented we return all the list
results.values = origMediaList;
results.count = origMediaList.size();
}
else {
// We perform filtering operation
List<Media> nMediaList = new ArrayList<Media>();
for (Media m : list) {
if (m.getName().toUpperCase().contains(constraint.toString().toUpperCase()))
nMediaList.add(m);
}
results.values = nMediaList;
results.count = nMediaList.size();
}
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
// Now we have to inform the adapter about the new list filtered
if (results.count == 0)
notifyDataSetInvalidated();
else {
list = (List<Media>) results.values;
notifyDataSetChanged();
}
}
}
}
I have the following setup so I know which has been selected.
private boolean[] selection;
private int count;
// After I fetch my list
count = getMediaList.size();
selection = new boolean[count];
// Inside onOptionsItemSelected
#Override
public boolean onOptionsItemSelected(MenuItem item) {
final ArrayList<Integer> posSel = new ArrayList<Integer>();
posSel.clear();
storeSelectedMedia.clear();
/*
* Construct the list of selected items
*/
boolean noSelect = false;
//Log.i("MediaSelection", "" + selection.length);
for (int i = 0; i < selection.length; i++) {
//Log.i("MediaSelect", "" + getMediaList.get(i).isSelected());
if (selection[i] == true) {
//if (getMediaList.get(i).isSelected() == true) {
noSelect = true;
Log.e("Mediasel pos thu-->", "" + i);
posSel.add(i);
storeSelectedMedia.add(getMediaList.get(i).getPath());
}
}
switch (item.getItemId()) {
case R.id.action_sfd:
if (noSelect) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
final ScrollView s_view = new ScrollView(getApplicationContext());
final TextView t_view = new TextView(getApplicationContext());
StringBuilder sBuilder = new StringBuilder();
sBuilder.append("\n Name: \t " + getMediaList.get(posSel.get(0)).getName());
sBuilder.append("\n Parent: \t " + getMediaList.get(posSel.get(0)).getParent());
sBuilder.append("\n Type: \t " + getMediaList.get(posSel.get(0)).getType());
sBuilder.append("\n Size: \t\t " + util.readableFileSize(getMediaList.get(posSel.get(0)).getSize()));
sBuilder.append("\n ");
t_view.setText(sBuilder);
t_view.setTextSize(14);
s_view.addView(t_view);
builder.setTitle("File Properties")
.setView(s_view);
AlertDialog dialog = builder.create();
dialog.show();
Toast.makeText(this,
"Selected Items:" + storeSelectedMedia.toString(),
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this,
"No files selected",
Toast.LENGTH_SHORT).show();
}
break;
Hopefully that would be enough information.
Anyone has any idea on this? Appreciate it greatly!
I have solved my own problem. As I was using getMediaList to get the item to display, I forgotten that I have no update my getMediaList to the "after-filtered" list.
Thus, on the publishResult method, before notifyDataSetChanged();, I basically just assign the getMediaList = list.
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
// Now we have to inform the adapter about the new list filtered
if (results.count == 0)
notifyDataSetInvalidated();
else {
list = (List<Media>) results.values;
// HERE
getMediaList = list;
notifyDataSetChanged();
}
}
Checked through it for quite a long time before I realize this stupid mistake. Hope it helps someone in the future.
Subject says it all. I have seen examples implementing a custom Filter. The Android developer docs talk about implementing a Filterable interface. Does anyone have any advice and/or sample code on the best way to implement filtering in a ListView ?
This video comes from the latest Google I/O (2010), it's called "The world of ListView".
http://code.google.com/intl/it-IT/events/io/2010/sessions/world-of-listview-android.html
at 34:25 it explains how to implement a text filter
here, near line 437, an example of use:
https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/ArrayAdapter.java
enjoy :)
Since this was the first answer I have found on google I decided to post some code to save the next person some time. I have come up with the code thanks to this blog:
http://www.mokasocial.com/2010/07/arrayadapte-filtering-and-you/
public void buildSearchList {
lv2 = new ListView(this);
edi = new EditText(this);
edi.setHint(R.string.teclear);
edi.addTextChangedListener(filterTextWatcher);
lv2.addFooterView(cancelButton);
lv2.addHeaderView(edi);
lv2.setAdapter(mAdapter2);
lv2.setTextFilterEnabled(true);
}
private class EventAdapter extends ArrayAdapter<Articulo> implements
Filterable {
public ArrayList<Articulo> mEvents = null;
private final Object mLock = new Object();
private Filter filter;
public EventAdapter(Context c, ArrayList<Articulo> clientes) {
super(c, android.R.layout.test_list_item);
mContext = c;
mEvents = clientes;
filter = new MyFilter();
}
#Override
public Filter getFilter() {
if (filter == null) {
filter = new MyFilter();
}
return filter;
}
public int getCount() {
return mEvents.size();
}
public Articulo getItem(int position) {
return mEvents.get(position);
}
public long getItemId(int position) {
return mEvents.get(position).getIdCodigo();
}
public View getView(int position, View convertView, ViewGroup parent) {
EventEntryView btv;
if (convertView == null) {
btv = new EventEntryView(mContext, mEvents.get(position));
} else {
btv = (EventEntryView) convertView;
String title1 = mEvents.get(position).getDescripcion();
if (title1 != null) {
btv.setText1Title(title1);
}
}
btv.setBackgroundColor(Color.BLACK);
return btv;
}
private Context mContext;
private class MyFilter extends Filter {
protected FilterResults performFiltering(CharSequence prefix) {
// Initiate our results object
FilterResults results = new FilterResults();
// Collection<? extends Articulo> mItemsArray = null;
// If the adapter array is empty, check the actual items array
// and use it
if (mEvents == null) {
synchronized (mLock) { // Notice the declaration above
if(cual==1)
mEvents = new ArrayList<Articulo>(clientes);
else mEvents = new ArrayList<Articulo>(ventas);
}
}
// No prefix is sent to filter by so we're going to send back
// the original array
if (prefix == null || prefix.length() == 0) {
synchronized (mLock) {
if(cual==1){
results.values = clientes;
results.count = clientes.size();
}else {
results.values = ventas;
results.count = ventas.size();
}
}
} else {
// Compare lower case strings
String prefixString = prefix.toString().toLowerCase();
// Local to here so we're not changing actual array
final ArrayList<Articulo> items = mEvents;
final int count = items.size();
final ArrayList<Articulo> newItems = new ArrayList<Articulo>(
count);
for (int i = 0; i < count; i++) {
final Articulo item = items.get(i);
final String itemName = item.getDescripcion()
.toString().toLowerCase();
// First match against the whole, non-splitted value
if (itemName.startsWith(prefixString)) {
newItems.add(item);
} else {
// else {} // This is option and taken from the
// source of
// ArrayAdapter
final String[] words = itemName.split(" ");
final int wordCount = words.length;
for (int k = 0; k < wordCount; k++) {
if (words[k].startsWith(prefixString)) {
newItems.add(item);
break;
}
}
}
}
// Set and return
results.values = newItems;
results.count = newItems.size();
}
return results;
}
#SuppressWarnings("unchecked")
protected void publishResults(CharSequence prefix,
FilterResults results) {
// noinspection unchecked
mEvents = (ArrayList<Articulo>) results.values;
// Let the adapter know about the updated list
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
}
private class EventEntryView extends LinearLayout {
private TextView text1;
public EventEntryView(Context context, Articulo subSolicitud) {
super(context);
this.setOrientation(VERTICAL);
text1 = new TextView(context);
text1.setTextSize(20);
text1.setPadding(10, 20, 10, 20);
text1.setTextColor(Color.WHITE);
String t = subSolicitud.getDescripcion();
text1.setText(t);
addView(text1, new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
public void setText1Title(String title1) {
text1.setText(title1);
}
}
private TextWatcher filterTextWatcher = new TextWatcher() {
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
public void onTextChanged(CharSequence s, int start, int before,
int count) {
mAdapter2.getFilter().filter(s);
}
};
There are two possible ways of Resolving this
1.
Use your own Filtering Algorithm to filter the adapter(As said by others).
2.
The second and much simpler method is to override the tostring method in the Custom RowItem class you might have defined
#Override
public String toString() {
return name + "\n" + description;
}
where name and description are the possible text you have stored in the row items on which you want filtering
and use the adapter.getFilter().filter(s); as such you were using it will work now because your adapter now returns a valid string to filter
I looked at some sample code from other developers and learned a lot by simply reading through the source for ArrayAdapter. Armed with that info I managed to implement my own filtering.