Custom Android control with children - android

I'm trying to create a custom Android control that contains a LinearLayout. You can think of it as an extended LinearLayout with fancy borders, a background, an image on the left...
I could do it all in XML (works great) but since I have dozens of occurences in my app it's getting hard to maintain. I thought it would be nicer to have something like this:
/* Main.xml */
<MyFancyLayout>
<TextView /> /* what goes inside my control's linear layout */
</MyfancyLayout>
How would you approach this? I'd like to avoid re-writing the whole linear layout onMeasure / onLayout methods. This is what I have for the moment:
/* MyFancyLayout.xml */
<TableLayout>
<ImageView />
<LinearLayout id="container" /> /* where I want the real content to go */
</TableLayout>
and
/* MyFancyLayout.java */
public class MyFancyLayout extends LinearLayout
{
public MyFancyLayout(Context context) {
super(context);
View.inflate(context, R.layout.my_fancy_layout, this);
}
}
How would you go about inserting the user-specified content (the TextView in main.xml) in the right place (id=container)?
Cheers!
Romain
----- edit -------
Still no luck on this, so I changed my design to use a simpler layout and decided to live with a bit of repeated XML. Still very interested in anyone knows how to do this though!

This exact question bugged me for some time already but it's only now that I've solved it.
From a first glance, the problem lies in the fact that a declarative content (TextView in Your case) is instantiated sometime after ctor (where we're usually inflating our layouts), so it's too early have both declarative and template content at hand to push the former inside the latter.
I've found one such place where we can manipulate the both: it's a onFinishInflate() method. Here's how it goes in my case:
#Override
protected void onFinishInflate() {
int index = getChildCount();
// Collect children declared in XML.
View[] children = new View[index];
while(--index >= 0) {
children[index] = getChildAt(index);
}
// Pressumably, wipe out existing content (still holding reference to it).
this.detachAllViewsFromParent();
// Inflate new "template".
final View template = LayoutInflater.from(getContext())
.inflate(R.layout.labeled_layout, this, true);
// Obtain reference to a new container within "template".
final ViewGroup vg = (ViewGroup)template.findViewById(R.id.layout);
index = children.length;
// Push declared children into new container.
while(--index >= 0) {
vg.addView(children[index]);
}
// They suggest to call it no matter what.
super.onFinishInflate();
}
A labeled_layout.xml referenced above is not unlike something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation ="vertical"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:layout_marginLeft ="8dip"
android:layout_marginTop ="3dip"
android:layout_marginBottom ="3dip"
android:layout_weight ="1"
android:duplicateParentState ="true">
<TextView android:id ="#+id/label"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:singleLine ="true"
android:textAppearance ="?android:attr/textAppearanceMedium"
android:fadingEdge ="horizontal"
android:duplicateParentState="true" />
<LinearLayout
android:id ="#+id/layout"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:layout_marginLeft ="8dip"
android:layout_marginTop ="3dip"
android:duplicateParentState="true" />
</LinearLayout>
Now (still omitting some details) elsewhere we might use it like this:
<com.example.widget.LabeledLayout
android:layout_width ="fill_parent"
android:layout_height ="wrap_content">
<!-- example content -->
</com.example.widget.LabeledLayout>

This approach saves me a lot of code! :)
As esteewhy explains, just swap the xml-defined contents into where you want them internally in your own layout, in onFinishInflate(). Example:
I take the contents that I specify in the xml:
<se.jog.custom.ui.Badge ... >
<ImageView ... />
<TextView ... />
</se.jog.custom.ui.Badge>
... and move them to my internal LinearLayout called contents where I want them to be:
public class Badge extends LinearLayout {
//...
private LinearLayout badge;
private LinearLayout contents;
// This way children can be added from xml.
#Override
protected void onFinishInflate() {
View[] children = detachChildren(); // gets and removes children from parent
//...
badge = (LinearLayout) layoutInflater.inflate(R.layout.badge, this);
contents = (LinearLayout) badge.findViewById(R.id.badge_contents);
for (int i = 0; i < children.length; i++)
addView(children[i]); //overridden, se below.
//...
super.onFinishInflate();
}
// This way children can be added from other code as well.
#Override
public void addView(View child) {
contents.addView(child);
}
Combined with custom XML attributes things gets very maintainable.

You can create your MyFancyLayout class by extending LinearLayout. Add the three constructors which call a method ("initialize" in this case) to set up the rest of the Views:
public MyFancyLayout(Context context) {
super(context);
initialize();
}
public MyFancyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public MyFancyLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
Within initialize, you do anything you need to to add the extra views. You can get the LayoutInflater and inflate another layout:
final LayoutInflater inflator = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflator.inflate(R.layout.somecommonlayout, this);
Or you can create Views in code and add them:
ImageView someImageView = new ImageView(getContext());
someImageView.setImageDrawable(myDrawable);
someImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addView(someImageView);
If you're going to use the Context a lot, you can store a reference to it in your constructors and use that rather than getContext() to save a little overhead.

just use something like this:
<org.myprogram.MyFancyLayout>
...
</org.myprogram.MyFancyLayout>
Useful link - http://www.anddev.org/creating_custom_views_-_the_togglebutton-t310.html

Related

Group in ConstraintLayout prevents setting ProgressBar visibility

I'm flattening my layouts by employing the new ConstraintLayout (1.1.2) but I can no longer control the visibility of a ProgressBar when it's in a group. Here's the simplest version of my layout XML file which reproduces the issue:
<android.support.constraint.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- This group prevents me from hiding ProgressBar -->
<android.support.constraint.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="imageLoadingSpinner" />
<ProgressBar
android:id="#+id/imageLoadingSpinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</android.support.constraint.ConstraintLayout>
I've introduced the Group in order to control some of the layout, although for simplicity I've left all that out. The essence of the bug is introducing the group prevents me from setting the ProgressBar's visibility to GONE.
The following code no longer hides the ProgressBar:
find(R.id.imageLoadingSpinner).setVisibility(GONE);
To be clear, if I remove the group, as shown below, I can set the ProgressBar's visibility to GONE:
<android.support.constraint.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- Commenting out this group allows me to hide ProgressBar -->
<!--<android.support.constraint.Group-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--app:constraint_referenced_ids="imageLoadingSpinner" />-->
<ProgressBar
android:id="#+id/imageLoadingSpinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</android.support.constraint.ConstraintLayout>
I can't find any information about this issue. ProgressBar visibility issue is unrelated.
While, as has been said, you can't change the visibility of just a single Group's child (and for that I find this widget much less helpful and fun), you can always set its alpha property.
So if you don't mess in any way with an alpha value of a view you want to hide, you can just do the following:
find(R.id.imageLoadingSpinner).setAlpha(0);
And I can't quite find a reason not to do this, except maybe the fact that the view will probably still be rendered and then all the rendered pixels will be converted to 0-alpha and thus will become invisible wasting a few clock's cycles, but in most cases it would be an exaggeration to consider this as a problem.
I don't think it's an issue because essentially that's the use of the Group, according to the docs:
This class controls the visibility of a set of referenced widgets.
Also:
The visibility of the group will be applied to the referenced widgets. It's a convenient way to easily hide/show a set of widgets without having to maintain this set programmatically.
So you have to set the visibility on the group itself. I don't know what you use the group for, because you didn't specify, but maybe you should restructure to better take advantage of it, or get rid of it completely.
Just to add to the variety of solutions - you could extend Group and make it behave like you would expect. It's a bit more convenient if you use groups a lot and you expect normal parent-child behaviour in terms of visibility.
public class LayoutGroup extends Group {
public LayoutGroup(Context context) {
super(context);
}
public LayoutGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LayoutGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void updatePreLayout(ConstraintLayout container) {
int visibility = this.getVisibility();
float elevation = 0.0F;
if (Build.VERSION.SDK_INT >= 21) {
elevation = this.getElevation();
}
for(int i = 0; i < this.mCount; ++i) {
int id = this.mIds[i];
View view = container.getViewById(id);
if (view != null) {
// If the group is visible, let the child control visibility
view.setVisibility(visibility == VISIBLE ? view.getVisibility() : visibility);
if (elevation > 0.0F && Build.VERSION.SDK_INT >= 21) {
view.setElevation(elevation);
}
}
}
}
}
For reference, here is the code from the base class (Group):
for(int i = 0; i < this.mCount; ++i) {
int id = this.mIds[i];
View view = container.getViewById(id);
if (view != null) {
view.setVisibility(visibility);
if (elevation > 0.0F && VERSION.SDK_INT >= 21) {
view.setElevation(elevation);
}
}
}
Then in your layout file of course you use your own component:
<yourpackage.LayoutGroup
android:id="#+id/group_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="view1,view2,view3"/>

Custom Android Components with XML children

I'm an iOS developer that is relatively new with Android and I'm trying to create reusable components that separate the controller logic and View definition. I want a pattern similar to iOS IBOutlets where you define a class that can be used with different xib files or storyboard layouts.
For example, say I want to create a custom progress bar component. I want the user to be able to provide the required children and design and style them separately in xml.
Here's some pseudo code of what I'm trying to accomplish:
layout.xml
<FrameLayout>
<!-- A vertical progress bar -->
<CustomProgressBar>
<LinearLayout orientation="vertical">
<ImageView id="#+id/bar" src="#drawable/bar_image" />
<TextView id="#+id/label" text="Bar 1"/>
</LinearLayout>
</CustomProgressBar>
<!-- A horizontal bar using same controller class -->
<CustomProgressBar>
<LinearLayout orientation="horizontal">
<ImageView src="#drawable/background_image" />
<ImageView id="#+id/bar" src="#drawable/bar_image" />
<TextView id="#+id/label" text="Bar 1"/>
</LinearLayout>
</CustomProgressBar>
<FrameLayout>
Then my custom class might look like:
public class CustomProgressBar extends FrameLayout {
private ImageView bar;
private TextView label;
.
.
#Override
protected void onFinishInflate() {
super.onFinishInflate();
// Store the references of the components
bar = (ImageView) findViewById(R.id.bar);
label = (TextView) findViewById(R.id.label);
// Now I should be able to write general code for this component
// using the reference components I found
}
}
In the above example, the developer is instantiating 2 CustomProgressBars in the same xml file. But each bar's layout is drastically different (child display tree and orientation is different). The obvious problem here is that the xml won't compile because I'm using the same ids for different views in the xml. To fix the compilation issue, I could change the id names, but then the controller class won't know how to find references to those children.
Is there another way to approach this problem?
There are methods in ViewGroup called getChildCount and getChildAt which will allow you to pull the child views by index.
So what you will have to do is something like:
#Override
protected void onFinishInflate() {
super.onFinishInflate();
getProgressViews(this);
}
private ImageView bar;
private TextView label;
private void getProgressViews(ViewGroup viewGroup) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = viewGroup.getChildAt(i);
if (view.getClass().getSuperclass() == ViewGroup.class) {
getProgressViews((ViewGroup) view);
}
if (view instanceof ImageView) {
bar = (ImageView) view;
}
if (view instanceof TextView) {
label = (TextView) view;
}
}
}
If you are looking to use these to also manually draw them to screen you need to override the onLayout method.

Inflate into "this"?

I've been creating apps without much XML, creating views programmatically. I'd like to switch to XML. So I wrote an XML file for a RelativeLayout, and I need to inflate it into an existing class (a subclass of RelativeLayout, of course) that has all the implementation logic.
How do I inflate into "this" in the constructor?
By the way, what's really the advantage of XML? When I create views in the code, I scale fonts and images and also move views around depending on the screen's size, orientation, aspect ratio, etc. With XML approach, I'd have to create a separate XML for all possible configurations...
Constructor code:
public OrderEditControl()
{
super(LmcActivity.W.getApplicationContext());
Resources res = LmcActivity.W.getResources();
setBackgroundColor(Color.TRANSPARENT);
headers = res.getStringArray(R.array.item_list_columns);
widths = new int[headers.length];
createLabels();
createButtons();
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_TOP);
lp.addRule(RIGHT_OF, labels[LabelType.CUSTOMER.ordinal()].getId());
lp.addRule(LEFT_OF, buttons[ButtonType.FIND_CUSTOMER.ordinal()].getId());
customerView = new TextView(LmcActivity.W.getApplicationContext());
customerView.setTextColor(Color.BLACK);
customerView.setId(400);
customerView.setTypeface(Typeface.DEFAULT_BOLD);
customerView.setGravity(Gravity.CENTER_VERTICAL);
addView(customerView, lp);
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_TOP, labels[LabelType.SHIP_TYPE.ordinal()].getId());
lp.addRule(ALIGN_BOTTOM, labels[LabelType.SHIP_TYPE.ordinal()].getId());
lp.addRule(RIGHT_OF, labels[LabelType.SHIP_TYPE.ordinal()].getId());
shipSpinner = new Spinner(LmcActivity.W);
shipSpinner.setId(401);
shipSpinner.setAdapter(shipAdapter);
shipSpinner.setOnItemSelectedListener(this);
addView(shipSpinner, lp);
deliveryView = new EditText(LmcActivity.W.getApplicationContext());
deliveryView.setGravity(Gravity.CENTER_VERTICAL);
deliveryView.setSingleLine();
deliveryView.setId(402);
addView(deliveryView);
lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(RIGHT_OF, labels[LabelType.COMMENTS.ordinal()].getId());
lp.addRule(LEFT_OF, buttons[ButtonType.ITEMS.ordinal()].getId());
lp.addRule(ALIGN_TOP, labels[LabelType.COMMENTS.ordinal()].getId());
commentView = new EditText(LmcActivity.W.getApplicationContext());
commentView.setGravity(Gravity.TOP);
commentView.setId(403);
addView(commentView, lp);
lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
lp.addRule(BELOW, commentView.getId());
itemList = new ListView(LmcActivity.W.getApplicationContext());
itemList.addHeaderView(createRow(null, null), null, false);
itemList.setOnItemClickListener(this);
itemList.setAdapter(itemAdapter);
itemList.setCacheColorHint(0);
itemList.setBackgroundColor(Color.TRANSPARENT);
itemList.setId(404);
addView(itemList, lp);
lays[0] = new LayParm(false);
lays[1] = new LayParm(true);
}
/** create the view's buttons */
private void createButtons()
{
for (int i = 0; i < N_BUT; ++i)
{
Button but = i == ButtonType.ITEMS.ordinal() ?
new TextGlassButton(2.4f, LmcActivity.W.getResources().getString(R.string.items), Color.WHITE) :
new EffGlassButton(1.2f, butEffects[i]);
but.setId(BUT_ID + i);
but.setOnClickListener(this);
buttons[i] = but;
if (i == ButtonType.DATE.ordinal())
addView(but);
else
{
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (i < 2)
lp.addRule(ALIGN_PARENT_TOP);
else
lp.addRule(BELOW, BUT_ID + i - 2);
if (i % 2 == 0)
lp.addRule(ALIGN_PARENT_RIGHT);
else
lp.addRule(LEFT_OF, BUT_ID + i - 1);
addView(but, lp);
}
}
}
/** create text labels */
private void createLabels()
{
Paint paint = AFDraw.W.textPaint;
paint.setTextSize(Universe.TEXT_SIZE);
paint.setTypeface(LmcActivity.W.defaultTypeface);
String[] titles = LmcActivity.W.getResources().getStringArray(R.array.order_labels);
for (int i = 0; i < titles.length; ++i)
{
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_LEFT);
if (i == 0)
lp.addRule(ALIGN_PARENT_TOP);
else
lp.addRule(BELOW, LABEL_ID + i - 1);
TextView tv = new TextView(LmcActivity.W.getApplicationContext());
tv.setText(titles[i]);
tv.setTextColor(Color.BLACK);
tv.setId(LABEL_ID + i);
tv.setTypeface(LmcActivity.W.defaultTypeface);
tv.setGravity(Gravity.CENTER_VERTICAL);
labels[i] = tv;
addView(tv, lp);
labelWidth = Math.max(labelWidth, paint.measureText(titles[i]));
}
labelWidth += Universe.TEXT_SIZE * 0.5f;
dateWidth = paint.measureText("00/00/00") + Universe.TEXT_SIZE * 1.5f;
}
#scriptocalypse is generally right, but subclassing some layouts and inflating custom layout to this class helps to separate different abstractions. There are so many bad tutorials, in which everything is done in the Activity. I see that the world's new comming programmers will code only crap looking applications.
With custom layout you can do in Activity only such a thing:
medicineView.putMedicine(medicineList);
instead of all crappy adapter creations and looking for views...
Firstly you should create some view for your custom View:
<RelativeLayout ...>
<!-- You put all your views here -->
</RelativeLayout>
Secondly if you are sattisfied with your view, you should change the root to merge tag:
<merge ...>
<!-- You put all your views here -->
</merge>
This is very important. We begin design with RelativeLayout tags in order to IDE know how to draw layouts, and how to do completions. But if we leave it as it is, we will end up in two nested RelativeLayouts it will be something like that in the end:
<RelativeLayout ...> <!-- That is your class -->
<RelativeLayout ...> <!-- This is inflated from layout -->
<!-- You put all your views here -->
</RelativeLayout>
</RelativeLayout>
If you change your layout to "merge" then it will look like this:
<RelativeLayout ...> <!-- That is your class -->
<merge...> <!-- This is inflated from layout -->
<!-- You put all your views here -->
</merge>
</RelativeLayout>
and will be merged to its root:
<RelativeLayout ...> <!-- That is your class, merged with layout -->
<!-- You put all your views here -->
</RelativeLayout>
At the end you must subclass demanded View or ViewGroup:
public class CustomView extends RelativeLayout {
public CustomView(Context context) {
super(context);
initialize();
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
private void initialize() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.id.your_layout, this, true);
// find your views, set handlers etc.
}
}
Usage
Just like #scriptocalypse already said. In another layout you use this like that:
<SomeLayout>
<com.foo.CustomView>
</SomeLayout>
First, to answer your main question:
you would not want to inflate an XML RelativeLayout into your RelativeLayout class. You'd extend RelativeLayout and then declare an instance of your RelativeLayout in an XML file, like so:
// com.foo.MyRelativeLayout.java
public class MyRelativeLayout extends RelativeLayout{
/**
* Implement MyRelativeLayout
*/
}
and...
// layout_example.xml
<?xml version="1.0" encoding="utf-8"?>
<com.foo.MyRelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Put More Views in here... -->
<TextView
android:id="#+id/customer_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/customer_name_placeholder" />
<!-- and on... -->
</com.foo.MyRelativeLayout>
But more to the point, if you're using the XML to lay out your file, you don't need any of those instantiations or .addRule() method invocations inside your MyRelativeLayout file because you've done it declaratively in XML instead.
To answer your second question of "Why do you want to use XML anyway?"
There are many reasons. Maybe these apply to you, and maybe they don't, but they're ones that I can think of fairly easily that have been relevant in my work.
You don't actually have to create a new layout file for every separate screen size or use case. For the most part, a single layout file will suffice for most screens. You might find that you will have size/resolution/orientation specific dimens.xml or style.xml files, but unless you want a dramatically different arrangement for your different possibilities then the layouts themselves don't repeat themselves too often.
You can use a visual editor. This is important if you're working in teams, and your teammates don't like to or want to use only Java to lay out their screens. While I and others gladly create View and Layout subclasses to fit our needs, I know of literally nobody who prefers to use Java as their primary layout language. Finding people who will work with you (or a job where everyone else uses the XML tools) could be challenging.
If you're creating tools for other people to use (like the above-mentioned folks who prefer XML) you can actually give them custom attributes to work with, that make positioning and layout more powerful. These attributes could be hard-coded in the XML, or they could be references to any of the other Android resources (drawable/string/color/integer/boolean/etc...). As a contrived example, but one based on your code, you could give your users the ability to specify a number of buttons to create rather than rely on the N_BUT variable. You could give it a default value, but offer users a way to change it in XML.
Here is an example:
// somelayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.foo.MyRelativeLayout
xmlns:param="http://schemas.android.com/apk/res-auto"
style="#style/MyRelativeLayoutStyle"
param:numberOfButtons="3">
</com.foo.MyRelativeLayout>
and in a different file...
//attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyRelativeLayout">
<attr name="numberOfButtons" format="reference|integer" />
</declare-styleable>
</resources>
and in your MyRelativeLayout, you access those attributes from the AttributeSet in its constructor (the one called by Android when it uses XML to create a layout).
Using the style="#style/foo" syntax can allow you to create "classes" of styles that can apply to all kinds of views without actually making a View subclass. Let's say you know that you always want to have a set of parameters that hold true for all your Button elements but don't want to subclass Button.
For example:
// styles.xml
<style name="BaseButton">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:background">#drawable/bg_common_button</item>
<item name="android:textColor">#color/white</item>
<item name="android:textSize">#dimens/base_button_text_size</item>
<!-- ^^ that dimen value could vary from screen size to screen size, but the style likely won't -->
</style>
// button_layout.xml
<Button
android:id="#+id/styled_button"
style="#style/BaseButton" /> <!-- and you're done -->
// some_other_layout.xml
<LinearLayout
style="#style/BaseLinearLayout">
<Button style="#style/BaseButton" android:text="Button1" />
<Button style="#style/BaseButton" android:text="Button2" />
<Button style="#style/BaseButton" android:text="Button3" />
</LinearLayout>
If you would like to instantiate that button using code, then you can use the LayoutInflater to inflate that specific button's layout and use that wherever you want. In fact, you can create all manner of components in XML and then inflate them at runtime.
LayoutInflater inflater = LayoutInflater.from(YourActivity.this);
Button theInflatedButton = inflater.inflate(R.layout.button_layout.xml, null);
Of course, the canonical example is ListViews and the items that you wish to populate them. You'd create a listview item layout xml and then inflate that whenever your Adapter is in need of a new convertView instance.

android, adding XML Layout Views to an inflated view in a custom class

i havent found an answer on the internet for quite a while and now im asking you if you can help me.
Short:
How should i override addView() (or something else) to add Views defined in XML to my "custom view inflated XML Layout"
Long:
I want to create a custom view for my android app, so i created a clean subclass from RelativeLayout. In this, i let the Inflater load a xml layout to get a nice style.
But now, i want to add something inside the custom view, but dont want to add it programattically (this simply works), but with xml. I cant cross the gap in my mind to find the solution...
code:
Custom Class:
public class Slider extends RelativeLayout {
private RelativeLayout _innerLayout;
public Slider(Context context) {
super(context);
init();
}
public Slider(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
protected void init() {
LayoutInflater layoutInflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
_innerLayout = (RelativeLayout) layoutInflater.inflate(R.layout.layout_r, this);
}
#Override
public void addView(View child) {
//if (_innerLayout != null) _innerLayout.addView(child);
super.addView(child);
}
... all other addView's are overridden in the same way
XML File using the subclass:
<packagename....Slider
android:id="#+id/slider1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#color/Red" >
<TextView
android:id="#+id/heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="HEADING" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="bbb" />
...
The TextView And Button are added to the subclass... sure... but after that, i got 3 children in Slider, the TextView, the Button and my inflated layout from R.layout.layout_r. But i just want 1 child (the layout_r) with Button and TextView in it.
As you can see in addView i tried to simply add the passed "View child" to the _innerLayout. That doesnt work. Android framework keeps calling addView and it ends with StackOverFlowError
Two thing to tell you too:
I know adding Views from XML doesnt call the given addView, but i've overriden all others too and all are looking the same so theres no need to show them.
Debugger said me, that addView is called BEFORE the _innerLayout gets the inflated Layout
Is 2. the reason?
Can u help me?
You can take look on how to inflate children into custom view here (vogella tutorial).
What you need is:
Define layout with children using <merge> tag
Inflate this layout in custom view constructor using LayoutInflater.inflate(res, this, true)
Just override your addView() method in your custom view Slider and check count of childs.
If getChildCount() == 0, then this is first addition and it is view initializing.
Kotlin example:
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
if (childCount == 0) {
super.addView(child, index, params)
} else {
// Do my own addition
}
}

Using View.getParent() in View's constructor

Why doesn't this work?
private ScrollView findScrollParent(View v)
{
if(v==null)
{
return null;
}
else if(v instanceof ScrollView)
{
return (ScrollView) v;
}
else
{
return findScrollParent((View)v.getParent());
}
}
CustomView(Context context, AttributeSet as)
{
super(context,as);
ScrollView sv = findScrollParent(this);
}
This is my xml file which is inflated:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:layout_height="wrap_content"
android:layout_width="wrap_content">
<com.myapp.CustomView android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
</ScrollView>
</RelativeLayout>
I want to have a reference to the scrollView in the custom View. My findScrollParent method finds the LinearLayout then returns null. I call this method in the View v's constructor, is that the problem?
You can safely access your parent in onAttachedToWindow() method.
See View Life Cycle.
If you are calling it in the view's constructor then surely it will not have it's parent assigned yet?
As far as I can see from skimming the source the parent attribute is not set until the view is added to the parent at which point it will do something like:
child.mParent = this;
So if you are in the constructor for the view then my guess is it is too early and the view will be added after it is constructed. I was not 100% sure what combination of classes you were talking about / how you had them fitting together so may have looked at the wrong source.
EDIT
Just to clarify with pseudo code the process might look like this
View view = new View(); // Inside here you are calling your method - which won't work
addView(view); // The parent is only just being set as a result of this method
view.getParent(); // At this point we can now return the expected result
EDIT 2 - #codelark 's suggestion
private _scrollView; // member variable
private ScrollView getScrollView()
{
if (null == _scrollView) {
_scrollView = findScrollParent(this);
}
return _scrollView;
}

Categories

Resources