I wanted to make a listview with alphabetical adapter so that you can see the letters mapped to each section when fastscrolling. The following works correctly when the listview is initialised. However, if the list changes (e.g. adding or removing rows) the indexer does not seem to update even though a new adapter is created each time. It uses the same set of alphabets as the original list.
private void GenerateListView (final ArrayList<String> listItems) {
try {
listView = (ListView) findViewById(R.id.listView_browser);
// generate section index adapter
AlphabeticalAdapter adapter = new AlphabeticalAdapter(this,
android.R.layout.simple_list_item_1, listItems);
listView.setAdapter(adapter);
// recall scroll position
if (_currPos < listItems.size())
listView.setVerticalScrollbarPosition(_currPos);
} catch (Exception e) {
e.printStackTrace();
}
}
The following is the alphabetical adapter class.
public class AlphabeticalAdapter extends ArrayAdapter<String> implements SectionIndexer
{
private HashMap<String, Integer> alphaIndexer;
private String[] sections;
public AlphabeticalAdapter(Context c, int resource, List<String> data)
{
// create ArrayAdapter<String>
super(c, resource, data);
// generate HashMap
alphaIndexer = new HashMap<>();
// generate index
for (int i = 0; i < data.size(); i++)
{
// convert first letter of each entry to upper case
String s = data.get(i).substring(0, 1).toUpperCase();
// map letter with corresponding index
if (!alphaIndexer.containsKey(s))
alphaIndexer.put(s, i);
}
// assign set view of keys in the HashMap
Set<String> sectionLetters = alphaIndexer.keySet();
// generate list from the set view
ArrayList<String> sectionList = new ArrayList<>(sectionLetters);
// sort list alphabetically
Collections.sort(sectionList);
// define and populate string array with the letters
sections = new String[sectionList.size()];
for (int i = 0; i < sectionList.size(); i++)
sections[i] = sectionList.get(i);
}
See this solution Code.
And to update SectionIndexer using filter or any data change override this method. It's work for me.
#Override
public void notifyDataSetInvalidated() {
//write your code
super.notifyDataSetInvalidated();
}
Related
I'm trying to add items to an arraylist using this class template:
public class Template {
public String username;
public String email;
}
Here's the whole code:
public void JsonToArrayList(JSONArray myJsonArray) throws JSONException
{
ArrayList<Template> listItems = new ArrayList<Template>();
JSONObject jo = new JSONObject();
Template tem = new Template();
ListView lv = (ListView) findViewById(R.id.listView1);
for(int i = 0; i<myJsonArray.length(); i++)
{
jo = myJsonArray.getJSONObject(i);
tem.username = jo.getString("username");
tem.email = jo.getString("user_email");
listItems.add(tem);
Log.e("Ninja Archives", tem.username);
}
// This is the array adapter, it takes the context of the activity as a first // parameter, the type of list view as a second parameter and your array as a third parameter
ArrayAdapter<Template> arrayAdapter = new ArrayAdapter<Template>(this,android.R.layout.simple_list_item_1, listItems);
lv.setAdapter(arrayAdapter);
}
The problem is, instead of filling my listview with nice username and email strings, it's filling up with items like this:
com.android.ninjaarchives.
Template#40585690
I think somewhere along the line I have become lost, but I've been trying all sorts for ages now and getting nowhere. Can anyone point me in the right direction?
Thanks for any help.
Note: not really sure what's going on with the code; it doesn't appear to be pasting correctly.
Use below code, it can be a solution for you
public void JsonToArrayList(JSONArray myJsonArray) throws JSONException
{
ArrayList<Template> listItems = new ArrayList<Template>();
JSONObject jo = new JSONObject();
Template tem = new Template();
ListView lv = (ListView) findViewById(R.id.listView1);
String listItemString[] = new String[myJsonArray.length];
for(int i = 0; i<myJsonArray.length(); i++)
{
jo = myJsonArray.getJSONObject(i);
tem.username = jo.getString("username");
tem.email = jo.getString("user_email");
listItemString[i] = tem.username +" - " + tem.email; // u can change it according to ur need.
listItems.add(tem);
Log.e("Ninja Archives", tem.username);
}
// This is the array adapter, it takes the context of the activity as a first // parameter, the type of list view as a second parameter and your array as a third parameter
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, listItemString);
lv.setAdapter(arrayAdapter);
}
But better to write Custom adapter by extending BaseAdapter, and do listItem handling in getView method here is one simple tutorial
Take a class extending Base
private class CustomAdapter extends BaseAdapter
{
LayoutInflater inflater;
public CustomAdapter(Context context)
{
inflater = LayoutInflater.from(context);
}
public int getCount()
{
return listItems.size();
}
public Object getItem(int position)
{
return listItems.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.listlayout, parent, false);
Template data = (Template) getItem(position);
TextView v=new TextView(context);
v.setText(data.name);
return v;
}
}
and set adapter to your listview
lv.setAdapter(new CustomAdapter(this));
In this case you have to use a custom adapter (that extends from ArrayAdapter) and override the getView method to display in a custom layout the username and the email.
ListViews have always been my weak point and right now I am practicing putting a Listview, within a Listview. Anyway, I first call my ListView at the start of my program and it loads it with an array saved in my strings.xml:
String[] departments = getResources().getStringArray(
R.array.departments_array);
setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item,
departments));
setContentView(R.layout.main);
ListView lv = getListView();
lv.setTextFilterEnabled(true);
What I want to do is update this ListView with a new array of values each time a list item is clicked. The reason why I am trying to do it this way is because I plan on having 27 different arrays with different values for each position, and I feel it would be lighter on my resources if instead of making a ListView for each array of items, I would update this one ListView. I know I am probably not doing this the most efficient way, but if there is another way of implementing my idea please tell me.
lv.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// When clicked, show a toast with the TextView text
switch (position) {
case 0:
try {
//It is here that i dont know what to do, I was going to call
//the Listview the same way i did previously using my setlistadapter,
//but i kept getting errors about the code being undefined
String[] listitems1 = getResources().getStringArray(
R.array.items_array);
} catch (ClassCastException e) {
Toast.makeText(getApplicationContext(), "Error",
Toast.LENGTH_SHORT).show();
}
break;
case 1:
try {
//The listview will be changed again here
} catch (ClassCastException e) {
Toast.makeText(getApplicationContext(), "Error",
Toast.LENGTH_SHORT).show();
}
break;
}
};
});
Have you thought of using a BaseAdapter and setting it as the list adapter
http://developer.android.com/reference/android/widget/BaseAdapter.html
Your approach is wrong( if I understand what are you doing). Instead of replacing the adapter of the ListView every time the user clicks(and simply setting a new adapter should work) a element in the initial list you should start a new activity passing the clicked position and in your new activity set the adapter on a ListView with the correct array based on that position.
A small example:
Main class:
/**
* The main class with the initial 27 items array.
*/
public class Class1 extends ListActivity {
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// start the second activity that will show our array depending on the
// position clicked
Intent i = new Intent(this, Class2.class);
// put the position in the Intent so we can know in the other activity
// what array to load.
i.putExtra("pos", position);
startActivity(i);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// I just used a simple array of 2 items, you'll load your 27 items
// array
String[] items = { "1", "2" };
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, items));
}
}
Secondary activity that will show the array based on the previously selected position:
public class Class2 extends ListActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// get the Intent that started the activity
Intent i = getIntent();
// find out what position did that other activity send to us.
int position = i.getIntExtra("pos", -1);
// load the ListView with an adapter based on the array that you
// want(according to that position)
if (position == 0) {
// the first element in the main list
String[] items = getResources().getStringArray(R.array.a1);
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, items));
} else if (position == 1) {
// the second element in the main list
String[] items = getResources().getStringArray(R.array.a2);
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, items));
} else {
// etc
}
}
}
Luksprog's answer is indeed correct, and it is very useful for lists many levels deep (you do not put limits, just keep spawning new activity instances with the proper list loaded)
BUT
If your list isn't more than 2 levels deep you can use ExpandableListActivity instead of ListActivity which is basically an enhanced version of the single-level list you're using which natively handle group collapsing/expanding and therefore you do not need the spawn of a new activity for each sublevel.
again note that this approach works only for lists which do not go deeper than 2 levels
ExpandableListActivity documentation
ExpandableListView documentation
ExpandableListAdapter documentation - you should be fine with the BaseExpandableListAdapter implementation
And here you have some nice example from Google itself:
public class ExpandableList3 extends ExpandableListActivity {
private static final String NAME = "NAME";
private static final String IS_EVEN = "IS_EVEN";
private ExpandableListAdapter mAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
List<Map<String, String>> groupData = new ArrayList<Map<String, String>>();
List<List<Map<String, String>>> childData = new ArrayList<List<Map<String, String>>>();
for (int i = 0; i < 20; i++) {
Map<String, String> curGroupMap = new HashMap<String, String>();
groupData.add(curGroupMap);
curGroupMap.put(NAME, "Group " + i);
curGroupMap.put(IS_EVEN, (i % 2 == 0) ? "This group is even" : "This group is odd");
//filling with dummy data...
List<Map<String, String>> children = new ArrayList<Map<String, String>>();
for (int j = 0; j < 15; j++) {
Map<String, String> curChildMap = new HashMap<String, String>();
children.add(curChildMap);
curChildMap.put(NAME, "Child " + j);
curChildMap.put(IS_EVEN, (j % 2 == 0) ? "This child is even" : "This child is odd");
}
childData.add(children);
}
// Set up our adapter
mAdapter = new SimpleExpandableListAdapter(
this,
groupData,
android.R.layout.simple_expandable_list_item_1,
new String[] { NAME, IS_EVEN },
new int[] { android.R.id.text1, android.R.id.text2 },
childData,
android.R.layout.simple_expandable_list_item_2,
new String[] { NAME, IS_EVEN },
new int[] { android.R.id.text1, android.R.id.text2 }
);
setListAdapter(mAdapter);
}
}
I want to create a contact list with fast scroll. In the right I need to place the alphabetic list"ABCD...Z". On the screen I need to pop up the A, or B...everytime I click on the right list or..scroll up and down.
In my code: I have 2 problems:
First the alphabetic list image disappears when I scroll down the list a
Secondly on the screen doesn't appear anything if I click in the middle of the list, for example. I mean I need to put my finder on the scroll button in order to see the down list characters when going though the list. I don't want that button when scrolling to be visible and I also want it to be activated in the place where i put my finder.
Got any advices?
.java
class MyIndexerAdapter<T> extends ArrayAdapter<T> implements SectionIndexer {
ArrayList<String> myElements;
HashMap<String, Integer> alphaIndexer;
String[] sections;
public MyIndexerAdapter(Context context, int textViewResourceId,
List<T> objects) {
super(context, textViewResourceId, objects);
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
}
Set<String> keys = alphaIndexer.keySet(); // set of letters ...sets
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);
}
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");
return 0;
}
public Object[] getSections() {
return sections; // to string will be called each object, to display }
}
}
I would like to know if a ListView in Android has an option to place alphabets on the right like the paradigm of iPhone ListView, like below
If yes, can someone provide me with sample codes.
I am not looking for the one with an Alphabet overlay from ApisDemo but an exact one like the iPhone paradigm. Is it possible?
You can check this project https://github.com/woozzu/IndexableListView.
Here is the screenshot
I implemented somthing similar a while back so i've modified my activity and you can take a look below, sorry its not very well commented - hope it helps!
public class AZIndexer extends Activity {
ListView myListView;
ArrayList<String> elements;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// elements
String s = "MNBVCXZLKJHGFDSAQWERTYUIOP";
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!
// listview
myListView = (ListView) findViewById(R.id.myListView);
myListView.setFastScrollEnabled(true);
MyAZAdapter<String> adapter = new MyAZAdapter<String>(
getApplicationContext(), android.R.layout.simple_list_item_1,
elements);
myListView.setAdapter(adapter);
}
class MyAZAdapter<T> extends ArrayAdapter<T> implements SectionIndexer {
ArrayList<String> myElements;
HashMap<String, Integer> azIndexer;
String[] sections;
public MyAZAdapter(Context context, int textViewResourceId, List<T> objects) {
super(context, textViewResourceId, objects);
myElements = (ArrayList<String>) objects;
azIndexer = new HashMap<String, Integer>(); //stores the positions for the start of each letter
int size = elements.size();
for (int i = size - 1; i >= 0; i--) {
String element = elements.get(i);
//We store the first letter of the word, and its index.
azIndexer.put(element.substring(0, 1), i);
}
Set<String> keys = azIndexer.keySet(); // set of letters
Iterator<String> it = keys.iterator();
ArrayList<String> keyList = new ArrayList<String>();
while (it.hasNext()) {
String key = it.next();
keyList.add(key);
}
Collections.sort(keyList);//sort the keylist
sections = new String[keyList.size()]; // simple conversion to array
keyList.toArray(sections);
}
public int getPositionForSection(int section) {
String letter = sections[section];
return azIndexer.get(letter);
}
public int getSectionForPosition(int position) {
Log.v("getSectionForPosition", "called");
return 0;
}
public Object[] getSections() {
return sections; // to string will be called to display the letter
}
}
}
With xml as:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="#+id/myListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</ListView>
</LinearLayout>
ScreenShot:
It's possible if you're willing to write the code, but the fast scroller as shown in the AOSP Contacts app is the platform-provided way to do present an section-indexed list.
I am working on a ListActivity that has a inner class that is a child of SimpleAdapter that implements SectionIndexer.
class MySimpleAdapter extends SimpleAdapter implements SectionIndexer {
HashMap<String, Integer> letters;
Object[] sections;
AlphabetIndexer alphaIndexer;
public MySimpleAdapter(Context context, List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to,
HashMap<String, Integer> letters, Object[] sections) {
super(context, data, resource, from, to);
this.letters = letters;
this.sections = sections;
}
#SuppressWarnings("unchecked")
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.common_image_row1, null);
}
HashMap<String, Object> data = (HashMap<String, Object>) getItem(position);
Integer idArtist = Integer.parseInt((String) data.get("idArtist"));
convertView.setTag(idArtist);
((TextView) convertView.findViewById(R.id.item_name))
.setText((String) data.get("sName"));
ImageView image = (ImageView) convertView.findViewById(R.id.item_image);
image.setTag((String) data.get("sImageUrl"));
image.setImageResource(R.drawable.default_artwork);
((TextView) convertView.findViewById(R.id.item_count))
.setText((String) data.get("iSongs") + " Songs");
if (!bScrolling) {
new API.DownloadImagesTask().execute(image);
}
return convertView;
}
public int getPositionForSection(int section) {
String letter = (String) sections[section];
return letters.get(letter);
}
public int getSectionForPosition(int position) {
return 0;
}
public Object[] getSections() {
return sections;
}
}
The activity is receives a JSON object in a separate AysncTask. This object is made up of various JSONArrays whose keys are first letter of the item the items of the array. So, the array is made up of a bunch of items that begin with the letter "B". Therefore, that JSONArray's key is "B".
{
B: ["ball", "buck", "bill"]
C: ["charlie", "chuck", "chap"]
}
The object does not necessarily have all of the letters of the alphabet. I am also aware that order is not guaranteed with JSONObjects so I sort them.
List> maps = new ArrayList>();
ArrayList sections = new ArrayList();
HashMap letters = new HashMap();
String[] alphabet = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N"
,"O","P","Q","R","S","T","U","V","W","X","Y","Z"};
int k = 0;
for (int i = 0; i < alphabet.length; i++) {
if (playlistArtists.optJSONArray(alphabet[i]) != null) {
try {
JSONArray artists = playlistArtists.getJSONArray(alphabet[i]);
sections.add(alphabet[i]);
for (int j = 0; j < artists.length(); j++) {
JSONObject artist = (JSONObject) artists.get(j);
HashMap<String, String> map= new HashMap<String, String>();
map.put("idArtist", artist.getString("idArtist"));
map.put("sName", artist.getString("sName"));
map.put("sImageUrl", artist.getString("sImageUrl-thumb"));
map.put("iSongs", artist.getString("iSongs"));
maps.add(map);
letters.put(alphabet[i], k);
k++;
}
} catch (JSONException e) {
Log.d(TAG,"JSONException in Music::GetMusicCatlog.doInBackground");
e.printStackTrace();
}
}
}
SimpleAdapter adapter = new MySimpleAdapter(Catalog.this,
maps,
R.layout.common_image_row1,
new String[] {"idArtist","sName", "sImageUrl", "iSongs" },
new int[] {R.id.item_id, R.id.item_name, R.id.item_image, R.id.item_count },
letters,
sections.toArray());
setListAdapter(adapter);
The problem I am having is with FastScroll. I have everything working for the most part. The list is grouped by first letter and when using FastScroll the letter appears in the popup and goes to the correct group. The issue is when I let go of the FastScroll after going to the desired section the FastScroll "Jumps" to a random part of the list. It doesn't stay in the location where I let go of it. I think it is going to an arbitrary place in the section because I do not have the SectionIndexer implemented right. I think the problem is with this method.
public int getSectionForPosition(int position) {
return 0;
}
I am just not sure how to implement the SectionIndexer methods properly...
TL;DR First answer can't be correct. If you need a sample implementation of the getSectionForPosition method, see here.
You're right, it looks like a problem with your implementation of getSectionForPosition. The answer provided so far can't be correct because you need to return the index of the section a certain position is related to and not the position a section is related to (that is what you getPositionForSection method should do).
So you should rework your getSectionForPosition method (again). You need a valid mapping for every position to a section. Documentation says:
Given a position within the adapter, returns the index of the corresponding section within the array of section objects.
If you need an example of how to build such a mapping, see here. Implemented correctly, it will solve the problem of a jumping scrollbar and instead will position it properly.
I saw many time getSectionForPosition implemented in the same way like getPositionForSection
public int getPositionForSection(int section) {
String letter = (String) sections[section];
return letters.get(letter);
}
public int getSectionForPosition(int position) {
String letter = (String) sections[position];
return letters.get(letter);
}