I've tried setting an ObservableField or a String value, still doesn't work. If I just set a static true or false value instead of the viewModel reference, it works.
Layout file:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="app.viewmodel.ViewModel"/>
</data>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:fastScrollEnabled="@{viewModel.isUserAdmin}"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</layout>
ViewModel:
class AppointmentsViewModel()
{
val isUserAdmin: Boolean = sharedPreferencesRepo.isUserAdmin
}
RecyclerView
currently has no public method for setting the fast-scrolling attributes from code. As of this writing, the only way to enable fast-scrolling is by setting the relevant attributes in the layout XML. RecyclerView
then calls a private method from its constructor to set that up.
The data binding library, while generating its adapter code, can't find a public method for the fastScrollEnabled
attribute, which is why you're getting that error. It's just not possible yet.
There is a request in the Issue Tracker to add the relevant functionality to RecyclerView
, and even a comment there that points out the data binding limitation, but it doesn't seem to be a high priority, at the moment.
If you don't mind a little extra work, we can actually accomplish this manually, since RecyclerView
's FastScroller
implementation is simply an ItemDecoration
and OnItemTouchListener
. We can copy that class from the library source and set it up externally, allowing us to enable and disable fast-scrolling programmatically, and thusly handle it through data binding.
Directly copying the current FastScroller
class should require only a few minor edits that will be obvious (e.g., changing the package
, some annotations, etc.), and the following example assumes that you have it copied under its original name, FastScroller
.
There is one vital addition necessary to allow detaching a FastScroller
from its RecyclerView
without error. (Indeed, it seems possibly oversight that it's not there already.) In FastScroller
's destroyCallbacks()
method, add a call to mShowHideAnimator.cancel()
:
private void destroyCallbacks() {
mRecyclerView.removeItemDecoration(this);
mRecyclerView.removeOnItemTouchListener(this);
mRecyclerView.removeOnScrollListener(mOnScrollListener);
cancelHide();
// ADD THIS
mShowHideAnimator.cancel();
}
Fortunately, a FastScroller
basically requires no further attention after instantiation, and since this is replacing a RecyclerView
-internal functionality anyway, it makes sense to attach each FastScroller
instance to its RecyclerView
as a tag, so we don't have to track those instances anywhere else. We only need to be able to retrieve them once later to detach, and this arrangement works well with the data binding setup.
We need to define a unique resource ID as our tag key. It can be placed in any resource file under the res/values/
folder; /res/values/ids.xml
would be appropriate, if you like things neat.
<resources>
<id name="recycler_view_fast_scroller_tag_key" />
</resources>
Our FastScroller
initialization code1 is essentially a reordering of the initializations in RecyclerView
and its constructor, consolidated in a static
utility method:
static void initFastScroller(RecyclerView recyclerView) {
Resources resources = recyclerView.getContext().getResources();
Resources.Theme theme = recyclerView.getContext().getTheme();
StateListDrawable verticalThumbDrawable = (StateListDrawable)
ResourcesCompat.getDrawable(resources, R.drawable.thumb_drawable, theme);
Drawable verticalTrackDrawable =
ResourcesCompat.getDrawable(resources, R.drawable.line_drawable, theme);
StateListDrawable horizontalThumbDrawable = (StateListDrawable)
ResourcesCompat.getDrawable(resources, R.drawable.thumb_drawable, theme);
Drawable horizontalTrackDrawable =
ResourcesCompat.getDrawable(resources, R.drawable.line_drawable, theme);
if (verticalThumbDrawable == null || verticalTrackDrawable == null
|| horizontalThumbDrawable == null || horizontalTrackDrawable == null) {
throw new IllegalArgumentException(
"Trying to set fast scroller without all required drawables.");
}
FastScroller fastScroller =
new FastScroller(recyclerView, verticalThumbDrawable, verticalTrackDrawable,
horizontalThumbDrawable, horizontalTrackDrawable,
resources.getDimensionPixelSize(R.dimen.fastscroll_default_thickness),
resources.getDimensionPixelSize(R.dimen.fastscroll_minimum_range),
resources.getDimensionPixelOffset(R.dimen.fastscroll_margin));
recyclerView.setTag(R.id.recycler_view_fast_scroller_tag_key, fastScroller);
}
This example moves all of the specific resource selections into the initFastScroller()
method for simplicity, but this all can certainly be modified and rearranged however is needed; e.g., into a custom RecyclerView
subclass. Additionally, you can specify whatever dimension arguments you like here, though the example is using the default resource values from the RecyclerView
package.2
The drawables are being handled dynamically now, so their corresponding attribute settings need to be removed from the layout XML:3
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:fastScrollEnabled="@{viewModel.isUserAdmin}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
Lastly, to tie all of this together, the setFastScrollEnabled()
method shows how we can enable and disable fast-scrolling programmatically, and we annotate it as a BindingAdapter
to expose that functionality to data binding through the fastScrollEnabled
attribute:
@BindingAdapter("fastScrollEnabled")
public static void setFastScrollEnabled(RecyclerView recyclerView, boolean setEnabled) {
Object fastScroller = recyclerView.getTag(R.id.recycler_view_fast_scroller_tag_key);
boolean isEnabled = fastScroller instanceof FastScroller;
if (setEnabled != isEnabled) {
if (setEnabled) {
initFastScroller(recyclerView);
} else {
// Detach the FastScroller from the RecyclerView
((FastScroller) fastScroller).attachToRecyclerView(null);
// Nullify our only reference to it
recyclerView.setTag(R.id.recycler_view_fast_scroller_tag_key, null);
}
}
}
This method first checks for our FastScroller
tag on the RecyclerView
at R.id.recycler_view_fast_scroller_tag_key
. If it's there, then fast-scrolling is currently enabled, and disabled otherwise. If that state is different than what we've been called to set, continue either to initialize and setTag()
the FastScroller
(which handles attaching itself to the RecyclerView
internally upon instantiation), or to detach it and nullify the tag.
1 These methods are provided in Java solely because that is the language of the code borrowed from the original sources. If you would rather have it in Kotlin, Android Studio can auto-convert it rather adequately as a first step, though you may wish to make further syntax/structural/etc. adjustments.
2 The given three R.dimen
s all resolved and built without issue in my tests, somewhat unexpectedly. If you do want to use those default values but have problems doing so, you can copy the corresponding <dimen>
s from the library source as well.
3 This should work with or without the app
namespace prefix on the fastScrollEnabled
attribute, since it's apparently ignored/stripped by the data binding framework. It is left in the XML here only to show the minimal changes necessary.
Of course, this all works independently of data binding, as well, and it can be adapted into more suitable utility methods, or Kotlin extensions, for example:
var RecyclerView.isFastScrollEnabled: Boolean
get() = getTag(R.id.recycler_view_fast_scroller_tag_key) is FastScroller
set(value) {
if (value != isFastScrollEnabled) {
if (value) {
// Convert initFastScroller() from above as you like
initFastScroller(this)
} else {
// Detach the FastScroller from the RecyclerView
(getTag(R.id.recycler_view_fast_scroller_tag_key) as FastScroller)
.attachToRecyclerView(null)
// Nullify our only reference to it
setTag(R.id.recycler_view_fast_scroller_tag_key, null)
}
}
}