How to make my own XML layout view from other views - android

I have one RelativeLayout that has TextView (first label) , EditText(for input), TextView (second label). I have this in at least 10 activities in my project. How I can extract view and make my own. So, if I want to change textSize , I will have to change it on just one place, not 10.
For example I would like to have this
<RelativeLayout
android:width="match_parent"
android:height="wrap_content"
>
<TextView
android:id="firstTextView"
...
android:text="I like">
<EditText
android:id="edittextColor"
hint="type some color here"
... >
<TextView
android:id="secondTextView"
...
android:text="car.">
</RelativeLayout>
So, I need something like this on a lot of place. What I would like to have is:
<MySpecialView
firstText="I like"
colorEditTextHint="type color here"
secondText="car"/>

Inflaters
Let's suppose that your RelativeLayout file is called reusable_layout. This means that you could access it as R.layout.reusable_layout (considering that you have this file stored in the layouts folder of your project).
In your usual override of onCreate() add these variables at the start: LayoutInflater inflater = getSystemService(LAYOUT_INFLATER_SERVICE);
RelativeLayout layout = inflater.inflate(R.layout.reusable_layout, null);
Afterwards, call setContentView(layout);
If you want to edit the children you can call layout.getChildAt(int childNumber); This would return you a View
An example of editing the first TextView child:
TextView tv = (TextView) layout.getChildAt(0);
tv.setText("Example String");
UPDATE:
Another way to do what you want!
Creating a custom view may do the job!
A good tutorial on these is included here: https://developer.android.com/training/custom-views/create-view.html#subclassview
I think all you need to know is included in that.
Another possibly useful source would be included here: how to add views inside a custom View?
Hope I helped,
-Daniel

You can create one common layout and include in all the 10 activities layout like this
common_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/label1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Label1"/>
<EditText
android:id="#+id/input1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/label1"
android:text="Input1"/>
</RelativeLayout>
activity_layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/common_layout"/>
<TextView
android:id="#+id/textinactivity_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Activity text"/>
</LinearLayout>
I hope this is what you wanted.

Although Android offers a variety of widgets to provide small and
re-usable interactive elements, you might also need to re-use larger
components that require a special layout. To efficiently re-use
complete layouts, you can use the include and merge tags to
embed another layout inside the current layout.
https://developer.android.com/training/improving-layouts/reusing-layouts.html
What about <include>
create you your_base_layout.xml and <include> it in any other xml in the place where you want to add it
your_base_layout.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/some_other_id">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/button1" />
</LinearLayout>
<include
android:id="#+id/include_id"
layout="#layout/your_base_layout" />
example of usage: another_layout.xml
<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
android:id="#+id/include_id"
layout="#layout/your_base_layout" />
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/hello"
android:padding="10dp" />
...
</LinearLayout>
This is how you access views in it,
View includedLayout = findViewById(R.id.some_id_if_needed);
Button buttonInsideTheIncludedLayout = (Button) includedLayout.findViewById(R.id.button1); // if there is a button in your base layout that you included access like this
find great answers >here

You can define your own control with specified attributes.
Save ButtonPlus.java into your package.
e.g.
public class ButtonPlus extends Button {
public ButtonPlus(Context context) {
super(context);
}
public ButtonPlus(Context context, AttributeSet attrs) {
super(context, attrs);
CustomFontHelper.setCustomFont(this, context, attrs);
}
public ButtonPlus(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
CustomFontHelper.setCustomFont(this, context, attrs);
}
}
And you can use inside your layout XML file.

Related

Custom ViewGroup with children inserted at specific spot

I have several Activities in my Android app that have the same basic structure, and I'm trying to make my layouts DRY. The duplicated code looks like the below. It contains a scrollable area with a footer that has "Back" and "Dashboard" links. There's also a FrameLayout being used to apply a gradient on top of the scrollable area.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<ScrollView
android:layout_width="match_parent"
android:layout_height="689px">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- THE REAL PAGE CONTENT GOES HERE -->
</LinearLayout>
</ScrollView>
<ImageView
android:src="#drawable/GradientBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="50px"
android:background="?attr/primaryAccentColor">
<Button
android:layout_width="wrap_content"
android:layout_height="26px"
android:layout_gravity="center_vertical"
local:MvxBind="Click GoBackCommand" />
<Button
android:layout_width="wrap_content"
android:layout_height="26px"
local:MvxBind="Click ShowDashboardHomeCommand" />
</FrameLayout>
</LinearLayout>
To de-dupcliate my Activities, I think what I need to do is create a custom ViewGroup inherited from a LinearLayout. In that code, load the above content from an XML file. Where I am lost is how to get the child content in the Activity to load into the correct spot. E.g. let's say my Activity now contains:
<com.myapp.ScrollableVerticalLayoutWithDashboard
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- THE REAL PAGE CONTENT GOES HERE -->
<TextView android:text"blah blah blah" />
</com.myapp.ScrollableVerticalLayoutWithDashboard>
Now how do I cause the "blah blah blah" to appear in the correct place? I'm pretty sure if I did this, I would either end up with "blah blah blah" at the top or bottom of the page, not in the middle of the ScrollView as desired.
I'm using API 21 / v5.0+. Technically I'm doing all this with Xamarin, but hopefully that's irrelevant to the answer?
EDIT: An example of what the result would look like is this. The footer and gradient are part of the custom ViewGroup, but the rest would be content within the custom ViewGroup.
I don't know Xamarin so this is an native android solution, but should be easy to translate.
I think what I need to do is create a custom ViewGroup inherited from
a LinearLayout.
Yes, you could extend the LinearLayout class.
Where I am lost is how to get the child content in the Activity to
load into the correct spot.
In your custom implementation you need to handle the children manually. In the constructor of that custom class inflate the layout manually:
private LinearLayout mDecor;
public ScrollableVerticalLayoutWithDashboard(Context context, AttributeSet attrs) {
super(context, attrs);
// inflate the layout directly, this will pass through our addView method
LayoutInflater.from(context).inflate(R.layout.your_layout, this);
}
and then override the addView()(which a ViewGroup uses to append it's children) method to handle different types of views:
private LinearLayout mDecor;
public void addView(View child, int index, ViewGroup.LayoutParams params) {
// R.id.decor will be an id set on the root LinearLayout in the layout so we can know
// the type of view
if (child.getId() != R.id.decor) {
// this isn't the root of our inflated view so it must be the actual content, like
// the bla bla TextView
// R.id.content will be an id set on the LinearLayout inside the ScrollView where
// the content will sit
((LinearLayout) mDecor.findViewById(R.id.content)).addView(child, params);
return;
}
mDecor = (LinearLayout) child; // keep a reference to this static part of the view
super.addView(child, index, params); // add the decor view, the actual content will
// not be added here
}
In Xamarin you're looking for the https://developer.xamarin.com/api/member/Android.Views.ViewGroup.AddView/p/Android.Views.View/System.Int32/Android.Views.ViewGroup+LayoutParams/ method to override. Keep in mind that this is a simple implementation.
EDIT: Rather than putting a LinearLayout inside a LinearLayout, you could just use the 'merge' tag. Here's the final layout you'd want:
<?xml version="1.0" encoding="utf-8" ?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:id="#+id/svfFrame1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<ScrollView
android:layout_width="match_parent"
android:layout_height="689px">
<LinearLayout
android:id="#+id/svfContentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="23px" />
</ScrollView>
<ImageView
android:src="#drawable/GradientBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
</FrameLayout>
<FrameLayout
android:id="#+id/svfFrame2"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="50px"
android:background="?attr/primaryAccentColor">
<Button
android:id="#+id/FooterBackButton"
android:layout_width="wrap_content"
android:layout_height="26px"
android:layout_gravity="center_vertical"
android:layout_marginLeft="24px" />
<Button
android:id="#+id/FooterDashboardButton"
android:layout_width="wrap_content"
android:layout_height="26px"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="24px" />
</FrameLayout>
</merge>
And here's the final working C# view for Xamarin based on that layout:
public class ScrollableVerticalLayoutWithDashboard: LinearLayout
{
public ScrollableVerticalLayoutWithDashboard(Context context, IAttributeSet attrs) : base(context, attrs)
{
LayoutInflater.From(context).Inflate(Resource.Layout.ScrollableVerticalFooter, this);
base.Orientation = Orientation.Vertical;
}
public override void AddView(View child, int index, ViewGroup.LayoutParams #params)
{
// Check to see if the child is either of the two direct children from the layout
if (child.Id == Resource.Id.svfFrame1 || child.Id == Resource.Id.svfFrame2)
{
// This is one of our true direct children from our own layout. Add it "normally" using the base class.
base.AddView(child, index, #params);
}
else
{
// This is content coming from the parent layout, not our own inflated layout. It
// must be the actual content, like the bla bla TextView. Add it at the appropriate location.
((LinearLayout)this.FindViewById(Resource.Id.svfContentLayout)).AddView(child, #params);
}
}
}

Preview layout with merge root tag in Intellij IDEA/Android Studio

Let's imagine we are developing compound component based on LinearLayout. So, we create class like this:
public class SomeView extends LinearLayout {
public SomeView(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(LinearLayout.VERTICAL);
View.inflate(context, R.layout.somelayout, this);
}
}
If we'll use LinearLayout as a root of somelayout.xml, we'll have extra view level, so we use merge tag:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some text"
android:textSize="20sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some other text"/>
</merge>
But in Preview tab in IDE merge always acts as FrameLayout, and we'll see something like that:
(It is Android Studio, Intellij IDEA is just the same, about Eclipse I don't know)
Preview speed up developing layouts a lot, it's sad lose such a great help even for some layouts. May be there is a way to specify, how Preview should interpret merge tag in particular layout?
There is a new parentTag tools attribute (added in Android Studio 2.2) that you can use to specify the layout type for a merge tag, which will make the layout render correctly in the layout editor preview.
So using your example:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="LinearLayout"
tools:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some text"
android:textSize="20sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some other text"/>
</merge>
Note: Both android:layout_width and android:layout_height must be specified in order for the layout to display properly in the editor.
Edit: Outdated answer. See answer by starkej2.
Android Studio 0.5.8 added support for tools:showIn. By using it it is possible to preview < merge > layouts.
http://tools.android.com/recent/androidstudio058released
layout/layout_merge.xml with tools:showIn:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="#layout/simple_relativelayout">
......
</merge>
layout/simple_relativelayout.xml with include:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/layout_merge"/>
</RelativeLayout>
Is also possible use custom class as parent instead of merge like
<com.mycompany.SomeView xmlns:android="http://schemas.android.com/apk/res/android">
...
</com.mycompany.SomeView>
And then directly inflate this layout and cast result view to SomeView.
Android studio will directly check parent class of SomeView and handle preview like LinerLayout.
You can use onFinishInflate() method in the SomeView to bind views by findViewById().
Benefit of this solution is that you can put all layout definitions or style definition directly to the layout file, you can't use method like setOrientation() in code.

Get reference of custom view by id declared in xml from activity

I have a custom view that extends ImageView and I use it in an XML layout like this:
<?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:layout_gravity="center"
android:orientation="vertical" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp">
<com.android.example.MyView
android:id="#+id/myview1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp" />
<com.android.example.MyView
android:id="#+id/myview2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp" />
</LinearLayout>
</LinearLayout>
in my activity I do the usual: setContentView(R.layout.myLayout) .
Now, I need to get the reference of my class/View "MyView" in order to set a custom listener, but I i'm not able to get it from the id.
myview (MyView) findViewById(R.id.myview1);
returns null.
I tried to look at similar issues but haven't found any that helped me.
Please, note that if I add the View to the layout programmatically from the Activity everything is working fine, but I would like to be able to find what the issue is here.
Thanks in advance.
It does work.
The layout you're using for setContentView must be the same layout you have added your custom view to.
I found the issue was a stupid cut and paste mistake where I forgot to add the AttributeSet when calling the super()
having
public MyView(Context context, AttributeSet attrs) {
super(context);
}
instead of
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}

custom view becomes invisible in xml layout

I'm creating custom view derived from EditText. My view is declared as inner class like this:
package com.woodshy.glucoXpert.DPass;
//...
public class DPassValuesEditActivity extends GenericScreenActivity {
//...
public static class DPassValuesEditField extends EditText {
protected String mDbFieldName;
public DPassValuesEditField(Context context) {
super(context);
}
public DPassValuesEditField(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.DPassValuesEditField);
CharSequence s = a
.getString(R.styleable.DPassValuesEditField_dbFieldName);
if (s != null) {
mDbFieldName = s.toString();
}
}
//...
}
}
res/attrs.xml file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DPassValuesEditField">
<attr name="dbFieldName" format="string" />
</declare-styleable>
</resources>
I'm adding my custom view to xml layout:
<?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/com.woodshy.glucoXpert"
android:id="#+id/LinearLayout01" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:orientation="vertical">
<ScrollView android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_gravity="center_vertical"
android:scrollbars="vertical">
<RelativeLayout android:id="#+id/editFielsdLayout"
android:layout_height="fill_parent" android:layout_width="fill_parent"
android:clickable="true">
<view
class="com.woodshy.glucoXpert.DPass.DPassValuesEditActivity$DPassValuesEditField"
android:id="#+id/edtWeight" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:width="75px"
android:maxLines="1" android:layout_below="#+id/TextView01"
android:lines="1" android:gravity="center" android:imeOptions="flagNoExtractUi"
android:inputType="numberDecimal" android:maxLength="3"
app:dbFieldName="Weight"></view>
</RelativeLayout>
</ScrollView>
</LinearLayout>
but I cant see it in visual editor (Eclipse SDK, Version: 3.6.1) while it appears in application in run-time and works fine.
Am I doing something wrong? How could I get my custom view visible in visual editor?
thanks.
Had the same problem.
Looks like it (plugin) need to reload something before being able to display custom views.
Try switch target platform in Graphical Layout Editor from current to some other (for example, from 2.1 to 2.3). That helps and even after switching back to "current" (2.1 in example) it continues to display custom views.
Also sometimes it required to restart eclipse...
How could I get my custom view visible in visual editor?
You can't.

Reusing layout XML and the code behind

I am trying to turn a couple buttons into a reusable component in Android. I have successfully gotten the XML / UI portion working, but I can't figure out how to make code behind it reusable between activities, short of recoding it everywhere. I am new to Android, so I apologize if this is obvious.
I've already reviewed this post several times: Android layout Tricks 3 - Part 1 but it seems to be missing a few files, and I do not have enough experience to rebuild them.
A dumbed down version of my main layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<WebView android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="375px"
android:layout_alignParentTop="true" />
<include layout="#layout/navbar"/>
</RelativeLayout>
and then of my "component":
<?xml version="1.0" encoding="UTF-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center">
<ImageButton android:id="#+id/Button1"
android:layout_width="71px"
android:layout_height="wrap_content"
android:background="#drawable/button1"
android:layout_alignParentBottom="true" />
<ImageButton android:id="#+id/Button2"
android:layout_width="75px"
android:layout_height="wrap_content"
android:background="#drawable/button1"
android:layout_toRightOf = "#+id/Button1"
android:layout_alignParentBottom="true" />
</LinearLayout>
</merge>
If you have any additional critiques on my XML, I would appreciate that too.
What I do is make an actual component that contains all of the common code and use it directly. Here is a dumbed down version of the component class:
public class NavigationBar extends LinearLayout {
public NavigationBar(Context context) {
super(context);
setupView(context);
hookupButtons(context);
}
public NavigationBar(Context context, AttributeSet attrs) {
super(context, attrs);
setupView(context);
hookupButtons(context);
}
private void setupView(Context context) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// inflate whatever layout xml has your common xml
inflater.inflate(R.layout.navigation_bar, this);
}
}
In my class hookupButtons does exactly what you think it would do. :-)
Then my in all my layout xmls look similar to this:
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.dragonglobal.dragonplayer.ui.widgets.NavigationBar
android:id="#+id/nav_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ListView
android:id="#+id/list_of_playlists"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
In the onCreate of each activity I also have this (that you can adapt to whatever you need):
NavigationBar navbar = (NavigationBar) findViewById(R.id.nav_bar);
navbar.setText("Playlists");
Of course you will need to add whatever imports you need.
EDIT
Just a clarification: My navigation_bar.xml looks very similar to yours except I don't have the merge lines in mine.
EDIT 2
Here is what hookupButtons looks like. I'm only showing one button but you should get the idea.
private void hookupButtons(final Context context) {
ImageButton playlistsBtn = (ImageButton)findViewById(R.id.nav_playlists_btn);
if (context instanceof PlaylistsActivity) {
playlistsBtn.setBackgroundDrawable(getResources().getDrawable(R.drawable.nav_playlists_active));
} else {
playlistsBtn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent i = new Intent(context, PlaylistsActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
context.startActivity(i);
}
});
}
}
By using in XML layout, you're making only the layout reusable, not any Java code that uses it.
You must create a subclass of View, if you want to reuse Java logic as well. The tutorial from your link has a sample that fits your need very well:
public class OkCancelBar extends LinearLayout {
...

Categories

Resources