How to get a reference to an object in a NavigationView Header - android

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

Related

How to access child elements of a TextSwitcher using view binding?

I'm using the Android view binding to get an automatically generated binding class of my XML. In my XML definitions I'm using a TextSwitcher control with two child elements of the type TextView.
In code I access the child views of the TextSwitcher like this:
...
_binding = MyViewBinding.inflate((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE), this, true);
((TextView)_binding.myTextSwitcher.getChildAt(0)).setTextColor(_mySpecialColor);
((TextView)_binding.myTextSwitcher.getChildAt(1)).setTextColor(_mySpecialColor);
...
Is there an easier way to access the child views of the myTextSwitcher directly with the MyViewBinding class without the need to cast them?
The XML definitions looks like this:
<TextSwitcher
android:id="#+id/myTextSwitcher"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</TextSwitcher>
All the time I was searching at the wrong location in the generated binding class.
Those TextView's are there directly accessible on my _binding variable.
// this does work
_binding.text1.setTextColor(_headerLabelColor);
_binding.text2.setTextColor(_headerLabelColor);
I thought they need to be accessible as child's of my TextSwitcher but it seems I learned something now.
// this does not work but I expected it to
_binding.myTextSwitcher.text1.setTextColor(_headerLabelColor);
_binding.myTextSwitcher.text2.setTextColor(_headerLabelColor);

How to include an Android layout but also access its elements in binding?

I'm doind this on my activity:
<include
android:id="#+id/withGyroLayout"
layout="#layout/with_gyro_layout"/>
Where with_gyro_layout.xml is
<androidx.constraintlayout.widget.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.util.FixedTransformerViewPager
android:id="#+id/viewPagerTop"
android:layout_width="0dp"
android:layout_height="143dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.util.FixedTransformerViewPager
android:id="#+id/viewPagerBottom"
android:layout_width="0dp"
android:layout_height="143dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/viewPagerTop" />
</androidx.constraintlayout.widget.ConstraintLayout>
However, I can't access the elements viewPagerBottom or viewPagerTop from the binding for my activity:
binding.viewPagerBottom.setVisibility(View.VISIBLE);
binding.viewPagerTop.setVisibility(View.VISIBLE);
I tried putting with_gyro_layout.xml around <merge>...</merge> but it also didn't solve.
I want to be able to change programatically between with_gyro_layout.xml and without_gyro_layout.xml and also access its inner elements by the binding. How can I do that?
Two things are required in order to use ViewBinding with an included layout.
<merge> is not supported
The documentation only covers Data Binding, and not View Binding, but it does appear to be applicable to both. See https://developer.android.com/topic/libraries/data-binding/expressions#includes
Data binding doesn't support include as a direct child of a merge element.
In other words, the layout must have a real, concrete view as its root element. The following is supported:
<LinearLayout ...>
<TextView ... />
<TextView ... />
</LinearLayout>
But a layout with a <merge> root is not supported:
<merge ...>
<TextView ... />
<TextView ... />
</merge>
The <include> tag must specify an ID
It is, in general, possible to include a layout without explicitly specifying an ID. View Binding does not support this:
<include layout="#layout/included_layout"/>
Even if the included layout has an ID on its root element, it is still not supported. Instead, you must explicitly specify the ID on the <include> tag:
<include
android:id="#+id/some_id"
layout="#layout/included_layout"/>
Once both of these conditions are satisfied, your generated binding for the outer layout will include a reference to the binding of the included layout. Let's say our two files are outer_layout.xml and included_layout.xml. Then these two files would be generated:
OuterLayoutBinding.java
IncludedLayoutBinding.java
And you could reference the included views like this:
val outerBinding = OuterLayoutBinding.inflate(layoutInflater)
val innerBinding = binding.someId // uses the id specified on the include tag
val innerView = innerBinding.viewPagerTop
Or, for short:
val binding = OuterLayoutBinding.inflate(layoutInflater)
val innerView = binding.someId.viewPagerTop

Fragment Button is found by Activity's findViewById instead of Fragment's inflated view.findViewById

I'm seeing this strange behavior and couldn't find anything similar to this.
So I have a parent Activity and inside is a Fragment, which I'm including in parent via include element and then in parent's onCreate, create Fragment and replace it with this include layout (Tell me if this is a right way? I was using FrameLayout but then switched to include and defined an id to it).
Activity
<androidx.constraintlayout.widget.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.sourcey.materiallogindemo.CustomerDetailActivity"
tools:ignore="MergeRootFrame">
<com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.appbar.MaterialToolbar />
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="#id/app_bar"
app:layout_constraintBottom_toTopOf="#id/layout_bottom_bar"
layout="#layout/fragment_customer_detail" />
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomappbar.BottomAppBar>
</com.google.android.material.bottomappbar.BottomAppBar>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.sourcey.materiallogindemo.CustomerDetailFragment"
android:layout_height="match_parent"
android:layout_width="match_parent">
<!-- THIS IS THE CULPRIT -->
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_update_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/sku_list"
app:layoutManager="LinearLayoutManager"
tools:context="com.sourcey.materiallogindemo.CustomerDetailFragment"
tools:listitem="#layout/fragment_s_k_u_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment is inflated correctly but when I do this inside onCreateView
rootView.btn_update_position.setOnClickListener {
// ... log something
}
and press the Button, it doesn't do anything? Even though most findings were led to this suggestion that I should inflate the view and then set onClickListener.
I also tried doing these
rootView.findViewById<MaterialButton>(R.id.btn_update_position).setOnClickListener {
// ... log something
}
and
val button = rootView.findViewById<MaterialButton>(R.id.btn_update_position)
button.setOnClickListener {
// ... log something
}
but none of them works.
I also tried above approaches in onViewCreated to see if maybe I was not getting the reference but no errors were thrown and no reaction was coming.
Only thing that works is this
activity?.findViewById<MaterialButton>(R.id.btn_update_position)
?.setOnClickListener {
// ... log something
}
I'm trying to understand why this happens? Could this be the issue of using include the Fragment?
NOTE I'm not a pro in android just do hobby work in it so don't know very deeply about it.
EDIT As you can see I have a RecyclerView in Fragment layout, I'm inflating the layout and then setting its adapter items which seems to work fine opposed to button.
rootView.sku_list.adapter = Adapter()
I'm bit confused about what you want to do here
First,<include> doesn't create new view, it just include the xml into the parent xml file so basically it still on activity and you need activity to findViewById
Second, about your question what different between FrameLayout and <include>.
With <include> like i said above, it just add xml file to the parent file, the main usage is for re-use layout (you can include it anywhere) .
With FrameLayout, from official doc : "FrameLayout is designed to block out an area on the screen to display a single item". E.g : you want your layout have a header and footer for all screen, only the middle part change so place a frame layout at middle then load different view for each screen, because that flexibility frame layout usually use for display fragment (you can google how to use frame layout for more details)

RecyclerView not updating the view properly with ConstraintLayout Group Visibility

Problem:
It seems when I have a specific combination of views and setVisibility between Visible and Gone the RecyclerView does not update properly after initial load. I have a RelativeLayout->ConstraintLayout->Constraint Group(with visibility dynamically set). The view within the constraint group is not updating properly.
Example Use Case of Problem:
So the linked code below it will show a search view at the top. The initial state of empty shows the view properly(With the search icon showing). Then if you type "T" then the search icon will disappear(it shouldn't). So if you either delete the T or type the next letter "E" then it shows again. Also if you delete all search text and type T again it will show.
View Code:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.example.davidcorrado.myapplication.PlayerVM" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="#+id/playerCell_main_layout"
android:layout_width="match_parent"
android:layout_height="84dp"
android:layout_alignParentTop="true">
<android.support.constraint.Group
android:id="#+id/playerCell_status_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="#{vm.showingStatusIcons}"
app:constraint_referenced_ids="playerCell_lineup_status_image" />
<ImageView
android:id="#+id/playerCell_lineup_status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="#drawable/ic_search" />
</android.support.constraint.ConstraintLayout>
</RelativeLayout>
</layout>
Quirks that might help:
1) If I move the visibility line to the relativeLayout or the ConstraintLayout the things seem to work
2) If I remove the RelativeLayout view things seem to work.
See the below for the full code example:
https://github.com/DavidCorrado/LayoutIssue
As far as I can tell, the reason the search icon disappears is due to the call to ObservableList.clear() then ObservableList.addAll(*)
So the disappear happens on the clear, then the reappear happens on the addAll.
As the callbacks in the adapter kick in on both calls, my hunch is it animates the view to disappear, then animates to show when the addAll is triggered.
I have verified this by not actioning a 'List.clear()' and instead in your textChange listener simply adding more views to the list.
I'm not sure how you would clear and add items to the list without animating the clear, perhaps there are some settings you can toggle in the RecyclerAdapter to ignore the remove of Views or not animate the entry of Views?
My adjusted code in your callback for class PlayersListVM
class PlayersListVM {
val filteredVms: ObservableList<ViewModel> = ObservableArrayList<ViewModel>()
val showing = PlayerVM(isShowing = true)
val missing = PlayerVM()
init {
filteredVms.add(showing)
}
fun onQueryChanged(newText: String) {
if (filteredVms.size % 2 == 1) filteredVms.add(missing)
else filteredVms.add(showing)
}
}

How can content placeholder be implemented in Android RecyclerView just like it is in Facebook?

I want a content placeholder for my TextViews in a RecyclerView until the data is fetched & parsed from the server.
By content placeholder, I don't mean default text or a text saying no results. Please look how Facebook displays gray colored animated blocks before displaying the actual data on the web. I want to achieve that.
Basically a kind of loader unless my data is ready to be displayed (please don't suggest me with horizontal or spinning loaders).
e.g.: I display a gray colored block and as soon as my data is ready to be displayed I'll remove the block and then display the text.
All of that needs to be done in a RecyclerView. I hope I am clear with my need.
How do I achieve this?
I can't place dummy views in the RecyclerView because, unless I have the data my getCount will return 0 so no views will be displayed.
I don't want to display a dummy layout and then make it gone when I get the actual data, that doesn't look clean.
Without knowing exactly Facebook's implementation I assume they load an initial data source with "lean" data from which you can tell how many units you will need to display, based on that you could create a placeholder (that would be a type in your RecyclerView), once the full data for that placeholder was loaded you notify data set change and replace the placeholder with the full item (again another type in your RecyclerView).
Basically a kind of loader unless my data is ready to be displayed
If you have absolutely no data, for example you don't have the amount of "blocks" that will be displayed you can't display a placeholder as it might be discarded if you have an empty data set returned from your data source.
this should work,
<TextView android:id="#android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="No Results" />
The id does the trick!
You have to be using a ListViewActivity for it to work!
If not extending ListViewActivity
try,
ListView.setEmptyView() read here
You need to create the skeleton layout and inflate it on the whole screen. Then you can use different libraries to add the shimmer effect.
drawable/skeleton.xml:
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<solid android:color="#color/skeleton"/>
<corners android:radius="4dp"/>
</shape>
layout/skeleton_row_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="#dimen/row_layout_height">
<View
android:id="#+id/icon"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="15dp"
android:layout_gravity="center_vertical"
android:background="#drawable/skeleton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<View
android:id="#+id/topText"
android:layout_width="200dp"
app:layout_constraintTop_toTopOf="#id/icon"
app:layout_constraintStart_toEndOf="#id/icon"
android:layout_height="15dp"
android:layout_marginLeft="16dp"
android:background="#drawable/skeleton"/>
<View
android:id="#+id/bottomText"
android:layout_width="250dp"
android:layout_height="15dp"
app:layout_constraintTop_toBottomOf="#id/topText"
android:layout_marginTop="10dp"
app:layout_constraintStart_toEndOf="#id/icon"
android:layout_marginLeft="16dp"
android:background="#drawable/skeleton"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#drawable/skeleton"
app:layout_constraintTop_toBottomOf="#id/icon"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</android.support.constraint.ConstraintLayout>
In this tutorial I wrote you will find how to replace the skeleton with the recyclerView: https://medium.com/#sha17187/upgrade-progress-loading-with-a-skeleton-and-shimmer-effect-in-android-863ea4ff5b0b

Categories

Resources