I have search on StackOverflow and other websites but no one can answer to my question.
I have a gridView with items. I have a button to add item to this gridView.Each element on the GridView is a relativeLayout with an Imageview and an EditText.
When i add item to the gridView using the button, I want to get my relativeLayout and request Focus on the editText to set a name on it.
Imagine i have 3 elements in my gridView.
I add element to my ArrayList and call adapter.notifiyDataSetChanged().
The new element is displayed on the grid but when i use getChildCount(), the gridView still has 3 children.
It cause problem because i want to request focus on the last added EditText.
How can i update my gridView object ?
Fragment :
//Get gridView
final GridView gridCat = (GridView) v.findViewById(R.id.gridCategory);
adapter = new GridCategoryAdapter(getActivity(), subcatList);
gridCat.setAdapter(adapter);
gridCat.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
SubCategory subcat = subcatList.get(position);
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.fragment_middle, SubCategoryFragment.newInstance(subcat.getProducts(), subcat.getName()));
transaction.addToBackStack(null);
transaction.commit();
}
});
Button catAddButton = (Button) v.findViewById(R.id.catAddButton);
catAddButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "old size gridview : " + gridCat.getChildCount());
subcatList.add(new SubCategory());
Log.d(TAG, "new size list : " + subcatList.size());
adapter.notifyDataSetChanged();
Log.d(TAG, "new size gridview : " + gridCat.getChildCount());
//HERE : childCount is the same !
RelativeLayout rl = (RelativeLayout) gridCat.getChildAt(gridCat.getChildCount()-1);
rl.findViewById(R.id.subcatName).setFocusable(true);
rl.findViewById(R.id.subcatName).setLongClickable(true);
rl.findViewById(R.id.subcatName).requestFocus();
}
});
My Adapter :
public class GridCategoryAdapter extends BaseAdapter {
private static final String TAG = "com.zester.manager.ListViewSizeAndPriceAdapter";
private LayoutInflater mInflater;
private final Context context;
private ArrayList<SubCategory> listSubCat;
private ViewHolder holder;
public GridCategoryAdapter(Context context, ArrayList<SubCategory> values) {
super();
this.context = context;
listSubCat = values;
mInflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
return listSubCat.size();
}
#Override
public Object getItem(int position) {
return listSubCat.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.subcat_view, null);
holder = new ViewHolder();
holder.SubCatName = (EditText) convertView.findViewById(R.id.subcatName);
holder.imageSubCat = (ImageView) convertView.findViewById(R.id.imageSubCatView);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
SubCategory subCat = (SubCategory) getItem(position);
if (subCat != null) {
holder.SubCatName.setText(subCat.getName());
holder.imageSubCat.setImageDrawable(context.getResources().getDrawable(R.drawable.subcat_default));
}
return convertView;
}
static class ViewHolder {
public EditText SubCatName;
public ImageView imageSubCat;
}
}
XML for each item on the gridview :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="100dp">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/imageSubCatView"
android:src="#drawable/subcat_default"
android:layout_centerHorizontal="true"/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/subcatName"
android:gravity="center_horizontal"
android:hint="Ex : Bières"
android:layout_marginTop="20dp"
android:layout_below="#+id/imageSubCatView"
android:background="#android:color/transparent"
android:singleLine="true"
android:focusable="false"
android:longClickable="false"/>
Thx a lot !
When i tried to get my RelativeLayout, null is return : RelativeLayout rl = (RelativeLayout) gridCat.getChildAt(gridCat.getCount()-1);
I think your answer is this:
when you add
subcatList.add(new SubCategory());
adapter.notifyDataSetChanged();
to your code it is not guaranteed that new view for them has been created, because it is possible that your gridView has 100 children and you are just looking at children from 7 to 20 , so new child at index 100 has not yet inflated because the getView is called upon request in order to save memory so when there is no need to show the 100th child, why it must be called? so relativelayout for that child is null because it has not inflated.
in catAddButton listener you must not touch any gridview item, because when the button click happens it first runs your listener then scrolls to the end of gridView so still you have problem, what sholud you do?
in class of SubCategory() put variable that indicates it has not shown for the first time. in getView of gridview each time you want to inflate new items look at that variable (in the list of your SubCategory at item list.get(position)) and for example if it is boolean toggle it to false so that means the new object is going to be seen by user. So in this way each time you are going to inflate the view you know that if it is the first time or not, if it is first time your boolean is true else it has already been false. if it is not first time remove focus else put focus by calling reqesFocuse.
Related
I have implemented a list View with CheckedTextView . When I am selecting a particular row and clicking on checkbox then checkbox becomes invisible. Also sometimes any other row get selected.I want to select multiple items.
please help...
List_row Layout is..
<CheckedTextView
android:id="#+id/service_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="20sp"
android:layout_marginBottom="10dp"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:textColor="#000000"
android:checked= "false"
>
CustomListView is:
import java.util.ArrayList;
public class CustomListView extends ArrayAdapter {
//to reference the Activity
private final Activity context;
String value;
//to store the list items
private final String[] nameArray;
CheckedTextView nameTextField;
public CustomListView(Activity context, ArrayList nameArra) {
super(context, R.layout.row_list_view, nameArrayParam);
this.context = context;
this.nameArray = nameArrayParam;
}
public View getView(int position, View view, ViewGroup parent)
{
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.row_list_view, null, true);
//this code gets references to objects in the listview_row.xml file
nameTextField = (CheckedTextView)
rowView.findViewById(R.id.service_name);
//this code sets the values of the objects to values from the arrays
nameTextField.setText(nameArray[position]);
// perform on Click Event Listener on CheckedTextView
nameTextField.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.v("customList",";clicked row is " +
nameTextField.getText().toString());
if (nameTextField.isChecked()) {
// set check mark drawable and set checked property to false
value = "un-Checked";
nameTextField.setCheckMarkDrawable(R.color.colorAccent);
nameTextField.setChecked(false);
}
else {
// set check mark drawable and set checked property to true
value = "Checked";
nameTextField.setChecked(true);
}
Toast.makeText(context, value, Toast.LENGTH_SHORT).show();
}
});
return rowView;
}
In my MainActivity.java, I have used listView setOnItemClickListener
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
selected_Item = (String) arg0.getItemAtPosition(arg2);
Toast.makeText(AskForService.this, "Clicked item is" + selected_Item, Toast.LENGTH_LONG).show();
}
});
After implementing these code I can see -
ListView with checked TextView
ListView Can Scroll showing all the list items
on clicking a particular row..listview click listener is called and showing the Toast but checkbox becomes white(invisible) and sometimes another row get selected...Please help
Create a model class which contains a flag "isChecked" and set the data according to the model inside adapter
This link might be helpful : https://stackoverflow.com/a/40285759/8770539
Use an arraylist to store checked item's position.
call notifyDataSetChanged() for onclick()
Use this,
if(list.contains(position)) {
nameTextField.setChecked(true);
}
else {
nameTextField.setChecked(false);
}
nameTextField.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(list.contains(position))
{
list.remove(position);
}
else
{
list.add(position);
}
notifyDataSetChanged();
}
});
I created a custom listview layout with images which are loaded from web like this:
http://i.stack.imgur.com/l8ZOc.png
It works fine when scrolling down. However, when you scroll down, the previous items go out of screen then destroyed. When you try to scroll up again, it gets loaded again (from cache, faster but not instant) which causes a delay and it is not fluent as it should be.
1.Is there an example of how to do this properly?
2.Is there a way to prevent listview items being destroyed when they are out of screen?
3.If so, will it cause problems to keep too many items?
Bellow is my code:
MenuAdapter:
public class MenuAdapter extends BaseAdapter{
Context context;
List<MyMenuItem> menuItems;
MenuAdapter(Context context, List<MyMenuItem> menuItems) {
this.context = context;
this.menuItems = menuItems;
}
#Override
public int getCount() {
return menuItems.size();
}
#Override
public Object getItem(int position) {
return menuItems.get(position);
}
#Override
public long getItemId(int position) {
return menuItems.indexOf(getItem(position));
}
private class ViewHolder {
ImageView ivMenu;
TextView tvMenuHeader;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.menu_item, null);
holder = new ViewHolder();
holder.tvMenuHeader = (TextView) convertView.findViewById(R.id.tvMenuHeader);
holder.ivMenu = (ImageView) convertView.findViewById(R.id.ivMenuItem);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
MyMenuItem row_pos = menuItems.get(position);
Picasso.with(context)
.load(row_pos.getItem_image_url())
.into(holder.ivMenu);
holder.tvMenuHeader.setText(row_pos.getItem_header());
Log.e("Test", "headers:" + row_pos.getItem_header());
return convertView;
}
}
MyMenuItem:
public class MyMenuItem {
private String item_header;
private String item_image_url;
public MyMenuItem(String item_header, String item_image_url){
this.item_header=item_header;
this.item_image_url=item_image_url;
}
public String getItem_header(){
return item_header;
}
public void setItem_header(String item_header){
this.item_header=item_header;
}
public String getItem_image_url(){
return item_image_url;
}
public void setItem_image_url(String item_image_url){
this.item_image_url=item_image_url;
}
}
MainActivity:
public class MyActivity extends Activity implements AdapterView.OnItemClickListener {
List<MyMenuItem> menuItems;
ListView myListView;
JSONArray jsonArray;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
Bundle extras = getIntent().getExtras();
if(extras!=null){
try{
jsonArray = new JSONArray(extras.getString("Data"));
}catch (Exception e){
e.printStackTrace();
}
}
menuItems = new ArrayList<MyMenuItem>();
for (int i = 0; i < jsonArray.length(); i++) {
try {
MyMenuItem item = new MyMenuItem(jsonArray.getJSONObject(i).getString("title"), jsonArray.getJSONObject(i).getString("imageURL"));
menuItems.add(item);
}catch (Exception e){
e.printStackTrace();
}
}
myListView = (ListView) findViewById(R.id.list);
MenuAdapter adapter = new MenuAdapter(this, menuItems);
myListView.setAdapter(adapter);
myListView.setOnItemClickListener(this);
}
}
MenuItem.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/ivMenuItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="center"
android:src="#drawable/em" />
<TextView
android:id="#+id/tvMenuHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#55000000"
android:paddingBottom="15dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="15dp"
android:textColor="#android:color/white"
android:layout_gravity="left|top"
android:layout_alignBottom="#+id/ivMenuItem"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
</RelativeLayout>
1. Is there an example of how to do this properly?
Your code looks pretty close to perfect. The Adapter's getView method is usually the critical path to optimize. Compare for example Picasso's own example SampleListDetailAdapter.java. The important points it (as well as your code) does
check for & re-use already inflated views, inflation is expensive.
use ViewHolder so you don't have to call findViewById every time. Not terribly expensive on simple views. Also cached afaik.
Picasso.with(context).load(url)... each time you need to display an image. This should finish instantly but still use caches and other magic.
There are some minor optimizations you can add, but I doubt that there are noticeable or even measurable changes:
pure style change: use BaseAdapter#getItem(position). This method
exists for you only. The framework doesn't use it.
#Override
public MyMenuItem getItem(int position) { // << subclasses can use subtypes in overridden methods!
return menuItems.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
...
MyMenuItem row_pos = getItem(position);
Use a sane id method
#Override
public long getItemId(int position) {
return menuItems.indexOf(getItem(position));
}
is equivalent to
#Override
public long getItemId(int position) {
return position;
}
but now infinitely faster. indexOf(Object) scales really badly with the number of objects.
Cache objects that don't change:
MenuAdapter(Context context, List<MyMenuItem> menuItems) {
this.mLayoutInflater = LayoutInflater.from(content);
this.mPicasso = Picasso.with(context);
}
..
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.menu_item, null);
...
mPicasso
.load(row_pos.getItem_image_url())
.into(holder.ivMenu);
2. Is there a way to prevent listview items being destroyed when they are out of screen?
No(*).
..(*) well you can essentially cache the result of getView e.g. in LruCache(position, View) or LruCache(MyMenuItem, View), then don't touch the convertView - they need to remain unconverted or you would kill those views in your cache. Also
#Override
public int getItemViewType(int position) {
return Adapter.IGNORE_ITEM_VIEW_TYPE;
}
seemed to be required because the standard adapter using code assumes that views it removes from visibility are gone. They are not and messing with them messes with your cache and caused weird display problems for me.
3. If so, will it cause problems to keep too many items?
Yes. This behavior is not intendend / expected. There is also more or less nothing you gain. You might be able to save you the call to holder.tvMenuHeader.setText(). Likewise the one to Picasso but both of them should complete instantly. Picasso should have your image cached already. By caching all Views you essentially add another cache that also contains all the images. I would rather check that the picasso cache works as intended and holds most items. The only reason you may want to do it with view caching is for cases that require complicated setup of the view, so it becomes worth caching the completely constructed view rather than just some content parts.
Profile
Profiling can actually tell you where you can / need / should improve. The first to look at IMO is traceview. You'll see if code blocks the main thread which results in choppy list scrolling. If you're doing complicated views and you see that the draw methods are executed most of the time, profile them as well.
http://www.curious-creature.org/docs/android-performance-case-study-1.html
http://blog.venmo.com/hf2t3h4x98p5e13z82pl8j66ngcmry/performance-tuning-on-android
http://www.vogella.com/tutorials/AndroidTools/article.html
There is ListView with correct values:
public class FragmentTab1 extends SherlockFragment {
ListView list;
LazyAdapter adapter;
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
list = (ListView) getActivity().findViewById(android.R.id.list); //also I tried view.findViewById(android.R.id.list)
............
adapter = new LazyAdapter(getActivity(), mSource);
list.setAdapter(adapter);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View rootView = inflater.inflate(R.layout.fragmenttab1, container, false);
return rootView;
}
when I try:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId(); //correct
int itemCount = list.getCount(); // 10 ps as show Logcat
if (R.id.save == id) {
CheckBox cb;
for(int i = itemCount - 1; i >= 0; i--) {
cb = (CheckBox)list.getChildAt(i).findViewById(R.id.checkBox1); //Error here
}
}
return true;
}
xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save to database"
android:id="#+id/checkBox1" /> // same id
and adapter is next:
public class LazyAdapter extends BaseAdapter {
private Activity activity;
ArrayList<Bitmap> bitmapArray = new ArrayList<Bitmap>();
private ArrayList<Data> mObjects;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
public LazyAdapter(Activity a, ArrayList<Data> mObjects1) {
activity = a;
mObjects = mObjects1;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return mObjects.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Data item = mObjects.get(position);
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.item_internet, null);
TextView text=(TextView)vi.findViewById(R.id.title1);
ImageView image=(ImageView)vi.findViewById(android.R.id.icon);
text.setText(item.getmTitle());
bitmapArray.add(imageLoader.getBitmap());
imageLoader.DisplayImage(item.getmImageUrl(), image);
return vi;
}
I receive correct ListView, but receive error when I try click save button from action bar.
Probably, I should init CheckBox in adapter?
Somebody can help me?
list.getChildAt(i) will be null if the child item is not visible. So check for null before use.
So you cannot retrieve all checked items in this way.
Please post complete .xml and the definition of <Data>.
I'd think you'd get an indexoutofbounds but since it's null, this might be why: ListView getChildAt returning null for visible children
Also, put a log statement in your for loop to display the value of all variables concerned, so: i and itemCount etc.
And set a breakpoint just before the loop and run debug mode to step over to check the values as it loops through and you'll see what i value caused the nullpointer in the debugger or if you miss it, it will be in logcat
I know this is very old post. But I'm answering because people are still looking for a work around on ListView getChildAt() null pointer exception.
This is because the ArrayApdater is REMOVING and RECYCLING the views that are not visible yet on the ListView because of height. So that if you have 10 item views, and ListView can display 4 - 5 at a the time :
The Adapter REMOVE the item views at position 5 to 9, so that any attempt to adapter.getChildAt(5... to 9) will cause null pointer exception
The Adapter also RECYCLE the item view, so that any reference you made on position 3 for example will be lost when you scroll down to 5 to 9, and also any Input that you make on position 3 (EditText, Checkbox, etc.) will be recycled when you scroll down to 5 to 9 and will be reused at another position later (ex position 1, 2 or 3, etc.) with the same value
The only way I found to control this is to forget about getting the View and to have :
Attribute HashMap<Integer, Boolean> cbValues or any type you want for handling the values you want to use for each item on the list. The first type must be unique for item like item->getId() or position. Initialize it with new HashMap<>() in the Constructor;
Add InputListener for Input Views, (addTextChangedListener for EditText, setOnCheckedChangeListener for Checkbox, etc.) And on input, update the HashMap key (item.getId() or position) and value (editable.toString() or true or false). Ex. on #Override public void onCheckedChanged, put boolean result cbValues.put(item.getId(), b);
Prevent Adapter from using recycled convertView, remove condition if(convertView == null) so that adapter always inflate a brand new view instance. Because the view instance is new each time, you must set the value from HashMap each time also like if it already contains the key if(cbValues.containsKey(item.getId())){cbItem.setChecked(cbValue.get(cbItem.getId()))};. Probably in this case there is not tons of Items, so that smooth scrolling won't be a must.
And finally create public methods to get the Values outside of Adapter passing item->getId() Integer as parameter. Ex : 'public bool getCheckboxValueForItemId(int itemId) { return cbValues.get(itemId); }` . It will be easy then to select Item from Adapter
Here is the Codes at the end :
public class LazyAdapter extends BaseAdapter {
private Activity activity;
ArrayList<Bitmap> bitmapArray = new ArrayList<Bitmap>();
private ArrayList<Data> mObjects;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
HashMap<Integer, Boolean> cbValues;
public LazyAdapter(Activity a, ArrayList<Data> mObjects1) {
activity = a;
mObjects = mObjects1;
cbValues = new HashMap<>();
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return mObjects.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Data item = mObjects.get(position);
View vi=convertView;
// Remove convertView condition
//if(convertView==null)
vi = inflater.inflate(R.layout.item_internet, null);
TextView text=(TextView)vi.findViewById(R.id.title1);
ImageView image=(ImageView)vi.findViewById(android.R.id.icon);
Checkbox cbItem = (Checkbox) vi.findViewById(android.R.id.checkbox1);
text.setText(item.getmTitle());
bitmapArray.add(imageLoader.getBitmap());
imageLoader.DisplayImage(item.getmImageUrl(), image);
if(cbValues.containsKey(item.getId())) {
cbItem.setChecked(cbValue.get(cbItem.getId()))};
}
cbItem.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
cbValues.put(item.getId(), b);
}
});
return vi;
}
// itemId : unique identifier for an Item, not the position of Item in Adapter
public bool getCheckboxValueForItemId(int itemId) {
return cbValues.get(itemId);
}
}
I am facing some problem for rendering ListView from a dynamic layout. I don't know why the getView is called only with position 0 and several times!
I searched over internet and stackoverflow but cannot find a suitable answer.
I am actually trying to do a demo of this: http://www.framentos.com/en/android-tutorial/2012/07/16/listview-in-android-using-custom-listadapter-and-viewcache/
Notably, my main layout file is surrounded by scrollbar.
main activity layout file:
<ListView
android:id="#+id/city_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#+id/questionsList"
android:paddingTop="20sp" >
</ListView>
My layout file for list view:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="80dip" >
<ImageView
android:id="#+id/ImageCity"
android:layout_width="90sp"
android:layout_height="90sp" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_toRightOf="#id/ImageCity"
android:orientation="vertical"
android:paddingLeft="10sp">
<TextView
android:id="#+id/cityName"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="25sp" />
<TextView
android:id="#+id/cityLinkWiki"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:textSize="15sp" />
</LinearLayout>
</RelativeLayout>
Adapter class:
import com.incidentreport.app.classes.objects.City;
public class CityListAdapter extends ArrayAdapter{
private int resource;
private LayoutInflater inflater;
private Context context;
public CityListAdapter ( Context ctx, int resourceId, List objects) {
super( ctx, resourceId, objects );
resource = resourceId;
inflater = LayoutInflater.from( ctx );
context=ctx;
}
#Override
public View getView ( int position, View convertView, ViewGroup parent ) {
Log.v("adapter", "pos: " + position + "#" + resource);
/* create a new view of my layout and inflate it in the row */
convertView = ( RelativeLayout ) inflater.inflate( resource, null );
/* Extract the city's object to show */
City city = (City)getItem( position );
/* Take the TextView from layout and set the city's name */
TextView txtName = (TextView) convertView.findViewById(R.id.cityName);
txtName.setText(city.getName());
/* Take the TextView from layout and set the city's wiki link */
TextView txtWiki = (TextView) convertView.findViewById(R.id.cityLinkWiki);
txtWiki.setText(city.getUrlWiki());
/* Take the ImageView from layout and set the city's image */
ImageView imageCity = (ImageView) convertView.findViewById(R.id.ImageCity);
return convertView;
}
}
main activity code snipps:
List listCity= new ArrayList();
listCity.add(new City("London","http://en.wikipedia.org/wiki/London","london"));
listCity.add(new City("Rome","http://en.wikipedia.org/wiki/Rome","rome"));
listCity.add(new City("Paris","http://en.wikipedia.org/wiki/Paris","paris"));
ListView listViewCity = ( ListView ) findViewById( R.id.city_list);
listViewCity.setAdapter( new CityListAdapter(this, R.layout.layout_city, listCity ) );
Okay, I figured out the issue by expanding ListView as much possible. Meaning to say, giving a dynamic full height so that all item becomes visible.
I followed the below solution:
Calculate the size of a list view or how to tell it to fully expand
Use a ViewHolder pattern for better performance.
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
static class ViewHolder
{
TextView txtName,txtWiki;
ImageView imageCity;
}
Change getView to
#Override
public View getView ( int position, View convertView, ViewGroup parent ) {
ViewHolder holder;
if(convertView == null)
{
convertView = ( RelativeLayout ) inflater.inflate( resource, parent, false );
holder = new ViewHolder();
holder.txtName = (TextView) convertView.findViewById(R.id.cityName);
holder.txtWiki = (TextView) convertView.findViewById(R.id.cityLinkWiki);
holder.imageCity = (ImageView) convertView.findViewById(R.id.ImageCity);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
City city = (City)getItem( position );
holder.txtName.setText(city.getName());
holder.txtWiki.setText(city.getUrlWiki());
return convertView;
}
ListView recyclues view's. You may also want to read
How ListView's recycling mechanism works
Try this way,hope this will help you to solve your problem.
public class CityListAdapter extends BaseAdapter{
private Context context;
private List objects;
public CityListAdapter ( Context context, int resourceId, List objects) {
this.context=context;
this.objects=objects;
}
#Override
public int getCount() {
return objects.size();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public Object getItem(int position) {
return objects.get(position);
}
#Override
public View getView ( int position, View convertView, ViewGroup parent ) {
ViewHolder holder;
if(convertView==null){
holder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.layout_city,null);
holder.txtName = (TextView) convertView.findViewById(R.id.cityName);
holder.txtWiki = (TextView) convertView.findViewById(R.id.cityLinkWiki);
holder.imageCity = (ImageView) convertView.findViewById(R.id.ImageCity);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.txtName.setText(((City)getItem(position)).getName());
holder.txtWiki.setText(((City)getItem(position)).getUrlWiki());
return convertView;
}
class ViewHolder{
TextView txtName;
TextView txtWiki;
ImageView imageCity;
}
}
I ran into this same issue. My list view is mostly working, but there's a certain sequence of actions which will make certain items disappear. Clicking on them afterwards causes a NullPointerException.
Here's the steps to reproduce the bug:
Drag an item to the top.
Drag another item up or down.
The item at the top will disappear.
Drag another item up or down.
The top item will reappear.
Behavior continues if you go to step 2
After debugging, I found that my StableArrayAdapter.getView() method was being called twice, only for the blank item at the top of the list.
To fix it, per masum7's answer, I set the layout_height for my DynamicListView to "wrap_content".
Try to get layout inflater as
LayoutInflater inflater = (LayoutInflater)context.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
This may work
I have an ArrayAdapter for a list view that has multiple buttons in it. For one toggle button, I want to have a default state based on a condition, and let users toggle the button as well.
However, when users click button on row 1, the button for row 3 actually gets selected. I'm not sure why this is happening. Below is snippet of relevant code from my getView method with comments.
layout of my toggle button
<ToggleButton android:id="#+id/color_toggle"
android:layout_width="50px"
android:layout_height="50px"
android:focusable="false"
android:textOn="" android:textOff="" android:layout_alignParentLeft="true"
android:layout_marginRight="10dp"
/>
class Color {
int id;
int something;
}
List<Color> colorsList;
class ColorHolder {
TextView colorNameText;
ToggleButton toggleButton;
}
public View getView(final int position, final View convertView, final ViewGroup parent) {
View rowView = convertView;
Color c = colorsList.get(position);
if (null == rowView) {
rowView = this.inflater.inflate(R.layout.list_item_color, parent, false);
holder = new ColorHolder();
holder.colorNameText = (TextView) rowView.findViewById(R.id.color_name);
holder.toggleButton = (ToggleButton) rowView.findViewById(R.id.color_toggle);
rowView.setTag(holder);
}
else {
holder = (ColorHolder)rowView.getTag();
}
holder.toggleButton.setTag(c.getId());
final ColorHolder thisRowHolder = holder;
holder.toggleButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (thisRowHolder.toggleButton.isChecked()) {
thisRowHolder.toggleButton.setBackgroundDrawable(//normal button);
thisRowHolder.toggleButton.setChecked(false);
for (int i = 0; i < colorList.size(); i++) {
if (colorList.get(i) == (Integer)v.getTag()) {
colorList.get(i).setSomething(0);
break;
}
}
adapter.notifyDataSetChanged();
}
else {
thisRowHolder.toggleButton.setBackgroundDrawable(//enabled button);
thisRowHolder.toggleButton.setChecked(true);
for (int i = 0; i < colorList.size(); i++) {
if (colorList.get(i) == (Integer)v.getTag()) {
colorList.get(i).setSomething(1);
break;
}
}
adapter.notifyDataSetChanged();
}
}
});
if (c.getSomething()>0) {
holder.toggleButton.setBackgroundDrawable(//enabled button);
holder.toggleButton.setChecked(true);
}
else {
holder.toggleButton.setBackgroundDrawable(//normal button);
holder.toggleButton.setChecked(false);
}
return rowView;
}
Question
What am I doing wrong? why are other buttons in third row toggling even though i'm toggling buttons in row one.
I read that this happens because the listView recycles, is there no way to fix it? Some strategies i've tried, to no avail, based on similar questions: 1) put onClickListener in the if clause. 2) instead of setting int in setTag instead set the holder and use that holder in onClickListener
update
I've updated all the code in the question with suggestions I received.
Hope This Helps.
Activity Code
public class DemoActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ColorInfo[] clr= new ColorInfo[20];
for(int i=0;i<20;i++){
clr[i] = new ColorInfo();
}
((ListView)findViewById(R.id.list)).setAdapter(new MyAdapter(this, 0, clr));
}
private static class MyAdapter extends ArrayAdapter<ColorInfo> implements OnClickListener{
LayoutInflater inflater;
public MyAdapter(Context context, int textViewResourceId,
ColorInfo[] objects) {
super(context, textViewResourceId, objects);
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
convertView = inflater.inflate(R.layout.row, null);
holder = new ViewHolder();
holder.tgl = (ToggleButton) convertView.findViewById(R.id.toggle);
convertView.setTag(holder);
}
holder = (ViewHolder) convertView.getTag();
holder.tgl.setTag(position);
holder.tgl.setOnClickListener(this);
holder.tgl.setChecked(getItem(position).isChecked);
return convertView;
}
private static class ViewHolder{
ToggleButton tgl;
}
public void onClick(View v) {
int pos = (Integer) v.getTag();
ColorInfo cinfo = getItem(pos);
cinfo.isChecked = !cinfo.isChecked;
}
}
private static class ColorInfo{
boolean isChecked=false;
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ToggleButton android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/toggle"
/>
</LinearLayout>
Your problem is listview recycling views
You have to store state of toggle button for each row of listview.
Eg.Create class which stores information about each row,suppose ColorInfo which contains color and isChecked boolean.
so instead of
Color c = colorsList.get(position);
it will be
ColorInfo colorInfo = colorsList.get(position);
and in getview
togglebutton.setCheck(colorInfo.isCheck)
and in onClick listener of toggle buttons you change state of object of ColorInfo for that position to toggleChecked true or false and notifyDatasetChanged,this will solve your problem.
You are using a member variable for your ViewHolder, rather than a final local variable. So your OnClickListener is referencing whatever the latest holder instance is, which will correspond with the most recently created or recycled list item.
Do this instead:
//Lock in this reference for the OnClickListener
final ColorHolder thisRowHolder = holder;
holder.favButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (thisRowHolder.toggleButton.isChecked()) {
thisRowHolder.toggleButton.setBackgroundDrawable(getResources().getDrawable(...);
thisRowHolder.toggleButton.setChecked(false);
}
else {
thisRowHolder.toggleButton.setBackgroundDrawable(getResources().getDrawable(...));
thisRowHolder.toggleButton.setChecked(true);
}
}
});
...
Edit:
Also noticed this. In these two lines:
holder.colorNameText = (TextView) itemView.findViewById(R.id.color_name);
holder.toggleButton = (ToggleButton) itemView.findViewById(R.id.color_toggle);
You are finding the views in some member variable itemView, but you need to be finding them in rowView so you are getting the instances for this specific row. All your view holders are looking at the same ToggleButton instance, which may not even be on screen.
Edit 2:
One more thing you're missing. You need to store the state of the toggle buttons and reapply them. So in your OnClickListener, when you call setChecked() you must also update the backing data in colorsList. Looks like you already cached a reference to the proper list element in your ToggleButton's ID, so should be easy. Then move this block of code out of your if/else block and put it afterwards, so the toggle button is always updated to the latest data:
if (c.getSomething()>0) {
holder.toggleButton.setBackgroundDrawable(getResource().getDrawable(...)));
holder.setChecked(false);
}
else {
holder.toggleButton.setBackgroundDrawable(getResource().getDrawable(...)));
holder.setChecked(true);
}