While working with view binding, I came across a couple of undocumented cases.
First: How do I get binding for included view layout parts? The main binding only sees items defined in the main layout.
Second: How do I get binding for merged layout parts. Again, the main binding only sees items in the main layout?
In case of:
Include with generic layout (not merge node), we need to assign ID to included part, this way in binding we will have access to included sub part
<include
android:id="#+id/your_id"
layout="#layout/some_layout" />
This way in your activity code:
private lateinit var exampleBinding: ActivityExampleBinding //activity_example.xml layout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exampleBinding = ActivityExampleBinding.inflate(layoutInflater)
setContentView(exampleBinding.root)
//we will be able to access included layouts view like this
val includedView: View = exampleBinding.yourId.idOfIncludedView
//[...]
}
Include with merge block in external layout. We can't add ID to it because merge block is not a view.
Let's say we have such eternal merge layout (merge_layout.xm):
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="#layout/activity_example">
<TextView
android:id="#+id/some_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World" />
</merge>
To properly bind such merge layout we need to:
In your activity code:
private lateinit var exampleBinding: ActivityExampleBinding //activity_example.xml layout
private lateinit var mergeBinding: MergeLayoutBinding //merge_layout.xml layout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exampleBinding = ActivityExampleBinding.inflate(layoutInflater)
//we need to bind the root layout with our binder for external layout
mergeBinding = MergeLayoutBinding.bind(exampleBinding.root)
setContentView(exampleBinding.root)
//we will be able to access included in merge layout views like this
val mergedView: View = mergeBinding.someView
//[...]
}
Regarding your first question, you can get a view binding for the included layout.
Here is a sample main_fragment.xml file:
<?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"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<include
android:id="#+id/toolbar"
layout="#layout/toolbar" />
</LinearLayout>
And MainFragment.java can be like this:
public class MainFragment extends Fragment {
private MainFragmentBinding binding;
private ToolbarBinding toolbarBinding;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = MainFragmentBinding.inflate(inflater, container, false);
toolbarBinding = binding.toolbar;
return binding.getRoot();
}
#Override
public void onDestroy() {
super.onDestroy();
toolbarBinding = null;
binding = null;
}
}
Now you have two bindings: One corresponds to the main layout and the other corresponds to the included layout.
If you want to bind included layout then,
For Activity
YourMainLayoutBinding mainLayoutBinding = MainLayoutBinding.inflate(getLayoutInflater);
View view = mainLayoutBinding.getRoot();
YourIncludedLayoutBinding includedLayoutBinding = YourIncludedLayoutBinding.bind(View);
For Fragment
YourMainLayoutBinding mainLayoutBinding = MainLayoutBinding.inflate(inflater,container,false);
View view = mainLayoutBinding.getRoot();
YourIncludedLayoutBinding includedLayoutBinding = YourIncludedLayoutBinding.bind(View);
Make Sure if your main layout binding parent root is LinearLayout then,
includedLayoutBinding parent layout also be a linear layout
Suppose I included a layout in my activity_main.xml file as follows:
<include
android:id="#+id/ll_layout1"
layout="#layout/layout1"
android:visibility="gone" />
And suppose I wanted to change its visibility. I can do so as follows:
activityMainBinding.llLayout1.root.visibility = View.VISIBLE
In my case, I forgot to assign id to the include tag
Now, when you've assigned the id, you'll be able to get the binding object as,
YourMainLayoutBinding.YourIncludeTagIDLayoutBinding
Follow steps:-
private val binding : FragmentBinding
by viewBinding(FragmentBinding::bind)
make sure to do the following in "onViewCreated(view: View, savedInstanceState: Bundle?)"
val binding2 = binding.root.include_layout_id
e.g. val binding2 = binding.root.tool_bar_layout
Now access your include layout, views here. e.g.
binding2.textView.text = "your text"
Using the data binding library. Then wrap your XML layout with <layout> tag
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
<include
android:id="#+id/toolbar"
layout="#layout/toolbar" />
...
</LinearLayout>
</layout>
toolbar.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"
android:orientation="vertical">
<ImageView
android:id="#+id/ivImage"
... />
<TextView
android:id="#+id/tvTitle"
... />
</LinearLayout>
MainActivity.kt
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// Access include layout views
binding.toolbar.rootView.ivImage.setImageResource(R.drawable.ic_back_arrow)
binding.toolbar.rootView.tvTitle.text = getString(R.string.home)
...
}
In the include layout you must create a Container layout and put here the id.
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Android Studio 3.1, java 1.8
I try to use data binding:
Here settings.xml layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="handler"
type="com.myproject.SettingsFragment" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="#+id/contentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:id="#+id/contactUsContainer"
android:layout_width="match_parent"
android:layout_height="50dp"
android:onClick="#{handler::onClickContactUs}">
<ImageView
android:id="#+id/contactUsImageView"
android:layout_width="28dp"
android:layout_height="28dp"
app:srcCompat="#drawable/ic_settings_contacts_us"
<TextView
android:id="#+id/contactUsTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/contact_us"/>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
</FrameLayout>
</ScrollView>
</android.support.constraint.ConstraintLayout>
</layout>
Here fragment SettingsFragment.java :
public class SettingsFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
SettingsBinding binding = DataBindingUtil.setContentView(getActivity(), R.layout.settings);
binding.setHandler(this);
return binding.getRoot();
}
public void onClickContactUs(View view) {
}
}
But I get error:
FATAL EXCEPTION: main
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:3337)
at android.view.ViewGroup.addView(ViewGroup.java:3208)
at android.view.ViewGroup.addView(ViewGroup.java:3165)
at android.view.ViewGroup.addView(ViewGroup.java:3145)
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1425)
android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)
android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799)
android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580)
android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2367)
android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322)
android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2229)
DataBindingUtil.setContentView() should be use for binding in an Activity because the method will set the content view for the Activity(like you would normally do with the setContentView() method). This is why you get the exception that the view(binding.getRoot()) is already appended to a parent view.
In your fragment use DataBindingUtil.inflate() to inflate the layout and create the binding without actually appending the view(which the fragment does itself later):
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
SettingsBinding binding = DataBindingUtil.inflate(inflater, R.layout.settings, container, false);
binding.setHandler(this);
return binding.getRoot();
}
My problem is to make a fragment full screen and apply custom theme on it. I tried this way but unable to get result. RssDetailViewFragment.cs consists following lines of code:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
// return inflater.Inflate(Resource.Layout.YourFragment, container, false);
// create ContextThemeWrapper from the original Activity Context with the custom theme
Context contextThemeWrapper = new ContextThemeWrapper(Activity, Android.Resource.Style.ThemeLightNoTitleBarFullScreen);
// clone the inflater using the ContextThemeWrapper
LayoutInflater localInflater = inflater.CloneInContext(contextThemeWrapper);
rootView = localInflater.Inflate(Resource.Layout.RSSFeedDetails, null, false);
// inflate progressbar
progressBar = rootView.FindViewById<ProgressBar>(Resource.Id.progress_bar);
progressBar.Visibility = ViewStates.Visible;
// setting control toolbar
Toolbar toolbar = rootView.FindViewById<Toolbar>(Resource.Id.top_control_toolbar);
((AppCompatActivity)this.Activity).SetSupportActionBar(toolbar);
// setting icon on toolbar
toolbar.SetNavigationIcon(Resource.Drawable.ic_back20);
toolbar.SetBackgroundColor(Resources.GetColor(Resource.Color.primary));
View navigationIcon = toolbar.GetChildAt(1); //NavigationIcon
and my xml file for RssFeedDetals is
<?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="wrap_content"
android:minHeight="60dp"
android:orientation="vertical"
android:background="#color/viewBackground">
<include
android:id="#+id/top_control_toolbar"
layout="#layout/TopControlToolbar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/viewBackground">
<WebView
android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</ScrollView>
<ProgressBar
android:id="#+id/progress_bar"
style="#android:style/Widget.DeviceDefault.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="invisible" />
</LinearLayout>
Main thing to ask this question is to make the fragment fullscreen on Min Sdk 15 plus.
Thank you.
I want to hide the toolbar of activity and statusBar of app and cover the area with fragment view.
When you show your RssDetailViewFragment you could hide the Statusbar and Toolbar like this :
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
SetHasOptionsMenu(true);
//Hide the StatusBar
WindowManagerLayoutParams attr = Activity.Window.Attributes;
attr.Flags = WindowManagerFlags.Fullscreen;
Activity.Window.Attributes = attr;
//Hide the ToolBar
ftoolbar = (V7Toolbar)Activity.FindViewById<V7Toolbar>(Resource.Id.toolbar);
ftoolbar.Visibility = ViewStates.Gone;
//Load views for this Fragment
View view = LayoutInflater.From(Activity).Inflate(Resource.Layout.MGradesView, null);
return view;
}
I am developing an Android app. I am using listview together with SwipefreshLayout. But when I refresh list view using SwipefreshLayout , the loading circle is never hidden back. I am using it in fragment.
This is layout file
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- place your view here -->
<ListView
android:dividerHeight="#dimen/list_item_divider_height"
android:padding="#dimen/list_padding"
android:divider="#color/whitesmoke"
android:id="#+id/listview_podcast_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"></ListView>
</android.support.v4.widget.SwipeRefreshLayout>
<TextView
android:layout_below="#+id/swipe_refresh_layout"
android:textSize="17dp"
android:textStyle="bold"
android:textColor="#color/black"
android:textAlignment="center"
android:id="#+id/tf_no_records"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
This is how I am using it in fragment
public class PodcastListFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
// property variables here
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
.
.
.
refresher = (SwipeRefreshLayout)view.findViewById(R.id.swipe_refresh_layout);
refresher.setOnRefreshListener(this);
return view;
}
#Override
public void onRefresh() {
refreshListView();
}
}
.
.
.
}
The loading circle is never get hidden back as in screenshot below
What is wrong with my code and how can I solve it?
You have to set refreshing to false after refresh
refresher.setRefreshing(false);
From doc
Update:
Kotlin:
refresher.isRefreshing = false
I want to have two or more Fragments which will be processed similarly. Can I reuse ids in each Fragment?
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
<Fragment
android:id="#+id/fragment_1"
class="com.example.DoesSomethingToTitle"
<TextView android:id="#+id/title" />
</Fragment>
<Fragment
android:id="#+id/fragment_2"
class="com.example.DoesSomethingToTitle"
<TextView android:id="#id/title" /> <!-- same id -->
</Fragment>
</FrameLayout>
And use it like this:
public class DoesSomethingToTitle {
private String titletext;
public DoesSomethingToTitle(String titletext) {
this.titletext = titletext;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.feed, container, false);
// find the title TextView and mutate it
((TextView) v.findViewById(R.id.title)).setText(titletext);
return v;
}
}
(I realize for something as simple as this, I could just specify the title string in the XML; my actual usage is more complex.)
Can I reuse ids in each Fragment?
Yes you can.