Custom TextInputLayout class, extra top margin when used in layout files - android

When creating a custom TextInputLayout View class, the layout file for the custom class looks normal, but when used in activity/fragment layout files the view puts in some extra top margin and I don't understand why.
Here is the custom view layout file:
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/text_input_layout"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="My Custom TextInputLayout">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/edit_text_date"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
When I check the design preview for this custom layout file, it appears normal, with only the small margin for the floating hint:
And below is the custom view class (I am using ViewBinding in the class, although I don't think this relates to the problem, it still appears with regular inflate()):
class CustomTextInputLayout(context: Context, attrs: AttributeSet): TextInputLayout(context, attrs) {
val binding: CustomTextInputLayoutBinding = CustomTextInputLayoutBinding.inflate(LayoutInflater.from(context), this, true)
}
So with this all set up, I am using it in an Activity layout, as below:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:context=".MainActivity">
<com.vinnorman.customtextfieldtesting.CustomTextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
However, when I run the app and when I see it in the activity_main.xml design preview, it is putting in a lot more top margin:
Any ideas as to why there is this extra top margin, and how to get rid of it?

If you look at your layout in the layout inspector, you will see that you have a TextInputLayout embedded in a another TextInputLayout. This is because your custom view is, itself, a TextInputLayout and you inflate another TextInputLayout from your layout into it. You don't see this in the Android Studio designer but only on a device/emulator.
The way around this is to use the merge tag. See Use the <merge> tag for details.

Related

Should I be able to add placeholder text in a custom view?

I have a pretty basic custom view that consists of a 2 TextViews styled in a particular way. I'm inflating the view from the following layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
...
>
<TextView
android:id="#+id/text_line1"
tools:text="10"
.../>
<TextView
android:id="#+id/text_line2"
tools:text="Reviews"
.../>
</LinearLayout>
When I add this custom view to another layout in Android Studio via xml, the placeholder/tools text does not appear in the Android Studio preview. I'd like to see "10 Reviews" in the preview without actually setting the custom attributes which change these fields. I have imported the tools namespace in the layout containing the custom view.
Is there a way to do this? Am I creating this view incorrectly?
If I'm not mistaken, tools information is not preserved if you used the layout to inflate a custom view.
You can try using isInEditMode to populate the data using the informaion you get from AttributeSet

Xml layout to Java code generator

Does anybody know a tool to create java code from a xml layout file.
It would be useful, to create quickly a custom view (I do not want to create a separate library project) that I would like to include in an activities layout.
So lets say my custom view would be a Relative Layout with some child views.
It would be great if the tool could generate from a layout file like this:
<RelativeLayout 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">
<!-- some child Views -->
</RelativeLayout>
a java class like this:
class CustomView extends RelativeLayout{
/*
* Generate all the Layout Params and child Views for me
*/
}
And at the end I could use this generated class in a normal XML
<LinearLayout
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"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
text="Hello World" />
<com.example.CustomView
android:layout_width="match_parent"
android:layout_height="100dp"
/>
</LinearLayout>
Does such a tool exists?
It would be useful, to create quickly a custom view (I do not want to
create a separate library project) that I would like to include in an
activities layout.
You can already do it. Create a custom view class and inflate custom layout there.
package com.example.view;
class CustomView extends LinearLayout {
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater.from(context).inflate(R.layout.custom_view, this, true);
}
}
Create a layout for that custom view class using <merge> tag as the root. Android will add content of tag into your custom view class, which is, in fact, LinearLayout in our case.
// custom_view.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android"
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
text="Hello World" />
</merge>
You are done. Now you can add this custom class to your layout.
<com.example.view.CustomView
android:id="#id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
/>
No because there's 2 better ways to do it.
1)Use an <include> tag. That allows you to include a 2nd xml file.
2)Use a custom class, but have it inflate the second xml in its constructor. That way you can keep the layout in xml for the class.
Typically I use 2 if I want to create custom functionality where you set/change multiple values at one time, and 1 if I just want to break up my xml file into chunks.

<merge/> in custom View xml layout

I have the following custom view which is based on RelativeLayout:
public class LearningModeRadioButton extends
RelativeLayout
implements
Checkable,
View.OnClickListener {
private void init(Context context, AttributeSet attrs) {
LayoutInflater.from(context).inflate(R.layout.rb_learning_mode, this, true);
}
}
R.layout.rb_learning_mode contents are:
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
>
<RadioButton
android:id="#+id/rb"
android:button="#drawable/sel_rb_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="#+id/tv_mode_title"
style="#style/text_regular"
android:layout_toRightOf="#+id/rb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="#+id/tv_description"
style="#style/text_small"
android:layout_below="#+id/tv_mode_title"
android:layout_alignLeft="#+id/tv_mode_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</merge>
It sort of works, but layout parameters (layout_xxx) are ignored. I could use another <RelativeLayout/> as root element of the layout but I want to avoid having extra level in view hierarchy.
So the question is: How do I make layout attributes inside <merge/> work?
For anyone that might still be struggling with this, you should specify the parentTag attribute inside the merge tag. You will also need to specify layout_height and layout_width to make it work.
<merge
tools:parentTag="android.widget.RelativeLayout"
tools:layout_width="match_parent"
tools:layout_height="match_parent"
>
// Child Views
</merge>
The editor should display everything properly now.
Merge useful for LinearLayout and FrameLayout its not suitable for RelativeLayout.
Obviously, using works in this case because the parent of an activity's content view is always a FrameLayout. You could not apply this trick if your layout was using a LinearLayout as its root tag for instance. The can be useful in other situations though.
check this:

Attaching custom view to the top of Android keyboard

What is the simplest way to attach a layout defined in xml at the top of Android soft keyboard.
This view should only appear when the keyboard appears.
I think the best way is to detect when the keyboard appears and then attach view to the bottom of the screen (it will be above the keyboard after resizing all layout).
I think the cleaner way to do this.
Define a layout resource file to use as your custom view
Create a method which inflates this layout resource file and return it as a view:
Code Snippet
public View returnPayKeyView(){
View simpleView = getLayoutInflater().inflate(R.layout.simpleresource,null);
return simpleView;
}
Set this returned view as the candidates using the setCandidatesView Method
The returned view will show above your keyboard, using the inflated xml layout resource.
One approach is to wrap your usual view layout in a FrameLayout and put the bit you want above the keyboard inside the frame along with your other view layout with a layout gravity of bottom.
My layout was along these lines:
<?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">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- all your stuff... -->
</LinearLayout>
</ScrollView>
<LinearLayout
android:id="#+id/stuffAboveKeyboardLayout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:background="#DDD">
<!-- stuff above keyboard... -->
</LinearLayout>
</FrameLayout>
The downside is that it will show even when the keyboard is hidden - but it ought to be possible to show / hide the view when the keyboard shows / hides: How do I Detect if Software Keyboard is Visible on Android Device?

Include layout with custom attributes

I'm building a complex layout and I want to use include tag for my custom component, like this:
<include layout="#layout/topbar"/>
The topbar layout has custom root component and in layout xml file it's defined like this:
<my.package.TopBarLayout
... a lot of code
Now, I wanna pass my custom defined attributes to "topbar" like this:
<include layout="#layout/topbar" txt:trName="#string/contacts"/>
And then get the value of those custom attributes in custom component code or ideally in xml.
Sadly, I cannot get value of txt:trName attribute to make it to the topbar layout, I just don't receive anything in code. If I understand correctly from that documentation page, I can set no attributes for layouts used via include, but id, height and width.
So my question is how can I pass my custom defined attributes to layout which is added via include?
I know this is an old question but I came across it and found that it is now possible thanks to Data Binding.
First you need to enable Data Binding in your project. Use DataBindingUtil.inflate (instead of setContentView, if it's Activity) to make it work.
Then add data binding to the layout you want to include:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="title" type="java.lang.String"/>
</data>
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/screen_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:gravity="center">
...
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:textStyle="bold"
android:text="#{title}"/>
...
</RelativeLayout>
</layout>
Finally, pass the variable from the main layout to the included layout like this:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
...
</data>
...
<include layout="#layout/included_layout"
android:id="#+id/title"
app:title="#{#string/title}"/>
...
</layout>
It's not possible to attributes other than layout params, visibility or ID on an include tag. This includes custom attributes.
You can verify this by looking at the source of the LayoutInflater.parseInclude method, around line 705:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/view/LayoutInflater.java#640
The inflater only applies the ID and visibility attributes to the included layout.
I ran into this issue today. For whatever it is worth, I think there is a straight-forward work around. Instead of adding attributes to the include tag, create a custom wrapper view for the include and add attributes to that. Then, do the include from the wrapper. Have the wrapper class implementation extract the attributes and pass along to its single child, which is the root view of the include layout.
So, say we declare some custom attributes for a wrapper called SingleSettingWrapper like this -
<declare-styleable name="SingleSettingWrapper">
<attr name="labelText" format="string"/>
</declare-styleable>
Then, we create two custom view classes - one for the wrapper (SingleSettingWrapper) and one for the child (SingleSettingChild) that will be included -
<!-- You will never end up including this wrapper - it will be pasted where ever you wanted to include. But since the bulk of the XML is in the child, that's ok -->
<com.something.SingleSettingWrapper
android:id="#+id/wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
custom:labelText="#string/my_label_string">
<!-- Include the child layout -->
<include layout="#layout/setting_single_item"/>
</com.something.SingleSettingWrapper>
For the child, we can put whatever complex layout in there that we want. I'll just put something basic, but really you can include whatever -
<com.something.SingleSettingItem
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout >
<!-- add whatever custom stuff here -->
<!-- in this example there would be a text view for the label and maybe a bunch of other stuff -->
<!-- blah blah blah -->
</RelativeLayout>
</com.something.SingleSettingItem>
For the wrapper (this is the key), we read all of our custom attributes in the constructor. Then, we override onViewAdded() and pass those custom attributes to our child.
public class SingleSettingWrapper extends FrameLayout
{
private String mLabel;
public SingleSettingWrapper(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SingleSettingWrapper,
0, 0);
mLabel = a.getString(R.styleable.SingleSettingWrapper_labelText);
a.recycle();
}
public void onViewAdded(View child)
{
super.onViewAdded(child);
if (!(child instanceof SingleSettingItem))
return;
((TextView)child.findViewById(R.id.setting_single_label)).setText(mLabel);
/*
Or, alternatively, call a custom method on the child implementation -
((SingleSettingItem)child)setLabel(mLabel);
*/
}
}
Optionally, you can implement the child too and have it receive messages from the wrapper and modify itself (instead of having the wrapper modify the child as I did above).
public class SingleSettingItem extends LinearLayout
{
public SingleSettingItem(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public void setLabel(String l)
{
// set the string into the resource here if desired, for example
}
}
At the end of the day, each of the XML files where you wanted to <include> your layout will contain about 7 lines of XML for the wrapper+include instead of the single include that you wanted, but if the included view contains hundreds of lines you're still way better off. For example -
<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="wrap_content"
android:orientation="vertical" >
<!-- this is the beginning of your custom attribute include -->
<com.something.SingleSettingWrapper
android:id="#+id/my_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
custom:labelText="#string/auto_lock_heading">
<include layout="#layout/setting_single_item"/>
</com.something.SingleSettingWrapper>
<!-- this is the end of your custom attribute include -->
</LinearLayout>
In practice, this seems to work pretty well and is relatively simple to set up. I hope it helps someone.
Unfortunately, the only thing I can contribute is that I was also unable to set custom attributes on an include tag, and have them pass through to the included layout.
It may well not be possible at this point.
It's not possible to use with custom attributes, or any attributes other than the ones stated on the API page (up through at least 5.0.0):
https://code.google.com/p/android/issues/detail?id=38023
http://grepcode.com/file/repo1.maven.org/maven2/org.robolectric/android-all/5.0.0_r2-robolectric-1/android/view/LayoutInflater.java
You have to include in your root xml element your custom namespace.
If your package name is com.example.test your xml shold be something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:txt="http://schemas.android.com/apk/res/com.example.test" />
A nice tutorial is: http://blog.infidian.com/2008/05/02/android-tutorial-42-passing-custom-variables-via-xml-resource-files/
I had the same question. After visiting this thread, I ended up using View's setTag() methods to attach identifying information to each View during onCreate(), and then getTag() methods to retrieve it later on.

Categories

Resources