Problem:
I've developed a custom compound view and I'm unsure of how to display it in my listview.
What I've done:
-> My custom compound view
public class HZScrollView extends LinearLayout {
public HZScrollView(Context context) {
super(context);
initView(context);
}
private void initView(Context context) {
mContext = context;
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
this.setLayoutParams(lp);
//inflate XML resource and attach
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mInflater.inflate(R.layout.hz_scroll_view, this, true);
}
}
public void addContent(String name, String age, String sex) {
//content is added to the individual widgets within this compound view
}
-> My Adapter
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = new HZScrollView(context); //<--- PROBLEM !
}
}
The major problem I'm experiencing is that the line marked with "PROBLEM" causes the exception java.lang.RuntimeException: Unable to start activity ComponentInfo{com.my.app/.MainActivity}: android.view.InflateException: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true
In sample code around the 'net, the getView() usually inflates an XML layout, but in my case the compound view is completely self-contained.
Question:
How is it possible to insert/attach my custom compound view into the listview item ?
Solution:
1) in the adapter, assigning the HZScrollView to convertView is fine
2) to fix another problem with LayoutParams, the initView() needed to be updated to use AbsListView.LayoutParams instead of ViewGroup.LayoutParams (since the parent container is a listView)
3) to fix the InflateException, child views used <merge> in the XML, I refactored that to wrap the child views in LinearLayout's. Note: using <merge> in the XML for "hz_scroll_view" file is just fine.
The really interesting part, for me, was point #1 because I was unsure whether assigning a custom compound view to a listview item would work.
Related
This question already has answers here:
What does LayoutInflater in Android do?
(15 answers)
Closed 5 years ago.
I can't able to understand the logic of layout inflater
What is use the of layout inflater in adapetr class.
public class WordAdapter extends ArrayAdapter<Word> {
public WordAdapter(Activity context, ArrayList<Word> words) {
// Here, we initialize the ArrayAdapter's internal storage for the context and the list.
// the second argument is used when the ArrayAdapter is populating a single TextView.
// Because this is a custom adapter for two TextViews and an ImageView, the adapter is not
// going to use this second argument, so it can be any value. Here, we used 0.
super(context, 0, words);
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
// Check if the existing view is being reused, otherwise inflate the view
View listItemView = convertView;
if (listItemView == null) {
listItemView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
}
You can go through the Android Official Documentation.
LayoutInflater (which coverts an XML layout file into corresponding ViewGroups and Widgets) and the way it inflates Views inside Fragment’s onCreateView() method.
LayoutInflater is a class used to instantiate layout XML file into its corresponding view objects which can be used in java programs.
the problems is, that I wan't to make ListView with elements which are containing image, description and two buttons. I'm making them in my own BaseAdapter extension, but fragment which is containing ListView is closing (wihtout errors in logcat..). I've found, that ListView is working well, when I'm not returning layout-type elements. So there is my sample with 'sample linear layout', which is not working.. Is there any possibility, to show layouts in ListView?
Here is my code:
Creating part:
lv = (ListView) getView().findViewById(R.id.main_wall_ambajes_lv);
AmbajAdapter aa = new AmbajAdapter(getActivity().getApplicationContext(), StaticData.ambajes);
lv.setAdapter(aa);
My getView method from adapter:
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout ll = new LinearLayout(getActivity());
ll.setOrientation(LinearLayout.VERTICAL);
ImageView iv = new ImageView(getActivity());
iv.setImageBitmap(placeholderBitmap);
ll.addView(iv);
ll.addView(iv);
ll.addView(iv);
ll.addView(iv);
return ll;
}
I don't know why you don't have any error however I don't think you proceed the correct way.
Usually you create the layout in the xml file of the layout folder and only inflate it in the getView(), for example as follow :
private LayoutInflater mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
view = mInflater.inflate(R.layout.your_custom_layout, parent, false);
}
//your code for setting the image or other things goes here
//for example if you have a textView
TextView textView = (TextView) view.findViewById(R.id.my_textview_id);
textView.setText("my custom text for this cell");
return (view);
}
and your_custom_layout is simply the xml file of your layout.
Note that for performance reason due to cell recycling I only inflate the view when it is null and I only read once the LayoutInflater context and put it in mInflater. However for the best performance you should use a ViewHolder, but it is out of the scope of your question.
I deleted almost all code in my project to find a hiding bug. There was a GridView that containing a frame layout, and the layout contained CheckBox. But I couldn't check the first check box.(others worked)
Finally (I think) I found an answer. But this is so weird. When I deleted lines for recycling convertView, the bug was gone. I changed from :
if(convertView == null) {
layout = (FrameLayout)View.inflate(maincon, R.layout.taste_brand, null);
} else {
layout = (FrameLayout) convertView;
}
to FrameLayout layout = (FrameLayout)View.inflate(maincon, R.layout.taste_brand, null);.
I really have no idea of this stuation. I attach rest codes.
TasteGridAdapter.java:
public class TasteGridAdapter extends BaseAdapter {
Context maincon;
public TasteGridAdapter(Context context) {
maincon = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
FrameLayout layout;
if(convertView == null) {
layout = (FrameLayout)View.inflate(maincon, R.layout.taste_brand, null);
} else {
layout = (FrameLayout) convertView;
}
layout.setLayoutParams(new GridView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
return layout;
}
#Override
public int getCount() {
return 3;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
}
onCreate of the activity :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.taste);
TasteGridAdapter adapter = new TasteGridAdapter(this);
GridView grid = (GridView) findViewById(R.id.taste_grid);
grid.setAdapter(adapter);
}
taste.xml:
<?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:orientation="vertical">
<GridView
android:layout_weight="1"
android:id="#+id/taste_grid"
android:layout_width="match_parent"
android:layout_height="0dip"
android:columnWidth="87dip"
android:gravity="center"
android:horizontalSpacing="4dip"
android:numColumns="auto_fit"
android:padding="2dip"
android:stretchMode="columnWidth"
android:verticalSpacing="4dip" />
</LinearLayout>
taste_brand.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="87dp"
android:layout_height="58dp">
<CheckBox
android:id="#+id/taste_brand_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</FrameLayout>
I encountered similar problem with first item in GridView. To resolve issue, remove 'new' keyword, and change existing views LayoutParams like that:
LayoutParams lp = layout.getLayoutParams();
lp.height = someHeight;
...do something with these LayoutParams. This hack resolves my issues. Conclusion, try to avoid creation of new LayoutParams object through "new".
layout.setLayoutParams(new GridView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
put this line in this condition,
if(convertView == null) {
}
I was faced same problem, but i try this and its work for me. I hope it also work for you.
What you're experiencing has to do with the way Android recycles views in ListView, GridView, etc. You mention that your first checkbox is uncheckable, while your others remain working. I think you'll notice that the others only appear to work properly, since you haven't handled the recycling properly.
The reason your line
FrameLayout layout = (FrameLayout)View.inflate(maincon, R.layout.taste_brand, null);
seems to fix the problem is because this now inflates the views again each time they are used. I'll admit, when I started with this, re-inflating the views seemed to be the best solution; it entirely defeats the purpose of recycling, however, and you lose all the performance benefits otherwise gained.
So now to fix your problem:
First, I highly recommend using the ViewHolder pattern in conjunction with your BaseAdapter. More information on that can be found here.
Second, you should probably create a boolean array to match all the items in your GridView, and use it to determine whether or not an item should be clicked. Set the value of the corresponding boolean inside your checkbox listener and use that value inside getView(..) to check or uncheck that particular box.
An overall better solution might be to use an array (or list) of models inside your adapter class, each of these containing a boolean field accessible through isChecked and setChecked(boolean). Again, you would use this inside your getView(..) to display the views properly and change the value inside your checkbox OnCheckedChangeListener.
Hope that helps.
As jonstaff says, it's to do with View recycling.
If you're using a custom Adapter class for your GridView View binding, try modifying its getView() method to always instantiate a new View like:
public View getView(int position, View convertView, ViewGroup parent){
SomeView v = new SomeView(context); // <--- here
...
return v;
}
Instead of the typical:
public View getView(int position, View convertView, ViewGroup parent){
SomeView v;
if (convertView == null)
v = new SomeView (context);
else
v= (SomeView)convertView;
...
return v;
}
This may affect performance, but it solved my problem for a small GridView of Buttons.
I created the Layout design using java code only not from the XML Layout Designs. The code I used is following
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv = new TextView(mContext);
tv.setText(hotelList.get(position).name);
return tv;
}
How to use layoutInflator for creating layout fro this. I need 2 more textviews in a single list item. the whole list contains 10 different list items
Please provide some codes for this. Help appreciated
I have gone through this before by having my static class too. Check this out, it will help:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
if ( rowView == null) {
LayoutInflater inflator = this._activity.getLayoutInflater();
rowView = inflator.inflate(R.layout.todolistlisting, null);
TodoListViewHolder viewHolder = new TodoListViewHolder();
viewHolder._name = (TextView) rowView.findViewById(R.id.tVTLName);
viewHolder._completed = (TextView) rowView.findViewById(R.id.tVTLCCount);
viewHolder._remaining = (TextView) rowView.findViewById(R.id.tVTLRCount);
rowView.setTag(viewHolder);
}
TodoListViewHolder holder = (TodoListViewHolder) rowView.getTag();
VO_TodoList votodolist = this._items.get(position);
holder._name.setText(votodolist._title);
holder._completed.setText(votodolist._completed);
holder._remaining.setText(votodolist._remaining);
return rowView;
}
TodoListViewHolder is my view component holder here. like your TextView.
I guess you know how to make XML layout for this layout. So just make the XML layout and get the object of the main layout using the following code:
LinearLayout mainLayout=(LinearLayout) View.inflate(R.layout.yourlayout); //if yourlayout.xml is the name of the xml file you made and put in the layout folder.
To get the child of the layout, let's say if it's a TextView with the id text, then the code would be:
TextView textView=(TextView)mainLayout.findViewById(R.id.text);
You can add view at runtime by using inflater like this
LinerLayout linearLayout = (LinearLayout)inflater.inflate(R.layout.news_categories_item, null);
TextView categoryValueTextView = (TextView)linearLayout.findViewById(R.id.news_category_item_value);
mMainLinearLayout.addView(categoryValueTextView);
Here i am inflating one text view which is there in another linear layout(this is simple linear layout which holds only textview) at runtime and adding it to my main linear layout.
you can get the inflater object in your acitivity by using getLayoutInflater(). And if you want to get inflater in adapter you have to pass inflater object to constructor of adapter from your activity.
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().