I am using an adapter to display the result of a cursor on a separate screen in alphabetical order. The problem is I need to use an EditText field to filter the result.
When I do this with the filter adapter always gives error due to the index properties of each alphabetical letter which he must redo. So I thought I'd change the adapter to a simple, no alphabetical division only when the user makes the filter for the search field.
I leave the original adapter and the adapter without a filter to search I add the filter that will run another cursor. This exchange of Adapter at runtime given problem: when I change the X Adapter (without filterqueryprovider) for the Y-Adapter (with filterqueryprovider) works normal .. but when the reverse is true filterqueryprovider just running in the X Adapter and the result of the cursor does not appear. How to solve this problem?
See the code below the adapter and my Activity
Adapter:
/**
* CursorAdapter that uses an AlphabetIndexer widget to keep track of the section indicies.
* These are the positions where we want to show a section header showing the respective alphabet letter.
* #author Eric
*
*/
public class OrdemAlfabeticaAdapter extends SimpleCursorAdapter implements SectionIndexer{
private static final int TYPE_HEADER = 1;
private static final int TYPE_NORMAL = 0;
private static final int TYPE_COUNT = 2;
private AlphabetIndexer indexer;
private int[] usedSectionNumbers;
private Map<Integer, Integer> sectionToOffset;
private Map<Integer, Integer> sectionToPosition;
private Context context;
public OrdemAlfabeticaAdapter(Context context, int layout, Cursor c, String coluna,
String[] from, int[] to) {
super(context, layout, c, from, to);
this.context = context;
indexer = new AlphabetIndexer(c, c.getColumnIndexOrThrow(coluna), "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
sectionToPosition = new TreeMap<Integer, Integer>(); //use a TreeMap because we are going to iterate over its keys in sorted order
sectionToOffset = new HashMap<Integer, Integer>();
final int count = super.getCount();
int i;
//temporarily have a map alphabet section to first index it appears
//(this map is going to be doing somethine else later)
for (i = count - 1 ; i >= 0; i--){
sectionToPosition.put(indexer.getSectionForPosition(i), i);
}
i = 0;
usedSectionNumbers = new int[sectionToPosition.keySet().size()];
//note that for each section that appears before a position, we must offset our
//indices by 1, to make room for an alphabetical header in our list
for (Integer section : sectionToPosition.keySet()){
sectionToOffset.put(section, i);
usedSectionNumbers[i] = section;
i++;
}
//use offset to map the alphabet sections to their actual indicies in the list
for(Integer section: sectionToPosition.keySet()){
sectionToPosition.put(section, sectionToPosition.get(section) + sectionToOffset.get(section));
}
}
#Override
public int getCount() {
if (super.getCount() != 0){
//sometimes your data set gets invalidated. In this case getCount()
//should return 0 and not our adjusted count for the headers.
//The only way to know if data is invalidated is to check if
//super.getCount() is 0.
return super.getCount() + usedSectionNumbers.length;
}
return 0;
}
#Override
public Object getItem(int position) {
if (getItemViewType(position) == TYPE_NORMAL){//we define this function in the full code later
//if the list item is not a header, then we fetch the data set item with the same position
//off-setted by the number of headers that appear before the item in the list
return super.getItem(position - sectionToOffset.get(getSectionForPosition(position)) - 1);
}
return null;
}
#Override
public int getPositionForSection(int section) {
if (! sectionToOffset.containsKey(section)){
//This is only the case when the FastScroller is scrolling,
//and so this section doesn't appear in our data set. The implementation
//of Fastscroller requires that missing sections have the same index as the
//beginning of the next non-missing section (or the end of the the list if
//if the rest of the sections are missing).
//So, in pictorial example, the sections D and E would appear at position 9
//and G to Z appear in position 11.
int i = 0;
int maxLength = usedSectionNumbers.length;
//linear scan over the sections (constant number of these) that appear in the
//data set to find the first used section that is greater than the given section, so in the
//example D and E correspond to F
while (i < maxLength && section > usedSectionNumbers[i]){
i++;
}
if (i == maxLength) return getCount(); //the given section is past all our data
return indexer.getPositionForSection(usedSectionNumbers[i]) + sectionToOffset.get(usedSectionNumbers[i]);
}
return indexer.getPositionForSection(section) + sectionToOffset.get(section);
}
#Override
public int getSectionForPosition(int position) {
int i = 0;
int maxLength = usedSectionNumbers.length;
//linear scan over the used alphabetical sections' positions
//to find where the given section fits in
while (i < maxLength && position >= sectionToPosition.get(usedSectionNumbers[i])){
i++;
}
return usedSectionNumbers[i-1];
}
#Override
public Object[] getSections() {
return indexer.getSections();
}
//nothing much to this: headers have positions that the sectionIndexer manages.
#Override
public int getItemViewType(int position) {
if (position == getPositionForSection(getSectionForPosition(position))){
return TYPE_HEADER;
}
return TYPE_NORMAL;
}
#Override
public int getViewTypeCount() {
return TYPE_COUNT;
}
//return the header view, if it's in a section header position
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final int type = getItemViewType(position);
if (type == TYPE_HEADER){
if (convertView == null){
LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
// convertView = getLayoutInflater().inflate(R.layout.header, parent, false);
convertView = inflater.inflate(R.layout.cabecalho_divisao_alfabetica, parent, false);
}
((TextView)convertView.findViewById(R.id.header)).setText((String)getSections()[getSectionForPosition(position)]);
return convertView;
}
return super.getView(position - sectionToOffset.get(getSectionForPosition(position)) - 1, convertView, parent);
}
//these two methods just disable the headers
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(int position) {
if (getItemViewType(position) == TYPE_HEADER){
return false;
}
return true;
}
public AlphabetIndexer getIndexer() {
return indexer;
}
}
My Activity:
public class BuscaProprietarioActivity extends ListActivity {
private SimpleCursorAdapter adapter1;
private SimpleCursorAdapter adapter2;
private EditText filterEditText;
private LinearLayout viewNenhumRegistroEncontrado;
private LinearLayout viewResultado;
private final String[] colunas = new String[] { ProprietarioProvider.Columns.ID,
ProprietarioProvider.Columns.NOME, ProprietarioProvider.Columns.TELEFONE };
private Cursor cursor;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.proprietario_busca);
filterEditText = (EditText) findViewById(R.id.busca_proprietario_campo_busca);
ContentResolver cr = getContentResolver();
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
String s = "'%" + query + "%'";
String selection = ProprietarioProvider.Columns.NOME + " LIKE " + s;
cursor = cr.query(ProprietarioProvider.CONTENT_URI, colunas, selection, null,
ProprietarioProvider.Columns.NOME + " COLLATE LOCALIZED ASC");
} else {
cursor = cr.query(ProprietarioProvider.CONTENT_URI, colunas, null, null,
ProprietarioProvider.Columns.NOME + " COLLATE LOCALIZED ASC");
}
if (cursor.moveToNext()) {
viewResultado = (LinearLayout) findViewById(R.id.busca_proprietario_resultado);
viewResultado.setVisibility(View.VISIBLE);
adapter1 = new OrdemAlfabeticaAdapter(this, R.layout.list_item_proprietario,
cursor, ProprietarioProvider.Columns.NOME, new String[]{ProprietarioProvider.Columns.NOME,
ProprietarioProvider.Columns.TELEFONE},
new int[]{R.id.list_item_proprietario_nome, R.id.list_item_proprietario_telefone});
setListAdapter(adapter1);
adapter2 = new SimpleCursorAdapter(getApplicationContext(),
R.layout.list_item_proprietario, cursor, new String[]{ProprietarioProvider.Columns.NOME,
ProprietarioProvider.Columns.TELEFONE},
new int[]{R.id.list_item_proprietario_nome, R.id.list_item_proprietario_telefone});
adapter2.setFilterQueryProvider(filterQueryProvider);
filterEditText = (EditText) findViewById(R.id.busca_proprietario_campo_busca);
filterEditText.addTextChangedListener(filterTextWatcher);
} else {
// mostra tela de registro nao encontrado
viewNenhumRegistroEncontrado = (LinearLayout) findViewById(R.id.busca_proprietario_nenhum_registro_encontrado);
viewNenhumRegistroEncontrado.setVisibility(View.VISIBLE);
}
}
private FilterQueryProvider filterQueryProvider = new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
String selection = ProprietarioProvider.Columns.NOME + " LIKE '"+constraint+"%'";
cursor = getContentResolver().query(ProprietarioProvider.CONTENT_URI, colunas, selection, null,
ProprietarioProvider.Columns.NOME + " COLLATE LOCALIZED ASC");
return cursor;
}
};
private TextWatcher filterTextWatcher = new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
if (s != null && s.length() > 0) {
setListAdapter(adapter2);
getListView().setTextFilterEnabled(true);
adapter2.getFilter().filter(s.toString());
getListView().setFastScrollEnabled(false);
} else {
setListAdapter(adapter1);
getListView().setFastScrollEnabled(true);
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
};
}
If you have already set your adapter, setting it again won't update the UI. you will need to use the adapter.notifyDataSetChanged()
https://stackoverflow.com/a/7920243/563306
Related
I need a little help for the redaction of a class. I want to add some item in my listView, and to do that I use a custom adapter found in this forum, but modified for my use :
public class AdvertisingAdapter extends cCursorAdapter {
private static final String ADMOB_PUBLISHER_ID = "YOUR_ADMOB_ID_HERE";
private final Activity activity;
private final cCursorAdapter delegate;
Context context;
static int rec = 15;
public AdvertisingAdapter(Activity activity, cCursorAdapter delegate, Context context, Cursor cursor)
{
super(context, cursor, 0);
this.activity = activity;
this.delegate = delegate;
this.context = context;
delegate.registerDataSetObserver(new DataSetObserver()
{
#Override
public void onChanged()
{
notifyDataSetChanged();
}
#Override
public void onInvalidated()
{
notifyDataSetInvalidated();
}
});
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if (position == 0 || position % rec == 0)
{
j = j + 1 ;
if (convertView instanceof AdView)
{
notifyDataSetChanged();
return convertView;
}
else
{
AdView adView = new AdView(context);
// Disable focus for sub-views of the AdView to avoid problems with
// trackpad navigation of the list.
for (int i = 0; i < adView.getChildCount(); i++)
{
adView.getChildAt(i).setFocusable(false);
}
adView.setFocusable(false);
// Default layout params have to be converted to ListView compatible
// params otherwise there will be a ClassCastException.
float density = activity.getResources().getDisplayMetrics().density;
int height = Math.round(AdSize.BANNER.getHeight() * density);
AbsListView.LayoutParams params
= new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
height);
adView.setLayoutParams(params);
AdRequest adRequest = new AdRequest.Builder().build();
adView.setAdUnitId("ca-app-pub-3940256099942544/6300978111");
adView.setAdSize(com.google.android.gms.ads.AdSize.BANNER);
adView.loadAd(adRequest);
notifyDataSetChanged();
return adView;
}
}
else
{
Log.i ("TEST AD ADAPTER", String.valueOf(j)
+ " " + String.valueOf(position)
+ " " + String.valueOf(getNbreAd())
+ " " + String.valueOf(getNbrePos(position))
+ " " + String.valueOf(getCount()));
return delegate.getView(position - getNbrePos(position), convertView, parent);
}
}
public int getNbreAd(){
int l = delegate.getCount();
return (l / rec) + 1;
}
public int getNbrePos(int position){
int k = (position/rec) + 1;
return k;
}
#Override
public int getCount()
{
return delegate.getCount() + getNbreAd();
}
#Override
public Object getItem(int i)
{
return delegate.getItem(i - 1);
}
#Override
public long getItemId(int i)
{
return delegate.getItemId(i - 1);
}
#Override
public int getViewTypeCount()
{
return delegate.getViewTypeCount() + 1;
}
#Override
public int getItemViewType(int position)
{
return position == 0 ? delegate.getViewTypeCount()
: delegate.getItemViewType(position - 1);
}
#Override
public boolean areAllItemsEnabled()
{
return false;
}
#Override
public boolean isEnabled(int position)
{
return position != 0 && delegate.isEnabled(position - 1);
}
}
My cCursorAdapter :
public class cCursorAdapter extends android.widget.CursorAdapter {
DataHelper mDBHelper;
public cCursorAdapter(Context context, Cursor cursor, int flags) {
super(context, cursor, 0);
}
// The newView method is used to inflate a new view and return it,
// you don't bind any data to the view at this point.
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.list_result2, parent, false);
}
// The bindView method is used to bind all data to a given view
// such as setting the text on a TextView.
#Override
public void bindView(View view, Context context, Cursor cursor){
// Find fields to populate in inflated template
TextView nameResult = (TextView) view.findViewById(R.id.nameResult2);
TextView ageG = (TextView) view.findViewById(R.id.ageG2);
TextView nbreJoueur = (TextView) view.findViewById(R.id.nbreJoueur2);
TextView timeResult = (TextView) view.findViewById(R.id.time2);
ImageView placeGD = (ImageView) view.findViewById(R.id.placeD2);
TextView IDR2 = (TextView) view.findViewById(R.id.IDR2);
// Extract properties from cursor
String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
String place = cursor.getString(cursor.getColumnIndexOrThrow("place"));
int ageMin = cursor.getInt(cursor.getColumnIndexOrThrow("agemin"));
int ageMax = cursor.getInt(cursor.getColumnIndexOrThrow("agemax"));
int nbreMin = cursor.getInt(cursor.getColumnIndexOrThrow("nbremin"));
int nbreMax = cursor.getInt(cursor.getColumnIndexOrThrow("nbremax"));
int time = cursor.getInt(cursor.getColumnIndexOrThrow("time"));
final int ID = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
// Populate fields with extracted properties
nameResult.setText(name);
ageG.setText(String.valueOf(ageMin) + " - " + String.valueOf(ageMax));
nbreJoueur.setText(String.valueOf(nbreMin) + " - " + String.valueOf(nbreMax));
timeResult.setText(String.valueOf(time));
IDR2.setText(String.valueOf(ID));
if (place.equals("extérieur")) {
Drawable placeD = MainActivity.mContext.getResources().getDrawable(R.drawable.ic_wb_sunny_black_24dp);
placeGD.setImageDrawable(placeD);
} else if (place.equals("intérieur")){
Drawable placeD = MainActivity.mContext.getResources().getDrawable(R.drawable.ic_event_seat_black_24dp);
placeGD.setImageDrawable(placeD);
} else if (place.equals("partout")) {
Drawable placeD = MainActivity.mContext.getResources().getDrawable(R.drawable.ic_explore_black_24dp);
placeGD.setImageDrawable(placeD);
}
//Cacher l'ID (c'est pas joli)
IDR2.setVisibility(View.INVISIBLE);
}
//Override the convert to string sequence
#Override
public CharSequence convertToString(Cursor cursor){
final int colIndex = cursor.getColumnIndexOrThrow("name");
return cursor.getString(colIndex);
}
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint)
{
Cursor currentCursor = null;
if (getFilterQueryProvider() != null)
{
return getFilterQueryProvider().runQuery(constraint);
}
String args = "";
if (constraint != null)
{
args = constraint.toString();
}
//Initialize that shit
mDBHelper = new DataHelper(MainActivity.mContext);
currentCursor = mDBHelper.getNameCursor(args);
return currentCursor;
}
}
In a certain way, it works. But when I swipe the listView too fast, I have this error :
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
at com.tanoshi.traveler.doctorgamesfree.cCursorAdapter.bindView(cCursorAdapter.java:57)
at android.widget.CursorAdapter.getView(CursorAdapter.java:254)
at com.tanoshi.traveler.doctorgamesfree.AdvertisingAdapter.getView(AdvertisingAdapter.java:102)
I think it is becausemy listView try to load an item in a view which is not loaded.
How can I do to fix this ?
Thank you for your time.
Situation
I have a muliple choice mode ListView with two view types- normal and header. I load the data from the contacts app by retrieving a cursor with the names and emails and make use of the AlphabetIndexer class. My adapter extends the SimpleCursorAdapter class and implements the SectionIndexer. Moreover I override the getCount() method of my adapter so that it returns the count of the cursor + the count of the sections. In addition, my layouts are correct and items highlight in the onListItemClickListener, according to the user actions.
Problem
However, I want to highlight all the items with a Check All Button but the code fails to do that.
for (int i = 0; i <= adapter.getCount() - 1; i++) {
adapter.getItem(i);
if (adapter.getItemViewType(i) == InviteContactListAdapter.TYPE_NORMAL) {
listView.setItemChecked(i, true);
}
}
It changes correctly the layout of the items with position smaller than the cursror.getCount() and then refuses to mark the rest with a bigger index.I log the ListView.getCheckedItemPositions()
and this list includes all the items, including the ones whose layout has not been checked.
Log.d("checkedItems:", listView.getCheckedItemPositions()):
So their state is changed but not their layout.
Example
I have a list with 55 contacts and 20 section headers. When i run the Select All Button items with position 0... 55 get highlighted. Items from 56 to 75 get only get checked, not highlighted.
Code
public class InviteContactListAdapter extends SimpleCursorAdapter implements
SectionIndexer {
public static final int TYPE_HEADER = 1;
public static final int TYPE_NORMAL = 0;
public static final int TYPE_COUNT = 2;
private AlphabetIndexer indexer;
private int[] usedSectionNumbers;
private Map<Integer, Integer> sectionToPosition;
private Context context;
private HashMap<Integer, Integer> sectionToOffset;
public InviteContactListAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to, 0);
ArrayList<String> stringCollection = new ArrayList<String>();
Character firstLetter;
String firstLetterAsString;
while (c.moveToNext()) {
firstLetter = c.getString(
c.getColumnIndex(ContactsContract.Data.DISPLAY_NAME))
.charAt(0);
firstLetter = Character.toUpperCase(firstLetter);
firstLetterAsString = firstLetter.toString();
if (!stringCollection.contains(firstLetterAsString)
&& Character.isLetter(firstLetter)) {
stringCollection.add(firstLetterAsString);
}
}
Collections.sort(stringCollection);
String alphabet = " ";
for (String s : stringCollection) {
alphabet = alphabet + s;
}
c.moveToFirst();
Log.d("length", "" + alphabet.length());
this.context = context;
indexer = new AlphabetIndexer(c,
c.getColumnIndexOrThrow(ContactsContract.Data.DISPLAY_NAME),
alphabet);
sectionToPosition = new TreeMap<Integer, Integer>();
sectionToOffset = new HashMap<Integer, Integer>();
final int count = super.getCount();
int i;
for (i = count - 1; i >= 0; i--) {
sectionToPosition.put(indexer.getSectionForPosition(i), i);
}
i = 0;
usedSectionNumbers = new int[sectionToPosition.keySet().size()];
for (Integer section : sectionToPosition.keySet()) {
sectionToOffset.put(section, i);
usedSectionNumbers[i] = section;
i++;
}
for (Integer section : sectionToPosition.keySet()) {
sectionToPosition.put(section, sectionToPosition.get(section)
+ sectionToOffset.get(section));
}
Log.d("", "");
}
#Override
public int getCount() {
if (super.getCount() != 0) {
return super.getCount() + usedSectionNumbers.length;
}
return 0;
}
#Override
public Object getItem(int position) {
if (getItemViewType(position) == TYPE_NORMAL) {
return super.getItem(position
- sectionToOffset.get(getSectionForPosition(position)) - 1);
}
return null;
}
#Override
public int getPositionForSection(int section) {
if (!sectionToOffset.containsKey(section)) {
int i = 0;
int maxLength = usedSectionNumbers.length;
while (i < maxLength && section > usedSectionNumbers[i]) {
i++;
}
if (i == maxLength)
return getCount();
return indexer.getPositionForSection(usedSectionNumbers[i])
+ sectionToOffset.get(usedSectionNumbers[i]);
}
return indexer.getPositionForSection(section)
+ sectionToOffset.get(section);
}
#Override
public int getSectionForPosition(int position) {
int i = 0;
int maxLength = usedSectionNumbers.length;
while (i < maxLength
&& position >= sectionToPosition.get(usedSectionNumbers[i])) {
i++;
}
return usedSectionNumbers[i - 1];
}
#Override
public Object[] getSections() {
return indexer.getSections();
}
// nothing much to this: headers have positions that the sectionIndexer
// manages.
#Override
public int getItemViewType(int position) {
if (position == getPositionForSection(getSectionForPosition(position))) {
return TYPE_HEADER;
}
return TYPE_NORMAL;
}
#Override
public int getViewTypeCount() {
return TYPE_COUNT;
}
// return the header view, if it's in a section header position
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final int type = getItemViewType(position);
LayoutInflater inflater = LayoutInflater.from(context);
if (type == TYPE_HEADER) {
if (convertView == null) {
convertView = inflater.inflate(
R.layout.list_item_alphabet_section_header, parent,
false);
}
((TextView) convertView.findViewById(R.id.letter_header))
.setText((String) getSections()[getSectionForPosition(position)]);
return convertView;
}
return super.getView(
position - sectionToOffset.get(getSectionForPosition(position))
- 1, convertView, parent);
}
// these two methods just disable the headers
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(int position) {
if (getItemViewType(position) == TYPE_HEADER) {
return false;
}
return true;
}
EDIT
I think the issue might be that you are calling the super methods all over the place, which in my opinion means that the default behavior of the SimpleCursorAdapter has (which includes highlighting a selection) only applies to the first size of the cursor items.
In other words, highlighting a selected row is not a default "feature" of an Adapter, but rather something you must implement explicitly.
While the SimpleCursorAdapter has the highlighting "built-in", it only does so for a number of items equal to the Cursor's size.
I don't see how you can change this, other than managing the views yourself manually in the getView method (i.e. do the highlighting yourself by changing the background of the views).
I have a ListView with custom Adapter . I have to add at position 2,6,9 separators. How to do that?
Here is my code
class MyIndexerAdapter<T> extends ArrayAdapter<T> implements SectionIndexer {
ArrayList<String> myElements;
HashMap<String, Integer> alphaIndexer;
private static final int TYPE_ITEM = 0;
private static final int TYPE_SEPARATOR = 1;
private static final int TYPE_MAX_COUNT = TYPE_SEPARATOR + 1;
TreeSet mSeparatorsSet = new TreeSet();
String[] sections;
LayoutInflater mInflater;
public MyIndexerAdapter(Context context, int textViewResourceId,
List<T> objects) {
super(context, textViewResourceId, objects);
mInflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
myElements = (ArrayList<String>) objects;
// here is the tricky stuff
alphaIndexer = new HashMap<String, Integer>();
// in this hashmap we will store here the positions for
// the sections
int size = elements.size();
for (int i = size - 1; i >= 0; i--) {
String element = elements.get(i);
alphaIndexer.put(element.substring(0, 1), i);
//We store the first letter of the word, and its index.
//The Hashmap will replace the value for identical keys are putted in
}
// now we have an hashmap containing for each first-letter
// sections(key), the index(value) in where this sections begins
// we have now to build the sections(letters to be displayed)
// array .it must contains the keys, and must (I do so...) be
// ordered alphabetically
Set<String> keys = alphaIndexer.keySet(); // set of letters ...sets
// cannot be sorted...
Iterator<String> it = keys.iterator();
ArrayList<String> keyList = new ArrayList<String>(); // list can be
// sorted
while (it.hasNext()) {
String key = it.next();
keyList.add(key);
}
Collections.sort(keyList);
sections = new String[keyList.size()]; // simple conversion to an
// array of object
keyList.toArray(sections);
// ooOO00K !
}
public int getItemViewType(int position)
{
return mSeparatorsSet.contains(position) ? TYPE_SEPARATOR : TYPE_ITEM;
}
public int getViewTypeCount()
{
return TYPE_MAX_COUNT;
}
public int getPositionForSection(int section) {
// Log.v("getPositionForSection", ""+section);
String letter = sections[section];
return alphaIndexer.get(letter);
}
public int getSectionForPosition(int position) {
// you will notice it will be never called (right?)
Log.v("getSectionForPosition", "called");
getSections();
return 0;
}
public Object[] getSections() {
return sections; // to string will be called each object, to display
// the letter
}
public void onListItemClick(ListView parent,View v, int position, long id)
{
Toast.makeText(getContext(), "you have selected" + elements.get(position), Toast.LENGTH_SHORT).show();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
int type = getItemViewType(position);
System.out.println("getView " + position + " " + convertView + " type = " + type);
if (convertView == null) {
holder = new ViewHolder();
switch (type) {
case TYPE_ITEM:
convertView = mInflater.inflate(R.layout.main, null);
holder.textView = (TextView)convertView.findViewById(R.id.text);
//break;
case TYPE_SEPARATOR:
convertView = mInflater.inflate(R.layout.item1, null);
holder.textView = (TextView)convertView.findViewById(R.id.textSeparator);
break;
}
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.textView.setText(elements.get(position));
return convertView;
}
}
}
In your getView() of MyIndexerAdapter, use the position argument to check whether it is 2.6.9 and then add the separator in code
if(position==2 || position==6||position==9){
//code for adding separators to the convertview
}
Try the below completed code, it was running for me :
This code also shows how to use adapter methods like getItemViewType(), getViewTypeCount(), getView() when needed to display different types of views at different positions.
It also shows how to implement a SectionIndexer and its methods.
I added some random data at position 2,6,9 where you wanted the separators. This made things easier.
public class FastScrollActivity extends Activity {
ListView myListView;
ArrayList<String> elements;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// elements
String s = "QWERTZUIOPASDFGHJKLYXCVBNM";
Random r = new Random();
elements = new ArrayList<String>();
for (int i = 0; i < 300; i++) {
elements.add(s.substring(r.nextInt(s.length())));
}
Collections.sort(elements); // Must be sorted!
//for 2,6,9 Adding some random data--use an array of positions if you want to :
elements.add(2,"Don't care");
elements.add(6,"Don't care");
elements.add(9,"Don't care");
// listview
myListView = (ListView) findViewById(R.id.myListView);
myListView.setFastScrollEnabled(true);
myListView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
Toast.makeText(FastScrollActivity.this, "clicked pos = "+arg2, Toast.LENGTH_SHORT).show();
}
});
//myListView.
MyIndexerAdapter<String> adapter = new MyIndexerAdapter<String>(
this, R.layout.simple_layout,
elements);
myListView.setAdapter(adapter);
// if (myListView.getFirstVisiblePosition() > adapter.getItemId( adapter.getCount()) || myListView.getLastVisiblePosition() <= adapter.getCount()) {
// myListView.smoothScrollToPosition( adapter.getCount());}
}
class MyIndexerAdapter<T> extends ArrayAdapter<String> implements SectionIndexer {
ArrayList<String> myElements;
HashMap<String, Integer> alphaIndexer;
private static final int TYPE_ITEM = 0;
private static final int TYPE_SEPARATOR = 1;
private static final int TYPE_MAX_COUNT = TYPE_SEPARATOR + 1;
TreeSet<String> mSeparatorsSet = new TreeSet<String>();
String[] sections;
LayoutInflater mInflater;
int MytextViewResourceId;
public MyIndexerAdapter(Context context, int textViewResourceId,
ArrayList<String> objects) {
super(context, textViewResourceId, objects);
mInflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
myElements = objects;
// here is the tricky stuff
alphaIndexer = new HashMap<String, Integer>();
// in this hashmap we will store here the positions for
// the sections
//Adding positions in the separator set
mSeparatorsSet.add("2");
mSeparatorsSet.add("6");
mSeparatorsSet.add("9");
int size = elements.size();
for (int i = size - 1; i >= 0; i--) {
String element = elements.get(i);
alphaIndexer.put(element.substring(0, 1), i);
//We store the first letter of the word, and its index.
//The Hashmap will replace the value for identical keys are putted in
}
// now we have an hashmap containing for each first-letter
// sections(key), the index(value) in where this sections begins
// we have now to build the sections(letters to be displayed)
// array .it must contains the keys, and must (I do so...) be
// ordered alphabetically
Set<String> keys = alphaIndexer.keySet(); // set of letters ...sets
// cannot be sorted...
Iterator<String> it = keys.iterator();
ArrayList<String> keyList = new ArrayList<String>(); // list can be
// sorted
while (it.hasNext()) {
String key = it.next();
keyList.add(key);
}
Collections.sort(keyList);
sections = new String[keyList.size()]; // simple conversion to an
// array of object
keyList.toArray(sections);
// ooOO00K !
}
public int getItemViewType(int position)
{
return mSeparatorsSet.contains(position+"") ? TYPE_SEPARATOR : TYPE_ITEM;
}
public int getViewTypeCount()
{
return TYPE_MAX_COUNT;
}
public int getPositionForSection(int section) {
// Log.v("getPositionForSection", ""+section);
String letter = sections[section];
return alphaIndexer.get(letter);
}
public int getSectionForPosition(int position) {
// you will notice it will be never called (right?)
Log.v("getSectionForPosition", "called");
getSections();
return 0;
}
public Object[] getSections() {
return sections; // to string will be called each object, to display
// the letter
}
public void onListItemClick(ListView parent,View v, int position, long id)
{
Toast.makeText(getContext(), "you have selected" + elements.get(position), Toast.LENGTH_SHORT).show();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
int type = getItemViewType(position);
System.out.println("getView " + position + " " + convertView + " type = " + type);
if (convertView == null) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.simple_layout, null);
holder.textView = (TextView)convertView.findViewById(R.id.textView1);
holder.textView.setBackgroundColor(android.R.attr.colorBackground);
switch (type) {
//break;
case TYPE_SEPARATOR:
/*convertView = mInflater.inflate(R.layout.item1, null);
holder.textView = (TextView)convertView.findViewById(R.id.textSeparator);
*/
holder.textView .setOnClickListener(null);
holder.textView .setOnLongClickListener(null);
holder.textView .setLongClickable(false);
holder.textView.setFocusable(false);
holder.textView.setTextColor(Color.WHITE);
holder.textView.setBackgroundColor(Color.RED);
break;
}
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
if(type==TYPE_ITEM)
holder.textView.setText(elements.get(position));
//holder.textView.setMinHeight(android.R.attr.listPreferredItemHeight);
//holder.textView.setTextAppearance(getContext(), android.R.attr.textAppearanceLarge);
return convertView;
}
}
/* public void quickScroll(View v) {
String alphabet = (String)v.getTag();
int index = 0;
//find the index of the separator row view
list.setSelectionFromTop(index, 0);
}*/
public static class ViewHolder {
public TextView textView;
}
}
the xml I used is :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
/>
</RelativeLayout>
This problem is discussed in this question Android: Wrong item checked when filtering listview. To summarize the problem, when using a listview with a CursorAdapter and a filter, items selected on a filtered list lose their selection after the filter is removed and instead, items at that position in the unfiltered list get selected.
Using the code sample in the linked question above, where should we put the code to mark the checkboxes. I believe it should be in the getView() method of the CustomCursorAdapter, but I am not sure. Also, how do we access the HashSet holding all the selectedIds in the custom adapter class, because it will be initialized and modified in the main activity holding the list.
My activity implementing the ListView
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.selectfriends);
Log.v(TAG, "onCreate called") ;
selectedIds = new ArrayList<String>() ;
selectedLines = new ArrayList<Integer>() ;
mDbHelper = new FriendsDbAdapter(this);
mDbHelper.open() ;
Log.v(TAG, "database opened") ;
Cursor c = mDbHelper.fetchAllFriends();
startManagingCursor(c);
Log.v(TAG, "fetchAllFriends Over") ;
String[] from = new String[] {mDbHelper.KEY_NAME};
int[] to = new int[] { R.id.text1 };
final ListView listView = getListView();
Log.d(TAG, "Got listView");
// Now initialize the adapter and set it to display using our row
adapter =
new FriendsSimpleCursorAdapter(this, R.layout.selectfriendsrow, c, from, to);
Log.d(TAG, "we have got an adapter");
// Initialize the filter-text box
//Code adapted from https://stackoverflow.com/questions/1737009/how-to-make-a-nice-looking-listview-filter-on-android
filterText = (EditText) findViewById(R.id.filtertext) ;
filterText.addTextChangedListener(filterTextWatcher) ;
/* Set the FilterQueryProvider, to run queries for choices
* that match the specified input.
* Code adapted from https://stackoverflow.com/questions/2002607/android-how-to-text-filter-a-listview-based-on-a-simplecursoradapter
*/
adapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
// Search for friends whose names begin with the specified letters.
Log.v(TAG, "runQuery Constraint = " + constraint) ;
String selection = mDbHelper.KEY_NAME + " LIKE '%"+constraint+"%'";
mDbHelper.open();
Cursor c = mDbHelper.fetchFriendsWithSelection(
(constraint != null ? constraint.toString() : null));
return c;
}
});
setListAdapter(adapter);
Log.d(TAG, "setListAdapter worked") ;
listView.setItemsCanFocus(false);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
// listView.setOnItemClickListener(mListener);
Button btn;
btn = (Button)findViewById(R.id.buttondone);
mDbHelper.close();
}
#Override
protected void onListItemClick(ListView parent, View v, int position, long id) {
String item = (String) getListAdapter().getItem(position);
Toast.makeText(this, item + " selected", Toast.LENGTH_LONG).show();
//gets the Bookmark ID of selected position
Cursor cursor = (Cursor)parent.getItemAtPosition(position);
String bookmarkID = cursor.getString(0);
Log.d(TAG, "mListener -> bookmarkID = " + bookmarkID);
Log.d(TAG, "mListener -> position = " + position);
// boolean currentlyChecked = checkedStates.get(position);
// checkedStates.set(position, !currentlyChecked);
if (!selectedIds.contains(bookmarkID)) {
selectedIds.add(bookmarkID);
selectedLines.add(position);
} else {
selectedIds.remove(bookmarkID);
selectedLines.remove(position);
}
}
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) {
Log.v(TAG, "onTextChanged called. s = " + s);
adapter.getFilter().filter(s);
}
};
#Override
protected void onDestroy() {
super.onDestroy();
filterText.removeTextChangedListener(filterTextWatcher);
}
My Custom Cursor Adapter:
public class FriendsSimpleCursorAdapter extends SimpleCursorAdapter implements Filterable {
private static final String TAG = "FriendsSimpleCursorAdapter";
private final Context context ;
private final String[] values ;
private final int layout ;
private final Cursor cursor ;
static class ViewHolder {
public CheckedTextView checkedText ;
}
public FriendsSimpleCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to);
this.context = context ;
this.values = from ;
this.layout = layout ;
this.cursor = c ;
Log.d(TAG, "At the end of the constructor") ;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.d(TAG, "At the start of rowView. position = " + position) ;
View rowView = convertView ;
if(rowView == null) {
Log.d(TAG, "rowView = null");
try {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rowView = inflater.inflate(layout, parent, false);
Log.d(TAG, "rowView inflated. rowView = " + rowView);
ViewHolder viewHolder = new ViewHolder() ;
viewHolder.checkedText = (CheckedTextView) rowView.findViewById(R.id.text1) ;
rowView.setTag(viewHolder);
}
catch (Exception e) {
Log.e(TAG, "exception = " + e);
}
}
ViewHolder holder = (ViewHolder) rowView.getTag();
int nameCol = cursor.getColumnIndex(FriendsDbAdapter.KEY_NAME) ;
String name = cursor.getString(nameCol);
holder.checkedText.setText(name);
Log.d(TAG, "At the end of rowView");
return rowView;
}
}
What I did that solved it:
cur = cursor.
c = the simplecursoradapter inner cursor.
in my MainActivity:
(members)
static ArrayList<Boolean> checkedStates = new ArrayList<Boolean>();
static HashSet<String> selectedIds = new HashSet<String>();
static HashSet<Integer> selectedLines = new HashSet<Integer>();
in the listView onItemClickListener:
if (!selectedIds.contains(bookmarkID)) {
selectedIds.add(bookmarkID);
selectedLines.add(position);
} else {
selectedIds.remove(bookmarkID);
selectedLines.remove(position);
if (selectedIds.isEmpty()) {
//clear everything
selectedIds.clear();
checkedStates.clear();
selectedLines.clear();
//refill checkedStates to avoid force close bug - out of bounds
if (cur.moveToFirst()) {
while (!cur.isAfterLast()) {
MainActivity.checkedStates.add(false);
cur.moveToNext();
}
}
}
In the SimpleCursorAdapter I added (both in getView):
// fill the checkedStates array with amount of bookmarks (prevent OutOfBounds Force close)
if (c.moveToFirst()) {
while (!c.isAfterLast()) {
MainActivity.checkedStates.add(false);
c.moveToNext();
}
}
and:
String bookmarkID = c.getString(0);
CheckedTextView markedItem = (CheckedTextView) row.findViewById(R.id.btitle);
if (MainActivity.selectedIds.contains(new String(bookmarkID))) {
markedItem.setChecked(true);
MainActivity.selectedLines.add(pos);
} else {
markedItem.setChecked(false);
MainActivity.selectedLines.remove(pos);
}
Hope this helps... you'll of course need to adjust it to your needs.
EDIT:
Downloaded FB SDK, couldn't get pass the FB login. you have a bug where you don't get a valid access_token if the FB app is installed on the device. Removed the FB app and got a FC in getFriends(). Solved it by wrapping its scope with runOnUiThread(new Runnable...).
The error you get has nothing to do with bad filtering, bad checkbox states... You get it because you're trying to access the cursor before querying it (already closed).
It seems like you're closing the cursor before the adapter is using it. Verified it by adding:
mDbHelper = new FriendsDbAdapter(context);
mDbHelper.open() ;
cursor = mDbHelper.fetchAllFriends();
to the getView scope in SelectFriendsAdapter.
After you add this, it won't FC and you can start taking care of your filter.
Make sure the cursor is not closed and basically if you're managing it with startManagingCursor(), there's not need to manually close it.
Hope you can take it from here!
adapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
return db.fetchdatabyfilter(constraint.toString(), "name");
}
});
I am rephrasing my question. Please bear with me.
I have a Cursor, which has carNum and tasks needs to be done on that. So carNum repeats in table. Right now I am using SimpleCursorAdapter and displaying all the info as a listitem. What I would like to do is set carNum as a header of a list and tasks need to be done as listitems. In addition to this, I would like to make header it's own layout so that I can display carNum and some info about it. So now where should I start ?
How should I get distinct carNum from Cursor and make that as header ? And then get listitem for that ?
Here is the .java
public class MyTask extends Activity{
String empid;
ListView list;
Cursor cursor = null;
TextView text;
private DbAdapter_Assignment assignment;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listlayout);
empid = getIntent().getExtras().getString("EmpID");
getData(empid);
}
public void getData(final String empid)
{
assignment = new DbAdapter_Assignment(getBaseContext());
assignment.open();
cursor = assignment.getAcceptedTasks(empid);
startManagingCursor(cursor);
text = (TextView) findViewById(R.id.employeename);
text.setText(getEmployeeName(empid) + " has "+ Integer.toString (cursor.getCount()) + " tasks assigned." );
list = (ListView) findViewById(R.id.mytasklist);
list.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> adapterView, View v, int arg2,
long arg3) {
}
});
SimpleCursorAdapter adapter = new SimpleCursorAdapter (this, R.layout.mytaskslayout, cursor, new String[] {"A","B", "C", "D", "E", "F"}, new int[] {R.id.a, R.id.b, R.id.c, R.id.d, R.id.e, R.id.f});
list.setAdapter(adapter);
extend SimpleCursorAdapter
Build list of header indexes in the constructor
c.moveToFirst();
int headerIndex = -1;
String headerValue = null;
do {
headerIndex++;
String header = c.getString(getColumnClass());
if (!header.equalsIgnoreCase(headerValue)) {
mHeaderIndexes.add(headerIndex);
headerValue = header;
headerIndex++;
}
}while (c.moveToNext());
c.moveToFirst();
mDataCount = c.getCount()+mHeaderIndexes.size();
Override methods
#Override
public int getCount() {
return mDataCount;
}
#Override
public int getItemViewType(int position) {
if (mHeaderIndexes.contains(position)) {
return TYPE_HEADER_ROW;
}
return TYPE_ROW;
}
#Override
public Object getItem(int position) {
if (mHeaderIndexes.contains(position)) {
// for header row need return first row with data for this group
getCursor().moveToPosition(position-mHeaderIndexes.indexOf(position));
}
else {
for (int i = 0; i < mHeaderIndexes.size(); i++) {
if (mHeaderIndexes.get(i) > position) {
// need move back by number of headers before position
getCursor().moveToPosition(position - i);
break;
}
}
if (position>mHeaderIndexes.get(mHeaderIndexes.size()-1)) {
// if position is in the last group (position >last group header position)
// move back by number of headers
getCursor().moveToPosition(position - mHeaderIndexes.size());
}
}
return getCursor();
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public boolean isEnabled(int position) {
return !mHeaderIndexes.contains(position);
}
Implement getView()
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
Cursor cursor = (Cursor) getItem(position);
View view = convertView;
if (type==TYPE_HEADER_ROW) {
boolean isHeader = view!=null && view.findViewById(R.id.title)!=null;
if (!isHeader) {
view = mInflater.inflate(R.layout.row_my_header, null);
}
TextView header = view.findViewById(R.id.title);
header.setText(...);
}
else {
if (view==null) {
view = mInflater.inflate(R.layout.row_my_data, null);
}
// fill data row
}
return view;
}