I have a main activity with a side navigation drawer in the action bar, specified as follows (note that a lot of code has been omitted for brevity) in default_screen.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="190dp"
android:background="#drawable/honeycomb"
android:orientation="vertical"
>
<android.support.design.widget.NavigationView
android:id="#+id/navigation_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
app:headerLayout="#layout/header"
app:menu="#menu/drawer"
/>
where layout/header is as follows (again, a bunch of lines omitted for brevity):
<data>
<variable name="user" type="oose2017.place2b.ClientUser"/>
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.displayName}"
android:textSize="14sp"
android:textColor="#FFF"
android:textStyle="bold"
android:gravity="left"
android:paddingBottom="4dp"
android:id="#+id/username"
android:layout_above="#+id/email"
android:layout_alignLeft="#+id/profile_image"
android:layout_alignStart="#+id/profile_image" />
</RelativeLayout>
Where I instantiate my default_screen in the main activity as follows:
setContentView(R.layout.default_screen);
How do I data bind to the header? I have tried a few things unsuccessfully, mainly:
DefaultScreenBinding binding = DataBindingUtil.setContentView(R.layout.default_screen);
Which does not work. How can I do this?
Valid solution will be following, add header view in main activity OnCreate:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
NavHeaderMainBinding _bind = DataBindingUtil.inflate(getLayoutInflater(), R.layout.nav_header_main, binding
.navView, false);
binding.navView.addHeaderView(_bind.getRoot());
_bind.setUser(Session.getUserProfile());
}
Note:
Important line is binding.navView.addHeaderView(_bind.getRoot()); remove app:headerLayout="#layout/nav_header_main" from xml add programmatically.
You can bind any view with your parent binding if that view is your child of that parent. Like here: Navigation view header is a child of Navigation view so to bind navigation view header with navigation view need to like this.
default_screen binding for Main screen:
DefaultScreenBinding binding = DataBindingUtil.setContentView(R.layout.default_screen);
for header layout including binding will be:
HeaderBinding headerBinding = HeaderBinding.bind(binding.navigationView.getHeaderView(0));
binding.navigationView.getHeaderView(0) will give header view of navigation view which you want to bind.
Now you can use headerBinding for header layout references. Hope this you understand easily and get helpful.
With viewBinding
// navView is NavigationView
val viewHeader = binding.navView.getHeaderView(0)
// nav_header.xml is headerLayout
val navViewHeaderBinding : NavHeaderBinding = NavHeaderBinding.bind(viewHeader)
// title is Children of nav_header
navViewHeaderBinding.title.text = "Your Text"
Not sure if this is the best way but this is what worked for my scenario.
I created a custom view for NavigationView (extended the class) and then used DataBindingUtil.inflate as part of the custom view constructor(s) with which I set my data binding variable(s) and add then added that view as the header using NavigationView.addHeaderView. Of course this meant in xml I had to replace the NavigationView with my custom view and not specify the app:headerLayout attribute in the custom view. See custom view example below (note that I use Dagger2 to inject my data binding variable).
public class MyNavigationView extends NavigationView {
#Inject
MyViewModel myViewModel;
public MyNavigationView(Context context) {
super(context);
initialize();
}
public MyNavigationView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public MyNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
private void initialize() {
// NOTE: A private method that "injects" your view model dependency (ex: Using Dagger2)
inject();
NavHeaderHomeBinding binding = DataBindingUtil.inflate(LayoutInflater.from(getContext()),
R.layout.nav_header_home,
null,
false);
binding.setHomeNavDrawerViewModel(myViewModel);
addHeaderView(binding.getRoot());
}
}
My solution is following:
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/navigation"
app:menu="#menu/activity_drawer_drawer">
<include
layout="#layout/nav_header_drawer"
bind:thisDevice="#{thisDevice}" />
In order to use data binding, we can't use original headLayout so we use an 'included layout'. In this way, our header can show normally by data binding. But, the menu without a header layout will be overlapped. So we add an empty headLayout has the same size with our real layout to make menu show normally.
Detailed explanation can be found in my blog post.
Working example can be found here.
Notify me if I am not clear.
It seems no direct way to make data binding for NavigationView, so I have to implement it in somewhat hacker way:
First, in order to use bind, we can’t use direct headerLayout and replace it with a included layout
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header_drawer"
app:menu="#menu/drawer">
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:menu="#menu/drawer">
<include layout="#layout/nav_header_drawer"
bind:thisDevice="#{thisDevice}" />
</android.support.design.widget.NavigationView>
The newly included view is on the top of menu, so it will show normally. But part of menu items will move up because there is no header above it and are overlapped by newly included view( although those items can receive the touch event), so we can add a header.
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/navigation"
app:menu="#menu/activity_drawer_drawer">
<include
layout="#layout/nav_header_drawer"
bind:thisDevice="#{thisDevice}" />
</android.support.design.widget.NavigationView>
navigation layout is an empty layout having the same height and width with real nav_header_drawer. So both menu our real layout is shown normally.
Of course, the java code is necessary for data binding:
ActivityDrawerBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_drawer);
binding.setThisDevice(Device.now);
Layout file is here.
Working example can be found here.
Reference: http://tonyz93.blogspot.com.br/2016/08/learn-data-binding-of-android.html#navigationview-data-binding
in kotlin
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header_main"
app:menu="#menu/activity_main_drawer" />
where layout/header
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/nav_user_image"
android:layout_width="#dimen/nav_image_size"
android:layout_height="#dimen/nav_image_size"
android:layout_marginStart="#dimen/nav_content_margin_StartEnd"
android:layout_marginEnd="#dimen/nav_content_margin_StartEnd"
app:civ_border_color="#android:color/white"
app:civ_border_width="#dimen/nav_image_circular_border_width"
android:contentDescription="#string/image_contentDescription"
android:src="#drawable/ic_user_place_holder" />
With viewBinding
val viewHeader = binding?.navView?.getHeaderView(0)
val headerBinding = viewHeader?.let { NavHeaderMainBinding.bind(it) }
headerBinding?.tvUsername?.text = user.name
Related
In my activity, I am inflating the layout via data binding. The binding itself works correctly, and I am able to get references to objects in the layout file easily.
One of those objects is a NavigationView. It has a headerLayout:
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#android:color/white"
android:fitsSystemWindows="true"
app:itemIconTint="#color/navigation_view_color"
app:itemTextColor="#color/navigation_view_color"
app:headerLayout="#layout/nav_view_header" />
The header layout looks like this (extra objects removed for simplicity):
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/menu_blue"
android:paddingBottom="32dp">
<ImageView
android:id="#+id/navViewLogOutButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="#drawable/logout"
app:tint="#color/white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
android:contentDescription="#string/sz.proApp.support.logOut" />
</androidx.constraintlayout.widget.ConstraintLayout>
I need to set a click listener on this image view, but I can't get a reference to it. This is what I am using:
binding.navView.findViewById<ImageView>(R.id.navViewLogOutButton)?.setOnClickListener{
doSomething()
}
I've verified that binding.navView is not null. Also, by setting a break point there, I can see what the memory address for this ImageView is. When I change that line to this:
binding.navView.findViewById<ImageView>(213123403)
I get the reference that I want. But obviously, I can't just use the integer since that is dynamic. The code compiles and the app launches. But when it hits that line, findViewById returns null every time.
I read that findViewById may not work with data binding in a activity, but if that is true, then how can I get this reference so that I can set the listener?
app:headerLayout="#layout/nav_view_header"
So, the generated binding class should beNavViewHeaderBinding, you can get the binding with:
val headerBinding = NavViewHeaderBinding.bind(binding.navView.getHeaderView(0))
And access the header views through headerBinding:
headerBinding.navViewLogOutButton
I am developing an android application. What I am want to achieve in this app is, I want to make a custom progress bar which has to be available to all the activities. So I created BaseActivity and I added one custom gif in BaseActivity's layout then I created SecondActivity which extends BaseActivity.
BaseActivity(By default gif is invisible) has two methods called showProgressBar and hideProgressBar I am calling this method from SecondActivity but it is not showing a progress bar. Then I make a BaseActivity launcher activity and to check the gif it is showing.
How should I achieve this?
You can override the setContentView in your BaseActivity and add your base layout there, that way when the child activities call the setContentView, the base layout is also set. You'll need to set a FrameLayout that will 'hold' the child activity's layout. Something along the following lines should work.
//In BaseActivity
#Override
public void setContentView(int layoutResID)
{
DrawerLayout fullView = (DrawerLayout) getLayoutInflater().inflate(R.layout.activity_base, null);
FrameLayout activityContainer = (FrameLayout) fullView.findViewById(R.id.activity_content);
getLayoutInflater().inflate(layoutResID, activityContainer, true);
super.setContentView(fullView);
}
P.S - In my above example, I used a DrawerLayout, you should be able to use it with any ViewGroup. Just replace that line with the base ViewGroup in your base activity layout.
Edit - Added xml for clarity
//activity_base.xml
<android.support.v4.widget.DrawerLayout
android:id="#+id/activity_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="#color/background_material_dark"
/>
<FrameLayout
android:id="#+id/activity_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<android.support.design.widget.NavigationView
android:id="#+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="#menu/menu_base"/>
</android.support.v4.widget.DrawerLayout>
You can create a custom dialog class so it will be available for every Activity in your project
Create dialogClass:
public class ProgressDialog extends Dialog {
public ProgressDialog(#NonNull Context context) {
super(context);
setContentView(R.layout.progress_dialog); //this is your layout for the dialog
}
}
And all you need to do is to create dialog instant and call it like this:
ProgressDialog progressDialog = new ProgressDialog(getContext());
progressDialog.show(); // this line shows your dialog
And in your dialog class, you can put your logic, change the way your layout will look and do anything that you need to.
I have been using the v23.0.1 support library until now with no problems. Now when I switch to the new v23.1.0 library I am getting a null pointer on widgets in the drawer layout.
mNavigationView = (NavigationView) findViewById(R.id.navigation_view);
TextView username = (TextView) mNavigationView.findViewById(R.id.username_textView);
// ^^^^^^^^ is now null when using new library
// which causes the following to fail
username.setText(mUser.getName());
activity layout
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
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"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="#layout/toolbar" />
...
</LinearLayout>
<android.support.design.widget.NavigationView
android:id="#+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/drawer_header"
app:menu="#menu/drawer_items" />
</android.support.v4.widget.DrawerLayout>
drawer_header.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="150dp"
android:orientation="vertical">
<TextView
android:id="#+id/username_textView"
android:layout_width="match_parent"
android:layout_height="0dp" />
...
</LinearLayout>
Simply changing the gradle file to use the older version makes it work fine instantly so I don't think there is anything horribly wrong with my code. I checked out the revisions in the update and didn't see anything that I would think to cause this.
Surely this will be affecting others also, any clues?
With the design library v 23.1.0 the NavigationView works with a RecyclerView.
Also the Header is now a type of row.
It means that the header could not be immediately available in the view hierarchy.
It can cause issues if you are using methods like navigationView.findViewById(XXX) to get a view inside the header.
There is a bug in the Google Tracker.
EDIT 12/10/2015: Design library 23.1.1
The 23.1.1 introduces a new API for retrieving header views for NavigationView with getHeaderView()
BEFORE 23.1.1
workaround fot 23.1.0 can be to use a addOnLayoutChangeListener. Somenthing like:
navigationView.addOnLayoutChangeListener( new View.OnLayoutChangeListener()
{
#Override
public void onLayoutChange( ... )
{
navigationView.removeOnLayoutChangeListener( this );
View view = navigationView.findViewById( ... );
}
} );
Another possible workaround are:
remove the app:headerLayout attribute from the xml, and then add the header programatically.
Inflate the headerView programmatically.
Use somenthing like this:
View headerLayout = navigationView.inflateHeaderView(R.layout.navigation_header);
headerLayout.findViewById(xxx);
It appears attaching the header view to the navigation drawer using xml is currently broken. The solution is to inflate and attach the view manually.
activity layout
<android.support.design.widget.NavigationView
android:id="#+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/drawer_header" <!-- remove this line -->
app:menu="#menu/drawer_items" />
Then in your code inflate and attach the header by doing the following.
NavigationView navigationView = (NavigationView) findViewById(R.id.navigation_view);
View drawerHeader = navigationView.inflateHeaderView(R.layout.drawer_header);
TextView username = (TextView) drawerHeader.findViewById(R.id.username_textView);
in the new NavigationView the header is now a row type of RecyclerView in order for you or anybody to find the view by its id you'll need to workaround it and use addOnLayoutChangeListener listener and then you can find the view i know it should be documented somewhere but android be like meh!.
it is a bug at 23.1.0
23.1.1 fixed
https://plus.google.com/+AndroidDevelopers/posts/ebXLByBiEBU
I have updated build tools from Android sdk manager, then 23.1.0 is also working fine for me.
I am using
buildToolsVersion "23.0.2"
before this it was 23.0.1.
and there is no need of using
(View)navigationView.findViewById(R.id.idOfViewFromHeaderView);
In your activity you can directly use
(View)findViewById(R.id.idOfViewFromHeaderView);
I have an Activity with navigation drawer and full-bleed Fragment (with image in the top that must appear behind translucent system bar on Lollipop). While I had an interim solution where the Fragment was inflated by simply having <fragment> tag in Activity's XML, it looked fine.
Then I had to replace <fragment> with <FrameLayout> and perform fragment transactions, and now the fragment does not appear behind the system bar anymore, despite fitsSystemWindows is set to true across all required hierarchy.
I believe there might be some difference between how <fragment> gets inflated within Activity's layout vs on its own. I googled and found some solutions for KitKat, but neither of those worked for me (Lollipop).
activity.xml
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<FrameLayout
android:id="#+id/fragment_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
</FrameLayout>
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:fitsSystemWindows="true"/>
</android.support.v4.widget.DrawerLayout>
fragment.xml
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="224dp"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
...
It worked when activity.xml was this way:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/fragment"
android:name="com.actinarium.random.ui.home.HomeCardsFragment"
tools:layout="#layout/fragment_home"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:fitsSystemWindows="true"/>
</android.support.v4.widget.DrawerLayout>
When you use <fragment>, the layout returned in your Fragment's onCreateView is directly attached in place of the <fragment> tag (you'll never actually see a <fragment> tag if you look at your View hierarchy.
Therefore in the <fragment> case, you have
DrawerLayout
CoordinatorLayout
AppBarLayout
...
NavigationView
Similar to how cheesesquare works. This works because, as explained in this blog post, DrawerLayout and CoordinatorLayout both have different rules on how fitsSystemWindows applies to them - they both use it to inset their child Views, but also call dispatchApplyWindowInsets() on each child, allowing them access to the fitsSystemWindows="true" property.
This is a difference from the default behavior with layouts such as FrameLayout where when you use fitsSystemWindows="true" is consumes all insets, blindly applying padding without informing any child views (that's the 'depth first' part of the blog post).
So when you replace the <fragment> tag with a FrameLayout and FragmentTransactions, your view hierarchy becomes:
DrawerLayout
FrameLayout
CoordinatorLayout
AppBarLayout
...
NavigationView
as the Fragment's view is inserted into the FrameLayout. That View doesn't know anything about passing fitsSystemWindows to child views, so your CoordinatorLayout never gets to see that flag or do its custom behavior.
Fixing the problem is actually fairly simple: replace your FrameLayout with another CoordinatorLayout. This ensures the fitsSystemWindows="true" gets passed onto the newly inflated CoordinatorLayout from the Fragment.
Alternate and equally valid solutions would be to make a custom subclass of FrameLayout and override onApplyWindowInsets() to dispatch to each child (in your case just the one) or use the ViewCompat.setOnApplyWindowInsetsListener() method to intercept the call in code and dispatch from there (no subclass required). Less code is usually the easiest to maintain, so I wouldn't necessarily recommend going these routes over the CoordinatorLayout solution unless you feel strongly about it.
My problem was similar to yours: I have a Bottom Bar Navigation which is replacing the content fragments. Now some of the fragments want to draw over the status bar (with CoordinatorLayout, AppBarLayout), others not (with ConstraintLayout, Toolbar).
ConstraintLayout
FrameLayout
[the ViewGroup of your choice]
BottomNavigationView
The suggestion of ianhanniballake to add another CoordinatorLayout layer is not what I want, so I created a custom FrameLayout which handles the insets (like he suggested), and after some time I came upon this solution which really is not much code:
activity_main.xml
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.app.WindowInsetsFrameLayout
android:id="#+id/fragment_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/bottom_navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
WindowInsetsFrameLayout.java
/**
* FrameLayout which takes care of applying the window insets to child views.
*/
public class WindowInsetsFrameLayout extends FrameLayout {
public WindowInsetsFrameLayout(Context context) {
this(context, null);
}
public WindowInsetsFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WindowInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// Look for replaced fragments and apply the insets again.
setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
#Override
public void onChildViewAdded(View parent, View child) {
requestApplyInsets();
}
#Override
public void onChildViewRemoved(View parent, View child) {
}
});
}
}
OK, after several people pointing out that fitsSystemWindows works differently, and it should not be used on every view down the hierarchy, I went on experimenting and removing the property from different views.
I got the expected state after removing fitsSystemWindows from every node in activity.xml =\
Another approach written in Kotlin,
The problem:
The FrameLayout you are using does not propagate fitsSystemWindows="true" to his childs:
<FrameLayout
android:id="#+id/fragment_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true" />
A solution:
Extend FrameLayout class and override the function onApplyWindowInsets() to propagate the window insets to attached fragments:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
class BetterFrameLayout : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets {
childCount.let {
// propagates window insets to children's
for (index in 0 until it) {
getChildAt(index).dispatchApplyWindowInsets(windowInsets)
}
}
return windowInsets
}
}
Use this layout as a fragment container instead of the standard FrameLayout:
<com.foo.bar.BetterFrameLayout
android:id="#+id/fragment_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true" />
Extra:
If you want to know more about this checkout Chris Banes blog post Becoming a master window fitter.
The other horrendous problem with dispatching of Window insets is that the first View to consume window insets in a depth-first search prevents all other views in the heirarchy from seeing window insets.
The following code fragment allows more than one child to handle window insets. Extremely useful if you're trying to apply windows insets to decorations outside a NavigationView (or CoordinatorLayout). Override in the ViewGroup of your choice.
#Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
if (!insets.isConsumed()) {
// each child gets a fresh set of window insets
// to consume.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
WindowInsets freshInsets = new WindowInsets(insets);
getChildAt(i).dispatchApplyWindowInsets(freshInsets);
}
}
return insets; // and we don't.
}
Also useful:
#Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
return insets.consume(); // consume without adding padding!
}
which allows plain ordinary Views that are children of this view to be laid out without window insets.
I created this last year to solve this problem: https://gist.github.com/cbeyls/ab6903e103475bd4d51b
Edit: be sure you understand what fitsSystemWindows does first. When you set it on a View it basically means: "put this View and all its children below the status bar and above the navigation bar". It makes no sense to set this attribute on the top container.
As the title says, I want to know if there is any way to control views inside the NavigationView header? (Except for adding or removing header.)
For example: In the header, I have a user avatar. By default, it displays a guest image, but after the user logs in, the real avatar will be showed.
How can this be accomplished?
After updating your support library to version 23.1.1 or above,
You could do this -
View header = navigationView.getHeaderView(0);
TextView text = (TextView) header.findViewById(R.id.textView);
or if you have multiple headers
navigationView.getHeaderCount()
Ref : https://code.google.com/p/android/issues/detail?id=190226#c31
Since i can not accept a comment as an answer. So, i repost Moinkhan' answer here:
first create header XML like lay_header.xml
<TextView
android:id="#+id/tvThought"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
on your java file inflate this above header in a TextView. like
TextView headerView = (TextView) LayoutInflater.from(this).inflate(R.layout.lay_header, null);
headerView.setText("Your_thoght");
Now add it as a HeaderView
navView = (NavigationView) findViewById(R.id.navView);
navView.addHeaderView(headerView);
Thats it...
You can also check it out at: Customising NavigationView - Adding dynamic headerView, Android Support Design Library
Many thanks!
And again, we need a function that allow user to accept a comment as answer!
The other way to make this is using the method "inflateHeaderView()" of NavigationView object, like below (After using "setContentView()"):
View headerLayout = navigationView.inflateHeaderView(R.layout.yours_nav_header_layout);
After this point you can use "headerLayout" to access custom views in your header layout, like below:
View cutomView = (TextView) headerLayout.findViewById(R.id.lay_sign_in);
In my case i'm using ButterKnife framework to inject View "navigationView" like this:
#Bind(R.id.nav_view) NavigationView navigationView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
Simply try find your view in your activity:
tvUserName = (TextView) findViewById(R.id.tv_username);
It works for me. My header layout is:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="192dp"
android:background="?attr/colorPrimaryDark"
android:padding="16dp"
android:theme="#style/ThemeOverlay.AppCompat.Dark"
android:orientation="vertical"
android:gravity="bottom">
<TextView
android:id="#+id/tv_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="#style/TextAppearance.AppCompat.Body1"/>
</LinearLayout>
My NavigationView in Activity Layout is:
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/drawer_header"
app:menu="#menu/menu_drawer"/>
And BTW, I came across problem when inflating LinearLayout, where layout params are not provided:
TextView headerView = (TextView) LayoutInflater.from(this).inflate(R.layout.lay_header, null);
headerView.setText("Your_thoght");
Hope it helps.