Android Layout Templating [duplicate] - android

This question already has an answer here:
Custom Layout in android
(1 answer)
Closed 2 years ago.
I'm looking to a simple way to do layout templating in android.
I already check include and merge techniques without success.
(I think that it's possible creating custom Layouts and defining by code this behavior, but i wondered if that could be done by xml)
I want to define something like this:
[globalLayout]
<linearLayout params=xxx>
<linearLayout params=yyy>
<?yied ?>
</linearLayout>
</linearLayout>
[customView1]
<Linearlayout>
<ImageView />
<Button/>
</LinearLayout>
[customView2]
<Linearlayout>
<Button/>
<Button/>
<Button/>
</LinearLayout>
(these 3 xml should be reusable)
[HomeLayout]
<?include globalLayout >
<?include customView1 />
</include>
[ParamsLayout]
<?include globalLayout >
<?include customView2 />
</include>
The thing is that i want to have a reusable layout, if a perform a small change, it will affect all dependent views. somethink linked to "partial views or templating" in other languages.
Could anyone help me?

I have done something like this before by using view stub.
You can inflate any view you like inside that view.
<GlobalLayout>
<ViewStub>
<GlobalLayout>

Use LayoutInflater to do something like this:
On the Activity's onCreate:
super.onCreate(savedInstanceState);
setContentView(new TemplateInflater(this).apply(R.layout.products)
.onViewGroup(R.id.replace_here).ofTemplate(R.layout.template));
An implementation snippet:
public View ofTemplate(int templateId) {
LayoutInflater inflater = LayoutInflater.from(context);
View root = inflater.inflate(templateId, null);
View content = inflater.inflate(contentId, null);
((ViewGroup)root.findViewById(viewGroupId)).addView(content);
return root;
}
An example of a working code is in my git: https://github.com/erichegt/AndroidTemplating
I think this code will solve your problem, but you should use Fragments instead. You can have one Activity associated with a template and a Fragment to inflate it.

ViewStub is pretty straightforward and can cover basic layout templating needs.
It serves as a placeholder for some other layout which you can specify and inflate at runtime and then:
The inflated View is added to the ViewStub's parent with the ViewStub's layout parameters.
Here is example from one of my projects. In my layout template I have:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout ... >
...
<ViewStub
android:layout_width="0dp"
android:layout_height="0dp"
android:id="#+id/button_1_stub"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/split_guideline"
app:layout_constraintLeft_toRightOf="#+id/primary_left_guideline"
app:layout_constraintRight_toLeftOf="#+id/primary_right_guideline">
</ViewStub>
....
</android.support.constraint.ConstraintLayout>
... then, when I inflate it I am setting actual button layout that I need and inflate stub:
View contentView = inflater.inflate(R.layout.activity_main_template, null);
ViewStub button1Stub = contentView.findViewById(R.id.button_1_stub);
button1Stub.setLayoutResource(R.layout.work_button);
button1Stub.inflate();
... which inserts layout from R.layout.work_button instead of stub, imposing layout constraints I defined on the R.id.button_1_stub.

Related

Reuse a rendered layout in some other layout at run time

I am using a RecylerView inside a layout l1.xml. I am including this l1.xml inside l2.xml using include tag.
I update this RecyclerView after an api call but l2.xml is not showing the updated RecyclerView.
Is there a way to forcibly ask the parent to refresh?
invalidate(), refreshDrawableState(); on the parent layout didn't help?
Is there a smarter way to use a rendered layout in multiple places?
l1.xml
...
...
<LinearLayout
android:id="#+id/feed"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:background="#color/light_primary_background">
<include layout="#layout/events_list"/>
</LinearLayout>
...
...
events_list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/events_recycler_view"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
I update the events_recycler_view after an API call and the events_list.xml is updated but the include in l1.xml is not updated
Yes you can always use LayoutInflater to inflate a view, but the view must have the ids and type matching the id and type defined in your java code.
Check out this link for how to use layout inflater
http://www.programcreek.com/java-api-examples/android.view.LayoutInflater

StackOverflowError when inflating custom view which contains its own views

I am having a Fragment, where I inflate "fragment_board.xml":
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<view
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.app.BoardView"
android:id="#+id/view_board"/>
</FrameLayout>
As you can see, fragment_board contains a custom view "BoardView", from where I want to load the following "view_board.xml":
<com.app.BoardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:fadingEdge="none"
android:requiresFadingEdge="none"
android:overScrollMode="always"
android:id="#+id/board_scrollview_vertical">
<android.widget.HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none"
android:fadingEdge="none"
android:requiresFadingEdge="none"
android:overScrollMode="always"
android:id="#+id/board_scrollview_horizontal"
android:foregroundGravity="left">
</android.widget.HorizontalScrollView>
</com.app.BoardView>
My custom view contains two scroll views (I use it for panning), and I want to be able to re-use it in other layouts. The BoardView extends the outer (vertical) scroll view like this:
public class BoardView extends ScrollView
When I use it stand-alone, it inflates fine and I can findViewById() both scroll views in the inflated layout. But when I use it in a layout tree (such as a fragment), I run into problems.
In the fragment, I inflate the layout tree like this:
View view = inflater.inflate(R.layout.fragment_board, container, false)
From the fragment, I can findViewById() the outer (vertical) scroll view, but findViewById() returns null for the inner (horizontal) scroll view.
In BoardView, I inflate like this:
View view = inflate(getContext(), R.layout.view_board, this);
As said, I can find both scroll views fine when inflated by itself, but when inflated as part of the fragment, I get StackOverflowError.
It is clear why: I inflate in the fragment first, and then I inflate the same XML in the view again, which triggers another inflation of the view and so on. I get a cyclic reference here. Problem I can't figure out how I can add the inner (horizontal) scroll view to the already existing layout. I think I need to merge somehow or inflate layouts manually, but I can't figure out how.
Here are some refs (which didn't help in my case):
Android - Writing a custom (compound) component
How to inflate XML-Layout-File correctly inside Custom ViewGroup?
Create a custom View by inflating a layout?
InvocationTargetException on inflating an xml - android
Android: StackOverFlowError with InvocationTargetException when inflating layout
Can anybody suggest what I should do. If possible, I'd want BoardView to work as a generic component, which I can plug into a layout tree where needed.
As Android [according to one of the refs above] does not officially support composite components, would my best option be to drop XML layouts for the inner views and add them in code?
Please check if something like this fixes the issue:
fragment_board.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.app.BoardView
android:id="#+id/view_board"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
view_board.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:fadingEdge="none"
android:requiresFadingEdge="none"
android:overScrollMode="always"
android:id="#+id/board_scrollview_vertical">
<android.widget.HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none"
android:fadingEdge="none"
android:requiresFadingEdge="none"
android:overScrollMode="always"
android:id="#+id/board_scrollview_horizontal"
android:foregroundGravity="left">
</android.widget.HorizontalScrollView>
</merge>
More information on using MERGE and INCLUDE

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);

Warning: This <FrameLayout> can be replaced with a <merge> tag

I have a FrameLayout that contains a TextView and two LinearLayouts:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
... a textview and 2 linearlayouts
</FrameLayout>
After running Android Lint, I get this warning: This <FrameLayout> can be replaced with a <merge> tag.
Why does this warning exist? What can I do to fix it (other than ignore)?
To understand this, you need to understand how layouts are inflated and placed.
Let's say for example, you have an activity and this is the layout xml you use. Here is what the layout of the activity looks before you put your layout file.
<FrameLayout> // This is the window
<FrameLayout> // This is activity
</FrameLayout>
</FrameLayout>
There might be few other layers depending on the device/OS.
Now when you inflate your layout file and put it in, this is how it will look.
<FrameLayout> // This is the window
<FrameLayout> // This is activity
//A textview and 2 linearlayouts
</FrameLayout>
</FrameLayout>
Do you see the FrameLayout inside the other FrameLayout? It is redundant to have that since it does not add much value. To optimize, you can replace your outer FrameLayout with the <merge> tag. This is what it will look like.
<merge> // This is the window
<FrameLayout> // This is activity
//A textview and 2 linearlayouts
</FrameLayout>
</merge>
Notice how there is no extra FrameLayout. Instead, it is just merged with the FrameLayout of the activity. Whenever you can, you should use <merge>. This doesn't only apply to FrameLayouts. You can read more about it here. http://developer.android.com/training/improving-layouts/reusing-layouts.html#Merge
Hope this helps.
Are you using this as the main layout of your activity? If so, you can replace it with a merge tag like this:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
... a textview and 2 linearlayouts
</merge>
At setContentView, Android will take the children of the merge tag and directly insert them to the FrameLayout with #android:id/content. Examine both approaches (FrameLayout vs merge) with HierarachyViewer to see the difference.
Refer this post by the Romain Guy for more information. It tells you why the merge option is suggested.
To render the XML in the device a process is followed in phases, and the first one is Measure.
Measure: In this phase, the sizes of the parent and their children and
their children, and so on, is measured. So your CPU scans all the
ViewGroup and View in your layout to measure their sizes. And sometimes for some
reasons, it may require this scanning recursively because of the
dependency of the size of the parent and their children on each other.
So why Lint is giving this warning?
Because your XML eventually will be loaded into a window which contains FrameLayout and in your XML file you are also using the FrameLayout, so eventually your XML will be placed in
FrameLayout of the window and it will be like this:
<FrameLayout> <!--belongs to window-->
<FrameLayout> <!--belongs to your XML-->
...
</FrameLayout>
</FrameLayout>
Now there is an overhead for CPU in the measure phase, to measure both the FrameLayout. And this overhead can be overcome if we can somehow manage to use the outer FrameLayout in our XML, that's exactly possible via merge tag.

Does LayoutInflater work with a layout xml file that uses includes?

Here's the rub: I have a layout that uses the include tag. The layout is simple ->
<LinearLayout>
<TextView ...>
<LinearLayout>
<include ...>
</LinearLayout>
</LinearLayout>
Now the included file is nothing more than:
<LinearLayout>
<TextView ... android:id="#+id/inner_text">
</LinearLayout>
Now, if I try to access the TextView inner_text in either the create or onStart call, it throws a NPE. I have tried using LayoutInflater to infate the included xml file and then access inner_text but to no avail - it always fails.
So, the question is : does LayoutInflater work with included xml files of the parent xml file? What I would like to do is grab the contents of the included xml file - and set the whole thing to be either visible or not visible based on preferences.
Nothing seems to allow me to grab the TextView object.
Now, when I include the xml as just a nested element in the parent file (not using the include tag) - then it accesses that LinearLayout just fine - but that defeats my purpose of trying to make the layout of the view dynamic - that is, I can change the contents of the child included layout at will - and not have to do any changes to the parent layout.
Any help, pointers, suggestions -> greatly appreciated.
As suggestion I think you can have in the main xml some Layout component left empty, them in runtime, inflate the main xml, then the included xml and add the content of the included in the space you left in the main.
But I think you example have to work, internally the Android uses the inflater to inflate resources. Can you edit and tell a bit more? Code examples can be helpfully.
well, very strange, when you inflate a view from any xml resource (if xml resources have or have not nested resources with include) the view must hold all the parsed xml. So, yes, LayoutInflater work with included xml files of the parent xml file.
i copied here the way i'm doing now...
my generic layout (a header with text, progress bar and image)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip">
<ImageView
android:id="#+id/genericHeaderLogoPin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:src="#drawable/logo_pin"/>
<TextView
android:id="#+id/genericHeaderTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="#id/genericHeaderLogoPin"
android:textSize="18dip"
android:textStyle="bold"
android:textColor="#FF0C9994"/>
<ProgressBar
android:id="#+id/genericHeaderProgressBar"
android:layout_width="25dip"
android:layout_height="25dip"
android:layout_centerVertical="true"
android:layout_toLeftOf="#id/genericHeaderLogoPin"
style="#android:style/Widget.ProgressBar.Inverse"
android:visibility="gone"/>
</RelativeLayout>
my parent layout... include the generic layout and a ListView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#drawable/background_new_search_activity_1">
<include layout="#layout/generic_header_layout" android:id="#+id/listaBusquedasHeader"/>
<ListView
android:id="#android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:cacheColorHint="#00000000"/>
</LinearLayout>
my onCreate method that inflate the parent layout mentioned above:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
requestWindowFeature(Window.FEATURE_NO_TITLE);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.lista_busquedas_activity, null);
setContentView(view);
listView = (ListView) findViewById(android.R.id.list);
listView.setOnItemClickListener(listItemClickListener);
((TextView)findViewById(R.id.genericHeaderTitle)).setText(R.string.messagesActivityTitle);
}
and that´s all, i hope this helps you.
Thanx for the help - I discovered what my problem was - I was trying to inflate the included file - not the parent container - so that's why it never found the elements. I wish the documentation would have mentioned that - logically I would think the parent xml file would load - but the included files may need inflating - guess not.
Works now thanks to your code examples you submitted.
Thanx.

Categories

Resources