I'm working on implementing an ExpandableListView with my custom Adapter that extends BaseExpandableAdapter .
Working with the MVP model i successfully inflate all the groups and views in the right way. Piece of my GetChildView()
public override View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent)
{
BetViewHolder holder = new BetViewHolder(_BetPresenter);
var view = convertView;
if (view != null)
holder = view.Tag as BetViewHolder;
else
{
LayoutInflater layoutInflater = (LayoutInflater)_Context
.GetSystemService(Context.LayoutInflaterService);
holder = new BetViewHolder(_BetPresenter);
view = layoutInflater.Inflate(Resource.Layout.live_bet_view_item, null);
holder.InitEventViews(view);
view.Tag = holder;
}
_BetPresenter.OnBindBetEventView(holder, groupPosition, childPosition);
return view;
}
Inside the presenter at OnBindBetEventView i set the data on the corresponding view.
The problem
Each child item has a 3 custom components(Buttons) which are selectable. And in general each child view is relatively complex. When i select one of the buttons of the child view it successfully changes the color like it should, but also other child views from other groups change get selected too. The strange part comes when i scroll or collapse/expand a group.
The selected views are changing and some others are getting selected instead. 'Playing' with the groups it messes the all the selected buttons without a specific pattern.
Clicking the first button also checks another button on another group. It was not already selected by me. Scrolling and expanding groups changes the order of selected buttons
NOTE
After lots of debugging i realized that every 2 buttons the Unique view id's of the buttons are duplicate. Meaning that the instance of the view is exactly the same. Maybe is due to recycling or something. The point is that i need to preserve the state of my selected views without letting the adapted change the position of my views.
The recycling mechanism of the ExpandableList was causing this strange behavior due to the reuse-ability of the views. The ChildView with the clicked item was recycled once it was out of screen and inflating it again on a different position
I realize that due to the fact that the views had exactly the same unique hash created by Android. There was a duplication
I solved it by checking my model's state everytime the adapter tries to inflate the view, on the position it tries to inflate it.
Here is the ViewHolder method called from the adapter at GetChildView()
public void SetEventToView(Event eventItem)
{
txtCompetitor1.Text = eventItem.Competitor1;
txtCompetitor2.Text = eventItem.Competitor2;
txtTime.Text = eventItem.Time;
txtScore.Text = eventItem.Score;
for (int i = 0; i < _OddButtonList.Count; i++)
{
_OddButtonList[i].OddID = eventItem.BetItems[i].ID;
_OddButtonList[i].SetPriceText(eventItem.BetItems[i].BetOdd);
_OddButtonList[i].SetOnClickListener(this);
SetButtonState(eventItem, _OddButtonList[i], i);
}
}
Related
I'm trying to implement a custom listview. Everything works fine until I use a if () statement inside the getView() method
Without the if() condition a single item gets selected when I select an item but when I add the if() condition, the views are displayed properly but two items (non-adjacent) get selected (1st and last 1st or and second-last, any such combination).
View getView(...){
....
if (!item.getPriceTo().equals(""))
priceToTV.setText(item.getPriceTo());
else
priceToTV.setText(item.getPriceFrom());
return view;
}
Also I'm using saving the previous view to show the selection so the current selection has a red_border and when it is selected a black_border is set to it.:
subItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Log.d("New Order", "........");
if (previousViewOfSubItems != null && previousViewOfSubItems != view) {
previousViewOfSubItems.setBackgroundResource(R.drawable.black_border);
if (quantity.getText().toString().equals("xx") || quantity.getText().toString().equals("0")) {
viewForVisibility.setVisibility(View.GONE);
layoutForQuantity.setVisibility(View.GONE);
}
}
if (previousViewOfSubItems == view)
return;
previousViewOfSubItems = view;
previousViewOfSubItems.setBackgroundResource(R.drawable.red_border);
viewForVisibility = previousViewOfSubItems.findViewById(R.id.viewForVisibility);
viewForVisibility.setVisibility(View.VISIBLE);
layoutForQuantity = (LinearLayout) previousViewOfSubItems.findViewById(R.id.layoutForQuantity);
layoutForQuantity.setVisibility(View.VISIBLE);
quantity = (TextView) previousViewOfSubItems.findViewById(R.id.subTypeQuantity);
}
});
previousViewOfSubItems = view; seems to be causing the problem,
In Listviews with adapter you should avoid saving view instances, because views are reused by adapters so view can be same for two rows so rather than saving view instance's reference use ViewHolder Design pattern and use view tagging
You need to use ViewHolder Pattern and view tagging to properly identify every view in different position. ListView always recycle the view instead of re-inflating the view again and again.
You can refer to Android Training documentation on how to implement ViewHolder pattern.
ListViews recycle the views in the list. so as you scroll, top views are reused and content replaced using the methods.
Where you set background to Red etc, use Else statements to set it back to your default black.
its beacause it listview reuses view to display items, once the first view is scrolled out the the same view is reused to display the view at bottom of the listview. instead of comparing the view try compairing the position of the view clicked
I have explained about this abnormal behavior in my blog on recyclerview you refer that to solve this problem.
use pojo class to get the status and update the view accordingly
I have an ExpandableListView that holds certain objects. These objects store a property that determines the background of the view which they are held in. My question is, how do I make sure that these backgrounds are always correct when android recycles views? The issue that I am running into is that when I change this background determining property on a certain row and the xml resource changes on that child row, the next item created for the list uses that same view even though it may not be correct. Here is my code below of my custom ExpandableListViewAdapter that determines the xml resource for the view of the ExpandableListView child row.
#Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
ToDoItem item = mParent.get(groupPosition).getParentChildren().get(childPosition);
view = convertView;
if(view == null) {
// Check item priority and set background accordingly
if(item.getPriority() == 2) {
view = inflater.inflate(R.layout.list_item_medium, null);
} else if(item.getPriority() == 3) {
view = inflater.inflate(R.layout.list_item_high, null);
} else if(item.getPriority() == 1) {
view = inflater.inflate(R.layout.list_item, null);
}
}
TextView tv = null;
if (view != null) {
tv = (TextView) view.findViewById(R.id.tasktext);
tv.setText(item.getTask());
}
return view;
}
As you can see I am checking if the view is equal to null to make sure my app runs at an acceptable speed. How can I keep my optimization while maintaining the functionality of the backgrounds?
The issue that I am running into is that when I change this background determining property on a certain row and the background changes on that row, the next item created for the list uses that same background even though it may not be correct.
Presumably, that is because you have not implemented getViewTypeCount() and getItemViewType(), to teach the AdapterView what object pools to use for caching and recycling purposes. If you will have different row structures based on position, you need to properly implement those two methods.
Also, be very careful about modifying list row backgrounds so that you don't interfere with the touch feedback on taps or the selection highlight bar when used with a pointing device (D-pad, trackball, arrow keys, etc.). I would recommend you do just about anything else to distinguish the rows other than manipulating the background.
Since you apparently are using ExpandableListView, not ListView, there are up to four different methods that you need to override on your subclass of BaseExpandableListAdapter.
Two are getGroupTypeCount() and getChildTypeCount(). These return the number of distinct row types you are using at the group level and at the child level, respectively. By "distinct row types", I mean "situations where the rows differ enough that your concerns about recycling come into play". Based on the snippet of code in your answer, we can see that you have three distinct child row types, as you are inflating three different layouts based upon priority. Hence, you would have:
#Override
public int getChildTypeCount() {
return(3);
}
If you also have different row structures at the group level, you would need to override getGroupTypeCount() in a similar fashion.
Then, there are getGroupType() and getChildType(). These return a value, from 0 to getGroupTypeCount()-1 and getChildTypeCount()-1, indicating which recycling bucket should be used for a given groupPosition (or groupPosition and childPosition for getChildType()). If you override get...TypeCount(), you must also override the corresponding get...Type() method.
So, we know we need getChildType(). Given your existing code above, that implementation would look like:
#Override
public View getChildView(int groupPosition, int childPosition) {
ToDoItem item = mParent.get(groupPosition).getParentChildren().get(childPosition);
return(item.getPriority()-1);
}
Since you happen to have getPriority() running from 1 to 3, we can just subtract 1 from that to get a value in our desired 0 to 2 range. If you were basing the row layouts on something else (e.g., third character in the title of the to-do item), you'd need to use that logic in getChildView() instead.
And, if you override getGroupTypeCount(), you'd also override getGroupType().
i am creating an app which contains an expandable list view. the child views are created dynamically. and it run successfully. During debugging i found that the getChildview function runs 2 times.
i create dynamic layouts and put it into a list. when the getChildView runs 2 times the layouts added 2 times in to the list..
getChildView() is not an appropriate place to create children. It might be called pretty often. The rendering process needs to visit children twice, anyway.
It's not possible to judge where the appropriate place would be for adding children to your list, or even if your list approach is the right way to do it, without much more information.
If you regenerate the list in group click, then removing it can be a solution. For example, in the following code, the getChildView() always called twice because of myList.expandGroup(groupPosition).
public boolean onGroupClick(ExpandableListView parent, View v,
int groupPosition, long id) {
//get the group header
HeaderInfo headerInfo = medicationDate.get(groupPosition);
myList.expandGroup(groupPosition);
//set the current group to be selected so that it becomes visible
//myList.setSelectedGroup(groupPosition);
//display it or do something with it
Toast.makeText(getBaseContext(), "Child on Header " + headerInfo.getHeaderInfo()+"with childsize"+headerInfo.getChildInfo().size(),
Toast.LENGTH_SHORT).show();
return false;
}
I'm new to android development and maybe wrong, but as I see it getChildView() has 4th argument View convertView which is null first time view need to be rendered. Once created it is stored and reused again when needed. So if you create new views in getChildView() it is enough to have something like this
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
if (convertView != null) {
// View is already created here, update it if you like
return convertView;
}
// Else create your view(s) here and return the root of view container as usual
...
return convertView; // or whatever your root view is
}
The height of the listview should be match_parent instead of wrap_content.
one thing worked for me was.
#Override
public boolean hasStableIds() {
// To avoid refreshing return true and makesure Ids each position have same view.
return true;
//return false;
}
because I need to display something more than just a list in a Fragment.
So I choose Fragment rather than ListFragment, and my layout is something looks like
<linearlayout...>
<TextView...>...<TextView/>
<Button...>...<Button/>
<ListView android:id = "#"+id/mylist" ...></ListView>
</linearylayout>
And I implemnt "MyAdapter" extend BaseAdapter, which has getView like following
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if(position == 0)
{
return categroyView("Team leader");
}
else if (position == 2)
{
return categroyView("Team memebers");
}
else
{
LayoutInflater inflater = context.getLayoutInflater();
View v = inflater.inflate(R.layout.row_group, null, false);
return v;
}
}
protected View categroyView(String text)
{
TextView txtView = new TextView(context);
txtView.setText(text);
return txtView;
}
It turns out that I can receive onItemClick when its position is 0 or 2 (which as you can see I dynamically generate textView.
Meanwhile I can't receive onItemClick when its position is not 0 or 2 (which I return inflate view from XML)
I've seen some discussion about if customized row layout has some clickable item (like button), this situation will happen, but even my row layout has only one textView, it still failed to receive onItemClick.
p.s.
Also, I select Fragment rather than Activity for other other design issue.
I know I can alternatively add v.setOnClickListene in getView to help this issue, but then still the item won't highlight if I pressed on it.
What is in the position two view? If that thing might be able to take focus sit will do it instead of the list item if you don't want the inards to be clickabke then disable that it's click and it will the be passed to the item
Also are these long lists? You will run into trouble if you inflate a lot
I have a custom adapter extended from the SimpleCursorAdapter. Using this I'm binding a ListView which contains a checkbox and Two textboxes. On opening the activity, the list appears fine. But on clicking the checkboxes and entering some text in the textboxes and scrolling down, and then up again, the data disappears.
In fact any change disappears, even if they were already checked. I uncheck them, then scroll down and up, the go back to checked. So basically, they go back whatever state they were when retrieved.
Any ideas why? Thanks.
You need to have an arraylist of the states of each item in the list,, then load these states each time the list item view is loaded.Do this by overriding GetView() method in the adapter and add your saved state to the list based on the item position
Listview tends to recreate its views every time your list is scrolled up or down. You need to have some kind of model class that can save the state of your checkbox and textbox in memory in case some change is done(for that particular row) and later display it on the view.
As mentioned on other answers in this post u can use getview to programatically induce values that you have stored in your model classes to your views based on the list view position.
Something like this
#Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("getView " + position + " " + convertView);
ViewHolder holder = null;
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.item1, null);
holder = new ViewHolder();
holder.textView = (TextView)convertView.findViewById(R.id.text);
convertView.setTag(holder);
}
else {
holder = (ViewHolder)convertView.getTag();
}
// Pass on the value to your text view like this. You can do it similarly for a check box as well
holder.textView.setText(mData.get(position));
return convertView;
}
Android does not render all ListView entries at once, but only those visible on the screen. When "new" List-Rows come into view the
public View getView(int position, View convertView, ViewGroup parent)
method of your Adapter gets called and the view is recreated.
To fill in previousely saved values you probalby have to overwrite the getView method.