There is a CoordinatorLayout
that contains an AppBarLayout
and a NestedScrollView
, which has a TextView
. The TextView
has Markdown text. I want to scroll to the anchor (#header
). The code below works as expected, but after calling NestedScrollView.scrollTo
, the CoordinatorLayout
doesn't trigger the behaviour of hiding the AppBarLayout
when scrolling.
What needs to be done in order for the CoordinatorLayout
to receive a scroll event triggered by the code? I have tried to use smoothScrollTo
, scrollBy
, doesn't work. When the user scrolls by hand, the AppBarLayout
hides as expected.
fun scrollToAnchor(view: View, anchor: String) {
val textView = view as TextView
textView.setTextIsSelectable(false)
val spanned = textView.text as Spanned
val spans = spanned.getSpans(0, spanned.length, AnchorSpan::class.java)
if (spans != null) {
for (span in spans) {
if (anchor == span.anchor) {
val start = spanned.getSpanStart(span)
val line = textView.layout.getLineForOffset(start)
val top = textView.layout.getLineTop(line)
val scrollToFunc = (view.parent as? NestedScrollView)?.let { it::scrollTo } ?: view::scrollTo
scrollToFunc.invoke(0, top)
break
}
}
}
textView.setTextIsSelectable(true)
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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/doc_page_coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/doc_paga_top_app_bar"
style="@style/Widget.Material3.Toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="0dp"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:menu="@menu/menu_only_close"
app:title="@string/docs_title" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:id="@+id/markdown_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/view_indent_horizontal"
android:isScrollContainer="true"
android:scrollbarThumbVertical="@android:color/transparent"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textColor="?attr/colorOnBackground"
android:textIsSelectable="true"
tools:text="Lorem ipsum dolor sit amet" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
When you programmatically scroll the NestedScrollView, the AppBarLayout may not hide automatically because the scrolling behavior of the AppBarLayout relies on the user's scroll gestures, rather than programmatic scrolls. However, you can trigger the hide behavior of the AppBarLayout manually when you scroll programmatically.
Use setExpanded(false, true) on the AppBarLayout after the scroll. This will collapse the app bar and ensure it hides as you scroll to the specified header. Steps:
give id to app_bar Layout
setExpanded(false, true) for it.
fun scrollToAnchor(view: View, anchor: String) {
val textView = view as TextView
textView.setTextIsSelectable(false)
val spanned = textView.text as Spanned
val spans = spanned.getSpans(0, spanned.length,
AnchorSpan::class.java)
if (spans != null) {
for (span in spans) {
if (anchor == span.anchor) {
val start = spanned.getSpanStart(span)
val line = textView.layout.getLineForOffset(start)
val top = textView.layout.getLineTop(line)
val appBarLayout = findViewById<AppBarLayout>(R.id.app_bar_layout)
appBarLayout.setExpanded(false, true)
val scrollToFunc = (view.parent as? NestedScrollView)?.let { it::scrollTo } ?: view::scrollTo
scrollToFunc.invoke(0, top)
break
}
}
} textView.setTextIsSelectable(true)
}