I have a chat functionality in my app and I want to group sequential messages from the same user, something similar to that picture bellow:
So, I have my message_row.xml with one TextView to display the content of one message. My Message model has an ArrayList with all the sequential messages from the same user.
My CustomMessageAdapter.java looks like that:
if (inflater == null) {
inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
// [...]
if (getItemViewType(position) == MINE) {
convertView = inflater.inflate(R.layout.my_message_row, null);
} else {
convertView = inflater.inflate(R.layout.message_row, null);
}
// [...]
TextView senderName = (TextView) convertView.findViewById(R.id.senderName);
TextView content = (TextView) convertView.findViewById(R.id.content);
senderName.setText(message.getSenderName());
//I want to do something like this:
for (int i = 0; i < message.getMessages().size(); i++) {
// display a different TextView for each different message...
}
// [...]
return convertView;
What I cannot do is display a new TextView for each different messages in messages arraylist.
As inflating view classes is expensive and updating them can also be expensive it would be wise to take a simpler approach to this problem. I would suggest simply concatenating the strings with newlines delimiters and apply the entirety to the message.
Further Thoughts
Should you need complex control over the display of the text, consider using Spannable to format your text inside a single TextView.
http://developer.android.com/reference/android/text/Spannable.html
As demonstrated here: Is there any example about Spanned and Spannable text
spanbuffer = new TextView(context);
spanbuffer.setText(newText, TextView.BufferType.SPANNABLE);
Spannable s = (Spannable) spanbuffer.getText();
s.setSpan(new ForegroundColorSpan(Color.RED), 0, newText.length() - 1, 0);
this.append(s);
You can control several text attributes in this manner.
I would suggest in your message_row.xml instead of inflating TextView inflate linear layout and in that linear layout add TextViews programmatically for each iteration in
for (int i = 0; i < message.getMessages().size(); i++) {
// display a different TextView for each different message...
}
But, since inflating is expensive i would suggest to split your messages with new row and add them all in one TextView.
in your array create a id for two persons of conversation when you paint in the adapter indentify waths id is and paint the texview corresponding something like this:
if (iduser.equals("1")){ textviewUser.setText("some text");}else{texviewOther.setText("sometext")}
add dynamic textview to your row,
private void createTextView(LayoutInflater inflater, LinearLayout llTv) {
llTv.removeAllViews();
int i = 0;
while (i < mMessages.size()) {
View llTv= inflater.inflate(R.layout.textTtem, llTv, false);
TextView textView = (TextView) llTv.findViewById(R.id.tv_message);
textView.setText(mMessage.get(i).getMesage());
textView.setOnClickListener(this);
llTv.addView(textView);
i++;
}
}
your textitem should look like this,
<?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:animateLayoutChanges="true"
android:orientation="vertical">
<TextView
android:id="#+id/tv_message"
android:layout_width="match_parent"
android:padding="..."
android:layout_margin="..."
android:layout_height="wrap_content"
android:textSize="14sp"/>
</LinearLayout>
you can send inflater like this:
mInflater = LayoutInflater.from(mContext);
Related
I need to generate TextView inside a layout from a random, API generated, ArrayList. I cant seem to find a way in which they appear in one line, like a String, one after the other and also shift below once the line has reached the max layout width limit. I want each TextView to be sperate as I want to click them.
Would like to achieve something like this...
This is the current code but the line stops and I don't know how to shift it below. Currently I am using a relative layout as the base layout but it is not necessary.
for (int i = 0; i < abc.size(); i++) {
titleText = new TextView(this);
titleText.setId(i);
titleText.setText(abc.get(i));
relativeLayout.addView(titleText, i);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(10, 10, 10, 10);
layoutParams.addRule(RelativeLayout.RIGHT_OF, titleText.getId() - 1);
titleText.setLayoutParams(layoutParams);
titleText.setTextColor(getResources().getColor(R.color.teal_700));
tvArray.add(titleText);
}
Example
In your xml
<LinearLayout
android:id="#+id/my_ll"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
</LinearLayout>
And write code in yourjava.class
LinearLayout my_ll = (LinearLayout) findViewById(R.id.my_ll);
for(int i=0;i<your_number_of_textviews;i++)
{
TextView text = new TextView(this);
text.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
text.setText(""+i);
my_ll.addView(text);
}
I think this solution would have solved it. First before the loop retrieve somewhere the width of the screen by using -
Point screenSizePoint = new Point();
// this gives you the furthest point from 0,0 e.g. 1440x3168
getWindowManager().getDefaultDisplay().getSize(screenSizePoint);
int sum = 0;
Then you retrieve your abc list somewhere.
And then you always compute the width of the current TextView by using this
titleText.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int width = titleText.getMeasuredWidth();
sum += width;
if (sum >= screenSizePoint.x) {
// shift to next line
}
and sum it up with the width of text views placed before and if the width is bigger than the screen width you just begin on another line.
This is not a problem, but more of an efficiency question. I have multiple TextViews (2 of them) in my XML Layout of my Android App. My question is that can I select multiple TextViews, findViewById multiple TextViews on a single line?
Is this valid for my question?
TextView title, darkThemeTitle = findViewById(R.id.title); findViewById(R.id.darkThemeTitle);
When you use TextView title, darkThemeTitle = findViewById(R.id.title); findViewById(R.id.darkThemeTitle); in your code .
This line TextView title, darkThemeTitle = (TextView) findViewById(R.id.title); will show that Variable 'title' might not have been initialized .So title never initialized in the code .
And findViewById(R.id.tab_layout); will return View in your code .And it never return darkThemeTitle in your code .
And you can do like this .
TextView title = (TextView) findViewById(R.id.title); TextView darkThemeTitle = (TextView) findViewById(R.id.darkThemeTitle);
Another way
TextView title = null, darkThemeTitle = null;
TextView[] textViews = {title, darkThemeTitle};
Integer[] ids = {R.id.title, R.id.darkThemeTitle};
for (int i = 0; i < textViews.length; i++) {
textViews[i] = (TextView) findViewById(ids[i]);
}
The only recommendation is to use template ids to find views:
TextView[] themedViews = new int[NUMBER_OF_THEMES];
for (int k = 0; k < NUMBER_OF_THEMES; k++)
themedViews[k] = findViewById(context.getResources().getIdentifier("some_prefix" + String.valueOf(k), "id", packageName));
This will find all views for current activity.
Or you can use parent.findViewById to find subviews of a specified view.
I discourage you from doing this because it's more difficult for other programmers to read and doesn't save you much time typing. Use:
TextView title = findViewById(R.id.title), darkThemeTitle = findViewById(R.id.darkThemeTitle);
Have you tried using ButterKnife? This library help you with Dependency Injection so you don't need to worry about the findViewById. Just call the #BindView(view_id) and the type and name of the variable that you want to bind.
#BindView(R.id.title)
TextView title;
#BindView(R.id.darkThemeTitle)
TextView darkThemeTitle;
Remember that you need to add the dependency in your build.gradle file
compile 'com.jakewharton:butterknife:8.8.1'
And call the bind method in the onCreate of your activity
ButterKnife.bind(this);
I want to dynamically add a list of items inflated from layout XMLs to a LinearLayout within a ScrollView. This involves calling findViewById numerous times for each item, which I have been told is very expensive. How can I recycle my views to avoid this?
I would use a ListView, except each item can have any number of content and comment elements within it and I have been told in Google I/O 2010 - The world of ListView that ListViews should not be overcomplicated.
Here is my code for the relevant method:
private void addQuotes(NodeList quoteNodeList){
LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for(int i = 0; i < quoteNodeList.getLength(); i++){
Log.i(getClass().getSimpleName(), "Adding quote number " + i);
Node quoteNode = quoteNodeList.item(i);
// Inflate view to hold multiple content items, single additional content TextView, and multiple comment items
LinearLayout quote = (LinearLayout) layoutInflater.inflate(R.layout.quote, null);
LinearLayout contentList = (LinearLayout) quote.findViewById(R.id.dialog_list);
TextView additionalContent = (TextView) quote.findViewById(R.id.additional_content);
LinearLayout commentList = (LinearLayout) quote.findViewById(R.id.comment_list);
// Get data for content items and add to contentList
NodeList contentNodeList =
XmlUtilities.getChildWithTagName(quoteNode, NetworkHelper.XML_TAG_QUOTE_CONTENT).getChildNodes();
for(int contentIndex = 0; contentIndex < contentNodeList.getLength(); contentIndex++){
Log.i(getClass().getSimpleName(), "Adding content number " + contentIndex + " to quote number " + i);
Node contentItemNode = contentNodeList.item(contentIndex);
// Inflate view to hold name and dialog TextViews
LinearLayout contentItem = (LinearLayout) layoutInflater.inflate(R.layout.dialog_item, null);
TextView nameView = (TextView) contentItem.findViewById(R.id.speaker);
TextView dialogView = (TextView) contentItem.findViewById(R.id.dialog);
// Get data and insert into views
String nameString = XmlUtilities.getChildTextValue(contentItemNode, NetworkHelper.XML_TAG_QUOTE_CONTENT_ITEM_NAME);
String dialogString = XmlUtilities.getChildTextValue(contentItemNode, NetworkHelper.XML_TAG_QUOTE_CONTENT_ITEM_DIALOG);
nameView.setText(nameString + ":");
dialogView.setText("\"" + dialogString + "\"");
// Add to parent view
contentList.addView(contentItem);
}
// Get additional content data and add
String additionalContentString = XmlUtilities.getChildTextValue(
quoteNode, NetworkHelper.XML_TAG_QUOTE_ADDITIONAL_CONTENT);
Log.d(getClass().getSimpleName(), "additionalContentString: " + additionalContentString);
additionalContent.setText(additionalContentString);
// TODO: Get comment data and add
// Add everything to ScrollView
mQuoteList.addView(quote);
Log.d(getClass().getSimpleName(), "additionalContent: " + additionalContent.getText());
}
The parameter quoteNodeList is an org.w3c.dom.NodeList.
XmlUtilities is a helper class I wrote myself but the methods should be self explanatory.
Any help is much appreciated.
This involves calling findViewById numerous times for each item, which I have been told is very expensive.
Not nearly as expensive as the inflation itself. If you have 10,000 comments, you will inflate 10,000 rows, putting you at risk of running out of heap space and/or freezing your UI entirely too long.
How can I recycle my views to avoid this?
You can't, given your structure.
I would use a ListView, except each item can have any number of content and comment elements within it and I have been told in Google I/O 2010 - The world of ListView that ListViews should not be overcomplicated.
Since your proposed solution will be significantly more "overcomplicated" -- by two or three orders of magnitude -- use a ListView.
Alright, I'm trying to create textviews dynamically with strings i have in an array. Everything works right now besides when i create the textviews instead of them going down they each stay on the same line and run off the screen. I want each textview i create under the next. Code Below works just need it to create under the next instead all on one line.
public void GenList(){
DataBase entry = new DataBase(this);
entry.open();
String data = entry.getData();
int datanumber = entry.FindShit();
if(datanumber == 0 || datanumber == 1){
setContentView(R.layout.nowordlist);
}else{
int length = entry.results.length;
View linearLayout = findViewById(R.id.sayLinear);
for(int i=0;i<length;i++)
{
TextView value = new TextView(this);
value.setText(entry.results[i]);
value.setId(i);
value.setTextSize(50);
value.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
((LinearLayout) linearLayout).addView(value);
}
}
}
You'll need to change the orientation of the LinearLayout (R.id.sayLinear) you're adding the TextViews to. By default the orientation is set to 'horizontal', which will make the TextViews appear next to each other on a single line. Try changing it to 'vertical':
<LinearLayout
<!-- other attributes -->
android:orientation="vertical" />
I'm attempting to create a few radio buttons and add them a RadioGroup dynamically. When I use the LayoutInflater method of pulling in the xml and adding it to the current view, everything works fine. The correct radio buttons show up.
However when I try to cast the View that LayoutInflater.inflate returned to a RadioButton (so I can setText), I get a force close with a java.lang.ClassCastException.
for (int i = 0; i < options.length(); i++) {
JSONObject option = options.getJSONObject(i);
View option_view = vi.inflate(R.layout.poll_option, radio_group, true);
option_view.setId(i);
RadioButton rb = (RadioButton) option_view.findViewById(i);
rb.setText(option.getString("response"));
}
poll_option.xml:
<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
android:text="RadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
The problem is you're not getting the views you think you're getting. LayoutInflater.inflate() called with a supplied root view means the view returned to you is THAT root view (not the inflated view). The method in which you are calling it inflates a new RadioButton and attaches it to the Group, but the return value (option_view) is the group itself, not the individual item. Since you need to play with the view before attaching it to the group, I'd recommend code like this (which works):
//I added these for posterity, I'm sure however you get these references is fine
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
RadioGroup radio_group = new RadioGroup(this);
//Get the button, rename it, then add it to the group.
for(int i = 0; i < options.length(); i++) {
JSONObject option = options.getJSONObject(i);
RadioButton option_view = (RadioButton)vi.inflate(R.layout.poll_option, null);
option_view.setText(option.getString("response"));
radio_group.addView(button);
}
Editorial Note:
Just my $0.02, for such a simple layout, running this inflation process over and over in a loop may be a bit too much (inflation is expensive). You could easily create the same RadioButton in code, and add it with your LayoutParams, like:
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
for (int i = 0; i < options.length(); i++) {
RadioButton option_view = new RadioButton(this);
option_view.setText(option.getString("response"));
radio_group.addView(option_view, params);
}
This code I didn't test, but it should be pretty close :p
Hope that Helps!
You probably want to use findViewById and locate the radio button in the inflated view. Something like:
RadioButton rb = (RadioButton)option_view.findViewById(R.id.yourButtonId);
See http://developer.android.com/reference/android/view/View.html#findViewById(int)
you want to radiobutton.setId(INT)
and then later get it by findViewById() to get the button.
The setID(Int) should be used when you dynamically create the button. You can now access it later with findViewById.