Why does the text in ListView item disappear when scrolling? - android

I have ListView which contains items containing a View and a TextView:
chat_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="90dp"
android:id="#+id/chat_message_wrapper"
xmlns:pixlui="http://schemas.android.com/apk/com.neopixl.pixlui">
<View
android:id="#+id/message_indicator"
android:layout_width="10dp"
android:layout_height="90dp"/>
<com.neopixl.pixlui.components.textview.TextView
android:id="#+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/midnight_blue"
android:padding="14dp"
android:layout_centerVertical="true"/>
</RelativeLayout>
And this is getView() in my Adapter class:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.chat_item, null);
}
ChatMessageItem item = getItem(position);
if(item != null) {
TextView messageTextView = (TextView) view.findViewById(R.id.message);
ViewGroup messageWrapper = (ViewGroup) view.findViewById(R.id.chat_message_wrapper);
View messageIndicatorView = view.findViewById(R.id.message_indicator);
if(messageTextView != null) {
messageTextView.setText(String.valueOf(item.getMessage()));
RelativeLayout.LayoutParams textParams = (RelativeLayout.LayoutParams)messageTextView.getLayoutParams();
RelativeLayout.LayoutParams indicatorParams = new RelativeLayout.LayoutParams(10, ViewGroup.LayoutParams.MATCH_PARENT);
RelativeLayout.LayoutParams messageWrapperParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 90);
if(item.getSender() == ChatMessageItem.Sender.ME) {
textParams.addRule(RelativeLayout.LEFT_OF, R.id.message_indicator);
indicatorParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
messageIndicatorView.setBackgroundColor(getContext().getResources().getColor(R.color.light_blue));
}
else if(item.getSender() == ChatMessageItem.Sender.OTHER) {
textParams.addRule(RelativeLayout.RIGHT_OF, R.id.message);
indicatorParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
messageIndicatorView.setBackgroundColor(getContext().getResources().getColor(R.color.silver));
}
messageTextView.setLayoutParams(textParams);
messageIndicatorView.setLayoutParams(indicatorParams);
messageWrapper.setLayoutParams(messageWrapperParams);
}
}
return view;
}
I added the following line to at least keep the height of the items constant, which used to also change (which will probably give me problems later, as the content is dynamic, but ok..)
RelativeLayout.LayoutParams messageWrapperParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 90);
The problem is when I scroll up and down more and more text disappears. messageIndicatorView does not disappear though, only the text disappears. If I keep scrolling enough, all text will disappear. What am I doing wrong and how can I fix it? Thanks. (I know I must use a ViewHolder for better performance, but I will do that when this problem is fixed)

The problem is that as the ListView recycles and reuses the views, conflicting rules are added to the RelativeLayout.LayoutParams instances for the #id/message TextView. In particular this happens whenever a view for a "ME" message is reused for an "OTHER" message, or vice-versa.
RelativeLayout.LayoutParams keeps a list of rules (actually an array by verb, so that you cannot add, say, two LEFT_OF rules -- but any other combination is possible, including problematic ones).
The easiest solution is to use a new RelativeLayout.LayoutParams object each time, by changing this line:
RelativeLayout.LayoutParams textParams =
(RelativeLayout.LayoutParams)messageTextView.getLayoutParams();
into:
RelativeLayout.LayoutParams textParams =
new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
There are other solutions, such as having different actual layouts for each kind of ListView item (via getItemViewType()) but it's probably overkill in this case. However if the differences between the two kinds of views were greater, it might be worth considering.

Related

Change gravity programmatically on ListView item

I would like to have a ListView in which some items render on the left, and some on the right. I don't really know how to make this happen though. I was thinking of calling setGravity(Gravity.RIGHT) on the View my adapter's getView() method returns, but that method apparently exists only for ViewGroup, which makes me think it would actually change the gravity of the object's contents. It would look something like this:
getView(int position, View toReturn, ViewGroup parent) {
// Holder pattern, *yawn*
if (needsToBeOnRight) {
toReturn.setGravity(Gravity.RIGHT)
// or whatever it is I'm actually supposed to do
}
return toReturn;
}
The View represented by toReturn is expected to be a RelativeLayout, so I supppose in theory I could cast it to one and try the above, but as discussed above, I doubt that will work. How should I proceed?
Turns out I was almost there. In order to make it work, I had to wrap the view I want to right-or-left-orient in a FrameLayout. That would make toReturn in the above code a FrameLayout.
ViewHolder holder = (ViewHolder) toReturn.getTag();
// Get the view's LayoutParams. In this case, since it is wrapped by a FrameLayout,
// that is the type of LayoutParams necessary.
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) holder.viewThatMightNeedToBeOnRight.getLayoutParams();
// Set gravity to right or left as necessary within the LayoutParams.
if (params != null) {
if (needsToBeOnRight) {
params.gravity = Gravity.RIGHT;
} else {
params.gravity = Gravity.LEFT;
}
// Assign the newly edited LayoutParams to the view.
holder.viewThatMightNeedToBeOnRight.setLayoutParams(params);
}
LayoutParams lay = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
lay.gravity = Gravity.END;
mListView.setLayoutParams(lay);

Prevent GridView reset scroll to top when element is tapped?

I have a GridView that is populated by all apps installed on the device. The user can select certain apps here. I want the selected apps to be opaque and non-selected to be partially transparent. I did this with the following:
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout linearLayout = new LinearLayout(mContext);
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setGravity(Gravity.CENTER_HORIZONTAL);
LinearLayout.LayoutParams layoutParamsText = new LinearLayout.LayoutParams(150, 90);
ImageView imageView = new ImageView(mContext);
TextView appLabel = new TextView(mContext);
final OurAppInfo info = (OurAppInfo) getItem(position);
if(!installedApplications.contains(info)){
AlphaAnimation alpha = new AlphaAnimation(0.4F, 0.4F);
alpha.setDuration(0);
alpha.setFillAfter(true);
linearLayout.startAnimation(alpha);
}
String appName = info.label;
if (appName.length() > 25) {
appName = appName.substring(0, 25);
appName = appName + "...";
}
appLabel.setText(appName);
appLabel.setTextColor(Color.BLACK);
appLabel.setGravity(Gravity.CENTER_HORIZONTAL);
appLabel.setTypeface(null, Typeface.BOLD);
imageView.setImageDrawable(info.drawableAppIcon);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new GridView.LayoutParams(110, 110));
appLabel.setTextSize(15);
linearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (installedApplications.contains(info)){
installedApplications.remove(info);
receiveUpdate(installedApplications, false, false);
} else {
installedApplications.add(info);
Collections.sort(installedApplications);
receiveUpdate(installedApplications, false, false);
}
}
});
appLabel.setLayoutParams(layoutParamsText);
linearLayout.addView(imageView);
linearLayout.addView(appLabel);
return linearLayout;
}
This is part of the GridAdapter extends BaseAdapter. The code works as expected, when I tap on an app it is either removed from or added to the list and according to transparency is set. However, whenever I tap on an element in the GridView, the view is reset and I am brought to the top of the scrollable GridView. Obviously, this isn't a problem for a small number of apps, but if you're selecting apps near the XYZ letters, every time you select one you are brought back to ABC. How can I prevent this from happening?
It looks like you're refreshing the adapter whenever you make changes that makes the grid go back to initial position. You could try saving and restoring the position before making any changes to the adapter.
//Before refreshing the adapter you get both X and Y position
int xPos = grid.getScrollX();
int yPos = grid.getScrollY();
Then you update your adapter.
After the adapter is recreated you restore the grid position:
grid.scrollTo(xPos, yPos);
You could also use (everytime possible) the method notifyDataSetChanged() instead of creating a new adapter.
Hope it helps.
Check all child views for automatic height or width.
I guess gridview calculates size of this views whenever you change data.
This was solution in my case.
In my case changed this:
<ImageView
android:id="#+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
To this:
<ImageView
android:id="#+id/image"
android:layout_width="100dp"
android:layout_height="100dp" />

How to set gravity in programmatically in a relativelayout

How to set gravity in programmingcally in a relativelayout. I have a XML layout with name chat_viewer_message.xml as below:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dip"
android:paddingBottom="4dip"
android:gravity="left"
android:background="#drawable/chat_bg_in">
<ImageView
android:id="#+id/avatar"
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_marginLeft="4dip"
android:src="#drawable/avatar_1_1"
/>
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/avatar"
android:paddingLeft="4dip"
/>
</RelativeLayout>
And code view in programming is as below:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final int type = getItemViewType(position);
final View view;
if (convertView == null) {
final int resource = R.layout.chat_viewer_message;
view = activity.getLayoutInflater()
.inflate(resource, parent, false);
if (type == TYPE_MESSAGE)
((TextView) view.findViewById(R.id.text)).setTextAppearance(
activity, appearanceStyle);
} else
view = convertView;
final MessageItem messageItem = (MessageItem) getItem(position);
String name;
final String account = messageItem.getChat().getAccount();
final String user = messageItem.getChat().getUser();
final String resource = messageItem.getResource();
final boolean incoming = messageItem.isIncoming();
final String server_host = view.getResources().getString(
R.string.server_host);
if (isMUC) {
name = resource;
}
if (incoming) {
view.setBackgroundResource(R.drawable.chat_bg_in);
} else {
// I WANT TO GRAVITY TO RIGHT. HOW TO CODE? THANKS
view.setBackgroundResource(R.drawable.chat_bg_out);
}
return view;
}
See gravity at the relativelayout with id = background, the default gravity is LEFT, So I want to if !incoming the value gravity at relativelayout be RIGHT instead LEFT.
Thanks.
Cast view to container layout, in this case RelativeLayout, and use setGravity(int) on it:
if (incoming) {
view.setBackgroundResource(R.drawable.chat_bg_in);
} else {
// I WANT TO GRAVITY TO RIGHT. HOW TO CODE? THANKS
view.setBackgroundResource(R.drawable.chat_bg_out);
((RelativeLayout) view).setGravity(Gravity.RIGHT);
}
A word of caution (from the docs):
Note that since RelativeLayout considers the positioning of each child
relative to one another to be significant, setting gravity will affect
the positioning of all children as a single unit within the parent.
This happens after children have been relatively positioned.
There are two main ways to programmatically set the gravity in a RelativeLayout. You can either set the gravity for all child views (setGravity(int)) or set the gravity individually (params.addRule(int)). Note: These methods are not available for LinearLayouts.
To set the gravity for all children:
RelativeLayout relativeLayout = findViewById(R.id.myRelativeLaout);
relativeLayout.setGravity(Gravity.CENTER);
To set the gravity for a single view within a RelativeLayout:
MyView myView = findViewById(R.id.myView);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
myView.setLayoutParams(params);
Sources:
RelativeLayout.setGravity(int)
RelativeLayout.LayoutParams.addRule(int)
RelativeLayout.LayoutParams.removeRule(int)
It works perfectly for my case:
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.topMargin = 700;//in my case
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);//in my case
varButton.setLayoutParams(layoutParams);
You can use:
RelativeLayout.LayoutParams paramas = new RelativeLayout.LayoutParams();
params.gravity = Gravity.RIGHT;
view.setLayoutParams(params);
For more info about gravity parameters u can check this: Gravity
Use method, setGravity(int)
http://developer.android.com/reference/android/widget/RelativeLayout.html#setGravity(int)
It's better way to set child's gravity by using RelativeLayout.LayoutParams.
See this.
How to programmatically set the layout_align_parent_right attribute of a Button in Relative Layout?
Building up chat layout and manipulating it programmatically &
sDirectionHasmap contains direction of each message.
if (sDirectionHashMap.get(idsList.get(position)).equals("incoming")) {
holder.messageLayout.setGravity(Gravity.LEFT);
} else {
holder.messageLayout.setGravity(Gravity.RIGHT);
}

how to insert a new LinearLayout after a specific one everytime i click a button?

I want to build some sort of twitter application. In DashboardActivity i need to add a status box everytime i click the "Post" button.My dashboard xml looks like this:
<RelativeLayout>
<LinearLayout></LinearLayout> -->header layout
<LinearLayout></LinearLayout> -->some layout with some titles
<LinearLayout></LinearLayout> --> post status layout with the post button
<LinearLayout></LinearLayout> --> layout with a horizontal rule
<LinearLayout></LinearLayout> --> this is the layout with id "rootStatusBox" where i want to add the status box
</RelativeLayout>
Now, i want to be able to add a new LinearLayout after the horizontal rule layout everytime i click the "Post" button.
I tried something like this in my DashboardActivity:
postStatus.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
addUserStatusBox(firstname,lastname,status);
}});
And addUserStatusBox() looks like this:
public void addUserStatusBox(String firstname, String lastname,String status) {
LinearLayout rootStatusBox = (LinearLayout)findViewById(R.id.rootStatusBox);
LinearLayout userStatusBox = new LinearLayout(this);
userStatusBox.setOrientation(LinearLayout.HORIZONTAL);
userStatusBox.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
LinearLayout.LayoutParams layout = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
layout.setMargins(0, 300, 0, 0); // llp.setMargins(left, top, right, bottom);
userStatusBox.setLayoutParams(layout);
TextView friendName = new TextView(this);
TextView friendStatus = new TextView(this);
TextView dataCrearePost = new TextView(this);
friendName.setText(firstname+ " " + lastname);
friendName.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
friendName.setTextSize(10);
friendName.setTypeface(null, Typeface.BOLD);
friendStatus.setText(status);
friendStatus.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
llp.setMargins(-70, 20, 0, 0); // llp.setMargins(left, top, right, bottom);
friendStatus.setLayoutParams(llp);
friendStatus.setTextSize(10);
userStatusBox.addView(friendName);
userStatusBox.addView(friendStatus);
rootStatusBox.addView(userStatusBox);
}
This is working only for the first time when i add a status.I don't know how to add more posts after the horizontal rule layout and to be able to see the old posts below my new one.I would appreciate a little bit of help.Thank you
I would use a customized list view for this purpose.
You need to create the following:
Layout for ListItem: This represents single row in the list. You can customize it by creating separate layout for this. Say you create: listitem_post.xml
Adapter: Write an adapter by extending BaseAdapter class (say: PostsAdapter.java). Fill in all the overridden methods. Most importantly, in the getView() method, inflate the post_listitem. Assign that to convertView object (which is passed in as an argument).
public View getView(int index, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
convertView = inflater.inflate(R.layout.listitem_post, parent, false);
}
//Code other parts
return convertView;
}
Activity: In your xml code of activity, insert a ListView say listview_posts. In the java file for the activity, set adapter created in step 2 for listview_posts inside onCreate() method.
PostsAdapter postsListAdapter = new PostsAdapter();
ListView postsListView = (ListView) this.findViewById(R.id.listview_posts);
postsListView.setAdapter(postsListAdapter);
That is how you specify that each list element is listitem_post.
Follow this tutorial

Android findViewById on dynamically created textviews in custom list adapter does not seem to work

I'm building a configuration program which prints out value/item lists dynamically. Sometimes you will have one item to output, for example, a name, but other times you might have multiple items that you want outputted, for example, an ip address, username, password, etc.
The idea is to build up a list dynamically depending on the amount of parameters outputted. On a small screen the amount of parameters might be 2, but in landscape mode or on a tablet the amount of parameters might be much higher.
I have this working when I have a hard coded XML file with Android IDs, i.e. referring to
android:id="#+id/item1
but I'm struggling with doing this programmatically. Before the edit I was doing this with IDs but on advice I am now rather trying this with tags.
Here are all the relevant parts of the code. Is a multi-line list of parameters is outputted we branch to buildLayout to build up the custom layout:
public void processAsyncTask(String result) {
ConfigList list = getConfigList(result);
ArrayList<ConfigCollection> mParamsList = list.getAllItems();
// Once we have a list of parameters we build the layout according to size
buildLayout(mParamsList.size());
mMultiRowAdapter = new ParamsAdapter(this, R.layout.multi_row_config, mParamsList);
mCollectionListView.setAdapter(mMultiRowAdapter);
}
Here is buildLayout:
private void buildLayout(int size) {
TableRow tr = (TableRow)findViewById(R.id.row_multi_row);
for (int i = 0; i < size - 1; i++) {
TextView tv = new TextView(this);
tv.setId(i);
tv.setTag("id" + String.valueOf(i));
tv.setTextAppearance(this, android.R.style.TextAppearance_Large);
tv.setText("id" + String.valueOf(i));
tr.addView(tv);
}
}
And here is how I am trying to refer to the IDs:
/**
* Custom Array Adapter with a view that displays multi-line item/value pairs
*/
private class ParamsAdapter extends ArrayAdapter<ConfigCollection> {
private ArrayList<ConfigCollection> objectList;
public ParamsAdapter(Context context, int textViewResourceId, ArrayList<ConfigCollection> objectList) {
super(context, textViewResourceId, objectList);
this.objectList = objectList;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.multi_row_config, null);
// It works with hard coded IDs, e.g.:
TextView tv1 = (TextView) v.findViewById(R.id.item1);
TextView tv2 = (TextView) v.findViewById(R.id.item2);
// BELOW DOES NOT, tv1 and tv2 is null after assignment
TextView tv1 = (TextView) v.findViewWithTag("id0");
TextView tv2 = (TextView) v.findViewWithTag("id1");
Here is the XML file that I am using to test with:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
android:id="#+id/table_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:id="#android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TableRow
android:id="#+id/row_multi_row"
>
<TextView
android:id="#+id/item1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="item1"
android:padding="3dip"
/>
<TextView
android:id="#+id/item2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="item2"
android:padding="3dip"
/>
</TableRow>
</TableLayout>
EDIT:
I've isolated the problem further visible here with screenshots. It seems in buildLayout I am adding textviews but in the custom array adapter the layoutinflator does not find these fields. I've also updated the code to try and work with tags instead of IDs.
Debug from buildLayout. Notice four children, the two fixed in the XML code and two created
Debug from ParamsAdapter. Notice only two children at this stage where I am actually trying to access these objects.
If you are creating items programmatically, I would advise using the "tag" parameter. It's not as fast as id, but then you are not worried about speed for this - as long as you have the parent view you can "findViewWithTag" - see http://developer.android.com/reference/android/view/View.html - scroll down to findViewWithTag

Categories

Resources