This question already exists:
categorise listView items into different sections - Android
Closed 6 years ago.
how can we create a Adapter (for ListView) which can take categorized data
and show them in a listView with headers and rows,
by categorized data i meant grouped data e.g.
record = ["US",("California","Alabama","Alaska","Wisconsin")]
the above is a record which holds a country and its states.
i have list of states with their countries and i wanna show it on a listView (sorted by countries) and i want to use country's name as section header of my list
this is what its going to look like :
Country1
state1ofC1
state2ofC1
state3ofC1
Country2
state1ofC2
state2ofC2
state3ofC3
................
a basic Adapter for listView looks like this :
public class NewAdapter extends BaseAdapter {
#Override
public int getCount() {
return 0;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
which takes one array of records. but how can i create an Adapter which is compatible with Categorized data so that i can use listView with section headers please guide me
You need to use an ExpandableListView.
Related
Apologies if this is poorly explained, I am having difficulty in understanding it myself. If you point out anything you don't understand I will do my best to correct any issues. Okay so here we go.
Several classes. (D&D sheet, sheet has weapons user can equip, this is about equipping said weapons which is stored in a list)
A fragment activity - CombatFragment
The arrayadapter list which is declared in CombatFragment -
AttackListViewContentAdapter
The realm object - Weapon
The realm object where a list of Weapon is held - Sheet
A number of XML files (The code of which I won't paste here as SO has a limit on code. content_combat, attack_list_item
What I've gathered so far is that when I create a new attackListViewContentAdapter it loops at a rapid and continued pace. So much so that the screen does not respond to me touching any of the widgets. I've done things like log a number each time it passes so it shows when it's doing it again and again. If you need information on that I can show you where I put the logs and what shows in my Logcat when I add an additional view (row).
I believe that it's something to do with the onChangedListener which keeps being triggered, even if I found the reason why how do I then get to a stage where I can create a new view and have the listener so it can record changes.
Please note in the interests of space I will be using abbreviated code. I've ignored things like dialog boxes and widgets which aren't relevant. So if it seems like something missing or you need to view the classes, it's possibly in the file which I've linked above each one.
CombatFragment
public class CombatFragment extends Fragment {
#BindView(R.id.lv_attack_spellcasting_content)
ListView lv_attack_spellcasting_title;
#Nullable
#Override
public View onCreateView(final LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.content_combat, container, false);
RealmList<Weapon> weaponList = sheet.getWeaponList();
final AttackListViewContentAdapter attackListViewContentAdapter = new AttackListViewContentAdapter(getActivity(), sheet, realm, weaponList);
weaponList.addChangeListener(new RealmChangeListener<RealmList<Weapon>>() {
#Override
public void onChange(RealmList<Weapon> weapons) {
/* Gives the adaptor a kick to know that the weapon realm list has changed */
attackListViewContentAdapter.notifyDataSetChanged();
loopOnChanged++;
}
});
lv_attack_spellcasting_title.setAdapter(attackListViewContentAdapter);
playerInit();
return rootView;
}
// This is a fake method, this is just to show that the .add is in it's own method which is triggered by a button press and not in onCreate
public void buttonPress() {
sheet.getWeaponList().add(realm.createObject(Weapon.class));
}
} `
AttackListViewContentAdapter
public class AttackListViewContentAdapter extends ArrayAdapter<Weapon> {
public AttackListViewContentAdapter(Context context, Sheet sheet, Realm realm, List<Weapon> weaponList) {
super(context, 0, weaponList);
this.sheet = sheet;
this.realm = realm;
}
#Override
#NonNull
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
//Because you're returning the view (AttachToRoot is false) the ArrayAdaptor (This class) will handle adding the view to the list.
convertView =
LayoutInflater.from(getContext()).inflate(R.layout.attack_list_item, parent, false);
return convertView;
}
}
Weapon
public class Weapon extends RealmObject {
#PrimaryKey
int weaponID;
//properties, set get methods etc.
}
Sheet
public class Sheet extends RealmObject {
#PrimaryKey
private int sheetID;
private RealmList<Weapon> weaponList;
public RealmList<Weapon> getWeaponList() {
return weaponList;
}
public void setWeaponList(RealmList<Weapon> weaponList) {
this.weaponList = weaponList;
}
}
content_combat
<ListView
android:id="#+id/lv_attack_spellcasting_content"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:columnCount="7"
android:rowCount="1" />
attack_list_item
Nothing really in there to include
Your problem is happening because of bad initialization of your spinner widgets within AttackListViewContentAdapter class.
You must set your spinners selection before setOnItemSelectedListener is set.
You must check whether your spinner selection is not equal to current selection, to avoid an infinite loop between onChange and onItemSelected methods. I mean, your spinners onItemSelected callbacks, execute a realm transanctions, then, those transanctions fire your onChange callback and finally, your onChange callback invokes notifyDataSetChanged() which make cycle start again going into an infinite loop.
To solve your problem, you should follow the next steps inside AttackListViewContentAdapter.java:
A) Remove the following lines from addWeaponToUI() method:
private void addWeaponToUI() {
et_name_value.setText(weapon.getWeaponName());
np_damage_number_of_die_value.setValue(weapon.getWeaponDamageNumberOfDie());
SheetEnum.Ability ability = SheetEnum.Ability.getEnumValue(weapon.getWeaponAbilityBonusInt());
tv_attack_bonus_value.setText(String.valueOf(sheet.getAbilityBonus(ability)));
// REMOVE below lines!
//s_damage_die_type_value.setSelection(weapon.getWeaponDamageDieTypeInt());
//s_damage_type_value.setSelection(weapon.getWeaponDamageTypeInt());
//s_ability_bonus_value.setSelection(weapon.getWeaponAbilityBonusInt());
}
B) Invoke spinner setSelection() before setOnItemSelectedListener(), then check selected item is not equal to selected position to avoid an infinite loop:
ArrayAdapter<CharSequence> damageDieTypeAdapter = ArrayAdapter.createFromResource(getContext(), R.array.die_type, android.R.layout.simple_spinner_dropdown_item);
s_damage_die_type_value.setAdapter(damageDieTypeAdapter);
//Set selection before listener
s_damage_die_type_value.setSelection(weapon.getWeaponDamageDieTypeInt());
s_damage_die_type_value.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View v, final int position, long id) {
//Check selected position is not equal to current position to avoid an infinite loop
if (position != weapon.getWeaponDamageDieTypeInt()) {
String[] value = getContext().getResources().getStringArray(R.array.die_type);
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
weapon.setWeaponDamageDieType(position);
}
});
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
C) Repeat Step B for s_damage_type_value and s_ability_bonus_value spinners
I have multiple variables, we'll call them varA, varB, varC, and varD. When a button is pressed these 4 variables are to be stored in an ArrayList, together. This creates a single entry which can be viewed by variable varA. All the entries can be viewed in a ListView which is done through an ArrayAdapter.
onCreate
ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, arrayList);
profileList.setAdapter(profileAdapter);
When a button is pressed
// Add data to a new profile in #+id/profileList ListView
ArrayList<String> arrayList= new ArrayList<>();
arrayList.add(varA);
System.out.println("varA's");
System.out.println(arrayList);
((ArrayAdapter)profileList.getAdapter()).notifyDataSetChanged();
Layout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/overviewLayout">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/profileList"/>
</RelativeLayout>
I am having issues with the ListView showing the items that were added. Also, I was trying to use TinyDB to store these items so they can be referenced later since that ArrayList used on button press is just temporary. I also I'm having trouble figuring out how to set this up so that each time the button is pressed those 4 vars will stay together in a "profile" so they don't get mixed up with some other data that might be added later.
To avoid repeated values from getting stored check if it already exists :
if(!arrayList.contains(varA)){
arrayList.add(varA);
}
For listview updation problem ,use notifyItemInserted(position) instead of notifyDataSetChanged()
yourAdapter.notifyItemInserted(position);
As I understand, you want to display a list which contains 4 datas in one entry and showing just the first of them.
If it is, you could create a custom Model, it will be easier. This will store your datas in one object:
ProfileModel
|
|-- varA
|-- varB
|-- varC
'-- varD
Your custom model could look like something like this:
public class ProfileModel {
private String varA;
private float varB;
private int varC;
private String varD;
// getters
...
// setters
...
public ProfileModel() { }
}
Initialize the list of profiles at start:
ArrayList<ProfileModel> list = new ArrayList<>();
To populate the main list do as follows:
// create a list of object to the datas
ProfileModel profile = new ProfileModel();
profile.setVarA(varA);
profile.setVarB(varB);
profile.setVarC(varC);
profile.setVarD(varD);
// then fill the first list
list.add(profile);
Since you want to fill the adapter with other datas than a simple String but a custom model, I'd prefer to use a custom adapter. It's not that hard to create one, and because sometimes, you maybe want a different behavior on an update, I'd also prefer to use a custom notifyDataSetChanged method.
So, here's a simple adapter with the same layout as the one you used:
public class ProfileAdapter extends BaseAdapter {
private Context context;
private ArrayList<ProfileModel> listProfiles;
public ProfileAdapter(Context context, ArrayList<ProfileModel> listProfiles) {
super();
this.context = context;
this.listProfiles = listProfiles;
}
private class ViewHolder {
private TextView textVarA;
}
#Override
public int getCount() {
return listProfiles != null ? listProfiles.size() : 0;
}
#Override
public Object getItem(int i) {
return listProfiles.get(i); // will return the profile model from position
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
view = LayoutInflater.from(context)
.inflate(android.R.layout.simple_list_item_1, null);
holder.textVarA = (TextView) view.findViewById(android.R.id.text1);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
ProfileModel profile = listProfiles.get(i);
holder.textVarA.setText(profile.getVarA()); // get varA value
return view;
}
public void addEntry(ProfileModel newEntry) {
listProfiles.add(newEntry);
this.notifyDataSetChanged();
}
}
Then, you can set the adapter as follows:
ProfileAdapter profileAdapter = new ProfileAdapter(this, list);
profileList.setAdapter(profileAdapter);
To update this adapter:
// create a list of object to the datas
ProfileModel profile = new ProfileModel();
profile.setVarA(varA);
profile.setVarB(varB);
profile.setVarC(varC);
profile.setVarD(varD);
profileAdapter.addEntry(profile); // this will call notifyDataSetChanged
Finally, I'm not very aware about TinyDB, but I saw it's using some JSON file to store data. So you could create a method to convert your datas to JSONArray which its name is varA and has values varB, varC, varD. There is many examples how to convert datas to JSON (as using Gson to convert your model to a JSON format).
Hope this will be useful.
I understand how a ViewHolder's onBindViewHolder works, however I'm unclear about how notifyItemRangeChanged(0, this.data.size()); works in this example and what it does exactly.
The data that is supplied to this adapter is in Json format.
The adapter is below:
public class AdapterQuestion extends RecyclerView.Adapter<AdapterQuestion.ViewQuestion>{
private LayoutInflater mLayoutInflater;
//this is an arrayList of questionData objects
private ArrayList<QuestionData> data =new ArrayList<>();
//Created the layoutInflator
public AdapterQuestion(Context context){
//get from context
mLayoutInflater=LayoutInflater.from(context);
}
public void setBloglist(ArrayList<QuestionData> data){
this.data =data;
notifyItemRangeChanged(0, this.data.size());
}
#Override
public ViewQuestion onCreateViewHolder(ViewGroup parent, int viewType) {
//inflates the customQuestion view or converts it to java code
View view= mLayoutInflater.inflate(R.layout.customquestion, null);
//We now want to convert the View into a ViewQuestion, view Question takes
//a view so we pass the view into view question and then return it.
ViewQuestion holder=new ViewQuestion(view);
return holder;
}
//ViewGroup parent and ViewType are not being assigned.
#Override
public void onBindViewHolder(ViewQuestion holder, int position) {
//here we need to bind the data to our view, there is currently no Data!
//We need to get the data from our JSON
//Parameters is a ViewHolder and a Position
//This gives us the current information object from the whole arraylist
//data.get(position) data is the arraylist and we are getting the current position or index;
//That current obj is of Type QuestionData
QuestionData currentObj= data.get(position);
//we are accessing the Inflated view, or saved view with holder
//holder.answerText is the textView in holder. We are then taking that current object
//We are getting the text of the current object and setting it to the AnswerText view
holder.answerText.setText(currentObj.getMtext());
holder.answerId.setText(currentObj.getId());
holder.mVotes.setText(currentObj.getVotes());
holder.mLikeButton.setTag(currentObj);
}
#Override
public int getItemCount() {
return data.size();
}
public class ViewQuestion extends RecyclerView.ViewHolder{
//once we create it once the reclycer view will automatically recycle it
private TextView answerText;
private TextView answerId;
private TextView mVotes;
private LikeButton mLikeButton;
public ViewQuestion (View itemView){
super(itemView);
//here we are finding the views by their ID
answerText=(TextView)itemView.findViewById(R.id.answerText);
answerId=(TextView)itemView.findViewById(R.id.answerId);
mVotes=(TextView)itemView.findViewById(R.id.VoteTextView);
mLikeButton= (LikeButton)itemView.findViewById(R.id.heart_buttons);
mLikeButton.setOnLikeListener(new OnLikeListener() {
#Override
public void liked(LikeButton likeButton) {
Voting vote = new Voting();
vote.onUpVote(convertToString(),
getAdapterPosition(),ViewQuestion.this);
System.out.print("Adapter Position"+getAdapterPosition());
}
#Override
public void unLiked(LikeButton likeButton) {
Voting onDown=new Voting();
onDown.onDownVote(convertToString(),
getAdapterPosition(), ViewQuestion.this);
}
});
}
public String getVoteView(){
String voteView=mVotes.getText().toString();
return voteView;
}
public String convertToString(){
String converted=answerId.getText().toString();
return converted;
}
public int convertToInt(){
String converted=answerId.getText().toString();
int ConvertedInt=Integer.parseInt(converted);
return ConvertedInt;
}
}
}
When the data that is to be set in RecyclerView is changed, the Adapter needs to get notified of the data change so that it can change the data in recyclerview.
The method
notifyItemRangedChanged(fromIndex,toIndex);
is used to notify the adapter that some set of data is changed among the whole data and it tells the adapter that adapter should refresh the data and reload it into the recyclerView starting from fromIndex to toIndex as passed into the method .
use this method if you have multiple data changed but not all , those changed data also are in cluster so that you can say from 5th to 10th index data are changed .
If all data are changed call :
notifyDataSetChanged();
if only one dataItem is changed then call :
notifyItemChanged(dataPosition);
Using notifyItemRangeChanged(0, this.data.size()) it’s bad practice.
Best way is using notifyItemChanged or notifyItemRangeChanged with payload.
Payload - optional parameter (key). That give you opportunity to check what kind of update do you need.
public void onBindViewHolder(/*...*/, List payloads) {
if (payloads.isEmpty()) {
setText(holder, position);
downloadBitmap(holder, position);
} else if (payloads.contains(SET_ONLY_TEXT)){
setText(holder, position);
}
}
In this example payloads used for checking when adapter should update only the text.
in your case you are not doing it(notifyItemRangeChanged) right as you might as well can call notifyDataSetChanged(); because you are telling the adapter that the entire list has changed and not specific position.
I have an arraylist and I want to update specific item.I am adding data to list with this line:
randomsList.add(new Random(result.get(i).getAsJsonObject(),0));
This is adding datas to 0,1,2,3,4... locations so when I try to update an item I don't know which object is where.
I am updating data with this line:
randomsList.set(position,new Random(user,1));
I think if I use the custom numbers for location I can update specific item.My prototype:
randomsList.add({USER_ID},new Random(result.get(i).getAsJsonObject(),0));
And if I want to update it then I use this line:
randomsList.set({USER_ID},new Random(user,1));
Is this a good approach ? If your answer is no,how should be ?
P.S. : I am using this arraylist with an adapter
As #itachiuchiha mentions, you should use a Map. The "custom numbers" you mention are your key (integers), and the value is the Random object.
As an aside, in response to your comment, below is an example of an Android Adapter that uses a Map as the underlying datasource.
public class RandomsAdapter extends BaseAdapter {
private Map<Integer, Random> randoms;
public RandomsAdapter(Map<Integer, Random> randoms) {
this.randoms = randoms;
}
public void updateRandoms(Map<Integer, Random> randoms) {
this.randoms = randoms;
notifyDataSetChanged();
}
#Override
public int getCount() {
return randoms.size();
}
#Override
public Random getItem(int position) {
return randoms.get(position);
}
#Override
public long getItemId(int position) {
// we won't support stable IDs for this example
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = createNewView();
}
update(view, songs.get(position));
return view;
}
private View createNewView() {
// TODO: create a new instance of your view and return it
return null;
}
private void update(View view, Random random) {
// TODO: update the rest of the view
}
}
Note the updateRandoms(Map<Integer, Random> randoms) method.
While you could expose a method in the adapter to update a single entry in the Map, it shouldn't be the responsibility of the Adapter to handle modifications to the map. I prefer passing the entire map again - it could still be a reference to the same object, but the Adapter doesn't know or care; it just knows: "my underlying datasource has been changed/modified, I need to tell my observers that they should refresh their views by calling notifyDataSetChanged()".
Alternatively, you could call notifyDataSetChanged() on the adapter externally when you modify the underlying Map (this tells the ListView that its data is out of date and to request its views from the adapter again).
So, I have an ArrayList that has name and id
Gil 232
Asty 2423 and so on.
To use it in the spinner, what I did was to create two separate String Arraylists. One containing the names and the other the corresponding ids. So, that once the user is selected, I will get the corresponding id from the position.
I am not sure how to do that last part. As to, how to get the position?> parent.getItemAtPosition(pos).toString();
This gives me the name again and not the position of the name in the ArrayList which I can use to retrieve the ID from the other list. How to do it? And, is their a better way to go about it?
public class User {
public int id;
public String name;
}
public class CustomAdapter extends ArrayAdapter<Data> implements Spinner
.OnItemSelectedListener {
#Override
public View getView(int position, View convertView, ViewGroup parent){
LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(android.R.layout.simple_spinner_item,
parent, false);
Data data = getItem(position);
// Do something with data and view here
return view;
}
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int position, long arg4) {
Data data = getItem(position);
// Do something with data here
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
}
Then create an ArrayList<Data> and use with your CustomAdapter.
The pos variable is the position of item selected from Spinner, so you can use this position to get the id (from second ArrayList) of corresponding name which is selected.
As a alternative you can also create a HashMap<String,String> map
and in that map put values like map.put(name,id); so when user will select value from Spinner you will have the name selected.And then use the map to get corresponding id
like String id = map.get(selected_name);
Hope it will help.