Android Limited Multi Select Preference, why no checkmark? - java

I'm trying to create a custom preference for an Android application that limits the number of items the user can select. Once the limit is reached the unselected items should be disabled and only the currently selected items are enabled.. If there are less items selected than the limit all items should be enabled.
Here are the two classes I've cobbled together. The code runs fine but no the CheckedTextView is not checked. As a result no state is maintained.
First the view class...
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckedTextView;
import android.widget.ListView;
public class LimitedMultiChoiceView extends ListView
implements AdapterView.OnItemClickListener
{
private static final String SEPARATOR = "OV=I=XseparatorX=I=VO";
private static final String LIMITEDSELECTION="limitedSelection";
private static final String SUPERSTATE="superState";
private String mSelection = "";
public LimitedMultiChoiceView(Context context, CharSequence[] items, int limit)
{
this(context, null, 0, items, limit);
} // LimitedMultiChoiceView(Context context)
public LimitedMultiChoiceView(Context context, AttributeSet attrs, CharSequence[] items, int limit)
{
this(context, attrs, 0, items, limit);
} // LimitedMultiChoiceView(Context context, AttributeSet attrs)
public LimitedMultiChoiceView(Context context, AttributeSet attrs
, int defStyle, CharSequence[] items, int limit)
{
super(context, attrs, defStyle);
setAdapter(new ItemAdapter(context, android.R.layout.simple_list_item_multiple_choice, items, limit));
} // LimitedMultiChoiceView(Context context, AttributeSet attrs, int defStyle)
public String getSelection()
{
return mSelection;
} // String getSelection()
public void onItemClick(AdapterView<?> parent, View child, int position, long id)
{
// For now we don't need anything...
// we think the ItemAdapter manages what we need for each item.
} // void onItemClick(AdapterView<?> parent, View child, int position, long id)
#Override
public void onRestoreInstanceState(Parcelable toParce)
{
Bundle state=(Bundle)toParce;
super.onRestoreInstanceState(state.getParcelable(SUPERSTATE));
mSelection = state.getString(LIMITEDSELECTION);
} // void onRestoreInstanceState(Parcelable toParce)
#Override
public Parcelable onSaveInstanceState()
{
Bundle state=new Bundle();
state.putParcelable(SUPERSTATE, super.onSaveInstanceState());
state.putString(LIMITEDSELECTION, mSelection);
return(state);
} // Parcelable onSaveInstanceState()
public void setSelection(String value)
{
mSelection = value;
} // void setSelection(String value)
class ItemAdapter extends ArrayAdapter<CharSequence>
{
CharSequence[] mItems = null;
int mLimit = 1;
public ItemAdapter(Context context, int viewResId, CharSequence[] items, int limit)
{
super(context, viewResId, items);
mItems = items;
mLimit = limit;
} // ItemAdapter(Context context, int viewResId, CharSequence[] strings, int limit)
public boolean areAllItemsEnabled()
{
// Since we are in a limited selection list not all items can be
// selected so this always returns false, there is no calculating
// to do.
return false;
} // boolean areAllItemsEnabled()
public boolean isEnabled(int position)
{
boolean[] clickedIndexes = new boolean[this.getCount()];
boolean result = false;
int selectedCount = 0;
mSelection = "";
for (int item=0; item < clickedIndexes.length; item++ )
{
View itemView = this.getView(item, null, LimitedMultiChoiceView.this);
if (itemView instanceof CheckedTextView)
{
CheckedTextView check = (CheckedTextView)itemView;
// First we turn the check mark on or off...
if (item == position)
{
check.setChecked(!check.isChecked());
} // (item == position)
// Now we count how many are checked and mark our tracking array
if (check.isChecked())
{
clickedIndexes[item] = true;
mSelection += check.getText() + SEPARATOR;
}
else
{
clickedIndexes[item] = false;
} // (check.isChecked())
} // (itemView instanceof CheckedTextView)
if (clickedIndexes[item])
selectedCount++;
} // (int item=0; item< clickedIndexes.length; item++ )
if (selectedCount >= mLimit)
{
if (clickedIndexes[position])
{
result = true;
}
else
{
result = false;
} // (clickedIndexes[position])
}
else
{
result = true;
} // (selectedCount >= mLimit)
return result;
} // boolean isEnabled(int position)
} // class ItemAdapter extends ArrayAdapter<CharSequence>
} // class LimitedMultiChoiceView extends ListView
This is the preference class...
import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.view.View;
public class ListPreferenceLimitedMultiSelect extends ListPreference
{
int mLimit = 3;
LimitedMultiChoiceView mList = null;
String mSelection = "";
public ListPreferenceLimitedMultiSelect(Context context)
{
this(context, null);
} // ListPreferenceLimitedMultiSelect(Context context)
public ListPreferenceLimitedMultiSelect(Context context, AttributeSet attr)
{
super(context, attr);
} // ListPreferenceLimitedMultiSelect(Context context, AttributeSet attr)
#Override
protected void onBindDialogView(View v)
{
super.onBindDialogView(v);
mList.setSelection(mSelection);
} // void onBindDialogView(View v)
#Override
protected View onCreateDialogView()
{
mList = new LimitedMultiChoiceView(getContext()
, this.getEntries(), mLimit);
return(mList);
} // View onCreateDialogView()
#Override
protected void onDialogClosed(boolean positiveResult)
{
super.onDialogClosed(positiveResult);
if (positiveResult)
{
if (callChangeListener(mList.getSelection()))
{
mSelection = mList.getSelection();
persistString(mSelection);
} // (callChangeListener(mList.getSelection()))
} // (positiveResult)
} // void onDialogClosed(boolean positiveResult)
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue)
{
mSelection =(restoreValue ? getPersistedString(mSelection) : (String)"");
} // void onSetInitialValue(boolean restoreValue, Object defaultValue)
} // class ListPreferenceLimitedMultiSelect extends ListPreference
Thanks,
\ ^ / i l l

Related

How to make recyclerview autoscroll horizontally.?

I have put one recycler view in my project. I want that it will be auto-scroll horizontally. For achieving this I have made one custom class. but I also want that existing functions which I put on my recycler view will also remain.
CustomLinearLayoutManager:
public class CustomLinearLayoutManager extends LinearLayoutManager {
public CustomLinearLayoutManager (Context context) {
super(context);
}
public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public CustomLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
final LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext()) {
private static final float MILLISECONDS_PER_INCH = 200f;
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return CustomLinearLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
#Override
protected float calculateSpeedPerPixel
(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
}
Home Class
private RecyclerView recyclerViewHeaderSlider;
private HeaderSliderAdapter headerSliderAdapter;
private List<Banner> banners;
banners = new ArrayList<>();
headerSliderAdapter = new HeaderSliderAdapter(getActivity(), banners);
recyclerViewHeaderSlider = view.findViewById(R.id.bannerSlider);
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerViewHeaderSlider);
recyclerViewHeaderSlider.setHasFixedSize(true);
recyclerViewHeaderSlider.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));
headerSliderAdapter.setOnClick(this);
recyclerViewHeaderSlider.setAdapter(headerSliderAdapter);
I want to implement my custom linear layout manager with Home class.
You can use Runnable to autoscroll Horizontal RV
public void autoScroll(){
speedScroll = 0;
handler = new Handler();
runnable = new Runnable() {
int count = 0;
#Override
public void run() {
if(count == tickerAdapter.getItemCount())
count = 0;
else {
if(count < tickerAdapter.getItemCount()){
rvTicker.smoothScrollToPosition(++count);
handler.postDelayed(this,speedScroll);
}else {
count = 0;
}
}
}
};
handler.postDelayed(runnable,speedScroll);
}

Spinner with sub-spinner not working as expected

I have two spinners, such that my second spinner changes options that it can offer according to the item selected in the first spinner. Easy?
Example: If I select 'a' in main spinner, the sub spinner should show 'a1' as option. If I select 'b' in main spinner, the sub spinner should show 'b1','b2' as options. If I select 'c' in main spinner, the sub spinner should show 'c1','c2','c3' as options.
I use a library called SearchableSpinner but that does not matter as it works just like the Android spinner.
public class PostComplaint extends AppCompatActivity {
String[] problems_main = {"a","b","c"};
String[][] problems_sub = {{"a1"},{"b1","b2"},{"c1","c2","c3"}};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_post_complaint);
spinner_main = (SearchableSpinner)findViewById(R.id.spinner_main);
spinner_sub = (SearchableSpinner) findViewById(R.id.spinner_sub);
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, problems_main);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner_main.setAdapter(spinnerAdapter);
spinnerAdapter.notifyDataSetChanged();
spinner_main.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setSubSpinner(position);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
Toast.makeText(PostComplaint.this, "Nothing selected", Toast.LENGTH_SHORT).show();
}
});
}
void setSubSpinner(int i){
String[] myArray = problems_sub[i]; //Note: problems_sub is a two dimensional array
ArrayAdapter<String> spinnerAdapter_sub = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, myArray);
spinnerAdapter_sub.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner_sub.setAdapter(spinnerAdapter_sub);
spinnerAdapter_sub.notifyDataSetChanged();
}
Problem: Whichever item I click on the main spinner for the first time, according to that the sub spinner is selected. Then if I change the main spinner, the sub spinner is not changing.
The question is open for suggestions. Comment if it is not understandable.
The error is coming because of the following function in the SearchableSpinner Class:
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
ArrayAdapter adapter = (ArrayAdapter) getAdapter();
if (null != adapter) {
if (_items.size() == 0) {
for (int i = 0; i < adapter.getCount(); i++) {
_items.add(adapter.getItem(i));
}
}
SearchableListDialog searchableListDialog = SearchableListDialog.newInstance
(_items);
searchableListDialog.setOnSearchableItemClickListener(this);
searchableListDialog.show(((Activity) _context).getFragmentManager(), "TAG");
}
}
return true;
}
The condition if(_items.size() == 0) becomes true only the first time you click on the sub spinner and hence it gets initialized correctly and you will see correct values. However, once you have clicked on the sub spinner, _item.size() will never be zero and thus, the updated sub spinner values will never be rendered.
I suggest you use the default Android Spinner or fork the repository and fix the error and use the same.
EDIT
You can try using this class:
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import com.toptoche.searchablespinnerlibrary.SearchableListDialog;
import java.util.ArrayList;
import java.util.List;
public class CustomSearchableSpinner extends Spinner implements View.OnTouchListener,
SearchableListDialog.SearchableItem {
private Context _context;
private List _items;
private boolean isDataSetChanged;
public CustomSearchableSpinner(Context context) {
super(context);
this._context = context;
this.isDataSetChanged = true;
init();
}
public CustomSearchableSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
this._context = context;
this.isDataSetChanged = true;
init();
}
public CustomSearchableSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this._context = context;
this.isDataSetChanged = true;
init();
}
private void init() {
_items = new ArrayList();
setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
ArrayAdapter adapter = (ArrayAdapter) getAdapter();
if (null != adapter) {
if (isDataSetChanged) {
if(_items.size() != 0) {
_items = new ArrayList();
}
for (int i = 0; i < adapter.getCount(); i++) {
_items.add(adapter.getItem(i));
}
isDataSetChanged = false;
}
SearchableListDialog searchableListDialog = SearchableListDialog.newInstance
(_items);
searchableListDialog.setOnSearchableItemClickListener(this);
searchableListDialog.show(((Activity) _context).getFragmentManager(), "TAG");
}
}
return true;
}
#Override
public void onSearchableItemClicked(Object item, int position) {
setSelection(_items.indexOf(item));
}
public void notifyDataChanged(Boolean hasDataChanged) {
this.isDataSetChanged = hasDataChanged;
}
}
Also, update your layout file and PostComplaint Class to use CustomSearchableSpinner in place of Searchable Spinner. It may not be the best approach, but it works.
EDIT 2
You will need to call spinnerAdapter_sub.notifyDataChanged(true) after you call spinnerAdapter_sub.notifyDataSetChanged();.

Get child name from ExpandableListView

I'm learning android and I'm testing right now with ExpandableListViews. I'm doing something like a dictionary with a SearchView. When I click on a child I want to start a new activity with the child name on the ActionBar and the meaning of the word(from strings.xml) in a TextView. The problem is that instead of the child name, the showed text is something like: com.example.jairo_2.myapplication.country#58f8f2e with each child. Could you help me?. I have simplified the code with a Toast that shows the child name. Thanks.
Error image
I have followed Android ExpandableListView Search Filter Example.
MainActivity.java:
package com.example.jairo_2.myapplication;
import android.app.SearchManager;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.View;
import android.widget.ExpandableListView;
import android.widget.SearchView;
import android.widget.Toast;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity implements
SearchView.OnQueryTextListener, SearchView.OnCloseListener{
private SearchView search;
private MyListAdapter listAdapter;
private ExpandableListView myList;
public ArrayList<Continent> continentList = new ArrayList<Continent>();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
search = (SearchView) findViewById(R.id.search);
search.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
search.setIconifiedByDefault(false);
search.setOnQueryTextListener(this);
search.setOnCloseListener(this);
//display the list
displayList();
//expand all Groups
//expandAll();
setListener();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
//method to expand all groups
private void expandAll() {
int count = listAdapter.getGroupCount();
for (int i = 0; i < count; i++){
myList.expandGroup(i);
}
}
//method to expand all groups
private void displayList() {
//display the list
loadSomeData();
//get reference to the ExpandableListView
myList = (ExpandableListView) findViewById(R.id.expandableList);
//create the adapter by passing your ArrayList data
listAdapter = new MyListAdapter(MainActivity.this, continentList);
//attach the adapter to the list
myList.setAdapter(listAdapter);
}
private void loadSomeData() {
ArrayList<Country> countryList = new ArrayList<Country>();
Country country = new Country("word1");
countryList.add(country);
country = new Country("word2");
countryList.add(country);
country = new Country("word3");
countryList.add(country);
Continent continent = new Continent("A",countryList);
continentList.add(continent);
countryList = new ArrayList<Country>();
country = new Country("China");
countryList.add(country);
country = new Country("Japan");
countryList.add(country);
country = new Country("Thailand");
countryList.add(country);
continent = new Continent("Asia",countryList);
continentList.add(continent);
}
#Override
public boolean onClose() {
listAdapter.filterData("");
expandAll();
return false;
}
#Override
public boolean onQueryTextChange(String query) {
listAdapter.filterData(query);
expandAll();
if (query.compareTo("") == 0){
int count = listAdapter.getGroupCount();
for (int i = 0; i <count ; i++)
myList.collapseGroup(i);}
return false;
}
#Override
public boolean onQueryTextSubmit(String query) {
listAdapter.filterData(query);
expandAll();
if (query.compareTo("") == 0){
int count = listAdapter.getGroupCount();
for (int i = 0; i <count ; i++)
myList.collapseGroup(i);}
return false;
}
void setListener() {
// This listener will show toast on group click
myList.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
#Override
public boolean onGroupClick(ExpandableListView listview, View view,
int group_pos, long id) {
Toast.makeText(MainActivity.this, "You clicked : " + listAdapter.getGroup(group_pos), Toast.LENGTH_SHORT).show();
return false;
}
});
// This listener will expand one group at one time
// You can remove this listener for expanding all groups
//myList.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
// Default position
//int previousGroup = -1;
//#Override
//public void onGroupExpand(int groupPosition) {
// if (groupPosition != previousGroup)
// Collapse the expanded group
// myList.collapseGroup(previousGroup);
//previousGroup = groupPosition;
// }
// });
// This listener will show toast on child click
myList.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView listview, View view,
int groupPos, int childPos, long id) {
Toast.makeText(MainActivity.this, "You clicked : " + listAdapter.getChild(groupPos,childPos), Toast.LENGTH_SHORT).show();
return false;
}
});
}
}
MyListAdapter.java:
package com.example.jairo_2.myapplication;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;
import java.util.ArrayList;
public class MyListAdapter extends BaseExpandableListAdapter {
private Context context;
public ArrayList<Continent> continentList;
private ArrayList<Continent> originalList;
public MyListAdapter(Context context, ArrayList<Continent> continentList) {
this.context = context;
this.continentList = new ArrayList<Continent>();
this.continentList.addAll(continentList);
this.originalList = new ArrayList<Continent>();
this.originalList.addAll(continentList);
}
#Override
public Object getChild(int groupPosition, int childPosition) {
ArrayList<Country> countryList = continentList.get(groupPosition).getCountryList();
return countryList.get(childPosition);
}
#Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
#Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View view, ViewGroup parent) {
Country country = (Country) getChild(groupPosition, childPosition);
if (view == null) {
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = layoutInflater.inflate(R.layout.child_row, null);
}
TextView name = (TextView) view.findViewById(R.id.name);
name.setText(country.getName().trim());
return view;
}
#Override
public int getChildrenCount(int groupPosition) {
ArrayList<Country> countryList = continentList.get(groupPosition).getCountryList();
return countryList.size();
}
#Override
public Object getGroup(int groupPosition) {
return continentList.get(groupPosition);
}
#Override
public int getGroupCount() {
return continentList.size();
}
#Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
#Override
public View getGroupView(int groupPosition, boolean isLastChild, View view,
ViewGroup parent) {
Continent continent = (Continent) getGroup(groupPosition);
if (view == null) {
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = layoutInflater.inflate(R.layout.group_row, null);
}
TextView heading = (TextView) view.findViewById(R.id.heading);
heading.setText(continent.getName().trim());
return view;
}
#Override
public boolean hasStableIds() {
return true;
}
#Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
public void filterData(String query){
query = query.toLowerCase();
Log.v("MyListAdapter", String.valueOf(continentList.size()));
continentList.clear();
if(query.isEmpty()){
continentList.addAll(originalList);
}
else {
for(Continent continent: originalList){
ArrayList<Country> countryList = continent.getCountryList();
ArrayList<Country> newList = new ArrayList<Country>();
for(Country country: countryList){
if(country.getName().toLowerCase().contains(query)){
newList.add(country);
}
}
if(newList.size() > 0){
Continent nContinent = new Continent(continent.getName(),newList);
continentList.add(nContinent);
}
}
}
Log.v("MyListAdapter", String.valueOf(continentList.size()));
notifyDataSetChanged();
}
}
Country.java:
package com.example.jairo_2.myapplication;
public class Country {
private String name = "";
public Country(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Continent.java
package com.example.jairo_2.myapplication;
import java.util.ArrayList;
public class Continent {
private String name;
private ArrayList<Country> countryList = new ArrayList<Country>();
public Continent(String name, ArrayList<Country> countryList) {
super();
this.name = name;
this.countryList = countryList;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ArrayList<Country> getCountryList() {
return countryList;
}
public void setCountryList(ArrayList<Country> countryList) {
this.countryList = countryList;
};
}
Use like this continentList.get(groupPosition).getCountryList().get(childposition)
in your Toast message section

Auto complete search view with custom delimiter

[SOLVED] Is it possible to make SearchView(android.support.v7.widget) in the ActionBar(android.support.v7.app) like a MultiAutoCompleteTextView?
Suggesting words like here, after custom delimiter.
I didn't extend MultiAutoCompleteTextView because I didn't want to lose some features like icon, "X" button.
UPDATE:
I tried to extend SearchView:
public class MultiAutoCompleteSearchView extends android.support.v7.widget.SearchView {
private MultiAutoCompleteSearchView.SearchAutoComplete mSearchAutoComplete;
public static class SearchAutoComplete extends android.support.v7.widget.SearchView.SearchAutoComplete {
private String mSeparator = "+";
public SearchAutoComplete(Context context) {
super(context);
}
public SearchAutoComplete(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void replaceText(CharSequence text) {
String newText = getText().toString();
if (newText.contains(mSeparator)) {
int lastIndex = newText.lastIndexOf(mSeparator);
newText = newText.substring(0, lastIndex + 1) + text.toString();
} else {
newText = text.toString();
}
super.replaceText(newText);
}
#Override
protected void performFiltering(CharSequence text, int keyCode) {
String newText = text.toString();
if (newText.indexOf(mSeparator) != -1) {
int lastIndex = newText.lastIndexOf(mSeparator);
if (lastIndex != newText.length() - 1) {
newText = newText.substring(lastIndex + 1).trim();
if (newText.length() >= getThreshold()) {
text = newText;
}
}
}
super.performFiltering(text, keyCode);
}
}
public void initialize() {
mSearchAutoComplete = (MultiAutoCompleteSearchView.SearchAutoComplete)
findViewById(android.support.v7.appcompat.R.id.search_src_text);
this.setAdapter(null);
this.setOnItemClickListener(null);
}
public MultiAutoCompleteSearchView(Context context) {
super(context);
initialize();
}
public MultiAutoCompleteSearchView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public MultiAutoCompleteSearchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {
mSearchAutoComplete.setOnItemClickListener(listener);
}
public void setAdapter(ArrayAdapter<?> adapter) {
mSearchAutoComplete.setAdapter(adapter);
}
}
And xml file for menu.
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".SearchActivity">
<item android:id="#+id/action_search"
android:title="#string/action_search"
android:icon="#drawable/ic_action_search"
app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="cullycross.com.searchview.MultiAutoCompleteSearchView" />
</menu>
And activity method:
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_search, menu);
final MenuItem searchItem = menu.findItem(R.id.action_search);
final MultiAutoCompleteSearchView searchView = (MultiAutoCompleteSearchView)
MenuItemCompat.getActionView(searchItem);
searchView.setQueryHint("Type any word");
MultiAutoCompleteSearchView.SearchAutoComplete searchAutoComplete =
(MultiAutoCompleteSearchView.SearchAutoComplete)searchView
.findViewById(R.id.search_src_text);
searchAutoComplete.setAdapter(new ArrayAdapter<String>(
this,
android.R.layout.simple_dropdown_item_1line,
options
));
return true;
}
But for some reason I get a NPE here: searchView.setQueryHint("Type any word");, so it means, that getActionView returns null.
A lot of hours are passed, I found a solution:
Custom adapter:
public class DelimiterAdapter extends ArrayAdapter<String> implements Filterable {
private final static String [] options = {
"Apple","Mango","Peach","Banana","Orange","Grapes","Watermelon","Tomato"
};
private final LayoutInflater mInflater;
private List<String> mSubStrings;
private String mMainString;
public String getMainString() { return mMainString; }
private AmazingFilter mFilter;
public DelimiterAdapter(Context context, int resource) {
super(context, -1);
mInflater = LayoutInflater.from(context);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final TextView tv;
if (convertView != null) {
tv = (TextView) convertView;
} else {
tv = (TextView) mInflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
}
tv.setText(getItem(position));
return tv;
}
#Override
public int getCount() {
return mSubStrings.size();
}
#Override
public String getItem(int position) {
return mSubStrings.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public Filter getFilter() {
if(mFilter == null) {
mFilter = new AmazingFilter();
}
return mFilter;
}
private class AmazingFilter extends Filter {
private final static String DELIMITER = "+";
#Override
protected FilterResults performFiltering(CharSequence constraint) {
final FilterResults filterResults = new FilterResults();
String request;
mSubStrings = new ArrayList<String>();
if(constraint != null) {
request = constraint.toString();
//cuts the string with delimiter
if (request.contains(DELIMITER) &&
request.lastIndexOf(DELIMITER) != request.length() - 1) {
final String[] splitted = request.split("\\" + DELIMITER);
request = splitted[splitted.length - 1].trim();
//save string before delimiter
int index = constraint.toString().lastIndexOf(request);
mMainString = constraint.toString().substring(0, index);
} else {
request = request.trim();
mMainString = "";
}
//checks for substring of any word in the dictionary
for(String s : options) {
if(s.contains(request)) {
mSubStrings.add(s);
}
}
}
filterResults.values = mSubStrings;
filterResults.count = mSubStrings.size();
return filterResults;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
clear();
for (String request : (ArrayList<String>)results.values) {
add(request);
}
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
}
Extended SearchView:
public class MultiAutoCompleteSearchView extends android.support.v7.widget.SearchView {
private SearchAutoComplete mSearchAutoComplete;
public void initialize() {
mSearchAutoComplete = (SearchAutoComplete)
findViewById(android.support.v7.appcompat.R.id.search_src_text);
this.setAdapter(null);
this.setOnItemClickListener(null);
}
public MultiAutoCompleteSearchView(Context context) {
super(context);
initialize();
}
public MultiAutoCompleteSearchView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public MultiAutoCompleteSearchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {
mSearchAutoComplete.setOnItemClickListener(listener);
}
public void setAdapter(ArrayAdapter<?> adapter) {
mSearchAutoComplete.setAdapter(adapter);
}
}
Activity method onCreateMenuOptions
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_search, menu);
final MenuItem searchItem = menu.findItem(R.id.action_search);
final MultiAutoCompleteSearchView searchView = (MultiAutoCompleteSearchView)
MenuItemCompat.getActionView(searchItem);
searchView.setQueryHint("Type any word");
MultiAutoCompleteSearchView.SearchAutoComplete searchAutoComplete =
(MultiAutoCompleteSearchView.SearchAutoComplete)searchView
.findViewById(R.id.search_src_text);
//since words are very short
searchAutoComplete.setThreshold(1);
searchAutoComplete.setAdapter(new DelimiterAdapter(
this,
android.R.layout.simple_dropdown_item_1line
));
searchView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String stringBefore, newString;
stringBefore = ((DelimiterAdapter)parent.getAdapter()).getMainString();
newString = parent.getAdapter().getItem(position).toString();
searchView.setQuery(stringBefore+newString, false);
}
});
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String s) {
if(s.length() > 0) {
mFindString = s;
// do smth with string
return true;
}
return false;
}
#Override
public boolean onQueryTextChange(String s) {
return false;
}
});
return true;
}
XML file of the menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".SearchActivity">
<item android:id="#+id/action_search"
android:title="#string/action_search"
android:icon="#drawable/ic_action_search"
app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="cullycross.com.searchview.MultiAutoCompleteSearchView" />
</menu>

ListView is blank while using getFilter function

I am trying to implement the getFilter() function in my ListView but everytime I enter something in the EditText my ListView disappears.
My SetHelpRows file:
public class SetHelpRows {
String name;
String id;
public String getName () {
return name;
}
public void setName (String name) {
this.name = name;
}
public String getID () {
return id;
}
public void setID (String id) {
this.id = id;
}
public SetHelpRows(String name, String id) {
super();
this.name = name;
this.id = id;
}
}
My SetHelpRowsCustomAdapter file:
import java.util.ArrayList;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.TextView;
public class SetRowsCustomAdapter extends ArrayAdapter<SetRows> {
Context context;
int layoutResourceId;
ArrayList<SetRows> data=new ArrayList<SetRows>();
private ArrayList<SetRows> original;
private ArrayList<SetRows> fitems;
private Filter filter;
public SetRowsCustomAdapter(Context context, int layoutResourceId, ArrayList<SetRows> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
this.original = data;//new ArrayList<Pkmn>();
this.fitems = data;//new ArrayList<Pkmn>();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ImageHolder holder = null;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new ImageHolder();
holder.txtTitle = (TextView)row.findViewById(R.id.txtTitle);
holder.imgIcon = (ImageView)row.findViewById(R.id.imgIcon);
holder.txtID = (TextView)row.findViewById(R.id.txtModDate);
row.setTag(holder);
}
else
{
holder = (ImageHolder)row.getTag();
}
SetRows myImage = data.get(position);
holder.txtTitle.setText(myImage.name);
holder.txtID.setText(myImage.id);
int outImage=myImage.image;
holder.imgIcon.setImageResource(outImage);
return row;
}
static class ImageHolder
{
ImageView imgIcon;
TextView txtTitle;
TextView txtID;
}
#Override
public Filter getFilter()
{
if (filter == null) {
Log.i("Before Filter", "Before Filter");
filter = new PkmnNameFilter();
}
return filter;
}
private class PkmnNameFilter extends Filter
{
#Override
protected FilterResults performFiltering(CharSequence constraint)
{
FilterResults results = new FilterResults();
String prefix = constraint.toString().toLowerCase();
if (prefix == null || prefix.length() == 0)
{
Log.i("prefix is null or 0", "prefix is null or 0");
ArrayList<SetRows> list = new ArrayList<SetRows>(original);
results.values = list;
results.count = list.size();
}
else
{
Log.i("prefix is !null or !0", "prefix is !null or !0");
final ArrayList<SetRows> list = new ArrayList<SetRows>(original);
final ArrayList<SetRows> nlist = new ArrayList<SetRows>();
int count = list.size();
for (int i=0; i<count; i++)
{
final SetRows pkmn = list.get(i);
final String value = pkmn.getName().toLowerCase();
if (value.startsWith(prefix))
{
nlist.add(pkmn);
}
}
results.values = nlist;
results.count = nlist.size();
}
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
Log.i("publish result", "publish result");
fitems = (ArrayList<SetRows>)results.values;
clear();
int count = fitems.size();
for (int i=0; i<count; i++)
{
SetRows pkmn = (SetRows)fitems.get(i);
add(pkmn);
}
}
}
}
My partial MainActivity file:
inputSearch = (EditText) findViewById(R.id.etSearch);
dataList = (ListView) findViewById(R.id.lvFiles);
for (int y=0; y<strNames.length;y++) {
name = strNamesOfAllah[y];
meaning = strMeaning[y];
rowsArray.add(new SetRows(R.drawable.icon, name, meaning));
}
adapter = new SetRowsCustomAdapter(MainActivity.this, R.layout.customlist, rowsArray);
dataList.setAdapter(adapter);
dataList.setClickable(true);
inputSearch.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {
// When user changed the Text
//MainActivity.this.adapter.getFilter().filter(cs);
//String text = inputSearch.getText().toString().toLowerCase();
adapter.getFilter().filter(cs.toString().toLowerCase(Locale.US));
}
#Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
//TODO Auto-generated method stub
}
#Override
public void afterTextChanged(Editable arg0) {
//TODO Auto-generated method stub
}
});
In the LogCat I do see the following:
12-12 12:15:57.077: I/Before Filter(720): Before Filter
12-12 12:15:57.106: I/prefix is !null or !0(720): prefix is !null or !0
12-12 12:15:57.216: I/publish result(720): publish result
I modified my code and was able to get it to work. If anyone wants to use it, be my guest :)
SetRows Java file:
public class SetRows {
int image;
String name;
String id;
public int getImage () {
return image;
}
public void setImage (int image) {
this.image = image;
}
public String getName () {
return name;
}
public void setName (String name) {
this.name = name;
}
public String getID () {
return id;
}
public void setID (String id) {
this.id = id;
}
public SetRows(int image, String name, String id) {
super();
this.image = image;
this.name = name;
this.id = id;
}
#Override
public String toString() {
return image + " " + name + " " + id;
}
}
SetRowsCustomAdapter Java file:
import java.util.ArrayList;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.TextView;
public class SetRowsCustomAdapter extends ArrayAdapter<SetRows> {
Context context;
int layoutResourceId;
ArrayList<SetRows> data=new ArrayList<SetRows>(); //data = countryList
private ArrayList<SetRows> originalList;
private NameFilter filter;
public SetRowsCustomAdapter(Context context, int layoutResourceId, ArrayList<SetRows> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
//this.data.addAll(data);
this.originalList = new ArrayList<SetRows>();
this.originalList.addAll(data);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ImageHolder holder = null;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new ImageHolder();
holder.txtTitle = (TextView)row.findViewById(R.id.txtTitle);
holder.imgIcon = (ImageView)row.findViewById(R.id.imgIcon);
holder.txtID = (TextView)row.findViewById(R.id.txtModDate);
row.setTag(holder);
}
else
{
holder = (ImageHolder)row.getTag();
}
SetRows myImage = data.get(position);
holder.txtTitle.setText(myImage.name);
holder.txtID.setText(myImage.id);
int outImage=myImage.image;
holder.imgIcon.setImageResource(outImage);
return row;
}
static class ImageHolder
{
ImageView imgIcon;
TextView txtTitle;
TextView txtID;
}
#Override
public Filter getFilter() {
if (filter == null){
filter = new NameFilter();
}
return filter;
}
private class NameFilter extends Filter
{
#Override
protected FilterResults performFiltering(CharSequence constraint) {
constraint = constraint.toString().toLowerCase();
FilterResults result = new FilterResults();
if(constraint != null && constraint.toString().length() > 0)
{
ArrayList<SetRows> filteredItems = new ArrayList<SetRows>();
for(int i = 0, l = originalList.size(); i < l; i++)
{
SetRows nameList = originalList.get(i);
if(nameList.toString().toLowerCase().contains(constraint))
filteredItems.add(nameList);
}
result.count = filteredItems.size();
result.values = filteredItems;
}
else
{
synchronized(this)
{
result.values = originalList;
result.count = originalList.size();
}
}
return result;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
data = (ArrayList<SetRows>)results.values;
notifyDataSetChanged();
clear();
for(int i = 0, l = data.size(); i < l; i++)
add(data.get(i));
notifyDataSetInvalidated();
}
}
}
Everything else stayed the same :)
I hope people find it useful for their own app.
You don't need to implement your own Filter, if you are using an ArrayAdapter you can just override the toString method in SetHelpRows to return the name. Then you can call the ArrayAdapters built-in Filter.
This is not a direct answer to your question but rather a suggestion to change your implementation.
please try this in your publishResults function
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
Log.i("publish result", "publish result");
data = (ArrayList<SetRows>)results.values;
notifyDataSetChanged();
}
In SiB's answer, the Filter can be improved by overriding getCount()
#Override
public int getCount()
{
return data.size();
}

Categories