(Ok, I've researched a lot and found a few answers but nothing really worked for me.. so I've finally opened an account to post here. Thanks to the community for all your answers earlier!)
Context
I am creating a category selector (ListView with a custom adapter)- each category can have multiple sub-categories and each sub-category can have multiple sub-categories. For the selector, I'm using a ListView with a custom List item - TextView (to display category) and an ImageView (the forward arrow) to indicate if the category has sub-categories - see this image:
I want the user to do the following actions
(1) Click on the category name to SELECT that category
(2) Click on the forward arrow to SEE all sub-categories (it will re-populate the ListView with all sub-categories)
Question Starts here
I want the user to see the following highlights/focus on the user does the about two actions (1) and (2)
Highlight on action (1)
See this image:
Highlight the row when the user clicks on the category name/row.
This happens automatically because I've setup
listView.setOnItemClickListener()
Highlight on action (2)
See this image:
I want the user to see the highlight only on the forward-arrow and not the row. I'm able to register the CLICK on the image with
imageView.setOnClickListener()
in my custom adapter but I'm unable to get the highlight on the forward-arrow
How do I get this highlight on the ImageView?
Code
Activity's onCreate() {
LayoutInflater inflater = getActivity().getLayoutInflater();
View categorySelectView = inflater.inflate(R.layout.cat_select, null);
LinearLayout categorySelectLayout = (LinearLayout) categorySelectView;
ListView categoryListView = (ListView) categorySelectLayout.findViewById(R.id.list_cat_select);
CategoryArrayAdapter categoryArrayAdapter = new CategoryArrayAdapter(this.getActivity(), CategoryAdapter.getAllCategories());
categoryListView.setAdapter(categoryArrayAdapter);
categoryListView.setOnItemClickListener(categorySelectClickHandler);
}
CategoryARrayAdapter's getView(int position, View covertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.cat_item, parent, false);
TextView textView = (TextView) rowView.findViewById(R.id.cat_name);
Category catToDisplay = categories.get(position);
textView.setText(catToDisplay.getName());
if(catToDisplay.isParent()) {
ImageView imageView = (ImageView) rowView.findViewById(R.id.cstrow_cat_expand);
imageView.setImageResource(R.drawable.cat_expand);
imageView.setOnClickListener(categoryExpandClickHandler);
imageView.setFocusable(false); // to make sure the highlight on row shows
imageView.setFocusableInTouchMode(false); // to make sure the highlight on row shows
}
return rowView;
}
So here is what I've done. This should be scalable too.
The CategoryArrayAdapter has the following getView()
public View getView(int position, View covertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.cat_select, parent, false);
TextView textView = (TextView) rowView.findViewById(R.id.cat_name);
Category catToDisplay = categories.get(position);
textView.setText(catToDisplay.getName());
if(catToDisplay.isParent()) {
ImageView imageView = (ImageView) rowView.findViewById(R.id.cstrow_cat_expand);
imageView.setImageResource(R.drawable.cat_expand);
imageView.setOnClickListener(categoryExpandClickHandler);
imageView.setOnTouchListener(categoryExpandTouchHandler);
imageView.setFocusable(false); // to make sure the highlight on the item shows
imageView.setFocusableInTouchMode(false); // to make sure the highlight on the item shows
imageView.setTag(catToDisplay);
}
return rowView;
}
Two things that made this work
imageView.setOnTouchListener() allows me to set the highlight (thanks Piyush Gupta for pointing out the setBackgroundColor() method
imageView.setOnClickListener() allows me to perform the action when the user clicks on arrow
/**
* This method listens to the TOUCH CLICK on the IMAGEVIEW to give it button like feeling
*/
private View.OnTouchListener categoryExpandTouchHandler = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN: {
v.setBackgroundColor(getContext().getResources().getColor(R.color.highlight_cat_expand));
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
v.setBackgroundColor(getContext().getResources().getColor(android.R.color.transparent));
break;
}
}
return false;
}
};
/**
* This method listens to EXPAND IMAGEVIEW CLICK in the LISTVIEW of categories
*/
private View.OnClickListener categoryExpandClickHandler = new View.OnClickListener() {
#Override
public void onClick(View view) {
// Expanding this category
... expand code here ...
}
};
To get the HIGHLIGHT and CLICK to work together, return false in onTouchListener(). This will pass the touch event to the onClickListener(). I got this solution from https://stackoverflow.com/a/10376887/3393044 comment by the thin
Related
I have a custom layout for a Listview, each row item contains a button that when clicked it shows a small imageview in the item, however the actions i perform in one item, repeats for another item down the list, for example, if i click the button in item 1, the imageview will show up in item 1 and item 10, if i click the button in item 2, the Imageview will show up on item 2 and item 11 and while i scroll it will keep repeating in different items, heres the code for my custom adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
mparent = parent;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.places_item, parent, false);
holder = new ViewHolder();
holder.placeimage = (CircularImageView) convertView.findViewById(R.id.locationimage_pilayout);
holder.addbtn = (TextView) convertView.findViewById(R.id.addbtn_pilayout);
holder.delbtn = (TextView) convertView.findViewById(R.id.delbtn_pilayout);
holder.oribtn = (TextView) convertView.findViewById(R.id.oribtn_pilayout);
holder.placename = (TextView) convertView.findViewById(R.id.locationname_pilayout);
holder.selected = (ImageView) convertView.findViewById(R.id.selected_pilayout);
holder.origin = (ImageView) convertView.findViewById(R.id.origin_pilayout);
holder.swipeLayout = (SwipeRevealLayout) convertView.findViewById(R.id.swipe_pilayout);
holder.mainLayout = (LinearLayout) convertView.findViewById(R.id.main_pilayout);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final Place item = getItem(position);
/*
my code assigning the button click listener and assigning the views
*/
return convertView;
}
Am i missing something? im sure this could be a simple fix, but i havent found it yet. any help would be kindly appreciated.
In ListView the individual views for rows, get reused. For example, let's say the window can show up to 10 rows inside the ListView. Now when you scroll down, the 1st view gets out of the window from the top, and a new 11th view comes into the window from the bottom. In order to save memory and CPU power, Android uses the same 1st view for the 11th view. That's the purpose of convertView and the if (convertView == null) {} else {} code.
So the reason why the image is being shown in the 1st item and also in the 11th item, is that they are exactly one view object. To tackle this issue, in the getView() method, you need to reset every attribute of every view and don't rely on the defaults.
So adding a line like the one below, will get rid of the mentioned problem:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// all your code ...
holder.placeImage.setImageResource(0); //<-- This clears any previously set image.
return convertView;
}
Im trying to implement instagram like listview , in each row there is like button and also comment box. the question is how can i achieve similar function in listview which means each row is specific and its not repeating for other rows , what i tries is , when a user clicks on button after some item , all other items are clicked! means the clicked item is repeating and its obvious i don't want this! how can i achieve instagram like row
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
viewHolder mHolder;
if(convertView==null){
mHolder = new viewHolder();
convertView = mInflator.inflate(R.layout.row_all_artist, parent, false);
mHolder.mArtistName = (TextView) convertView.findViewById(R.id.artist_name);
mHolder.mFollowers = (TextView) convertView.findViewById(R.id.artist_followers);
mHolder.mFollow = (Button) convertView.findViewById(R.id.artist_follow);
AppLogic.ChangeTextViewFont(mContext, mHolder.mArtistName);
convertView.setTag(mHolder);
} else{
mHolder = (viewHolder) convertView.getTag();
}
mHolder.mArtistName.setText(mItems.get(position).getArtistFullName());
mHolder.mFollowers.setText(mItems.get(position).getArtistFollowers());
mHolder.mFollow.setTag(R.id.artist_follow,mItems.get(position).getArtistID());
mHolder.mFollow.setOnClickListener((OnClickListener) mContext);
return convertView;
}
EDIT
my question is not about touching items or setting unlike listener or like this! the problem is when i click the button i change its background color , but this color is repeating on every 10 item on the list! there is a related question to me here : ListView repeated CheckBox behavior every 10th item erratically
public void onClick(final View v) {
if (!AppLogic.isLoggined(mShared)) {
AppLogic.ToastMaker(getApplicationContext(),
getResources().getString(R.string.msg_loginError));
startActivity(new Intent(FirstTimePage.this, Register.class)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
} else {
AppLogic.doFollow(FirstTimePage.this, v, mShared);
}
}
Prevent getting focus by the button.
Add this to the top element in your row_all_artist.xml custom view
android:descendantFocusability="blocksDescendants"
Create a new OnClickListener for every row, or check the View given in the argument at your current listener.
You are setting your mContext as the OnClickListener
mHolder.mFollow.setOnClickListener((OnClickListener) mContext);
You should just implement the ListView method setOnItemClickListener and then check for the position of the item that was clicked. Tags are also very helpful here.
Example: Adding an onclicklistener to listview (android)
After looking for some answers here, I find myself in a disturbing situation where my Listview is really getting on my nerve.
Here are the questions I looked for :
Maintain ListView Item State
How to save state of CheckBox while scrolling in ListView?
I'm using a custom adapter with a custom row as below.
My Listview is simple as it is displaying a custom row made of three elements :
1) an ImageView displaying contact picture cropped in a circle ;
2) a TextViewdisplaying the contact full name as plain text ;
3) and finally an ImageView that holds the purpouse of a CheckBox.
Please focus on the last element. The ImageView CheckBox-like will have its src changed upon click.
When the user click, the ImageView will switch between a check sign and an unchecked sign according to it's previous status. Possible status are : {checked | unchecked}
So far so good.
But as soon as I scroll the ListView, any aforementioned change will disappear as Android recycle unused view.
Here comes the so-called ViewHolder pattern. Unfortunately, this pattern is failling me on two issues :
First, when scrolling, my organized-in-an-alphabetical-order listview gets disorganized.
e.g. somehow, whitout any reason, the first displayed contact name gets displayed again later on the ListView as I scrolled. That can happen with any row ! So it would seem unused view are being wrongly re-used.
Second, and in accordance to the first issue, the checked status do seem to stay, but not always and if it does stay, it may very well stay on the wrong row ... and that can happen randomly, of course. Therefore ViewHoder is not a viable solution.
Before discouvering the ViewHolder pattern, I have been using a HashMap to store the item position upon click as followed :
ContactsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public final void onItemClick(final AdapterView<?> adapterView, final View view,
final int position, final long id) {
final ImageView check = (ImageView) view.findViewById(R.id.checkImage);
final TextView name = (TextView) view.findViewById(R.id.contactName);
final Boolean isChecked = Boolean.valueOf(checkedContactsList.isChecked(position));
if (isChecked != null && isChecked.booleanValue() == true) {
check.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.unchecked_sign));
checkedContactsList.(position);
} else {
check.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.checked_sign));
checkedContactsList.add(position, true);
}
}
});
I tried adding a different value instead of position.
I tried with ContactsListView.getPositionForView(view)
And I also tried with the View's ID, but still it doesn't work.
I wish I could use ContactsListView.getSelectedItemPosition() but it returns -1 as there is no selection event because I'm handling a touch/click event.
And this is how my Custom Adapter looks like :
public final View getView(final int position,
final View convertView, final ViewGroup parent) {
final LayoutInflater inflater = (LayoutInflater) this.context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View contactRowView = inflater.inflate(R.layout.contact_row, parent, false);
final ImageView contactPic = (ImageView) contactRowView.findViewById(R.id.contactPic);
final TextView contactName = (TextView) contactRowView.findViewById(R.id.contactName);
final ImageView checkImage = (ImageView) contactRowView.findViewById(R.id.checkImage);
// the list is the same as above and therefore contains the exact same entries
if (this.checkedContactsList.isChecked(position))
checkImage.setImageDrawable(this.context.getResources().getDrawable(R.drawable.checked_sign));
contactPic.setImageBitmap(cropePictureInCircle(this.contacts.get(position).getPicture()));
contactName.setText(this.contacts.get(position).getName());
return contactRowView;
}
Is there a good way to keep the checked row checked and the unchecked row unchecked in the given alphabetical order ?
Thanks !
For the list position change I know the solution but for the second problem I am still searching for a solution, anyway first make a viewHolder class;
public class ViewHolder{
//put all of your textviews and image views and
//all views here like this
TextView contactName;
ImageView checkImage;
ImageView contactImage;
}
Then edit your adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
final View contactRowView = convertView;
ViewHolder holder;
if (contactRowView == null) {
final LayoutInflater inflater = (LayoutInflater)
this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE
);
contactRowView =
inflater.inflate(R.layout.contact_row, parent,
false);
holder = new ViewHolder():
holder.contactPic = (ImageView)
contactRowView.findViewById(R.id.contactPic);
holder.contactName = (TextView)
contactRowView.findViewById(R.id.contactName);
holder.checkImage = (ImageView)
contactRowView.findViewById(R.id.checkImage);
contactRowView.setTag(holder);
} else {
holder = contactRowView.getTag();
// the list is the same as above and therefore contains the exact same entries
if (this.checkedContactsList.isChecked(position))
holder.checkImage.setImageDrawable(this.context.get.
Resources().getDrawable(R.drawable.checked_sign));
holder.contactPic.setImageBitmap(cropePictureInCircle(this.contacts.get(position).getPicture()));
holder.contactName.setText(this.contacts.get(position).getName());
return contactRowView;
}
}
Hope this helps and sorry because writing code from my phone is totally awkward.
I have a list with each row having a radio button. I'm using the folowing code to toggle between them(TEMP is a static variable used to keep track of the element which has been selected, so that when listview refreshes the view I'm able to select it again) :
public void onClickRadioButton(View view) {
final int position = listView.getPositionForView(view);
View rowElement = ((View) view.getParent());
// uncheck previous checked button.
if (listRadioButton != null)
listRadioButton.setChecked(false);
// assign to the variable the new one
listRadioButton = (RadioButton) view;
// find if the new one is checked or not, and set "listIndex"
if (listRadioButton.isChecked()) {
listIndex = ((ListView) rowElement.getParent())
.getPositionForView(rowElement);
TEMP = listIndex;
} else {
listRadioButton = null;
listIndex = -1;
TEMP = listIndex;
}
System.out.println("list index : " + listIndex);
}
enter code here
This is the getView method of adapter class:
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.manage_parameter_list_element,
parent, false);
TextView textView = (TextView) rowView
.findViewById(R.id.parameter_textView);
textView.setText("something");
TextView textView2 = (TextView) rowView
.findViewById(R.id.parameter_range_textView);
textView2.setText("something more");
if(position == SelectParameterActivity.TEMP)
// SelectParameterActivity is the class whose code I've written above
{
((RadioButton) rowView.findViewById(R.id.parameter_radioButton))
.performClick();
}
return rowView;
}
Under normal conditions the switch between radio buttons is fine
Now the problem is, consider this scenario:
I select option1....move down(so that option1 is not on screen anymore)....move up(option1 is visible again)...select option2(or any other apart from 1st)
Now the 1st option does'nt get deselected..
To deselect option1 I have to click on it twice.
FYI I've tried the performClick() method which does not work due to IllegalSateException.
Every time getView() is called you inflate a new View. This method gets called a lot! You cannot rely on the state of your radio button being stored/saved in the view. When the user clicks on a radio button, you need to save this information as part of your data, not in the view. Then, in getView() you need to set the state of the radio button based on the information that you have saved in the data.
I use even and odd rows to set backgrond to my listview rows. In my efficientAdapter I set the row background as follows:
public View getView(int position, View convertView, ViewGroup parent) {
vi = convertView;
if (convertView == null) {
vi = inflater.inflate(R.layout.ecran_multiple_row, null);
holder = new ViewHolder();
holder.txIndex = (TextView) vi.findViewById(R.id.txIndex);
holder.txSTitle = (TextView) vi.findViewById(R.id.txSTitle);
holder.btOnOFF = (ImageView) vi.findViewById(R.id.btOnOFF);
vi.setTag(holder);
} else
holder = (ViewHolder) vi.getTag();
/*
* CHANGE ROW COLOR 0 WHITE 1 GRAY
*/
if ( position % 2 == 0) //0 even 1 odd..
vi.setBackgroundResource(R.drawable.listview_selector_odd);
else
vi.setBackgroundResource(R.drawable.listview_selector_even);
/*
* ONE ITEM IN ARRAY
*/
if (data.toArray().length==1){
holder.btOnOFF.setBackgroundResource(R.drawable.air_radio_button_rouge);
}else {
holder.btOnOFF.setBackgroundResource(R.drawable.air_deezer_check);
}
return vi;
}
and in my MainActivity.Class. I select an item using on itemclicklistener() as shown below:
**lvRMultiple.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
imgview = (ImageView) view.findViewById(R.id.btOnOFF);
//And change its background here
imgview.setBackgroundResource(R.drawable.air_radio_button_rouge);
}
});**
When i clicked on an item btnOff image change successfully but when i scroll down it change to default background. Secondly when i click on one item after the other both becomes the new image but i want only the row clicked by the user to change to new image and the previous image are set to default.
All row view of a ListView created by the getView() method of BaseAdpter class. When ever we scroll the ListView all, new viable row create by getView() using recycle. So getView() called again and again when new row is viable on scroll.
There are two solution of your question:-\
You can save the status of ListView
// Save ListView state
Parcelable state = listView.onSaveInstanceState();
// Set new items
listView.setAdapter(adapter);
// Restore previous state (including selected item index and scroll position)
listView.onRestoreInstanceState(state)
And other solution is create RowView at runtime and add it on a Parent Layout by using addView() method.
LayoutInflater inflater = LayoutInflater.from(context);
// You should use the LinerLayout instead of the listview, and parent Layout should be inside of the ScrollView
parentView = (LinerLayout)this.findViewById(R.id.parentView);
for(int i = 0; i<=numberOfRow;i++){
LinearLayout rowView = (LinerLayout)inflater.inflate(R.layout.rowView);
ImageView rowImageView = (ImageView)rowView.findViewById(R.id.rowImage);
rowImageView.setOnClickListener(new View.onClickListListener(){
#Override
public void onClick(){
rowImageView.setImageBitmap(onClickBitmapImage);
}
});
parentView.addView(rowView);
}
Please check this answer Maintain/Save/Restore scroll position when returning to a ListView
More Reference
http://developer.android.com/reference/android/widget/Adapter.html#getView(int,android.view.View, android.view.ViewGroup)
The item changes back to the default background because the view gets recycled. This is the same problems of checkboxes losing their checked state
Check out this answer too see how to handle it:
CheckBox gets unchecked on scroll in a custom listview
As for your second problem, I believe it's already answered here:
highlighting the selected item in the listview in android
Hope it helps