I have developed an app in which I display data in Gridview. All data comes from local storage. I am able to display each data correctly and there are no issues with it. But when I have scroll the Gridview and goto the bottom and getback to Top,it changes position. And sometimes when I scroll down and getback to Top, a blank screen appears on screen;no data found at all!
So I thought that there is issue with getView(). I am unable to figure out the problem
Code of getView():
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder viewholder;
// LayoutInflater inflator = activit.getLayoutInflater();
if(convertView == null) {
// recycled view is null so create it.
viewholder = new ViewHolder();
convertView = inflator.inflate(R.layout.gridviewrow, null);
viewholder.imgvGridicon = (ImageView) convertView.findViewById(R.id.imgvGridicon);
viewholder.txtGridItemlabel = (TextView) convertView.findViewById(R.id.txtGridItemlabel);
convertView.setTag(viewholder);
} else {
viewholder = (ViewHolder) convertView.getTag();
}
if ((lstpinfo.get(position).appname.toString()) != null) {
viewholder.imgvGridicon.setImageDrawable((lstpinfo.get(position).icon));
viewholder.txtGridItemlabel.setText(lstpinfo.get(position).appname.toString());
}
return convertView;
}
Update::
Intitalize of inflater::
private LayoutInflater inflator;
private ArrayList<PInfo> lstpinfo = new ArrayList<PInfo>();
public GridViewAdapter(Context cntx, ArrayList<PInfo> lstpinfo) {
activit = cntx;
inflator = LayoutInflater.from(cntx);
this.lstpinfo = lstpinfo;
}
You have to set your view height with fixed value. There's a scrolling bug in android gridview with different heights. Please see this for reference.
Related
I'm trying to create a simple chat/message layout. My problem is with the Adapter. I want to use two different layouts. One for outgoing and one incoming messages. Furthermore I would like to use the ViewHolder approach. But my implementation causes the same layout to be inflated/reused everytime getView runs.
Can anyone please explain to me why this is happening? Do I need to use two viewholders?
The relevant code is shown below
#Override
public View getView(int position, View convertView, ViewGroup parent) {
int direction = getItemViewType(position);
ViewHolder holder;
if (convertView == null) {
int res = 0;
int resid = 0;
holder = new ViewHolder();
if (direction == DIRECTION_INCOMING) {
holder = new ViewHolder();
res = R.layout.message_l_box;
resid = R.id.left_bubble_text;
}
else {
res = R.layout.message_r_box;
resid = R.id.right_bubble_text;
}
convertView = LayoutInflater.from(mContext).inflate(res, parent, false);
holder.text = (TextView) convertView.findViewById(resid);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(getItem(position).first);
return convertView;
}
// Ensure that find by id is not called every time -> could cause slow scrolling
private class ViewHolder {
TextView text;
}
ListViews reuse Views when possible for performance. That is what the convertView View in your code is for. If Android already had another row that you scrolled away from it can be reused so it will be passed into your getView() method. Your code is not creating a new layout if convertView is not null.
You could do something like this, basically store the current layouts direction in the ViewHolder so you know if you can reuse the layout or have to re-inflate it.
private class ViewHolder {
TextView text;
int direction;
}
if (convertView == null || ((ViewHolder) convertView.getTag()).direction != direction) {
// same code you have now to layout a new row
holder.direction = direction
} else { // we know the existing view has same direction so it has the correct layout already inflated and we can reuse it
holder = (ViewHolder) convertView.getTag();
}
I am developing an app using a ListView with a simple custom adapter, each row containing a CheckBox object. However, due to ListView's recycling feature (that I don't plan on turning off), when any of the boxes are checked, others below or above in the ListView are also checked.
The following is my getView() in the adapter, along with the custom ViewHolder class:
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater mInflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.populate_friends_row, null);
holder = new ViewHolder();
holder.nameCheckBox = (CheckBox) convertView.findViewById(R.id.isFriend);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.nameCheckBox.setText(data.get(position).contactLabel);
holder.nameCheckBox.setChecked(checked.get(position));
holder.nameCheckBox.setTag(String.valueOf(position)); // to properly track the actual position
holder.nameCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
int pos = Integer.parseInt(buttonView.getTag().toString());
checked.set(pos, isChecked);
}
});
return convertView;
}
public static class ViewHolder {
public CheckBox nameCheckBox;
}
I am already holding the checked boxes in the ArrayList of booleans: checked.
Any help would be appreciated, thank you.
When you're calling holder.nameCheckBox.setChecked(checked.get(position)); to configure the view to be displayed for this view, the listener is called while the tag still has the position of the previous checkbox.
Try removing the listener (setting it to null) before calling setChecked
I know that many similar questions have been posted on stackoverflow, so please don't think I haven't searched high and low. I think my problems simply comes from now completely understanding listViews and the lifecycles of list items. I have a list view that can contain two types of messages, outbound or inbound. Originally, my listView would use a different background color depending on the type of message (outbound vs inbound), and it worked flawlessly. Now my application doesn't require a different background for list items, but it actually requires different layouts for different list items.
This is a clip of my Adapter.
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
SoapBoxMessage thisMessage = messages.get(position);
if (v == null) {
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (thisMessage.isOutbound()) {
v = inflater.inflate(R.layout.outbound_row, null);
} else {
v = inflater.inflate(R.layout.inbound_row, null);
}
}
Adapters can support different ViewItemTypes that will solve your recycling problems.
static public enum LAYOUT_TYPE {
INBOUND,
OUTBOUND
}
#Override
public int getViewTypeCount () {
return LAYOUT_TYPE.values().length;
}
#Override
public int getItemViewType (int position) {
if ( messages.get(position).isOutbound())
return LAYOUT_TYPE.OUTBOUND.ordinal();
else
return LAYOUT_TYPE.INBOUND.ordinal();
}
#Override
public View getView (int position, View convertView, ViewGroup parent) {
LAYOUT_TYPE itemType = LAYOUT_TYPE.values()[getItemViewType(position)];
... (code until inflater )
switch (itemType){
case INBOUND:
convertview = /inflate & configure inbound layout
break;
case OUTBOUND:
convertview = /inflate & configure outbound layout
break;
}
you don't need to worry about recycling views because the listview will respect the ViewItemTypes for each position and it will only provide a convertview of the correct viewtype for that position
The problem is that listview is recycling the view so when you check if the view is null it wont pass that because the view is not null when it is recycled
you would need to inflate the view each time getView is called, basically removing if(v == null)
Try to use a ViewHolder like this:
ViewHolder holder;
public View getView(int position, View convertView, ViewGroup parent) {
convertView = null;
SoapBoxMessage thisMessage = messages.get(position);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (thisMessage.isOutbound()) {
convertView = inflater.inflate(R.layout.outbound, null, false);
//specific to your outbound layout
holder = new ViewHolder();
holder.text= (TextView)convertView.findViewById(R.id.textview);
holder.group = (RadioGroup)convertView.findViewById(R.id.toggleGroup);
holder.toggle = (ToggleButton)convertView.findViewById(R.id.toggleButton);
} else {
convertView = inflater.inflate(R.layout.inbound, null, false);
//specific to your inbound layout
holder = new ViewHolder();
holder.text= (TextView)convertView.findViewById(R.id.textview);
holder.group = (RadioGroup)convertView.findViewById(R.id.toggleGroup);
holder.toggle = (ToggleButton)convertView.findViewById(R.id.toggleButton);
}
convertView.setTag(holder);
}
else{
holder = (ViewHolder) convertView.getTag();
}
//Here you can set the text or other code you want to implement
holder.text.setText("Whatever!");
return convertView;
}
static class ViewHolder {
//TODO put components you use like:
TextView text;
RadioGroup group;
ToggleButton toggle;
}
It's because of the recycling that is happening. You would need something along these lines:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
SoapBoxMessage thisMessage = messages.get(position);
if (convertView == null) {
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.listview_feedlog_item, null);
holder = new ViewHolder();
holder.txtTime = (TextView) convertView.findViewById(R.id.textTime);
holder.txtDate = (TextView) convertView.findViewById(R.id.textDate);
convertView.setTag(holder);
} else
holder = (ViewHolder) convertView.getTag();
// I don't know how your SoapBoxMessage is made up so here are two sample methods
holder.txtTime.setText(thisMessage.getTime());
holder.txtDate.setText(thisMessage.getDate());
return convertView;
}
/* private view holder class */
private class ViewHolder {
TextView txtTime;
TextView txtDate;
}
Also, remember to always reset or initiate a value in the getView method. Since a View can be recycled it might carry with it properties of its former life.
Whether this is a good practice or not, Removing if (v == null) will solve the problem.
Anyway, you will have to re-inflate the view.
Hi i'm having this kind of problem, when scrolling imageviews change their positions and
their background images. I saw other answers on this topic on this site, but non of them
helped me.
Like this once:
grid view scrolling issue
GridView scrolling problem on Android
GridView elements changes their place dynamically when scroll screen
and many others...but they don't solve my problem.
Important thing is that i don't use custom layout for gridview or gridview items(imageViews).I create them programmatically. This is very important to me so if someone know the answer pls help me...Thanks.
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null){
imageView = new ImageView(ctx);
} else {
imageView = (ImageView) convertView;
}
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
imageView.setBackgroundResource(tmp[position]);
imageView.setImageResource(blank);
return imageView;
}
Its a simple method. Adapters recycle each and every view while scrolling. So we just need to create the ones which are being re-used after the first time. Holders help to avoid expensive calls like findViewById and re-use the items by just getting the old ones and changing its properties.
One point you need to keep in mind is that we need to hold the images which are to be displayed in any one of the containers like array[] or List and reset each time before returning the view, or else it will display the previously last recycled view.
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_layout, null);
holder = new ViewHolder();
holder.cover = (ImageView) convertView
.findViewById(R.id.item_cover);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.cover.setBackgroundResource(tmp[position]);
return convertView ;
}
static class ViewHolder {
ImageView cover;
}
try this
class Holder {
ImageView imageView;
}
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
if(holder.imageView==null){
holder=new Holder();
holder.imageView = new ImageView(ctx);
holder.imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
holder.imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
holder.imageView.setPadding(8, 8, 8, 8);
imageView.setTag(holder);
} else {
holder= (Holder) imageView.getTag();
}
holder.imageView.setBackgroundResource(tmp[position]);
holder.imageView.setImageResource(blank);
return imageView;
}
I have a listview that uses a customadapter based on the baseadapter. The listview populates ok using external data and I can pick up the click events and know which item has been selected.
I'm having a problem updating the clicked item's views, like TextView and ViewFlipper.
Do I have to update something via the listview or is it via the adapter. I've tried things like the following;
View test = (View)adapter.getView(pos, null, myListView);
ViewFlipper temp = (ViewFlipper)test.findViewById(R.id.flipholder);
temp.showNext();
TextView temp2 = (TextView)test.findViewById(R.id.filmtitle);
temp2.setText("Hello World");
Which results in the viewfliper flipping the first and third item or second and fourth and the text not updating at all.
Any ideas?
Cheers
Try this pattern in getView:
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary calls
// to findViewById() on each row.
ViewHolder holder;
// When convertView is not null, we can reuse it directly, there is no need
// to reinflate it. We only inflate a new View when the convertView supplied
// by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
// Bind the data efficiently with the holder.
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
static class ViewHolder {
TextView text;
ImageView icon;
}
}