I have an Android App with a search field and a listview. When the user types in the first character the application should smooth scroll to the first item with the given character and highlight the item. If the user remove the input the application should smooth scroll back to the first item without highlighting it.
After trying a lot and reading a lot I think I need some help. What I did as a Android beginner does not work in the right way. The app is scrolling - but the behaviour is kind of strange:
- the wrong item is highlighted
- after trying a second time (to e) two wrong items are highlighted
- delete search input and this happened
There are some other strange things happened which can not be documented all here. E.g. when the app is scrolling behind the last viewed item and I scroll back then not the first item at position 0 is highlithed but the second. Scrolling down and back again and the second and the third item are highlighted.
And it is also wrong that they are highlighted coz with going back I do not want to highlight any item.
I think something is wrong with my index management - or something else which I can not explain is totally wrong.
Here is my project structure:
For the list I use an own class ItemsTask - but that should not matter too much for the problem. And the item adapter for the list is the ToDoListAdapter.
The whole project does a lot of more but I want to keep it short here due to find a solution only for the scrolling in the list. Here is the MainActivity.java:
> package com.wbapps.ListScrolling;
/**
* Created by Andreas on 9/4/2017.
*/
import android.app.SearchManager;
import android.content.Context;
import android.graphics.Color;
import android.support.v7.app.ActionBar;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ListView;
import android.widget.SearchView;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MainActivity extends AppCompatActivity implements
SearchView.OnQueryTextListener,SearchView.OnCloseListener {
private SearchView search;
/* object for the ListView from activity_main.xml*/
ListView list_view;
/*wb, 04Oct2017: List for the shopping items */
List<ItemsTask> list_items;
/*wb, 04Oct2017: adapter for items */
TodoListAdapter items_adapter;
/* wb, 04Oct2017: there must be a parser for the items-xml and another one for the categories-xml */
XmlParser xmlparser;
/* wb, 04Oct2017: create the two xml files for the shopping items and the categories */
File file_shoppingItems;
ActionBar actionBar;
/* wb, 06Nov Declaration of variables used for the AlertBuilder multiItemsChecked*/
ArrayList<Integer> mSelectedItems;
/* wb, 09Nov Declaration of variables used for the AlertBuilder of singleItemsChecked*/
String theChoice;
/* wb, 23Nov2017: no more filter
wb, 10Nov2017: Flag if filter is set or not
boolean filtered = false;
*/
Integer searchedItem = 0;
@Override
protected 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);
file_shoppingItems = new File(Environment.getExternalStorageDirectory(), "shoppingItems.xml");
xmlparser = new XmlParser();
list_items = new ArrayList<ItemsTask>();
if (file_shoppingItems.exists()) {
try {
list_items = xmlparser.read(file_shoppingItems);
if (list_items.isEmpty()) {
//Toast.makeText(this, "File exist but empty", Toast.LENGTH_SHORT).show();
file_shoppingItems.delete();
file_shoppingItems.createNewFile();
} else {
sortList();
}
} catch (XmlPullParserException ex) {
Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
} catch (FileNotFoundException ex) {
//Toast.makeText(this, "Error 2", Toast.LENGTH_SHORT).show();
Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
//Toast.makeText(this, "Error 3", Toast.LENGTH_SHORT).show();
Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
try {
//Toast.makeText(this, "File does not exist - will be created", Toast.LENGTH_SHORT).show();
file_shoppingItems.createNewFile();
} catch (IOException ex) {
//Toast.makeText(this, "Error 4", Toast.LENGTH_SHORT).show();
Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
}
}
/* id list_view identifies the listview from the activity_main.xmll */
list_view = (ListView) findViewById(R.id.list_view);
registerForContextMenu(list_view);
items_adapter = new TodoListAdapter(list_items, this);
list_view.setAdapter(items_adapter);
//To swipe away an item from the list
SwipeDismissListViewTouchListener touchListener =
new SwipeDismissListViewTouchListener(
list_view,
new SwipeDismissListViewTouchListener.DismissCallbacks()
{
/*wb,11Nov2017: Method "canDismiss" is required when calling
new SwipeDismissListViewTouchListener.DismissCallbacks()
but here not used */
@Override
public boolean canDismiss(int position) {return true;}
@Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
list_items.remove(position);
items_adapter.notifyDataSetChanged();
}
}
});
list_view.setOnTouchListener(touchListener);
}
@Override
public boolean onClose() {
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
return true;
}
@Override
public boolean onQueryTextChange(String query) {
int duration = 300; //miliseconds
int offset = 0; //fromListTop
searchedItem = -1;
if (query.isEmpty()) {
if (searchedItem > -1) {
list_view.getChildAt(searchedItem);
View v = (View) findViewById(R.id.list_task_view);
v.setBackgroundColor(Color.rgb(176,233,249));
searchedItem = -1;
}
for (int i=0; i < list_items.size();i++) {
View v = (View) findViewById(R.id.list_task_view);
v.setBackgroundColor(Color.rgb(176,233,249));
}
list_view.smoothScrollToPositionFromTop(0, offset, duration);
}
else
{
for (int i=0; i < list_items.size();i++) {
//v.setBackgroundColor(Color.rgb(176,233,249));
if (list_items.get(i).getTaskContent().toUpperCase().charAt(0) == query.toUpperCase().charAt(0)) {
searchedItem = i;
list_view.smoothScrollToPositionFromTop(i, offset, duration);
//list_view.setSelection(i);
View v = (View) findViewById(R.id.list_task_view);
v.setBackgroundColor(Color.rgb(238, 202, 197));
break;
}
}
}
return true;
}
@Override
protected void onPause() {
super.onPause();
//wb, Sep 13, 2017:
//before doing something else - like calling a new activity - write the list to
//the data sourc file list_items.xml file
try {
xmlparser.write(list_items, file_shoppingItems);
} catch (IOException ex) {
Logger.getLogger(MainActivity.class.getName()).log(Level.SEVERE, null, ex);
}
}
/* wb, 18Sep2017: sort the list_items list */
public void sortList() {
Collections.sort(list_items, new Comparator<ItemsTask>() {
@Override
public int compare(ItemsTask content1, ItemsTask content2) {
/* ignore case sentsitivity */
return content1.getTaskContent().compareToIgnoreCase(content2.getTaskContent());
}
});
}
};
The data for the list comes from an external xml-file.
And this is the ToDoListAdapter:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.wbapps.ListScrolling;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TodoListAdapter extends BaseAdapter {
private List<ItemsTask> listItemsTasks;
private List<ItemsTask> savedItemsTasks;
private final LayoutInflater inflater;
ItemsTask itemsTask;
/* wb, 04Oct2017: may be no more! */
String MyStr, MySubStr;
TodoListAdapter.ViewHolder holder;
public TodoListAdapter(List<ItemsTask> itemsTasks, Context context) {
this.listItemsTasks = itemsTasks;
inflater = LayoutInflater.from(context);
}
public int getCount() {
return listItemsTasks.size();
}
public Object getItem(int position) {
return listItemsTasks.get(position);
}
public long getItemId(int position) {return position;}
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.list_layout, parent, false);
holder = new TodoListAdapter.ViewHolder();
View v = convertView.findViewById(R.id.list_checkbox);
holder.task_view = (TextView) convertView.findViewById(R.id.list_task_view);
holder.done_box = (CheckBox) convertView.findViewById(R.id.list_checkbox);
convertView.setTag(holder);
} else {
holder = (TodoListAdapter.ViewHolder) convertView.getTag();
}
itemsTask = (ItemsTask) getItem(position);
/* wb, 15Sep,2017: Show checkbox only in MainActivity */
holder.done_box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
listItemsTasks.get(position).setIsDone(isChecked);
if (isChecked) {
//wb,21Sep2017: No strike out neccessary
//holder.task_view.setPaintFlags(holder.task_view.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
holder.task_view.setPaintFlags(holder.task_view.getPaintFlags());
} else {
holder.task_view.setPaintFlags(0);
}
}
});
holder.task_view.setText(itemsTask.getTaskContent());
holder.done_box.setChecked(itemsTask.isDone());
return convertView;
}
/* wb, 18Sep2017: sort the list_items list */
public void sortList() {
Collections.sort(listItemsTasks, new Comparator<ItemsTask>() {
@Override
public int compare(ItemsTask content1, ItemsTask content2) {
/* return o1.getTaskContent().compareTo(o2.getTaskContent()); */
/* ignore case sentsitivity */
return content1.getTaskContent().compareToIgnoreCase(content2.getTaskContent());
}
});
}
static class ViewHolder {
TextView spin_view;
TextView task_view;
CheckBox done_box;
TextView sl_task;
TextView sl_category;
}
}
There is one more important point one should know:
I do not use a filter to get the result of the users input. I just want to jump to the listitem. As far as I can see I can not use any onClick event like I have seen in many post for a similar question. So this will not help in my situation.
I just tried some scrolling back and forth and noticed this: with scrolling through the list back and forth (not deleting the input field!) the app highlighted always some different list items. For me I can not see any logical schema for that.