inflate view returns the old inflated result - android

I try to inflate a layout multiple times to be inserted inside another layout. Here is the code:
View oldView = null;
for (ProfileServiceCategory.ProfileService service : profileServiceCategory.getServices()) {
View view = LayoutInflater.from(context)
.inflate(R.layout.profile_service_list_item, viewHolder.rootView);
if(oldView == view) {
Log.d("test", "Error");
}
oldView = view;
TextView serviceName = (TextView) view.findViewById(R.id.profile_services_name);
serviceName.setText(service.getServiceValue());
}
The problem that I'm facing is the view variable that is being returned by inflate method is always the first inflated view. To be clear I added a Log.d , if everything worked as expected it should have been never called but it does hit.
I checked the view hierarchy and I can conform that new views in fact has been added to rootView but the reference that I get is for older view.

One of my colleague suggested a workaround by adding the view manually to container. Here is the edited code:
LayoutInflater layoutInflater= LayoutInflater.from(context);
for (ProfileServiceCategory.ProfileService service : profileServiceCategory.getServices()) {
View view = layoutInflater.inflate(R.layout.profile_service_list_item, null);
TextView serviceName = (TextView) view.findViewById(R.id.profile_services_name);
serviceName.setText(service.getServiceValue());
viewHolder.rootView.addView(view);
}
When null is getting passed to inflate method as second argument a new view gets returned each time. The view can be inserted inside the container by addView method.
This is obviously a bug in android SDK.

LayoutInflater.from(context).inflate(R.layout.profile_service_list_item,
viewHolder.rootView);
is always returning the same inflater service. so i think you have to take copy of layoutInflater using cloneInContext
View view = LayoutInflater.from(this)
.cloneInContext(R.layout.layout_fullscreen_image, viewHolder.rootView);

Related

what happens when a view id is used for setContentView()

The Android developer guide seems to suggest that Activity.setContentView() can only be called with a layout ID (R.layout.*). However, I can see view IDs (R.id.*) being used to call the method. For example, in org/xbmc/android/widget/slidingtabs/SlidingTabActivity.java of XBMC, I can see the following code:
private void ensureTabHost() {
if (mTabHost == null) {
this.setContentView(R.id.slidingtabhost);
}
}
So, what does it mean to call setContentView() with a view ID? Thanks!
Additional question based on comment - is "setContentView(viewId);" equivalent to "View v = findViewById(viewId); setContentView(v);"?
Not
that Activity.setContentView() can only be called with a layout ID (R.layout.)
Just any view id can be called by the setContentView().
And layout is also a view!
I think the document should say:Set the activity content from a view(not only a layout) resource. The resource will be inflated, adding all top-level views to the activity. Actually ,it works like this: If you make a setConentView(R.layout.my_layout); then android os will do the following works:
LayoutInflater inflater= (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.my_layout, null);
setConentView(layout);
if you make a setContentView(R.id.myview);it is also the same way to inflate.
LayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View myview = inflater.inflate(R.id.myview, null);
setConentView(myview); `
So I say they are the same.

How to inflate multiple instances of a layout with the same id inside an inflated layout

I have a LinearLayout with many nested LinearLayouts and TextViewss
My main activity inflates the main LinearLayout,
Then I load data from a server and based on the data received, I add multiple Layouts in a place holder (LinearLayout)
This is simple a news page where I load Images associated with the news and place it inside an initially empty LinearLayout.
Each Image has the following info: Title(TextView), Date(TextView), Image(ImageView) so what I actually do is the following:
*Please notice that this is only the essential coded in the question I elemenated all the try -> catch ... if/else ....etc
public void addImages(JSONArray images){
ViewGroup vg = (ViewGroup) findViewById(R.id.imagesPlaceHolder);
// loop on images
for(int i =0;i<images.length;i++){
View v = getLayoutInflater().inflate(R.layout.image_preview,vg);
// then
I think that here is the problem
ImageView imv = (ImageView) v.findViewById(R.id.imagePreview);
TextView dt = (TextView) v.findViewById(R.id.dateHolder);
TextView ttl = (TextView) v.findViewById(R.id.title);
// then
dt.setText("blablabla");
ttl.setText("another blablabla");
// I think the problem is here too, since it's referring to a single image
imv.setTag( images.getJSONObject(i).getString("image_path").toString() );
// then Image Loader From Server or Cache to the Image View
}
}
The code above works good for a single image
But for multiple images the Image Loader doesn't work I guess it's because all ImageViews (Inflated multiple times) have the same ID
When you provide a ViewGroup to be used as the parent, the View returned by inflate() is this parent (vg in your case) and not the newly created View. Therefore, v points toward the ViewGroup vg and not toward the newly created View and as all of your children have the same id, the same subviews (imv, dt, ttl) are returned each time.
Two solutions. The first one is to change their id right after you are finished with them, before the next iteration. Therefore, on the next creation at the beginning of the next iteration, the newly created Views will have a different IDs from the older Views because they will still use the old constant defined in R.
The other solution would be to add the parameter false to the call to inflate() so that the newly created view will not be attached to the ViewGroup and will then be returned by the inflate() function instead of the ViewGroup. The rest of your code will then works as attended with the exception that you will have to attach them to the ViewGroup at the end of the iteration.
Notice that you still need to provide a ViewGroup because it will be used to determine the value of the LayoutParams.
I had the same problem, and based on the answer from #SylvainL, here'a a working solution:
// myContext is, e.g. the Activity.
// my_item_layout has a TextView with id='text'
// content is the parent view (e.g. your LinearLayoutView)
// false means don't add direct to the root
View inflated = LayoutInflater.from(myContext).inflate(R.layout.my_item_layout, content, false);
// Now, before we attach the view, find the TextView inside the layout.
TextView tv = (TextView) inflated.findViewById(R.id.text);
tv.setText(str);
// now add to the LinearLayoutView.
content.addView(inflated);
Is there a reason why the ImageView in the layout XML needs to have an ID? Could you erase the android:id attributes from the image_preview.xml layout and then simply iterate through the children of the inflated LinearLayout? For example:
ViewGroup v = (ViewGroup)getLayoutInflater().inflate(R.layout.image_preview,vg);
ImageView imv = (ImageView) v.getChildAt(0);
TextView dt = (TextView) v.getChildAt(1);
TextView ttl = (TextView) v.getChildAt(2);
I inflate XML-Layout with dynnamic and get text of id
private val onAddView = View.OnClickListener {
val parent = viewForm.findViewById<LinearLayout>(R.id.layout_parent)
LayoutInflater.from(activity).inflate(R.layout.layout_child, parent) // layout_child has id "tv_attribute"
}
private val onSave = View.OnClickListener {
val parent = viewForm.findViewById<LinearLayout>(R.id.layout_parent)
for (i in 0 until parent.childCount) {
val getText = parent.getChildAt(i).findViewById<TextView>(R.id.tv_attribute).text
}
}

Changing View of items in an Adapter linked to a ListView during app execution

I'm trying to give the users of my app the option to change how they want their results displayed.
I've created a different layout item for each view and extended from BaseAdapter like so:
public View getView(int index, View recycledCompatibleView, ViewGroup parent)
{
// Just in case the view can be reused
View toReturn = recycledCompatibleView;
if(toReturn == null)
{
LayoutInflater inflator = LayoutInflater.from(parent.getContext());
toReturn = inflator.inflate(layoutId, null);
}
...
}
public void setResultsListStyle(int layoutId)
{
this.layoutId = layoutId;
}
Calling notifyDataSetChanged() is (observable through debug) refreshing the view because the new view is being inflated and returned from getView() method.
However the view on screen is not changing...
Is there something I'm missing ?
The problem here is that ListView may cache already inflated Views that are of old "format". You need to somehow reset this View "cache". For this you can use next trick:
mListView.setAdapter(mListView.getAdapter());
Instead of calling notifyDataSetChanged().

ListView in ArrayAdapter order get's mixed up when scrolling

I have a ListView in a custom ArrayAdapter that displays an icon ImageView and a TextView in each row. When I make the list long enough to let you scroll through it, the order starts out right, but when I start to scroll down, some of the earlier entries start re-appearing. If I scroll back up, the old order changes. Doing this repeatedly eventually causes the entire list order to be seemingly random. So scrolling the list is either causing the child order to change, or the drawing is not refreshing correctly.
What could cause something like this to happen? I need the order the items are displayed to the user to be the same order they are added to the ArrayList, or at LEAST to remain in one static order. If I need to provide more detailed information, please let me know. Any help is appreciated. Thanks.
I was having similar issues, but when clicking an item in the custom list, the items on the screen would reverse in sequence. If I clicked again, they'd reverse back to where they were originally.
After reading this, I checked my code where I overload the getView method. I was getting the view from the convertedView, and if it was null, that's when I'd build my stuff. However, after placing a breakpoint, I found that it was calling this method on every click and on subsequent clicks, the convertedView was not null therefore the items weren't being set.
Here is an example of what it was:
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.listitemrow, null);
RssItem rssItem = (RssItem) super.getItem(position);
if (rssItem != null)
{
TextView title = (TextView) view.findViewById(R.id.rowtitle);
if (title != null)
{
title.setText(rssItem.getTitle());
}
}
}
return view;
}
The subtle change is moving the close brace for the null check on the view to just after inflating:
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.listitemrow, null);
}
RssItem rssItem = (RssItem) super.getItem(position);
if (rssItem != null)
{
TextView title = (TextView) view.findViewById(R.id.rowtitle);
if (title != null)
{
title.setText(rssItem.getTitle());
}
}
return view;
}
I hope this helps others who experience this same problem.
To further clarify the answer of farcats below in more general way, here is my explanation:
The vi.inflate operation (needed here for parsing of the layout of a row from XML and creating the appropriate View object) is wrapped by an if (view == null) statement for efficiency, so the inflation of the same object will not happen again and again every time it pops into view.
HOWEVER, the other parts of the getView method are used to set other parameters and therefore should NOT be included within the if (view == null) statement.
Similarily, in other common implementation of this method, some textView, ImageView or ImageButton elements need to be populated by values from the list[position], using findViewById and after that .setText or .setImageBitmap operations.
These operations must come after both creating a view from scratch by inflation and getting an existing view if not null.
Another good example where this solution is applied for BaseAdapter appears in BaseAdapter causing ListView to go out of order when scrolled
The ListView reuses view objects when you scroll. Are you overriding the getView method? You need to make sure you set each property for every view, don't assume that it will remember what you had before. If you post that method, someone can probably point you at the part that is incorrect.
I have a ListView, AdapterView and a View (search_options) that contains EditText and 3 Spinners. ListView items are multiple copies of (search_options) layout, where user can add more options in ListView then click search to send sql query built according to users options.
I found that convertView mixing indecies so I added a global list (myViews) in activity and passed it to ArrayAdapter. Then in ArrayAdapter (getView) I add every newly added view to it (myViews).
Also on getView instead of checking if convertView is null, I check if the global list (myViews) has a view on the selected (position).. It totally solved problems after losing 3 days reading the internet!!
1- on Activity add this:
Map<Integer, View> myViews = new HashMap<>();
and then pass it to ArrayAdapter using adapter constructor.
mSOAdapter = new SearchOptionsAdapter(getActivity(), resultStrs, myViews);
2- on getView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder viewHolder;
if (!myViews.containsKey(position)) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(R.layout.search_options, parent, false);
/// ...... YOUR CODE
myViews.put(position, view);
FontUtils.setCustomFontsIn(view, getContext().getAssets());
}else {
view = myViews.get(position);
}
return view;
}
Finally no more mixing items...

How to inflate one view with a layout

I have a layout defined in XML. It contains also:
<RelativeLayout
android:id="#+id/item"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
I would like to inflate this RelativeView with other XML layout file. I may use different layouts depending on a situation. How should I do it? I was trying different variations of
RelativeLayout item = (RelativeLayout) findViewById(R.id.item);
item.inflate(...)
But none of them worked fine.
I'm not sure I have followed your question- are you trying to attach a child view to the RelativeLayout? If so you want to do something along the lines of:
RelativeLayout item = (RelativeLayout)findViewById(R.id.item);
View child = getLayoutInflater().inflate(R.layout.child, null);
item.addView(child);
You inflate an XML resource. See the LayoutInflater doc .
If your layout is in a mylayout.xml, you would do something like:
View view;
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.mylayout, null);
RelativeLayout item = (RelativeLayout) view.findViewById(R.id.item);
Though late answer,
but would like to add that one way to get this
LayoutInflater layoutInflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = layoutInflater.inflate(R.layout.mylayout, item );
where item is the parent layout where you want to add a child layout.
It's helpful to add to this, even though it's an old post, that if the child view that is being inflated from xml is to be added to a viewgroup layout, you need to call inflate with a clue of what type of viewgroup it is going to be added to. Like:
View child = getLayoutInflater().inflate(R.layout.child, item, false);
The inflate method is quite overloaded and describes this part of the usage in the docs. I had a problem where a single view inflated from xml wasn't aligning in the parent properly until I made this type of change.
Even simpler way is to use
View child = View.inflate(context, R.layout.child, null)
item.addChild(child) //attach to your item
Try this code :
If you just want to inflate your layout :
View view = LayoutInflater.from(context).inflate(R.layout.your_xml_layout,null); // Code for inflating xml layout
RelativeLayout item = view.findViewById(R.id.item);
If you want to inflate your layout in container(parent layout) :
LinearLayout parent = findViewById(R.id.container); //parent layout.
View view = LayoutInflater.from(context).inflate(R.layout.your_xml_layout,parent,false);
RelativeLayout item = view.findViewById(R.id.item); //initialize layout & By this you can also perform any event.
parent.addView(view); //adding your inflated layout in parent layout.
layout inflation
View view = null;
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.mylayout, null);
main.addView(view);
If you're not in an activity you can use the static from() method from the LayoutInflater class to get a LayoutInflater, or request the service from the context method getSystemService() too :
LayoutInflater i;
Context x; //Assuming here that x is a valid context, not null
i = (LayoutInflater) x.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//OR
i = LayoutInflater.from(x);
(I know it's almost 4 years ago but still worth mentioning)
AttachToRoot Set to True
Just think we specified a button in an XML layout file with its layout width and layout height set to match_parent.
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/custom_button">
</Button>
On This Buttons Click Event We Can Set Following Code to Inflate Layout on This Activity.
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.yourlayoutname, this);
Hope this solution works for you.!
If you want to add a single view multiple time then you have to use
layoutInflaterForButton = getActivity().getLayoutInflater();
for (int noOfButton = 0; noOfButton < 5; noOfButton++) {
FrameLayout btnView = (FrameLayout) layoutInflaterForButton.inflate(R.layout.poll_button, null);
btnContainer.addView(btnView);
}
If you do like
layoutInflaterForButton = getActivity().getLayoutInflater();
FrameLayout btnView = (FrameLayout) layoutInflaterForButton.inflate(R.layout.poll_button, null);
and
for (int noOfButton = 0; noOfButton < 5; noOfButton++) {
btnContainer.addView(btnView);
}
then it will throw exception of all ready added view.
If you are you trying to attach a child view to the RelativeLayout? you can do by following
RelativeLayout item = (RelativeLayout)findViewById(R.id.item);
View child = getLayoutInflater().inflate(R.layout.child, item, true);
I had the hardest time with this error, because of my unique circumstances, but finally found a solution.
My situation: I am using a separate view (XML) which holds a WebView, then opens in an AlertDialog when I click a button in my main activity view. But somehow or another the WebView belonged to the main activity view (probably because I pull the resource from here), so right before I assigned it to my AlertDialog (as a view), I had to get the parent of my WebView, put it into a ViewGroup, then remove all the views on that ViewGroup. This worked, and my error went away.
// set up Alert Dialog box
AlertDialog.Builder alert = new AlertDialog.Builder(this);
// inflate other xml where WebView is
LayoutInflater layoutInflater = (LayoutInflater)this.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
View v = layoutInflater.inflate(R.layout.your_webview_layout, null);
final WebView webView = (WebView) v.findViewById(R.id.your_webview_id);
// more code...
.... later on after I loaded my WebView ....
// first, remove the parent of WebView from it's old parent so can be assigned a new one.
ViewGroup vg = (ViewGroup) webView.getParent();
vg.removeAllViews();
// put WebView in Dialog box
alert.setView(webView);
alert.show();
With Kotlin, you can use:
val content = LayoutInflater.from(context).inflate(R.layout.[custom_layout_name], null)
[your_main_layout].apply {
//..
addView(content)
}
When add a layout to an Activity in Kotlin, see these steps:
Add just in Activity - One layout as a parent
Xml file of new
Layout Give the value of R.
val parent: LinearLayout =findViewById(R.id.stars)
val view =
LayoutInflater.from(applicationContext).inflate(R.layout.another, parent,false)
Where parent is not necessary, can be null But warning message will be appear
view.findViewById<ImageView>(R.id.ivTimer).setImageResource(R.drawable.t2)
Any view must be set value in this way, finally add
parent.apply { addView(view)}
I had used below snippet of code for this and it worked for me.
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.item);
View child = getLayoutInflater().inflate(R.layout.child, null);
linearLayout.addView(child);

Categories

Resources