Android layout placeholder for dynamically added view - android

In my Android Activity, I need to add a single View dynamically (at runtime), at a specific position in a layout. The view to be added is determined at runtime. For example, may layout may look something like this:
<LinearLayout ...>
<TextView ... />
<!-- Dynamic item to be added here -->
<TextView ... />
</LinearLayout>
What is the best way to achieve this?
One solution is to use layout.addView(view, index), but I'd prefer not to hardcode the index.
Another solution is to use a FrameLayout as a placeholder, and place may dynamic view inside the FrameLayout, for example:
<LinearLayout ...>
<TextView ... />
<FrameLayout android:id="#+id/placeholder" />
<TextView ... />
</LinearLayout>
Then:
findViewById(R.id.placeholder).addView(view);
However, this adds an unnecessary view in the hierarchy.
What would be the recommended way to do this?

I think that using a FrameLayout as a placeholder for your view "added at runtime" is a good solution.
I would not worry about the impact of the extra "unnecesssary" view on performance.

You can also achieve this "lazy include" behaviour by using a ViewStub, as described in this (rather old) article from Android Developers.

You can use addView(View child, int index) without hard coding the exact index.
If one of the view's neighbors can be fixed (for example there is a specific label just before the place you want to insert the item), you can get the index of that view, and insert your dynamic view after that:
View previousView = findViewById(ID_OF_PREVIOUS_VIEW);
ViewGroup parent = (ViewGroup)previousView.getParent();
int previousIndex = parent.indexOfChild(previousView);
parent.addView(theDynamicallyCreatedView, previousIndex + 1);
Or you can use a ViewStub, but instead of inflating a layout, just remove it:
View placeholder = findViewById(ID_OF_STUB);
ViewGroup parent = (ViewGroup)previousView.getParent();
int index = parent.indexOfChild (placeholder);
parent.removeViewAt(index);
parent.addView(theDynamicallyCreatedView, index);
This way you won't end up with a huge view hierarchy.

Related

How to inject a custom view to a viewGroup declared in an xml at runtime?

I' ve designed a xml layout-file with some standard ui-components in it. So far it works fine.
In my Java-code I've implemented a custom view which extends a SurfaceView for the purpose of animating things. Now I want to inject this custom view at runtime to the ui defined in the layout-file.
Do I have to provide for an empty view filler for that custom view in the XML?
How does this work? Can you show me a simple code snippet please ? Thanks
Use ViewGroup addView() method.
Also you can use ViewStub
You may use
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:background="#color/app_bg"
android:gravity="center_horizontal">
<include layout="#layout/titlebar"/>
where, this linearLayout is in basic layout while this layout in include is another layout file. So you can even reuse this layout multiple times.
Check examples here
this way it works, but is there any more elegant way to do this, e.g. with a ViewStub ?
// the animation-view:
final LighthouseView lighthouseView = new LighthouseView(this, controller);
controller.registerView(lighthouseView);
setContentView(R.layout.activity_lighthouse);
ViewGroup container = (ViewGroup) findViewById(R.id.lighthouse_container);
container.addView(lighthouseView, 0);
setContentView(container);

ViewGroup - Add XML content into different container within inflated ViewGroup

Is it possible to inflate a LinearLayout from XML that contains some static objects and another LinearLayout and later when "XML" code is used inside the LinearLayout it's content is being added inside the inner LinearLayout.
Explanation with some code removed:
<LinearLayout id="main">
<LinearLayout id="top">
<TextView text="This is always here" />
<ImageView src="#drawable/image_alwayshere" />
</LinearLayout>
<LinearLayout id="bottom">
</LinearLayout>
</LinearLayout>
This is then inflated by my View "CustomLinearLayout" or whatever we call it
and when using the View in another Layout:
<com.my.views.CustomLinearLayout>
<ImageButton id="button1" src="#drawable/button1" />
</com.my.views.CustomLinearLayout>
In this case, the ImageButton should not be added below "bottom" it should be added into it. SO whatever I have in top, stays static and whatever I want to change is added to the Bottom LinearLayout.
Is this possible and if it is how could it be done?
Not sure if it's good or bad practice, if it would work. But if I have a constant layout (top container, middle container and bottom container) and I have 10 different activities and the only one changing content is the middle one, I can easily make one change to the top and bottom container at one place instead of 10 places and have whatever "View" I want to show in my activity be added inside.
Maybe I need to create a whole new ViewGroup for this? But currently working on LinearLayout since it's functionality is pretty much what I need.
If not, then what I'm looking for is where and when a LinearLayout reads the content of the XML and then override that to be added to my inner LinearLayout instead.
It is possible, you can either use include tag to add the other xml layout into the "bottom" layout and make its visibility as "gone" and change it to "visible" when you need it.
Or you can do that dynamically and inflate the xml whenever you want then add it to "bottom" using ViewGroup.addView(View) method.

Is there any way to get Mvx.MvxLinearLayout to respect android:layout_weight?

I have a horizontal list of items that i wish to share the available screen width equally.
Previously I had used index binding to each item and a static LinearLayout with each item having an equal layout_weight.
When I replace the static LinearLayout with an Mvx.MvxLinearLayout and ItemsSource binding, and move the markup for the items into an Itemtemplate the layout_weight is no longer respected by the MvxLinearLayout.
I've tried various ways of restructuring the item template and there seems to be no way to get this control to obey layout_weight when arranging its items.
Is there any way to get Mvx.MvxLinearLayout to respect android:layout_weight, or any alternative approach that could give me a dynamic list of items that will arrange equally given a boundary of fixed dimensions?
Edit
Assuming a list of 3 items, with a string property 'Name'.
This markup works as expected, giving 3 text items of equal width:
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
local:MvxBind="Text Items[0].Name"
/>
<TextView
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
local:MvxBind="Text Items[1].Name"
/>
<TextView
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
local:MvxBind="Text Items[2].Name"
/>
</LinearLayout>
This Approach does not work, the layout weight is not applied correctly to the TextViews:
<Mvx.MvxLinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxBind="ItemsSource Items"
local:MvxItemTemplate="#layout/item_template" />
The ItemTemplate layout file:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
local:MvxBind="Text Name" />
The problem is, mvvmcross inserts an additional view when using MvxLinearLayout with an ItemTemplate. So instead of getting a view hierarchy:
MvxLinearLayout -> TextView
you end up with:
MvxLinearLayout -> MvxListItemView -> TextView.
The layout properties applied to the TextView, notably the weight, are therefore invisible to the LinearLayout. The MvxListItemView is primed with the default layout properties (wrap content), and no weight effects are seen.
The location where the action happens is in MvxViewGroupExtensions (e.g. Refill), where the subviews are added to the LinearLayout, and the CreateBindableView method in MvxAdapter, where the subview is packed inside an IMvxListItemView.
One way to solve this is to override the methods in MvxViewGroupExtensions, and after the views are added to update the layout properties of the MvxListItemViews (e.g. copy them from the underlying view). For example:
private static void Refill(this ViewGroup viewGroup, IAdapter adapter) {
.... // rest of code
Invalidate();
for (int j = 0; j < viewGroup.ChildCount; j++)
{
var child = GetChildAt(j) as MvxListItemView;
var param = new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WrapContent, 1.0f);
child.LayoutParameters = param;
}
}
any way to get MvxLinearLayout to respect android:layout_weight
None that I know of - but it sounds like you are in a perfect position to debug and experiment.
To debug layout issues, one very useful tool is the hierarchy viewer - http://developer.android.com/tools/debugging/debugging-ui.html - using this on both layouts may show you what the difference is when they are actually running on a device.
The MvxLinearLayout is just a LinearLayout - it inherits from it and simply adds a data-bound Adapter to allow you to add/remove children dynamically - see MvxLinearLayout.cs.
Based on this, one hypothesis is that maybe LinearLayout itself does not support layout_weight with such dynamic children. To investigate this:
you could try prototyping the UI without using Mvx - does a plain LinearLayout "respect layout_weight" as you add/remove children? If the answer is yes, then what is the difference between your prototype and MvxLinearLayout.cs.. If the answer is no then you know that the issue is in LinearLayout somewhere.
you could try looking at the LinearLayout source - e.g. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/1.5_r4/android/widget/LinearLayout.java - this investigation may uncover some way forward - for example, it may find some way you can force the LinearLayout to remeasure and you could then call that 'force remeasure' method from the ChildViewAdded and ChildViewRemoved events.
any alternative approach that could give me a dynamic list of items that will arrange equally given a boundary of fixed dimensions?
If the built-in layouts do not work for you, then it's pretty straight-forward to create your own custom layouts - e.g. see http://arpitonline.com/blog/2012/07/01/creating-custom-layouts-for-android/ - once you have done that, then adding binding is as simple as copying and pasting the ViewGroup binding code from MvxLinearLayout.cs.

Programmatically add a view to middle of LinearLayout

I have a LinearLayout with two views
<LinearLayout>
<TextView />
<Textview />
</LinearLayout>
Through my program, I want to add a third TextView between these two existing TextViews'.
It was easy to do with a RelativeLayout with layout_below parameter. How do I do this for LinearLayout?
LinearLayout.addView(View v, int index)
The docs are a good place to start with this sort of thing.
Just pass an index to where you want it placed (i.e. 2nd position would be index 1).

How to bring view in front of everything?

I have activity and a lot of widgets on it, some of them have animations and because of the animations some of the widgets are moving (translating) one over another. For example the text view is moving over some buttons . . .
Now the thing is I want the buttons to be always on the front. And when the textview is moving I want to move behind the buttons.
I can not achieve this I tried everything I know, and "bringToFront()" definitelly doesn't work.
note I do not want to control the z-order by the order of placing element to layout cause I simply can't :), the layout is complex and I can not place all the buttons at the begging of the layout
You can call bringToFront() on the view you want to get in the front
This is an example:
yourView.bringToFront();
With this code in xml
android:translationZ="90dp"
I've been looking through stack overflow to find a good answer and when i couldn't find one i went looking through the docs.
no one seems to have stumbled on this simple answer yet:
ViewCompat.setTranslationZ(view, translationZ);
default translation z is 0.0
An even simpler solution is to edit the XML of the activity. Use
android:translationZ=""
bringToFront() is the right way, but, NOTE that you must call bringToFront() and invalidate() method on highest-level view (under your root view), for e.g.:
Your view's hierarchy is:
-RelativeLayout
|--LinearLayout1
|------Button1
|------Button2
|------Button3
|--ImageView
|--LinearLayout2
|------Button4
|------Button5
|------Button6
So, when you animate back your buttons (1->6), your buttons will under (below) the ImageView. To bring it over (above) the ImageView you must call bringToFront() and invalidate() method on your LinearLayouts. Then it will work :)
**NOTE: Remember to set android:clipChildren="false" for your root layout or animate-view's gradparent_layout. Let's take a look at my real code:
.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:hw="http://schemas.android.com/apk/res-auto"
android:id="#+id/layout_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/common_theme_color"
android:orientation="vertical" >
<com.binh.helloworld.customviews.HWActionBar
android:id="#+id/action_bar"
android:layout_width="match_parent"
android:layout_height="#dimen/dimen_actionbar_height"
android:layout_alignParentTop="true"
hw:titleText="#string/app_name" >
</com.binh.helloworld.customviews.HWActionBar>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/action_bar"
android:clipChildren="false" >
<LinearLayout
android:id="#+id/layout_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:gravity="center_horizontal"
android:orientation="horizontal" >
</LinearLayout>
<ImageView
android:id="#+id/imgv_main"
android:layout_width="#dimen/common_imgv_height"
android:layout_height="#dimen/common_imgv_height"
android:layout_centerInParent="true"
android:contentDescription="#string/app_name"
android:src="#drawable/ic_launcher" />
<LinearLayout
android:id="#+id/layout_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center_horizontal"
android:orientation="horizontal" >
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
Some code in .java
private LinearLayout layoutTop, layoutBottom;
...
layoutTop = (LinearLayout) rootView.findViewById(R.id.layout_top);
layoutBottom = (LinearLayout) rootView.findViewById(R.id.layout_bottom);
...
//when animate back
//dragedView is my layoutTop's child view (i added programmatically) (like buttons in above example)
dragedView.setVisibility(View.GONE);
layoutTop.bringToFront();
layoutTop.invalidate();
dragedView.startAnimation(animation); // TranslateAnimation
dragedView.setVisibility(View.VISIBLE);
GLuck!
Try FrameLayout, it gives you the possibility to put views one above another. You can create two LinearLayouts: one with the background views, and one with foreground views, and combine them using the FrameLayout. Hope this helps.
If you are using ConstraintLayout, just put the element after the other elements to make it on front than the others
i have faced the same problem.
the following solution have worked for me.
FrameLayout glFrame=(FrameLayout) findViewById(R.id.animatedView);
glFrame.addView(yourView);
glFrame.bringToFront();
glFrame.invalidate();
2nd solution is by using xml adding this attribute to the view xml
android:translationZ=""
You can try to use the bringChildToFront, you can check if this documentation is helpful in the Android Developers page.
There can be another way which saves the day. Just init a new Dialog with desired layout and just show it. I need it for showing a loadingView over a DialogFragment and this was the only way I succeed.
Dialog topDialog = new Dialog(this, android.R.style.Theme_Translucent_NoTitleBar);
topDialog.setContentView(R.layout.dialog_top);
topDialog.show();
bringToFront() might not work in some cases like mine. But content of dialog_top layout must override anything on the ui layer. But anyway, this is an ugly workaround.
You can use BindingAdapter like this:
#BindingAdapter("bringToFront")
public static void bringToFront(View view, Boolean flag) {
if (flag) {
view.bringToFront();
}
}
<ImageView
...
app:bringToFront="#{true}"/>
The order of the overlapping views really depends of 4 things:
The attribute android:elevation which is measured in dp/sp
The attribute android:translationZ which is also measured in dp/sp.
In Constraint Layout, the order in which you put the views in your Component Tree is also the order to be shown.
The programmatically order that you set through methods like view.bringToFront() in your kotlin/java code.
The numerals 1 and 2 compite with each other and take preference over the points 3 and 4: if you set elevation="4dp" for View 1 and translationZ="2dp" for View 2, View 1 will always be on top regardless of the numerals 3 and 4.
Thanks to Stack user over this explanation, I've got this working even on Android 4.1.1
((View)myView.getParent()).requestLayout();
myView.bringToFront();
On my dynamic use, for example, I did
public void onMyClick(View v)
{
((View)v.getParent()).requestLayout();
v.bringToFront();
}
And Bamm !
You can use elevation attribute if your minimum api level is 21. And you can reorder view to the bottom of other views to bring it to front. But if elevation of other views is higher, they will be on top of your view.
If you are using a LinearLayout you should call myView.bringToFront() and after you should call parentView.requestLayout() and parentView.invalidate() to force the parent to redraw with the new child order.
Arrange them in the order you wants to show. Suppose, you wanna show view 1 on top of view 2. Then write view 2 code then write view 1 code. If you cant does this ordering, then call bringToFront() to the root view of the layout you wants to bring in front.
Try to use app:srcCompat instead of android:src
You need to use framelayout. And the better way to do this is to make the view invisible when thay are not require. Also you need to set the position for each and every view,So that they will move according to there corresponding position
You can set visibility to false of other views.
view1.setVisibility(View.GONE);
view2.setVisibility(View.GONE);
...
or
view1.setVisibility(View.INVISIBLE);
view2.setVisibility(View.INVISIBLE);
...
and set
viewN.setVisibility(View.VISIBLE);

Categories

Resources