I have a hierarchy of custom views that looks like this:
Activity(RelativeLayout) -> ParentLayout(FrameLayout) -> ChildLayout(LinearLayout)
The activity and parent layout are added and displayed just fine, but the child is not. I have looked at the hierarchy viewer in the device monitor to confirm it is not being added to the view hierarchy.
Really all I'm trying to do here is create a view hierarchy so I can play around with handling touch events at various places in the view.
Here is everything:
main_activity.xml
<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"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity">
<net.openeye.touchevents.ParentLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#558833" />
</RelativeLayout>
parent_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<net.openeye.touchevents.ParentLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<net.openeye.touchevents.ChildLayout
android:id="#+id/child_view"
android:layout_width="300dp"
android:layout_height="300dp" />
</net.openeye.touchevents.ParentLayout>
ParentLayout.java:
public class ParentLayout extends FrameLayout implements View.OnTouchListener {
public ParentLayout(Context context) {
super(context);
}
public ParentLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ParentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
child_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<net.openeye.touchevents.ChildLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#0066dd"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hi"/>
</net.openeye.touchevents.ChildLayout>
ChildLayout.java:
public class ChildLayout extends LinearLayout {
public ChildLayout(Context context) {
super(context);
}
public ChildLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ChildLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
What am I missing? I have another project that is basically set up the same way, except the child views are dynamically inflated and added, instead of being directly added in the xml layout files. This seems like it should work and I don't understand why it doesn't.
So it looks like when you have a custom view class, you don't want to have the view of the layout file be the same type as the custom class. i.e., if I have ParentLayout.java, I don't want parent_layout.xml's root to be <net.openeye.TouchEvents.ParentLayout>. It seems that when you want both a custom layout file and custom view class, you need to have the view class inflate the layout. If the layout has an element (the root, on this case) that is the same as the class, it will cause infinite recursion as the view inflates the layout, which instantiates the class, which inflates the layout... and so on.
I got this to work finally by making the following changes:
parent_layout.xml:
Change the root element from net.openeye.TouchEvents.ParentLayout to the class it extends, FrameLayout. It now looks like this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- ... -->
</FrameLayout>
child_layout.xml:
Change the root element from net.openeye.TouchEvents.ChildLayout to the class it extends, LinearLayout. It now looks like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="300dp"
android:layout_height="300dp"
android:orientation="vertical">
<!-- ... -->
</LinearLayout>
ParentLayout.java: Inflate it's layout during instantiation. It now looks like this:
public ParentLayout(Context context) {
super(context);
init(context);
}
public ParentLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ParentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
inflate(context, R.layout.parent_layout, this);
}
ChildLayout.java: Same thing as ParentLayout.java, but inflate child_layout.
After getting this working and thinking about why this is happening, it makes sense that this is how it works.
Related
I created custom view based on Toolbar:
public class CommonToolbar extends Toolbar {
public CommonToolbar(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.common_toolbar, this, true);
}
common_toolbar.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="32dp"
android:background="#color/colorPrimary"
app:contentInsetStart="0dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/backIcon"
style="#style/ToolbarNavigationButton"
app:srcCompat="#drawable/icon_close" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_toEndOf="#+id/backIcon"
android:gravity="center"
android:text="Title"
android:textStyle="bold" />
</RelativeLayout>
</android.support.v7.widget.Toolbar>
As you can see i set attribute contentInsetStart value to 0, but when i use this Toolbar in any layout file, padding is still present (image). I can modify this by changing contentInsetStart value in destination layout file, but I prefer to keep this in source xml. Why is it happening?
Because your custom view extends Toolbar you are inflating your xml toolbar into a Toolbar. If you take a look at your layout with Layout Inspector, your view hierarchy will look like this:
<CommonToolbar> (Subclass of Toolbar)
<Toolbar>
<RelativeLayout>
...
So when you set contentInsetStart via xml, you are setting it on the inner toolbar. The outer toolbar still has a content inset.
To avoid this you can have Common Toolbar extend a ViewGroup i.e.
public class CommonToolbar extends RelativeLayout {
public CommonToolbar(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.common_toolbar, this, true);
}
Or inflate the common toolbar using an include tag. i.e.
<include
layout="#layout/common_toolbar"
/>
which allows you to remove the CommonToolbar custom view
Use setContentInsetsAbsolute(0, 0);
public class CustomToolbar extends Toolbar {
#BindView(R.id.txt_screen_title)
TextView txtScreenTitle;
public CustomToolbar(Context context) {
super(context);
initToolbarLayout(context);
}
public CustomToolbar(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
initToolbarLayout(context);
}
public CustomToolbar(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initToolbarLayout(context);
}
private void initToolbarLayout(Context context) {
View toolbarView = LayoutInflater.from(context).inflate(R.layout.app_toolbar, null);
this.addView(toolbarView);
ButterKnife.bind(this, toolbarView);
setContentInsetsAbsolute(0, 0);
}
public void setScreenTitle(String title) {
if(txtScreenTitle != null) {
txtScreenTitle.setText(title);
}
}
}
I have a custom preference:
public class ButtonPreference extends Preference {
public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ButtonPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected View onCreateView(ViewGroup parent) {
super.onCreateView(parent);
LayoutInflater li = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return li.inflate(R.layout.button_prederence, parent, false);
}
}
and xml button_prederence.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="Click me" />
</LinearLayout>
I want all my preferences to be in the same height, so I need to get the default preference height and apply it to the ButtonPreference.
How can I do that?
I get this problem too.
After some research, I found this workaround method. Try to add this line in your LinearLayout.
android:minHeight="?android:listPreferredItemHeight"
or you can check the original xml file for Preference at
sdk\platforms\android-24\data\res\layout\preferences.xml
I'm using data binding to bind the layouts in my Android app.
I have set up my layout ( my_custom.xml ) and the binding class is generated (MyCustomBinding), but Android Studio does not seem to find the .inflate(...) method of the Binding class right away, marking it as an error ( red text!).
The code seems to be correct though, since it compiles and builds just fine into an APK.
How do I get Android Studio to update correctly ?
Code example:
This is my custom View code:
public class MyCustomView extends FrameLayout {
public MyCustomView(Context context) {
this(context, null, 0);
}
public MyCustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
MyCustomBinding binding = MyCustomBinding.inflate(inflater, this, true);
binding.aButton.setText("Whatever");
}
}
layout is defined as:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="#+id/a_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click me!"
android:padding="10dp"
android:background="#000"
android:textColor="#fff"
android:layout_gravity="center"
/>
</FrameLayout>
</layout>
And here's the issue: (highlighted red)
Something is not completing in Android Studio because you haven't actually implemented data binding.
Once you add a variable to your layout's data element, the inflate method will be found as you expect. That said, you're really not getting the benefit of databinding by setting the value of the text field directly through the binding. You should instead be setting a View Model in your binding, and then let the binding update the views accordingly. For example:
create a View Model:
public class MyViewModel {
public final ObservableField<String> name;
public MyViewModel(String name) {
this.name = new ObservableField<>(name);
}
}
and use it in your layout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="model" type="com.victoriaschocolates.conceirge.MyViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{model.name}"
android:padding="10dp"
android:background="#000"
android:textColor="#fff"
android:layout_gravity="center"
/>
</FrameLayout>
</layout>
(note the variable declared in the data element, and how it is referenced in the TextView's text attribute)
then bind the two in your custom view:
public class MyCustomView extends FrameLayout {
public MyCustomView(Context context) {
this(context, null, 0);
}
public MyCustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
MyCustomBinding binding = MyCustomBinding.inflate(inflater, this, true);
MyViewModel model = new MyViewModel("Whatever");
binding.setModel(model);
}
}
Of course, it would probably be better still to have the data passed in through a setter in the custom view class, or even passed in from the container view (see http://developer.android.com/tools/data-binding/guide.html#includes)
You can try setting a custom class name to your Binding Layout, and referencing it in your custom view to make it clear which layout you using:
<layout>
<data class="MyCustomBinding">
</data>
</layout>
If this does not work, use DataBindingUtil instead of MyCustomBinding, which returns the base DataBinding class and has a inflate() method:
MyCustomBinding binding = DataBindingUtil.inflate(inflater, this, true);
From the docs:
Sometimes the binding cannot be known in advance. In such cases, the binding can be created using the DataBindingUtil class:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
this is updated answer based on latest databinding and androidx, i come here on this question to solve my own problem, after watching above answers i develop by own working code snippet. hope this helps
public class CustomActionBar1 extends RelativeLayout {
public MutableLiveData<String> title = new MutableLiveData<>("Sample text");
CustomActionBar1Binding binding;
public CustomActionBar1(Context context) {
super(context);
init(context, this);
}
public CustomActionBar1(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, this);
}
public CustomActionBar1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, this);
}
public CustomActionBar1(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, this);
}
private void init(Context context, ViewGroup viewGroup) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
binding = DataBindingUtil.inflate(inflater, R.layout.custom_action_bar_1, viewGroup, true);
}
// helper to change title
public void changeTitle(String title) {
if (title != null)
this.title.setValue(title);
}
public void setTitleVisibility(Boolean visibile){
binding.titleText.setVisibility(visibile ? View.VISIBLE : View.INVISIBLE);
}
}
Xml
<?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>
<variable
name="customActionBar1"
type="com.actionBar.CustomActionBar1" />
</data>
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:background="#color/yellow"
android:layout_height="#dimen/action_bar_height"
android:id="#+id/main_header_relative">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{customActionBar1.title}"
android:layout_centerInParent="true"
android:gravity="center"
android:id="#+id/titleText"
android:visibility="visible"
android:textColor="#color/black" />
</RelativeLayout>
</layout>
You dont need that type of stuffs, DataBinding includes DataBindingUtils class here we go.
public MyCustomView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
MyCustomBinding binding = DataBindingUtil.inflate(inflater, this, true);
binding.aButton.setText("Whatever");
}
As the title, i want to know if there is a the best way to control a view that added dynamically. (we have to keep reference to the view that was added)
Some time, for a complex request we have to add view in runtime. The is some ways to do that. In my case:
Some time i use a listview/recyclerview and control view via the list/recycleview adapter.
Other way is use a hashmap.
Do you have any other ideas? and how it work?
I prefer way, when I define View both by Java and XML file. View created like this, gives you ability to call your own Java methods, but you don`t need to create whole layout dynamically in Java. Little example:
MyView.java:
public class MyView extends LinearLayout {
TextView textView;
public MyView(Context context) {
super(context);
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void init() {
inflate(getContext(), R.layout.my_view, this);
setOrientation(VERTICAL);
textView = (TextView) findViewById(R.id.text_view);
}
public MyView setContent(String value) {
textView.setText(value);
return this;
}
}
my_view.xml:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="#+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</merge>
After that, you can simply add it in you layout through Java:
cont.addView(new MyView(this).setContent("Value"));
or xml:
<com.path.to.your.view.MyView
android:id="#+id/my_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
I am trying to extend a cardview to set the background image. I know that this can not be done with normal cardview. I have searched net and found plenty of solutions for setting a background color to the card view but none for image.
My code to do so:
public class CCView extends CardView {
public CCView (Context context) {
super(context);
init();
}
public CCView (Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CCView (Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setBackground(getResources().getDrawable(R.drawable.cc_background));
}
}
I get this exception when I populate the code from XML
android.graphics.drawable.BitmapDrawable cannot be cast to android.support.v7.widget.RoundRectDrawableWithShadow
Any solution?
As CardView extends FrameLayout you can layer layouts on top of it. To get around the problem you're having, I'd try adding a blank view "underneath" all the other elements in your view, and then set that view to inherit the state of its parent. Something like this:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
card_view:cardBackgroundColor="#DDFFFFFF"
card_view:cardElevation="#dimen/card_elevation">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:duplicateParentState="true"
android:background="#drawable/card_background"/>
<LinearLayout
....
....
....
/LinearLayout>
</android.support.v7.widget.CardView>