Listview style Each Row - android

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

Related

ListView deliver wrong position value in getView(..)

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());

initialize RelativeLayout in this getView, programmatic view generation

I am using a getView in an adapter where I am creating an imageview and making that equal to convertView where the view has already been initialized before. It contains image thumbnails, some of which represent videos.
#Override
public View getView(int position, View convertView, ViewGroup container) {
// First check if this is the top row
if (position < mNumColumns) {
if (convertView == null) {
convertView = new View(mContext);
}
// Set empty view with height of ActionBar
//convertView.setLayoutParams(new AbsListView.LayoutParams(
// LayoutParams.MATCH_PARENT, mActionBarHeight));
return convertView;
}
// Now handle the main ImageView thumbnails
ImageView imageView;
if (convertView == null) { // if it's not recycled, instantiate and initialize
imageView = new RecyclingImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(mImageViewLayoutParams);
} else { // Otherwise re-use the converted view
imageView = (ImageView) convertView;
}
// Check the height matches our calculated column width
if (imageView.getLayoutParams().height != mItemHeight) {
imageView.setLayoutParams(mImageViewLayoutParams);
}
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
if(images.get(position - mNumColumns).getUriString().contains("video")){
//display video icon
}
else
{
//don't display video icon
}
// Finally load the image asynchronously into the ImageView, this also takes care of
// setting a placeholder image while the background thread runs
if (images != null && !images.isEmpty())
mImageFetcher.loadImage(images.get(position - mNumColumns).getUriString()/*.imageUrls[position - mNumColumns]*/, imageView);
return imageView;
}
The thumbnails do not have a "play" button on them to designate that they are videos, so in those cases I need to add a play button, programmatically.
Typically I use a viewholder pattern with an inflated layout, I am not doing that in this case because I actually don't want some things in memory.
So instead I want to programmatically make a RelativeLayout as the root view of each cell (mRelativeLayout = (RelativeLayout)convertView) and add the imageview and playbutton imageview into that convertview
How do I do that? It requires modification of this statement but I'm not sure how to initialize all the re-used views
} else { // Otherwise re-use the converted view
imageView = (ImageView) convertView;
}
I think the best approach here would be to use an Adapter that returns different types of views (by overriding getViewTypeCount() and getItemViewType()), for example as described in this answer.
That way you do not need to programmatically alter the returned views, at all. Just define two XML layouts and inflate/reuse either one or the other according to whether the item at that position has a video or not.
Not only would this be clearer, you wouldn't have the performance penalty of "transforming" one view into the other whenever a video-row is supplied as convertView for another item without one, or vice-versa
I would make your getView() always return a RelativeLayout object (which I call containerView below) to which you add your ImageView(s) as children.
The only complication here is that you need to give these children identifiers so that you can retrieve them from a recycled convertView later. Note that I used the built-in, static View.generateViewId() for this, which is API level 17. If you need it to work pre-API-level-17 you can create your own ids using unique integers (such as 1, 2, etc.) -- just make sure they aren't greater than 0x0FFFFFF. Update: I added code that I use for this below.
See the comments I added in several points below.
#Override
public View getView(int position, View convertView, ViewGroup container) {
// First check if this is the top row
if (position < mNumColumns) {
if (convertView == null) {
convertView = new View(mContext);
}
// Set empty view with height of ActionBar
//convertView.setLayoutParams(new AbsListView.LayoutParams(
// LayoutParams.MATCH_PARENT, mActionBarHeight));
return convertView;
}
// Now handle the main ImageView thumbnails
RelativeLayout containerView;
ImageView imageView;
ImageView videoIconView; // TODO: or whatever type you want to use for this...
if (convertView == null) { // if it's not recycled, instantiate and initialize
containerView = new RelativeLayout(mContext);
// TODO: The layout params that you used for the image view you probably
// now want to use for the container view instead...
imageView.setLayoutParams(mImageViewLayoutParams); // If so, you can change their name...
imageView = new RecyclingImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
//imageView.setLayoutParams(mImageViewLayoutParams); // This probably isn't needed any more.
// Generate an Id to use for later retrieval of the imageView...
// This assumes it was initialized to -1 in the constructor to mark it being unset.
// Note, this could be done elsewhere in this adapter class (such as in
// the constructor when mImageId is initialized, since it only
// needs to be done once (not once per view) -- I'm just doing it here
// to avoid having to show any other functions.
if (mImageId == -1) {
mImageId = View.generateViewId();
}
imageView.setId(mImageId);
containerView.addView(imageView, RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
// NOTE: At this point, I would personally always just add the video icon
// as a child of containerView here no matter what (generating another unique
// view Id for it, mVideoIconId, similar to how was shown above for the imageView)
// and then set it to either VISIBLE or INVISIBLE/GONE below depending on whether
// the URL contains the word "video" or not.
// For example:
vidoIconView = new <whatever>;
// TODO: setup videoIconView with the proper drawable, scaling, etc. here...
if (mVideoIconId == -1) {
mVideoIconId = View.generateViewId();
}
videoIconView.setId(mVideoIconId);
containerView.addView(videoIconView, RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
final RelativeLayout.LayoutParams layout = ((RelativeLayout.LayoutParams)containerView.getLayoutParams());
layout.addRule(RelativeLayout.LayoutParams.CENTER_HORIZONTAL); // ... or whatever else you want
layout.addRule(RelativeLayout.LayoutParams.ALIGN_PARENT_BOTTOM); // ... or whatever else you want
} else {
// Otherwise re-use the converted view
containerView = (RelativeLayout) convertView;
imageView = containerView.findViewById(mImageId);
videoIconView = containerView.findViewById(mVideoIconId); // see comment above
}
// Check the height matches our calculated column width
if (containerView.getLayoutParams().height != mItemHeight) {
containerView.setLayoutParams(mImageViewLayoutParams);
}
if(images.get(position - mNumColumns).getUriString().contains("video")){
//display video icon
// see comment above, here you can probably just do something like:
videoIconView.setVisibility(View.VISIBLE);
}
else
{
//don't display video icon
videoIconView.setVisibility(View.GONE); // could also use INVISIBLE here... up to you.
}
// Finally load the image asynchronously into the ImageView, this also takes care of
// setting a placeholder image while the background thread runs
if (images != null && !images.isEmpty())
mImageFetcher.loadImage(images.get(position - mNumColumns).getUriString()/*.imageUrls[position - mNumColumns]*/, imageView);
return containerView;
}
Update:
In response the question in the comments, I use something like this (in my custom "ViewController" base class):
private static int s_nextGeneratedId = 1;
/**
* Try to do the same thing as View.generateViewId() when using API level < 17.
* #return Unique integer that can be used with setId() on a View.
*/
protected static int generateViewId() {
// AAPT-generated IDs have the high byte nonzero; clamp to the range under that.
if (++s_nextGeneratedId > 0x00FFFFFF)
s_nextGeneratedId = 1; // Roll over to 1, not 0.
return s_nextGeneratedId;
}
Note that you do not need a unique view id for every single cell in your grid. Rather, you just need it for each type of child view that you might want to access using findViewById(). So in your case, you're probably going to need just two unique ids. Since the view ids auto-generated from your xml layout files into your R.java typically are very large, I've found it convenient just to use low numbers for my hand-generated ids (as shown above).

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

Scrolling through ListView of ProgressBars

I'm working on an Android app that utilizes a ListView, in which each row is comprised of a text view and a progress bar. Things work smoothly unless the user has to scroll through a long list.
ProgressBars start taking on the progress of other ProgressBars not currently visible on the screen.
I understand that this is a common issue that stems from the implementation of GetView, but I'm wondering what the best course of action is to take with ProgressBars.
GetView:
public View getView(int position, View convertView, ViewGroup parent){
View row = convertView;
ViewWrapper wrapper;
ProgressBar myProgressBar;
int initProgress = myData.get(position).getProgress();
if (row == null){
LayoutInflater inflater = context.getLayoutInflater();
row = inflater.inflate(R.layout.row, null);
wrapper = new ViewWrapper(row);
row.setTag(wrapper);
}
else{
wrapper = (ViewWrapper)row.getTag();
}
RowModel model = getModel(position);
wrapper.getPid().setText(model.toString());
myProgressBar = wrapper.getProgressBar();
myProgressBar.setProgress(initProgress);
myProgressBar.setMax(100);
myProgressBar.setTag(new Integer(position));
return row;
}
ViewWrapper:
public class ViewWrapper {
View base;
TextView pid = null;
ProgressBar pb= null;
ViewWrapper(View base){
this.base = base;
}
TextView getPid(){
if(pid == null){
pid = (TextView)base.findViewById(R.id.pid);
}
return(pid);
}
ProgressBar getProgressBar(){
if(pb== null){
pb= (ProgressBar)base.findViewById(R.id.progressbar);
}
return(pb);
}
}
It seems that the issue is related to:
myProgressBar = wrapper.getProgressBar();
because that ProgressBar starts getting the behavior of a recycled ProgressBar. However, I want it to have its own behavior.
What's the best way to alleviate this? Thanks.
You may need to inflate the layout each time and not re-use the convertView that's passed in. This shouldn't be a problem unless you have A LOT of rows.
I had to implement a similar feature , here is what I did . I implemented the following outside getview
OnTouchListener - to listen to seekbar touch events
OnKeyListener - to listen to dpad and trakball event for the seekbar
I set these listeners for the seekbars from getView
Whenever the listeners were called , I would find the seekbars parent , then do the findviewbyid from the parent to the textview.
So now I have the textview which I have to update, and the seekbar. All I need to do was set the text from the array.
here is some code to help you.
private OnTouchListener touchListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
View parent = (View) v.getParent();
TextView textView = (TextView) parent
.findViewById(R.id.id_grade_tooltip);
if (textView == null) {
} else {
SeekBar seekBar = (SeekBar) v;
textView.setText(String.valueOf(seekBar.getProgress())
+ "%");
}
return false;
}
};

Categories

Resources