How to Inflate Extended LinearLayout from XML - android

Can anyone suggest a way to improve this API8 example? While they say the views could have been defined in XML, what they have in fact done is code them in java. I see why they wanted to. They have added some members to an extended LinearLayout, and the values are determined at runtime.
According to oh, everyone in the universe, the layout directives should move to XML. But for this app, it makes sense to keep setting the text as-is in the runtime logic. So we've got a hybrid approach. Inflate the views, then populate the dynamic text. I'm having trouble figuring out how to accomplish it. Here's the source and what I tried.
from API8 Examples, List4.java
private class SpeechView extends LinearLayout {
public SpeechView(Context context, String title, String words) {
super(context);
this.setOrientation(VERTICAL);
// Here we build the child views in code. They could also have
// been specified in an XML file.
mTitle = new TextView(context);
mTitle.setText(title);
...
I figured since the LinearLayout has an android:id="#+id/LinearLayout01", I should be able to do this in the OnCreate
SpeechView sv = (SpeechView) findViewById(R.id.LinearLayout01);
but it never hits the minimal constructor I added:
public class SpeechView extends LinearLayout {
public SpeechView(Context context) {
super(context);
System.out.println("Instantiated SpeechView(Context context)");
}
...

I just ran into this exact problem myself. What I think you(we) need is this, but I'm still working through some bugs so I can't yet say for sure:
public class SpeechView extends LinearLayout {
public SpeechView(Context context) {
super(context);
View.inflate(context, R.layout.main_row, this);
}
...
I'll be anxious to hear if you have any luck with it.
Edit: It's now working for me just like this.

It looks like you inflated your Layout, which resides in the file main_row.xml. Correct? My need is different. I want to inflate a TextView child of the Layout I have in main.xml.
Still, I used a similar solution. Since I had already inflated the LinearLayout from XML in the onCreate
setContentView(R.layout.main);
what remained is to inflate the TextView from XML in my View constructor. Here's how I did it.
LayoutInflater li = LayoutInflater.from(context);
LinearLayout ll = (LinearLayout) li.inflate(R.layout.main, this);
TextView mTitle = (TextView) ll.findViewById(R.id.roleHeading);
R.id.roleHeading is the id of the TextView I'm inflating.
<TextView android:id="#+id/roleHeading" ... />
To increase efficiency I was able to move the LayoutInflater to an Activity member, so that it just gets instantiated once.

Related

How to get a ConstraintSet working inside a programatically generated ConstraintLayout descendant?

I have a view that inherits from ConstraintLayout. Inside this layout I place the children by use of a ConstraintSet.
This works as long as use a given AttributeSet form outside in the constructor:
public AnswerView(Context context, AttributeSet attrs) {
super(context, attrs);
It does not work, if I try to assign the layout otherwise, when I don't have attrs available.
public AnswerView(Context context) {
super(context);
setLayoutParams(new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
In the first case the children get a pretty distribution like defined by the ConstraintSet, in the second case the all line up at the left border.
In both cases the layout stretches full width, as I can prove by setting a background color.
What is missing in my code?
This is not a direct answer to the question. A direct answer is still missing. This answer is of the type, "take a better approach!".
When there are few or no answers to a question, I am usually on a track, that few people go. Then there are reasons why they don't.
My approach to programatically nest views within other views is cool, but it turns out to be difficult to do this without the use of layouts. It's too expensive to set up all the benefits of the configurations programatically, that can easily done within a layout. The API of Android is not well prepared for this.
So I turned back to the approach, to create the view classes based on layouts. This means, I create the view with the two parameter constructor for layouts.
In the enclosing view class I can't create the nested view directly any more, as there is no constructor for this. Instead I read the configured view from a layout.
I created a small class to assist extracting configured subparts of a layout:
public class Cloner {
LayoutInflater inflater;
int layoutId;
public Cloner of(Context context) {
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return this;
}
public Cloner from(#LayoutRes Integer layoutId) {
this.layoutId = layoutId;
return this;
}
public View clone(#IdRes int id) {
assert inflater != null;
View layout = inflater.inflate(layoutId, null);
View view = layout.findViewById(id);
((ViewManager) view.getParent()).removeView(view);
return view;
}
}
It is used like this:
MultipleChoiceAnswerView mcav =
(MultipleChoiceAnswerView) new Cloner().of(getContext())
.from(layoutId).clone(R.id.multipleChoiceAnswerView);
mcav.plugModel(challenge.getAnswer());
This already shows, how I connect the model in a second step, because I can't feed it into by the constructor any more.
In the constructor I first evaluate the given attributes. Then I set up the view by inflating an accompanying second layout file, that I don't show here. So there are two layouts involved, one to configure the input to the constructor, one for the internal layout.
When the call to plugModel happens, the inflated internal layout is used and extended by objects matching the given model. Again I don't create this objects programatically, but read them from a third (or the second) layout file as templates. Again done with the assistance of the Cloner given above.
private Button getButton(final Integer index, String choice) {
Button button = (Button) new Cloner().of(getContext()).from(layoutId).clone(R.id.button);
button.setId(generateViewId());
button.setText(choice);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
answer.choiceByIndex(index);
}
});
return button;
}
In practice I put this object templates (like a button) as children into the second layout file. So I can style it as a whole in Android Studio Designer.
Programatically I remove the templates before I actually fill the layout with the cloned objects. By this approach I only need to maintain one layout file per view class. (The configuration of the constructor happens in the layout file of the enclosing view.)

Is there anyway to find out what type of View a View is?

So if I were to have a LinearLayout and had several children Views inside of it, say like a couple of Buttons a TextView and a CheckBox, using the LinearLayout's getChildAt(x) I would then get an unspecified View. To note, I'm not using an xml in this so it's all done programatically.
public class CustomViewClass extends LinearLayout {
private Context context;
public CustomViewClass (Context context) {
super(context);
this.context = context;
setOrientation(LinearLayout.VERTICAL);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
setBackgroundColor(Color.DKGRAY);
// Code which adds Buttons and such to the LinearLayout
getChildAt(1)
}
}
The getChildAt(1), is there anyway that I can find out what kind of View it is, whether it's a Button or a TextView or whatever progamatically?
One way to do this is to call getClass. This will get you a Class object representing the type of view.
For example:
Class clazz = getChildAt(1).getClass();
After you have the class, you can do all kinds of things with it. e.g. get the name:
System.out.println(clazz.getName());
Now you know what kind of view it is.
Another way is to use the instanceof operator. Here's an example:
if (getChildAt(1) instanceof TexView) {
// getChildAt(1) is a TextView or an instance of a subclass of TextView
}
Use instanceOf method. Sample example is
if (view instanceof ImageView)
Or You can use below to find out the type of view. But if you want to do any computation on it, instance of is the best choice.
view.getClass
if (view.getClass().getName().equalsIgnoreCase("android.widget.ImageView"))

LayoutInflater for using xml and View class

after I asked if I should use XML or a View class for my project you told me, that I should do everything possible in XML and use a class for the rest. You told me, that animating Sprites isn't possible with XML so I wanted to make a View Class. I got the tip to google "LayoutInflater" for this and I did.
There aren't many Informations about inflaters so I visited android's developers database and tried to find out how this works.
As far as I know now, you have to put something into the onCreate method of your main game activity (the setContentView has to be the mainXML).
So now I created a LinearLayout in my mainXML and called it "container" and made this being a ViewGroup called "parent".
Now I have created a global variable "private View view" and wrote this line:
view = LayoutInflater.from(getBaseContext()).inflate(new ViewClass(this),
null);
Thw Problem now is that u can't inflate a class like this and I think I'm doing this whole inflating thing wrong.
Do you have any tips and tricks for me for making it work to have a LinearLayout in my mainXML and being able to make the content from my View Class appear in it?
EDIT:
Got it to work without errors, but nothing happens if I start my game now.
Here is the code pls answer if u have any solutions:
super.onCreate(savedInstanceState);
// inflate mainXML->
View mainView = getLayoutInflater().inflate(R.layout.activity_game, null);
// find container->
LinearLayout container = (LinearLayout) mainView.findViewById(R.id.container);
// initialize your custom view->
view = new GameLayout(this);
// add your custom view to container->
container.addView(view);
setContentView(R.layout.activity_game);
And my GameLayout:
public GameLayout(Context context)
{
super(context);
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.BLACK);
}
There are two ways of going about this. I'll show you one of them. Do the following in your onCreate(Bundle) before calling setContentView(...):
// inflate mainXML
View mainView = getLayoutInflater().inflate(R.layout.mainXML, null);
// find container
LinearLayout container = (LinearLayout) mainView.findViewById(R.id.container);
// initialize your custom view
view = new ViewClass(this);
// add your custom view to container
container.addView(view);
Finally:
setContentView(mainView);
Alternatively, you can place your custom view inside mainXML:
<your.package.name.ViewClass
android:id="#+id/myCustomView"
android:layout_width="match_parent"
android:layout_height="match_parent"
.... />

Reuse a custom inflated view similar to convertView in a ListView

I'm currently working on an app that contains a timetable screen, which is built in a highly customised way and contains a lot of 'repeated' views.
I've got each individual view that I need (eg, a view for a box that contains the title of an event and the time that it's on) set up in XML, which I inflate in a custom view class. For example:
public class EventCell extends RelativeLayout {
private TextView eventTitle;
private TextView eventTime;
private Button favouritesButton;
public EventCell(Context context) {
super(context);
setupView(context);
}
public EventCell(Context context, AttributeSet attrs) {
super(context, attrs);
setupView(context);
}
private void setupView(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.timetable_event, this);
eventTitle = (TextView) findViewById(R.id.event_title);
eventTime = (TextView) findViewById(R.id.event_time);
favouritesButton = (Button) findViewById(R.id.favourites_button);
}
...
}
This is fine except for the fact that this view is reused quite a lot in its containing activity. Eg, it might be instantiated 50 times. My problem is that this wastes a lot of memory and causes some devices to crash.
In ListViews, there's the getView() method which gives an a convertView parameter that lets you check if the current row has already been instantiated, and then lets you update the values on that. What I'm after is a similar thing for this custom view; ideally reusing it rather than instantiating it a bunch of times.
If there isn't a way, what's the best method of getting around it? The views themselves aren't particularly complicated but still manage to bring the most devices to their knees it seems.

keeping references to inflated custom views

While researching how to create custom compound views in Android, I have come across this pattern a lot (example comes from the Orange11 blog) :
public class FirstTab extends LinearLayout {
private ImageView imageView;
private TextView textView;
private TextView anotherTextView;
public FirstTab(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.firstTab, this);
}
}
I mostly understand how this is working, except for the part where inflate() is called. The documentation says that this method returns a View object, but in this example the author does not store the result anywhere. After inflation, how is the new View created fromt eh XML associated with this class? I thought about assigning it to "this", but that seems very wrong.
thanks for any clarification.
The reference to this would be the viewgroup root. See here:
http://developer.android.com/reference/android/view/LayoutInflater.html#inflate(int, android.view.ViewGroup)
What this means is it is inflating the designated view from xml with this as the parent view. The xml ends up inside the Linear layout defined by the class.
edit: put in the full link as I can't seem to get URLs with brackets to escape properly

Categories

Resources