I've got a little piece of xml, which I'll be using in a lot of places in my app. For this reason I want to store it in a separate file. So I created mywidget.xml in which I have my xml. I then try to inflate this in mywidget.java, after which I want to include it in a different xml file like so:
<com.mycom.android.ui.widget.AmountWidget android:layout_width="fill_parent" android:layout_height="wrap_content"></com.mycom.android.ui.widget.AmountWidget>
In my java file, I try to inflate the initial xml like this:
public class AmountWidget extends LinearLayout {
public AmountWidget(Context context) {
super(context);
LinearLayout ll = (LinearLayout) findViewById(R.layout.amount_widget);
addView(ll);
}
}
But with the code above I get an error saying that there's an error inflating class com.mycom.android.ui.widget.AmountWiget.
My question: Does anybody know how I can inflate a layout so that I can use it as a class in another xml layout file?
The xml from the widget looks 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="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
android:background="#layout/border"
>
<EditText
android:id="#+id/payment_amount_major"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="35sp"
android:textStyle="bold"
android:inputType="number"
android:digits="0,1,2,3,4,5,6,7,8,9"
android:maxLength="9"
android:gravity="right"
/>
</LinearLayout>
Try this:
// ViewGroup to add views
LinearLayout list = (LinearLayout) findViewById(R.id.linear_layout_list);
// Inflate you custom xml to view
// Ex: res/layout/item_data.xml
View item = getLayoutInflater().inflate(R.layout.item_data, null);
// Link item in list here
list.addView(item);
mContainerView is LinearLayout which contain your EditText and row is your xml filename.
The View class has an inflate method which wraps LayoutInflater.inflate. You should be able to use:
LinearLayout ll = (LinearLayout) inflate(context, R.layout.amount_widget, this);
to inflate your widget from xml. The call to addView() won't be needed, as inflate will add the newly inflated view for you!
Edit: Just a note, because this View is already a LinearLayout, there's no need to have the root of the xml you're inflating also be a LinearLayout. It can increase your performance if you inflate only the EditText and just add that to the parent, rather than nesting a second LinearLayout within the parent. You can set the LinearLayout attributes (such as background and padding) directly on the AmountWidget wherever it's added in xml. This shouldn't matter too much in this specific case, but may be good to know going forward if you have a situation with many nested views.
Edit2: The View class has three constructors: View(Context), View(Context, AttributeSet), and View(Context, AttributeSet, int). When the system inflates a view from xml, it will use one of the latter two. Any custom View will need to implement all three of these constructors. An easy way to do this while reusing your code is like this:
public AmountWidget(Context context) {
super(context);
LinearLayout ll = (LinearLayout) inflate(context, R.layout.amount_widget, this);
}
public AmountWidget(Context context, AttributeSet attrs) {
this(context);
}
public AmountWidget(Context context, AttributeSet attrs, int defStyle) {
this(context);
}
This will work if you don't care what the attributes or style arguments are, and just want the AmountWidget created the same any time it's inflated.
Easiest Solution
LinearLayout item = (LinearLayout )findViewById(R.id.item);//where you want to add/inflate a view as a child
View child = getLayoutInflater().inflate(R.layout.child, null);//child.xml
item.addView(child);
ImageView Imgitem = (ImageView ) child.findViewById(R.id.item_img);
Imgitem.setOnClick(new ...
Related
I am trying to spawn some XML view for each instance of POJO data objects in a list, similar to how a ListView would do it, but it's not a ListView actually, but an empty LinearLayout instead, which is inside a ScrollView below other Fragment's content.
This is the item.xml file I want to inflate for each element of the list:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/item_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="0dp"
android:text="ITEM_LABEL"
android:textSize="16sp" />
<TextView
android:id="#+id/item_text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:text="ITEM_TEXT_1"
android:textSize="14sp" />
<TextView
android:id="#+id/item_text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:text="ITEM_TEXT_2"
android:textSize="14sp" />
</LinearLayout>
...and this is the way I am trying to inflate it in the Fragment Java class:
public class MyFragment extends Fragment {
// we skip the "boilerplate"...
// [...]
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.my_fragment, container, false);
// XXX I use LiveData<> with Room to retrieve
// rows from DB inside POJO objects
LiveData<List<MyItem>> items_list = itemsViewModel.getItemsByParentId(some_id);
// The "parent" is not relevant, let's just
// focus on the List
LinearLayout items_list_layout = view.findViewById(R.id.items_list_layout);
items_list.observe(getActivity(), new Observer<List<MyItem>>() {
#Override
public void onChanged(List<MyItem> items) {
// Just in case, (maybe I'm wrong but) I think otherwise
// previous inflated objects remain on loading this same
// fragment for another parent
objs_list_layout.removeAllViews()
for (MyItem item : items) {
// XXX Here I inflate item.xml as a LinearLayout
LinearLayout obj_item = (LinearLayout) View.inflate(view.getContext(), R.layout.item, items_list_layout);
// XXX ... and try to change it's TextView children
TextView item_label = obj_item.findViewById(R.id.item_label);
item_label.setText(item.item_label);
TextView item_text1 = obj_item.findViewById(R.id.item_text1);
item_text1.setText(item.item_text1);
TextView item_text2 = obj_item.findViewById(R.id.item_text2);
item_text2.setText(item.item_text2);
}
}
});
}
}
This works well when the method getItemsByParentId only return a List with one only MyItem instance. But when the query returns more than one Item, it works unexpectedly wrong:
The first inflated item.xml element (or the one shown first, on top) has its TextViews modificated as expected, but for the last item in the list.
The rest of "inflated elements" have not been changed (it shows just ITEM_LABEL, ITEM_TEXT1 and ITEM_TEXT2 hardcoded strings as they are in the template XML file).
However, it inflates the XML template items.size() times as I planned.
Actually, the objective is that each "inflated item.xml" is edited with attributes of each corresponding Item. Just like a ListView would do, but without using ListView at all, because the goal is, actually, bloating an existing ScrollView (the main container in the fragment) that shows other different Views outside of the "LinearLayout" dedicated to generate this "list". How can I make it work without these errors?
View.inflate imho is a not the clearest method ) it returns the root View of the inflated hierarchy. If root was supplied, this is the root View; otherwise it is the root of the inflated XML file.
So obj_item is not an item view. It is an items_list_layout view. Inside it you find R.id.item_label in first item view and set it. Other item views are not initialized because findViewById returns first item it found.
Change this part:
LinearLayout obj_item = (LinearLayout) View.inflate(view.getContext(), R.layout.item, items_list_layout);
to
LinearLayout obj_item = LayoutInflator.from(view.getContext()).inflate(R.layout.item, items_list_layout, false);
I prefer to add child views explicitly to make code more readable. So and add this code to add item view to parent layout:
items_list_layout.addView(obj_item)
I want to make a custom ConstraintLayout so I can trigger some functions from the activity where its initialized.
I have the following code:
First initialized my custom view in layout:
<package.com.app.Main.LavadasView
android:id="#+id/main_autolavados_lavadas_lavadas_view"
android:layout_width="0dp"
android:layout_height="25dp"
...
/>
This is my custom class LavadasView, constraint layout initialized from another XML file:
Java class
public class LavadasView extends ConstraintLayout {
public LavadasView(Context context,AttributeSet attrs) {
super(context);
//Inflate view from XML layout file
LayoutInflater inflater =(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.lavadas_view, this);
}
public void resetView(){
//Some ui updates
}
}
LavadasView xml file, just a normal constraint layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.constraint.ConstraintLayout>
And in my Activity I get the instance with findViewByIdmehtod, then I want to call the resetView function and that gives me a null pointer exception related to LavadasView:
LavadasView lavadasView = (LavadasView) findViewById(R.id.main_autolavados_lavadas_lavadas_view);
//Call this method later on
lavadasView.resetView();
SO what am I doing wrong? I've looked up and thats the correct way to get layout instance isnt?
Thanks.
The problem is with the constructor of your LavadasView. When your Activity is inflated it will call the LavadasView(Context context, AttributeSet attrs) constructor on you custom layout. In this constructor you need to call the super(context, attrs) for the ConstraintLayout to be properly inflated, but you're only calling super(context) which is why you get the NullPointerException.
I have a ListView, titled myListView, that I would like to populate with 3 LinearLayout elements, titled layout1.xml, layout2.xml, and layout3.xml. All 3 LinearLayout elements are very similar; here is one of them:
<?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="horizontal" >
<TextView
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Los Angeles" />
<TextView
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="California" />
</LinearLayout>
My goal is to populate a the ListView (myListView) with these three LinearLayout elements. Does anyone know how I would go about doing this?
First of all, if all three linear layouts are alike, I suggest you to only use one.
Anyway, you have to use a custom adapter for your ListView. You create a class that extends ArrayAdapter for example. If you are not familiar with custom adapters, I suggest you take a look here.
In your getView method, you practically have to inflate a different *.xml, depending on your cell position. Thus:
#Override
puclic View getView (int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
switch (position) {
case 0: view = inflater.inflate(R.layout.my_layout_1, null, true);
//rest of my code
break;
case 1: view = inflater.inflate(R.layout.my_layout_2, null, true);
//rest of my code
break;
case 2: view = inflater.inflate(R.layout.my_layout_3, null, true);
//rest of my code
break;
default: break;
//rest of my code
return view;
}
As DDsix points out, you really should be using one layout that can handle whatever data you want, and then populate the fields using an adapter. If I had to guess, I'd bet the only difference between your layouts is the text for the city and state.
The documentation explains how to do this very well: http://developer.android.com/guide/topics/ui/layout/listview.html
Basically, you should create a private List<Location> mLocations; variable to hold your locations (Location would be a simple class you define with strings to hold city and state). Then, you can use the following in your adapter.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater inflater = context.getLayoutInflater();
View view = inflater.inflate(R.layout.my_layout_1, parent);
Location location = mLocationList.get(position);
TextView cityView = view.findViewById(R.id.city_view);
TextView stateView = view.findViewById(R.id.state_view);
cityView.setText(location.getCity());
cityView.setText(location.getState());
return view;
}
I am creating a custom composite layout that consists of a clickable horizontal LinearLayout that contains an ImageView and two TextViews. Ultimately I want to be able to reference the entire thing using a single field and add, subtract, and edit these to my activity depending on user activity. I'm using an XML resource that I am inflating in the constructor. However my phone thinks I'm creating a LinearLayout and I get the following error when i try to implement the custom class:
D/ConnectionButton﹕ Ready to inflate
D/ConnectionButton﹕ constructor 2
D/AndroidRuntime﹕ Shutting down VM
W/dalvikvm﹕ threadid=1: thread exiting with uncaught exception (group=0x417be898)
E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.exampleapp.app/com.exampleapp.app.ManageFriends}: android.view.InflateException: Binary XML file line #3: Error inflating class com.exampleapp.utils.ConnectionButton
The error when I am setting NameText. If I leave that line out it runs but my later Log tells me android thinks it is LinearLayout, not ConnectionButton
So here's my definition class ConnectionButton:
public class ConnectionButton extends LinearLayout {
ImageView IconView;
TextView NameView;
TextView StatusView;
public ConnectionButton(Context context) {
super(context);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Log.d("ConnectionButton","Ready to inflate");
addView(inflater.inflate(R.layout.connection_row, null));
Log.d("ConnectionButton","Inflated");
IconView = (ImageView) findViewById(R.id.icon);
NameView = (TextView) findViewById(R.id.name);
StatusView = (TextView) findViewById(R.id.status);
}
public ConnectionButton (Context context, AttributeSet attrs) {
super(context, attrs);
Log.d("ConnectionButton","constructor 2");
/* LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Log.d("ConnectionButton","Ready to inflate");
addView(inflater.inflate(R.layout.connection_row, null));
Log.d("ConnectionButton","Inflated");*/
IconView = (ImageView) this.findViewById(R.id.icon);
NameView = (TextView) this.findViewById(R.id.name);
StatusView = (TextView) this.findViewById(R.id.status);
NameView.setText("From Constructor 2");
}
}
Here's my XML resource:
<com.exampleapp.utils.ConnectionButton xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:clickable="true"
style="#android:style/Widget.Button">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:adjustViewBounds="true"
android:paddingRight="5dp"
android:id="#+id/icon"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_weight="2"
android:id="#+id/name"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_weight="1"
android:id="#+id/status"/>
</com.exampleapp.utils.ConnectionButton>
And here is where I create it: (N.B. ConnectionList is the existing vertical LinearLayout that should contain these objects)
LinearLayout box = (LinearLayout)findViewById(R.id.ConnectionList);
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Log.d("ConnectionButton","Ready to inflate");
View CBv = inflater.inflate(R.layout.connection_row, box);
Log.d("CBv class",CBv.getClass().toString());
You're making the incorrect assumption that inflater.inflate returns the new view. According to the documentation it returns:
The root View of the inflated hierarchy. If root was supplied, this is
the root View; otherwise it is the root of the inflated XML file.
So in your case CBv is a LinearLayout because that's what the parent is. To get access to your view, just use findViewById.
I think that you're trying to inflate a ConnectionButton inside the constructor of ConnectionButton which poses a recursive problem.
I suggest that you change ConnectionButton to make it inherit from FrameLayout instead (since you only have one root child) and change the root type of its XML layout content to LinearLayout. Your control will then be an extended FrameLayout which contains a single LinearLayout.
I know there are dozens similar post, but it looks to me everything is correct here:
The custom widget:
public class DoubleTextItem extends LinearLayout {
private TextView txtMain;
private TextView txtDescription;
public DoubleTextItem(Context context) {
super(context);
}
public DoubleTextItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
((Activity)getContext()).getLayoutInflater().inflate(R.layout.widget_double_text_item, this);
setupViewItems();
}
private void setupViewItems() {
txtMain = (TextView) findViewById(R.id.txtMain);
txtDescription = (TextView) findViewById(R.id.txtDecription);
}
public void setDescription(String text) {
txtDescription.setText(text);
}
}
The custom widget layout xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/txtMain"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/txtDecription"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
ANd here inside an activity function i get a casting error,
LayoutInflater inflater = LayoutInflater.from(this);
DoubleTextItem item = (DoubleTextItem) inflater.inflate(R.layout.widget_double_text_item, layout);
item.setText(som-txt);
item.setDescription("#"+athlete.getString("position"));
Here, the root View is a LinearLayout but you try to cast it your custom class:
DoubleTextItem item = (DoubleTextItem) inflater.inflate(R.layout.widget_double_text_item, layout);
The standard advice is:
All DoubleTextItems are LinearLayouts, but not all LinearLayouts are DoubleTextItems.
Meaning you cannot downcast objects from a LinearLayout to a DoubleTextItem, there are too many assumptions and Java won't let you do it.
If you want a DoubleTextItem in your layout you need to use:
<your.package.name.DoubleTextItem
... />
(Also, calling inflate inside onFinishInflate() seems a little silly especially since you don't save the inflated item... If you want to inflate a different layout, don't inflate the first one.)
Overall it looks like you are trying to recreate the now deprecated TwoLineListItem, perhaps you can learn some pointers from it's source code (or just use the TwoLineListItem.)