ListView deliver wrong position value in getView(..) - android

I'm very confusing with this.
Question is simple, I'm trying to resize the ImageView height, to do, I get the display width and add on it 0.25 of percentage.
Problem, if I set the new value of height outside of the post() ImageView method, the position parameter is deliver in getView() wrong. If I do it inside post() the first elements showed are not rescaled.
Read comments inside code.
public static class ViewHolder {
ImageView imgAvatar;
public void loadAvatar(SocialService socialService, long userId) {
try {
// SocialService.loadAvatar(..) is working with UniversalImageLoader.
socialService.loadAvatar(this.imgAvatar, userId, true, false);
} catch (Exception ex) {
Log.e("APPERROR", ex.getMessage());
}
}
}
#Override
public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
final User user = this.users.get(position);
if (view == null) {
holder = new ViewHolder();
view = inflater.inflate(R.layout.adapter_list, parent, false);
holder.imgAvatar = (ImageView) view.findViewById(R.id.people_list_avatar);
// With this commented snippet of code, the first 4 elements (showed in the top of the listview) are not rescaled.
// Position is deliver ok.
// The rest of elements that are going showing while scrolling works pretty fine.
// If scroll down and come back to the top then the 4 top first elements are showing rescaled.
/*final ImageView imgAvatarAux = holder.imgAvatar;
holder.imgAvatar.post(new Runnable() {
#Override
public void run() {
imgAvatarAux.getLayoutParams().height =
(int) ((Display.deviceWidth(PeopleAdapter.this.context) / 2) * 1.25F);
}
});*/
view.setTag(holder);
// HERE IS THE QUESTION.
// If I remove this line of code, position is deliver ok, but if it works, position is deliver senseless. WHY?
holder.imgAvatar.getLayoutParams().height = (int) ((Display.deviceWidth(PeopleGridAdapter.this.context) / 2) * 1.25F);
holder.loadAvatar(this.socialService, user.getId());
}
holder = (ViewHolder) view.getTag();
...
if (position == 0) {
// An interface method for another purposes...
this.waitFinish.onFinish();
}
return view;
}

Post your ListView Item layout adapter_list. Code fix [operations on list items outside if(){}else{}]:
// HERE IS THE QUESTION.
// If I remove this line of code, position is deliver ok, but if it works, position is deliver senseless. WHY?
holder.imgAvatar.getLayoutParams().height = (int) ((Display.deviceWidth(PeopleGridAdapter.this.context) / 2) * 1.25F);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
holder.loadAvatar(this.socialService, user.getId());

Related

Android Listview scroll lag with view holder

I have noticed that a ListView in my application has started to stutter quite badly all of a sudden.
I am using Volley to load images for my listview items - downloading and caching the images are fine and scroll smooth as butter.
However I currently have a spinner that sits on top of the NetworkImageView while I wait for the image to load. The lag comes in once the image has successfully loaded - I set the spinner to be invisible and the image to visible. Changing the visibility of these items seems to be the source of the lag.
I am currently using the View Holder pattern, my onResponseLoad looks like the following:
#Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null){ //Check that the image is not null
ProgressBar progress = holder.getProgress(); //Find Spinner - this doesnt cause lag
progress.setVisibility(View.GONE); //Hide spinner (This causes lag)
img.setVisibility(View.VISIBLE); //Image is a network image from the holder (This causes lag)
}
}
(Note that commenting out those two offending lines results in buttery smooth scrolling again)
The other strange thing is that I haven't touched this part of the application in some time and in my current live version, as well as previous commits there is no lag. Comparing my current code base to previous non-lagging versions show that there has been 0 change to the code surrounding this aspect of the application. Furthermore other lists that I have implemented using almost the exact same technique have not experienced this issue.
The only thing I can think of that could be different is that I am now using the latest version of Gradle - although I don't think that should have an impact at run-time.
I am at a total loss as to what is going on, would appreciate any insight on what I should be doing to achieve smooth ListView scrolling (or what may have lead to my implementation's degradation)
EDIT: Posting code of getView() as requested
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View placeSelectorView = convertView;
PlaceViewHolder placeSelectorHolder = null;
if(placeSelectorView == null){ //If we are creating the row for the first time
LayoutInflater inflater = (LayoutInflater) mCtx.getSystemService(Context.LAYOUT_INFLATER_SERVICE); //Inflate the view
placeSelectorView = inflater.inflate(R.layout.place_selector, parent, false); //Get the view
placeSelectorHolder = new PlaceViewHolder(placeSelectorView); //Create holder object
placeSelectorView.setTag(placeSelectorHolder); //Attach reference to the view
}else{
placeSelectorHolder = (PlaceViewHolder) placeSelectorView.getTag(); //Load holder from memory
if(!placeSelectorHolder.isHasImage()){ //Need to optimise this
placeSelectorHolder.getLayout().addView(placeSelectorHolder.getrLayoutThumbnail(), 0);
placeSelectorHolder.setHasImage(true);
}
if(!placeSelectorHolder.isSpinnerVisible()){
placeSelectorHolder.getProgressBar().setVisibility(View.VISIBLE);
placeSelectorHolder.getPlaceImg().setVisibility(View.GONE);
placeSelectorHolder.setSpinnerVisible(true);
}
}
POI place = (values.get(position)); //Get POI object for the place
POI parentPlace = getParent(place); //Get parent POI for place
placeSelectorHolder.getPlaceName().setText(place.getName());
if(parentPlace != null){ //If place has a parent POI
placeSelectorHolder.getParentPlaceName().setText(parentPlace.getName());
}else{ //We don't want the parent text in the view
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) placeSelectorHolder.getParentPlaceName().getLayoutParams();
layoutParams.weight = 0; //Setting weight to 0 will remove it from the LinearLayout
placeSelectorHolder.getParentPlaceName().setLayoutParams(layoutParams);
}
final PlaceViewHolder holder = placeSelectorHolder;
loadThumbnail(holder, place);
return placeSelectorView;
}
public void loadThumbnail(final PlaceViewHolder placeSelectorHolder, POI place){
RealmList<poiPhoto> photos = place.getPhotos();
String mUrl;
if(!photos.isEmpty()){
mUrl = photos.get(0).getSmall();
}else{
mUrl = "";
}
final NetworkImageView placeImg = placeSelectorHolder.getPlaceImg();
if(!mUrl.equals("")){ //If there is an Image Available
ImageLoader imageLoader = ServerSingleton.getInstance(getContext()).getImageLoader(); //Get volley imageloader from Singleton
imageLoader.get(mUrl, new ImageLoader.ImageListener() { //Custom get so we can use override onResponse and OnErrorResponse
#Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null){ //Check that the image is not null
ProgressBar progressBar = placeSelectorHolder.getProgressBar(); //Find Spinner
placeImg.setVisibility(View.VISIBLE);
if(progressBar != null) progressBar.setVisibility(View.GONE); //Make the spinner invisible
placeSelectorHolder.setSpinnerVisible(false);
}
}
#Override
public void onErrorResponse(VolleyError error) {
//TO-DO: Get an error image
}
});
placeImg.setImageUrl(mUrl, imageLoader); //Send the request
placeSelectorHolder.setHasImage(true);
}else{ //There is no image
LinearLayout layout = placeSelectorHolder.getLayout(); //Find the horizontal layout
layout.removeView(placeSelectorHolder.getrLayoutThumbnail()); //Remove the Thumbnail layout
placeSelectorHolder.setHasImage(false);
}
}

Listview style Each Row

this is the code in my custom adapter (THE CODE IN BROWN COLOR) when initially list is build proper margin is applied to valid items when i scroll down and again scroll up all the rows in list shifts the margin left by 20 what i'm doing wrong please reply soon
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
// getting data
final ViewMovieDetailsModel_ViewComments movies = getItem(position);
if (convertView == null)
{
convertView = View.inflate(context, R.layout.comment_row, null);
holder = new ViewHolder();
//getting handles
holder.comments_linearLayout = (LinearLayout) convertView.findViewById(R.id.comments_linearLayout);
holder.commenter_textView = (TextView) convertView.findViewById(R.id.comment_row_commenter);
holder.commented_on_textView = (TextView) convertView.findViewById(R.id.comment_row_comment_time);
holder.comment_text_textView = (TextView) convertView.findViewById(R.id.comment_row_comment_text);
holder.reply_button = (Button) convertView.findViewById(R.id.comment_row_reply_button);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
if (movies != null)
{
if (((movies.getParent_comment_id()).toString().equals("null")) && session.isLoggedIn()==true) {
holder.reply_button.setVisibility(View.VISIBLE);
}else{
holder.reply_button.setVisibility(View.INVISIBLE);
}
`if (!((movies.getParent_comment_id()).toString().equals("null")))
{
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT);
params.setMargins(20, 0, 0, 0);
holder.comments_linearLayout.setLayoutParams(params);
}`
holder.commenter_textView.setText(movies.getUsername_commentor());
holder.commenter_textView.setTag(movies.getUser_id_commentor());
return convertView;
}
because you are setting margins (the brown font) in 'if' statement:
if (movies != null)
just take it out of this if block (for example put it just before the return point)
Right now this code is probably not executed at the first view load, since the movie is null. When the getView is called second time, the movie is not null, and the marigin is set according to your 'brown' code.
if this is not the solution - maybe the inside if statement condition is not true (the one that is in first 'brown' row). so.. your own logic prevents the marigins to be set as you want :)
Please let me know if it helps.
One way you could go about solving this problem is instead of using LayoutParams.setMargins(20, 0, 0, 0), you could create an empty TextView whose width is 20 dp by default and whose position will be to the left of your rows contents. It will be View.GONE by default, but when if (!((movies.getParent_comment_id()).toString().equals("null"))) happens, you can set that to View.VISIBLE

Using a ImageAdapter in a GridView

I'm trying to create a memory game - where I have 5x5 images on the screen and the user has to match the images.
I've been using a GridView and populate it with images using a ImageAdapter.
The game works something like this:
- when a user matches 2 images - the images remain on the screen
- when a user fails to match the 2 pictures - the imagess changes back to the question-mark.
The problem is that I can't manage to keep the pictures previously matched on the screen - when I use the notifyDataSetChanged() method - all the screen is filled again with question marks.
Here's my code:
// getView method in ImageAdapter
public View getView(int position, View convertView, ViewGroup arg2) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(100, 100));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(4, 4, 4, 4);
} else {
imageView = (ImageView) convertView;
}
for(int i=0;i<16;i++)
{
if(mThumbIds[i].equals(R.drawable.ic_launcher))
{
imageView.setImageResource(R.drawable.ic_launcher);
}
else
imageView.setImageResource(R.drawable.q_mark);
}
return imageView;
}
// the onClickListener when a user selects 1 image
gridview.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position,
long id) {
i++;
Toast.makeText(easyGame.this, "" + position, Toast.LENGTH_SHORT).show();
ImageView imgV=(ImageView)v;
if(i%2!=0)
{
firstClick=position;
imgV.setImageResource(ImageAdapter.mThumbIds[firstClick]);
}
else
{
secondClick=position;
imgV.setImageResource(ImageAdapter.mThumbIds[secondClick]);
}
if(i%2==0)
{
if(!(ImageAdapter.mThumbIds[firstClick].equals(ImageAdapter.mThumbIds[secondClick])))
{
Toast.makeText(easyGame.this, "Great!", Toast.LENGTH_SHORT).show();
ImageAdapter.mThumbIds[firstClick]=ImageAdapter.mThumbsIdsDone[0];
ImageAdapter.mThumbIds[secondClick]=ImageAdapter.mThumbsIdsDone[0];
im.notifyDataSetChanged();
gridview.setAdapter(im);
gridview.invalidate();
}
}
}
});
Can anyone help? Thanks!
// declare as class variable to keep track of views which should stay visible
private HashSet<Integer> keepVisibleViews = new HashSet<Integer>(25);
//in you on click listener
if(!(ImageAdapter.mThumbIds[firstClick].equals(ImageAdapter.mThumbIds[secondClick])))
{
// ... the rest of your code here
// keep track of views that should stay visible
keepVisibleViews.add(firstClick);
keepVisibleViews.add(secondClick);
}
// in your getView
// show ? mark if we should, else the picture
if (keepVisibleViews.contains(position)
setImageResource(ImageAdapter.mThumbIds[secondClick]);
else
imageView.setImageResource(R.drawable.q_mark);

Creating hierarchical listFragment in Android

I am building a forum-ish app, and need to display the forumposts in a hierarchical manner so that people can easily see which post are responding to (children of) which. Each forumpost has its own object, and this object contains an (int) depth variable which is intended to use for defining its position related to its parent.
I have a BroadcastReciever which takes in the data and sets a listadapter based on the data.
My plan was now to use getListAdapter().getChildAt(position).setLeft(pixels); method for each entry, however i get a nullpointerexception at the setLeft method. My code for this is:
#Override
public void onReceive(Context context, Intent intent) {
String jsonEmner = intent
.getStringExtra(RestService.PARAM_OUT_MSG);
emner = gson.fromJson(jsonEmner, EmneItem[].class);
Log.e(this.getClass().getSimpleName(), jsonEmner);
populateEmner();
int firstID = getListView().getFirstVisiblePosition();
int listSize = getListView().getCount();
for (int count = firstID; count < listSize; count++) {
EmneItem e = (EmneItem) getListView().getItemAtPosition(count);
getListView().getChildAt(count).setLeft(e.getDepth());
}
}
This code is inside the BroadCastReciever method inside the ListFragment class. I was hoping someone could point me in the right direction how to do this :)
I feel a bit stupid answering my own question, but I managed to find a workaround in case anyone gets in a similar situation.
I created my own custom ArrayAdapter, and an XML file which defines the layout for the rows. ArrayAdapter has a getView() method which lets you customize each entry, and based on my entries "depth" variable, i set the textviews LayoutParams.
In case anyone wants to see how its done:
class CustomAdapter extends ArrayAdapter<EmneItem> {
CustomAdapter() {
super(getActivity(), R.layout.row, emner);
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
//Reusing convertView if possible, if not then inflating a new
if(row == null) {
LayoutInflater inflater = getActivity().getLayoutInflater();
row = inflater.inflate(R.layout.row, parent, false);
}
TextView postedby = (TextView) row.findViewById(R.id.postedbytextview);
postedby.setText("Posted by: " + emner[position].getPosted_by());
TextView argument = (TextView) row.findViewById(R.id.argumentTextView);
argument.setText(emner[position].getArgument());
TextView procontratextview = (TextView) row.findViewById(R.id.procontratextview);
int procontra = emner[position].getProcontra();
if (procontra == EmneItem.PRO) {
procontratextview.setText("PRO");
}
if (procontra == EmneItem.CONTRA) {
procontratextview.setText("CONTRA");
}
LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if(emner[position].getDepth() == 1) {
llp.setMargins(10, 0, 0, 0);
procontratextview.setLayoutParams(llp);
argument.setLayoutParams(llp);
} else if (emner[position].getDepth() == 2) {
llp.setMargins(20, 0, 0, 0);
procontratextview.setLayoutParams(llp);
argument.setLayoutParams(llp);
} else if (emner[position].getDepth() == 3) {
llp.setMargins(30, 0, 0, 0);
procontratextview.setLayoutParams(llp);
argument.setLayoutParams(llp);
} else if (emner[position].getDepth() >= 4) {
llp.setMargins(40, 0, 0, 0);
procontratextview.setLayoutParams(llp);
argument.setLayoutParams(llp);
}
return (row);
}
}
This is mostly based on an article i found at http://commonsware.com/Android/excerpt.pdf
Cheers

Android: ListView with a Custom ArrayAdapter acting up on long lists -- Timing issue!

HI all,
I have this "search results" ListView.
The search results can be of different "kinds" (different sections, call it).
To separate the "kinds" I add a row with a title. (I know about the expandable list, but can't use it for other reasons).
In my getView(), I check for a property, and if it's set, I change the background color of the row.
The problem: when I run a query that returns just a few rows (say 15), everything is fine. But when I run another that returns, say 600 rows, something goes wacko and it changes the background randomly, at a somewhat regular interval. Same thing happens when I'm running in debug mode and stop things in the middle.
So, it's definitely a timing issue.
I'm thinking this might be due to having to re-render the big list as the on-screen keyboard closes.
So, is the Adapter to blame? Is there any solution for this?
If the keyboard is the problem, is there a mechanism to tell the list "wait until the thing closes" before start rendering? (Not sure I like that, but it's better than getting a cute little rainbow...)
Thanks!
Llappall
--
Here's the adapter and the element layout (below):
private class ElementAdapter extends ArrayAdapter<Element> {
private ArrayList<Element> rows;
private Element.typeEnum type;
public ElementAdapter(Context context, int textViewResourceId, ArrayList<Element> rows) {
super(context, textViewResourceId, rows);
this.rows = rows;
}
#Override
public View getView(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.element, null);
}
Element row = rows.get(position);
if (row == null) {
return v;
}
v.setTag(row);
type = row.getType();
boolean isSectionType = type == Element.typeEnum.DIV118SECTION || type == Element.typeEnum.APPASECT ||
type == Element.typeEnum.APPBSECT || type == Element.typeEnum.AZSECT;
TextView title = (TextView) v.findViewById(R.id.title);
TextView body = (TextView) v.findViewById(R.id.body);
if (isSectionType) {
body.setMaxLines(5000);
}
title.setText(row.getTitle());
if (row.getBody() != null) {
body.setText(row.getBody());
}
if (type == Element.typeEnum.SEARCHLISTHEADER) {
v.setBackgroundColor(Color.rgb(230, 230, 250));
title.setBackgroundColor(Color.rgb(230, 230, 250));
body.setBackgroundColor(Color.rgb(230, 230, 250));
star.setBackgroundColor(Color.rgb(230, 230, 250));
}
return v;
}
}
==ELEMENT LAYOUT==
<TextView
android:id="#+id/body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
style="#style/ListItemSubTitle" />
</LinearLayout>
It would be much easier if you would post a getView() method here.
From what I can tell, you might be using recycled views wrong.
Check if background is changed to something if property is not set.
For example:
if (peoperty.isSet()) {
changeBackGround();
}
Just by itself will be wrong if you are reusing convertView, since the background will stay the same color how it was, when this view was used for a different row.
must be something like:
if (peoperty.isSet()) {
changeBackGround();
} else {
changeBackgroundToSomethingNeutral()
}
I took a liberty to rewrite that code for you, since you make too much weird stuff. Here's what I think is a optimized working code for your situation (didn't test any of it, but should work):
private class ElementAdapter extends ArrayAdapter<Element> {
public ElementAdapter(Context context, int textViewResourceId, ArrayList<Element> rows) {
super(context, textViewResourceId, rows);
this.rows = rows;
}
private final ArrayList<Element> rows;
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
ViewsHolder holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.element, parent, false);
holder = new ViewsHolder();
holder.title = (TextView) v.findViewById(R.id.title);
holder.body = (TextView) v.findViewById(R.id.body);
convertView.setTag(holder);
} else {
holder = (ViewsHolder) convertView.getTag();
}
final Element row = rows.get(position);
final Element.typeEnum type = row.getType();
if (type.equals(Element.typeEnum.DIV118SECTION) || type.equals(Element.typeEnum.APPASECT) ||
type.equals(Element.typeEnum.APPBSECT) || type.equals(Element.typeEnum.AZSECT)) {
body.setMaxLines(5000);
}
holder.title.setText(row.getTitle());
if (row.getBody() != null) {
holder.body.setText(row.getBody());
} else {
holder.body.setText("");
}
if (type == Element.typeEnum.SEARCHLISTHEADER) {
convertView.setBackgroundColor(Color.rgb(230, 230, 250));
holder.title.setBackgroundColor(Color.rgb(230, 230, 250));
holder.body.setBackgroundColor(Color.rgb(230, 230, 250));
//star.setBackgroundColor(Color.rgb(230, 230, 250)); // Where did that come from?
}
return convertView;
}
private final class ViewsHolder {
public TextView title;
public TextView body;
}
}
Couple of notes on the original code:
if (row == null) {
return v;
}
is wrong. You shouldn't have any null elements in your list for any position in the list. Even if you have, you shouldn't just throw some random view for the row. What you are doing here, is returning "v", that can very well be (and probably will be) some recycled old row, that still displays old data, and that's going to confuse the user. I made an assumption that you won't have any empty elements when wrote the code.
if (row.getBody() != null) {
body.setText(row.getBody());
}
Is almost ok, but again, if you are reusing convertView (which is some random previous row that isn't displayed anymore), then if body is actually null you will just be displaying the old data, which again will confuse the user. If body is null, just set the string empty.
P.S.
I recommend you to watch this for tips and tricks about how to work with ListView: The world of ListView

Categories

Resources