Pretty much I have a listview, where if you press one button, the other is meant to change to a dark grey image. The code works fine on an individual level, but for some reason the action is replicated every 4th list item. Here is the getItemView code! (By the way I have indeed tried making the imagebuttons local!)
ImageButton thumbsUpButton;
ImageButton thumbsDownButton;
#Override
public View getItemView(FactListParseObject factRow, View v,
ViewGroup parent) {
if (v == null) {
v = View.inflate(getContext(), R.layout.fact_list_row, null);
}
super.getItemView(factRow, v, parent);
setTexts(v, factRow);
thumbsUpButton = (ImageButton) v.findViewById(R.id.thumbsup);
thumbsDownButton = (ImageButton) v.findViewById(R.id.thumbsdown);
if (thumbsDownButton != null&& thumbsUpButton!=null) {
thumbsUpButton.setTag(false);
thumbsDownButton.setTag(false);
thumbsUpButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if((boolean)thumbsUpButton.getTag()==true){
thumbsUpButton
.setImageResource(R.drawable.thumbsup);
thumbsUpButton.setTag(false);
}
if ((boolean)thumbsDownButton.getTag() == false) {
// thumbsdown not currently disabled
thumbsDownButton
.setImageResource(R.drawable.thumbsdowndisabled);
thumbsDownButton.setTag(true);
} else {
// thumbsdown is disabled
thumbsDownButton.setImageResource(R.drawable.thumbsdown);
thumbsDownButton.setTag(false);
}
}
});
thumbsDownButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("called","howmany");
if((boolean)thumbsDownButton.getTag()==true){
thumbsDownButton
.setImageResource(R.drawable.thumbsdown);
thumbsDownButton.setTag(false);
}
if ((boolean)thumbsUpButton.getTag() == false) {
// thumbsdown not currently disabled
thumbsUpButton
.setImageResource(R.drawable.thumbsupdisabled);
thumbsUpButton.setTag(true);
} else {
// thumbsdown is disabled
thumbsUpButton.setImageResource(R.drawable.thumbsup);
thumbsUpButton.setTag(false);
}
}
});
}
return v;
}
Because the ListView recycles the view, therefore the fourth is the recycled one ...
So you have to "reset" the state (in your case the color of the button) for every item in your list (or in other words for each cell in your listview) ...
btw. the methods name is getView and not getItemView()
Here you can find an abstract base adapter class, that helps you to handle better the "lifecycle" of a listview cell:
https://github.com/sockeqwe/appkit/blob/master/adapter/src/main/java/com/hannesdorfmann/appkit/adapter/SimpleAdapter.java
The idea is that there are two key methods:
newView() Here you inflate the View from layout and setup the ViewHolder pattern.
bindView() Here you bind the view cell (may be a recycled or a new created one from newView() ) to your data object that should "fill" the cell with the desired data. So here is where you should set the color of the button ...
Related
I have the following code inside my adapter:
public View getView(int position, View convertView, ViewGroup parent)
{
if (convertView==null)
{
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.box_afisare, null);
}
final TextView titlu = (TextView) convertView.findViewById(R.id.titlu);
titlu.setText(Html.fromHtml(textt.get(position)));
titlu.setTextSize(TypedValue.COMPLEX_UNIT_SP,font);
final Integer pos = position;
titlu.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
if (main_contextt.selectie.contains(id_post.get(pos)))
{
Toast.makeText(mContext," REMOVE ",Toast.LENGTH_LONG).show();
titlu.setBackgroundColor(Color.parseColor("#0077CC"));
}
else
{
main_contextt.selectie.add(id_post.get(pos));
titlu.setBackgroundColor(Color.parseColor("#404040"));
}
}
});
return convertView;
}
I manage to colorate the selected line or lines. But when i scroll the listview and those selected lines are no longer in view range of the phone....the background color disapear.
It disapear only if that line/lines is out of view. I think the adapter is redrawing?
What to do to remain the color set on the line/lines even after i scroll the listview?
thanks
ListViews recycle their child views. So if you have 20 items in your ListView but only enough room on the screen to show 4 at a time, the ListView will only have 4 children which it will recycle to show all 20 items. When one view scrolls out of view, it is recycled for the next view coming into view. See this answer for a great explanation.
What you need to do is tie the color to some underlying data. Then you would use code like.
titlu.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
// set data.get(pos).color
}
});
titlu.titlu.setBackgroundColor(data.get(pos).color);
...something like that.
I want to create a ListView with a header view that can be expanded and retractet just as in a ExpandableListView (but just the header view, all other views just be normal list items).
I'm using an ExpandableListView with a BaseExpandableListAdapter and treat all the other items in the list as groups that have no children.
This is cumbersome and error-prone and has unwanted sideeffects.
I also tried using a normal ListView and a view for the header where I setVisivility(View.VISIBLE) to the expanded part of the view when the header view is clicked, but this doesn't seem to have any effect:
private View getHeaderView() {
final View v = layoutInflater.inflate(R.layout.headertest, null);
View v1 = v.findViewById(R.id.view1);
final View v2 = v.findViewById(R.id.view2);
v1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("head", "click");
v2.setVisibility(View.VISIBLE);
notifyDataSetChanged();
}
});
return v;
}
When v1 is clicked, the visibility of v2 is not changed, although the clickListener is called. I also tried .invalidate() on all kinds of views, to no avail.
Any ideas on how to solve this one or the other way?
You can do it with the expandable list view. First disable the indicators (open/close icons) on your ListView:
mExpList.setIndicator(null);
Next, your header (first group) will have to be a special type of group which has an image (an indicator) in its layout. Basically you want to have 2 types of group views:
#Override
public int getGroupTypeCount() {
return 2;
}
#Override
public int getGroupType(int groupPosition) {
if (groupPosition == headerPosition) {
return 1;
} else {
return 0;
}
}
You will also need to Override onGroupExpanded and onGroupCollapsed to change the image of your header (if a header was clicked of course).
Lastly you'll need to override the ListView's OnGroupClickListener:
mExpList.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
#Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
if (groupPosition == header) {
// Click was not handled
return false;
} else {
// Do some normal stuff with items
// Click was handled group won't be (or tried to be) expanded
return true;
}
}
});
Also a good idea would be to override adapters getChildCount to return 0 when it's not a header.
Hope this helps. Good luck!
I have a list view and in each list item i have for image views and three text view, three of these image view are to act like an image button i.e respond to on click events and so are two of the text view. I have tried using ItemOnClickListeneri mean like this
#Override
public void onItemClick(AdapterView<?> arg0, View convertView, int pos,
long arg3) {
// TODO Auto-generated method stub
bomb = (ImageView) convertView.findViewById(R.id.bomb);
Log.i("Item Clicked", "Item was clicked at pos" + position);
bomb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
// Perform action on click
//Run what ever task neccessary
}
});
}
But this has a problem it only responds on the second click. i know it has something to do with the parent and child focus but i haven't been able to get around that.
I also tried using the
static class View Holder except i got the implementation wrong it does not respond at all even after two clicks.
Also am using a custom adapter, i used to do it directly from the getView overide method but i found out the hard way that is isn't the best ways to implement what i want to do.
Please i need something that would work for me cause i tried a coupleof thing other than the above mentioned but they have failed.
Get View Codes
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
pos = position;
if(convertView == null)
{
convertView = inflater.inflate(R.layout.singlepost, parent, false);
holder = new ViewHolder();
holder.bomb = (ImageView) convertView.findViewById(R.id.bomb);
holder.bomb.setOnClickListener(bomb_listener);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
holder.bomb.setOnClickListener(bomb_listener);
}
return convertView;
}
private OnClickListener bomb_listener = new OnClickListener()
{
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i("HOMEADAPTER", "BOOMB WAS CLICKED AT POSITON" + pos);
holder.bomb.setImageResource(R.drawable.redheart);
}
};
static class ViewHolder {
TextView reporter;
TextView shell;
TextView sheller;
TextView likesnum;
TextView favsnum;
TextView comnum;
ImageView bomb;
ImageView star;
ImageView comment;
}
With this new getview implementation i still don't get the exact item i intend to click
Add the clicklistener code block inside getView() i.e where you create the view,
getView(...) {
if (view == null) {
viewHolder = new ViewHolder();
view = ...inflateView code...
View bomb = view.findViewById(R.id.bomb);
bomb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
// Perform action on click
//Run what ever task neccessary
}
});
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)view.getTag();
}
}
Views respond to events in a bottom-up fashion. Meaning events start with the child view and are passed up to the parent views. If a view cannot or does not respond to an event, it is passed up the chain. When you first click an image, the image view has no OnClickListener associated with it, and therefore cannot respond to the event. On that first click though, you are setting a listener to it. So the next time you click that image, it now has a listener and can respond to the event. This is why it is not responding as expected on the first click. As 66CLSjY suggested, you probably want to override getView (int position, View convertView, ViewGroup parent) in your list adapter to set the listener when the image is added to the list instead of when you click on it.
In response to your comment on 66's answer, keep in mind that ListViews reuse views as much as possible. So even if convertView is not null, you still need to either set a new OnClickListener to it or account for the reuse in some way or it will basically be like you clicked a different image.
After complaining about this issue and trying to get a work around it. Personally i think androids API of the list view is terrible and should be improved upon so that it is easy to use and implement. I think its a bad idea to call your listeners in the override get view method cause you can't always trust the android system to return to you the exact view in which you are requesting due to performance reasons. I am quoting for the developers of the list view. using the View Holder static class only helps to hold the views and access them faster but it doesn't assist in handling event listeners for specific items view within the list view item and honestly i haven't seen any reasonable solution on the internet or Google developer site.
Okay after two nights of racking my brains and endless testing i finally have a solution.
Using the setTagand the ViewHolder implementation but only differently, turn your ViewHolder static implementation into a View what i mean is this
static class ViewHolder extends View implements OnClickListener {
public ViewHolder(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
ImageView bomb;
public void assignList(){
if(bomb != null)
bomb.setOnClickListener(this);
}
public int position = -1;
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i("HOMEADAPTER", "OUR OWN STUFF");
}
}
Once you have done that in your activity where you implement your OnItemClickListener
all you need to do is get the tag you set like so
ViewHolder holder = (ViewHolder) view.getTag();
and thats all baby! you are 90% done the final 10% thing you need to do is what ever you wish to do with your gotten view.
I have a listview and it's scrolable. I have few listview items and it's pulled from database. My problem is, each listview items have a button and i've setOnclickListener to the button inside getView. Now let's say there's 5 items and i tap on button for item number 1, the position is 0 and when i scroll to the end of the list, i can see the button for item number 5 is clicked. When i scroll till middle of the list, sometimes the button randomly clicked and i can see from my logcat, the particullar button's position is 0. How come?
Here is my getView' code
public View getView(final int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.newsfeed_layout, parent, false);
}
final Button btnLike = (Button) v.findViewById(R.id.buttonLike);
btnLike.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String buttonText = btnLike.getText().toString();
if(buttonText.equals("LIKE")){
Toast.makeText(postedItems.this, "Liked This item" + position, Toast.LENGTH_SHORT).show();
btnLike.setPadding(4,0,12,0);
btnLike.setText("UNLIKE");
btnLike.setTextColor(Color.parseColor("#ffffff"));
btnLike.setBackgroundResource(R.drawable.likedredbutton);
}else if(buttonText.equals("UNLIKE")){
Toast.makeText(postedItems.this, "Unlike This Item" + position, Toast.LENGTH_SHORT).show();
btnLike.setPadding(4,0,20,0);
btnLike.setText("LIKE");
btnLike.setTextColor(Color.parseColor("#737373"));
btnLike.setBackgroundResource(R.drawable.cornerstyledbutton);
}
}
});
return v;
}
First, to understand the problem: this occurs because ListView reuses views as you scroll, to improve performance. That's the explanation for the convertView parameter.
Because of this, you need to make sure that whatever state you want to store for each item is stored in the adapter itself or wherever you store its backing data -- and that when you implement getView(), the UI is fully updated to reflect this data (since it will have whatever properties you set on it the last time it was used).
In this case, you need to store whether each item is "liked" or "unliked". And then, always set the properties of btnLike to reflect this before returning.
As an example, your code would have to be more or less like this:
public View getView(final int position, View convertView, ViewGroup parent)
{
View v = convertView;
if (v == null) {
LayoutInflater vi = ...;
}
final Button btnLike = (Button) v.findViewById(R.id.buttonLike);
if (isLiked(position))
setButtonAsUnlike(btnLike);
else
setButtonAsLike(btnLike);
btnLike.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if (!isLiked(position))
{
// Not liked before, so like now.
setLiked(position, true); // store this value.
setButtonAsUnlike(btnLike); // the button is now "unlike"
}
else
{
// Liked before, unliked now.
setLiked(position, false); // store this value.
setButtonAsLike(btnLike); // the button is now "like"
}
}
});
return v;
}
where isLiked(position) queries the data, setLiked(position, boolean) updates it, and setButtonAsLike(Button) and setButtonAsUnlike(Button) update the visual properties of the Button as you are doing now.
An activity has a Button and a ListView.
Initially, only the Button is visible. When the button is pressed, the ListView is displayed.
When displayed, is it possible for me to show one particular item as selected/focussed?
A use case could be that suppose it is a list of language settings and when the list opens, the currently selected language must be shown as highlighted.
If I know the index of the item, how to set it as focused on display?
I post my solution, because google still doesn't know the answer.
getListView().setItemChecked(selectedGroupIndex, true);
In short, ListView::setSelection(int position) is what you need. However, depending on whether the device is in touch mode or not, it may or may not have visual effect (background highlighting). For more details, refer to Android ListView Selection Problem
If you use an Adapter for your ListView add this code to your adapter:
public class MyAdapter extends ArrayAdapter<MyClass> {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflator = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rowView = inflator.inflate(R.layout.my_adapter, null);
} else {
rowView = (View) convertView;
}
//...
// set selected item
LinearLayout ActiveItem = (LinearLayout) rowView;
if (position == selectedItem){
ActiveItem.setBackgroundResource(R.drawable.background_dark_blue);
// for focus on it
int top = (ActiveItem == null) ? 0 : ActiveItem.getTop();
((ListView) parent).setSelectionFromTop(position, top);
}
else{
ActiveItem.setBackgroundResource(R.drawable.border02);
}
}
private int selectedItem;
public void setSelectedItem(int position) {
selectedItem = position;
}
}
In your Activity:
myAdapter.setSelectedItem(1);
I am using an Adapter and didn't want to set custom background colors, but use the android:state_selected in drawable xml. SetSelection didn't work for me, but maybe that's also since I needed SetNotifyDataChanged which shows that the Selected State is not persistent.
I also found that the Selected state for an item in a ListView is not persistent, since SetNotifyDataChanged results in updating the ListView layout which clears them all. Setting the item to Selected in the Adapter's GetView is too soon too.
Eventually I set the Selected state for the view of the selected item after the layout of the listview has been changed, which is when LayoutChange event is being triggered (in Java it's probably attaching a to OnLayoutChangeListener of the ListView).
To make it really easy I store the view of the selected item as Adapter's SelectedItemView.
In the ListView's LayoutChange eventhandler I just set the adapter's SelectedItemView.Selected to true.
Here's the code from my Activity where I set the Adapter for the ListView and also subscribe to LayoutChange (or in Java attach an OnLayoutChangeListener)
ringTonesListView.Adapter = ringTonesListAdapter;
ringTonesListView.LayoutChange += (s, layoutChangeArgs) => {
//At this stage the layout has been updated and the Selected can be set to true for the view of the selected item. This will result in android:state_selected logic to be applied as desired and styling can be completely done per layout in Resources.
ringTonesListAdapter.SelectedItemView.Selected = true;
};
Here's my code for the Adapter:
public class RingTonesListAdapter : BaseAdapter<RingToneItem>
{
List<RingTone> Items { get; set; }
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
// re-use an existing view, if one is available
// otherwise create a new one
if (view == null)
{
view = Context.LayoutInflater.Inflate(Resource.Layout.AlertSoundItem, parent, false);
view.Click += SelectRingTone;
}
RingTone ringTone = this[position];
if (ringTone.Selected)
{
//==> Important
//Store this view since it's the view for the Selected Item
SelectedItemView = view;
//Setting view.Selected to true here doesn't help either, since Selected will be cleared after.
}
return view;
}
private void SelectRingTone(object sender, EventArgs args)
{
View view = (View)sender;
string title = view.FindViewById<TextView>(Resource.Id.ringToneTitle).Text;
RingToneItem ringToneItem = Items.First(rt => rt.Title == title);
if (!ringToneItem.Selected)
{
//The RingTone was not selected and is selected now
//Deselect Old and Select new
foreach (RingToneItem oldItem in Items.Where(rt => rt.Selected))
{
oldItem.Selected = false;
}
// Select New RingTone
ringToneItem.Selected = true;
//Update the ListView.
//This will result in removal of Selected state for all Items when the ListView updates it's layout
NotifyDataSetChanged();
}
//Now play the test sound
NotifierService.TestSound(Context, ringToneItem);
}
public View SelectedItemView { get; set; }
}