Focus gets trapped in Viewpager2 and impossible to select something outside - android

I have a layout with a NavigationView and a ViewPager2 side by side
<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/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.arn.scrobble.MainActivity">
<com.google.android.material.navigation.NavigationView
android:id="#+id/sidebar_nav"
android:layout_width="#dimen/margin_for_sidebar"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="#dimen/margin_for_sidebar" />
The Viewpager2 has 3 Fragments, each containing a RecyclerView.
Focusing to the NavigationView from ViewPager2 and back is only possible when it is showing the first Fragment.
Absurdly, if I keep pressing dpad right, focus moves off screen to the second and then to the third fragment, without the viewpager actually scrolling.
As a workaround for that, I used the following PageTransformer to set the offscreen views as invisible, so that they won't get focus.
binding.pager.setPageTransformer { page, position ->
when {
position <= -1 -> page.visibility = View.INVISIBLE
position < 1 && position > -1 -> {
page.visibility = View.VISIBLE
page.alpha = 1 - abs(position)
}
else -> page.visibility = View.INVISIBLE
}
}
Now if the second or the third fragment is displayed, the focus gets trapped within the children of that fragment view of the viewpager.
So still, focusing to back to the NavigationView from ViewPager2 is only possible when it is showing the first Fragment.
Are there any workarounds for this? Perhaps by explicitly telling the view to focus on the NavigationView, if focus gets trapped and it could not find a focusable element to the left?

Related

Unable to scroll to item in RecyclerView that is inside NestedScrollView

I'm trying to programmatically scroll to a particular item within my RecyclerView which is nested within a NestedScrollView.
The Problem
The NestedScrollView scrolls to the complete bottom rather than the desired item.
Note: It works correctly when desired item is the 2nd item, probably since that item is visible in the screen.
What I've tried
I've searched through a dozen solutions from StackOverFlow and came up with the function below.
I've tried:
binding.variantsRecyclerView.getChildAt()
binding.variantsRecyclerView.findViewWithTag()
binding.variantsRecyclerView.findViewHolderForAdapterPosition()
All these do return the correct item, (I know since the edit text within that item is focused as coded) however, the NestedScrollView does not scroll correctly to that item. It is almost always scrolling to the bottom. Sometimes however it scrolls to somewhere in between the required item instead of it's start. The only time this works is when the item is either the 1st or 2nd item. (As stated before)
private fun scrollToPosition(position: Int) {
if (position in 0 until variantsAdapter.itemCount) {
val view = binding.variantsRecyclerView.findViewWithTag<ConstraintLayout>("pos_$position")
if (view != null) {
val height = binding.nestedScrollView.height
val target = view.y.toInt()
binding.nestedScrollView.postDelayed({
binding.nestedScrollView.smoothScrollTo(0, target)
view.requestFocus()
}, 200)
}
}
}
My XML
<?xml version="1.0" encoding="utf-8"?>
<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:background="#eff1f4">
<LinearLayout>...</LinearLayout>
<androidx.core.widget.NestedScrollView
android:id="#+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constrainedHeight="true"
app:layout_constraintVertical_bias="0"
app:layout_constraintBottom_toTopOf="#+id/btnNextLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbarLayout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/ui_10_dp"
android:layout_marginEnd="#dimen/ui_10_dp"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/variantsRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:paddingTop="12dp"
android:paddingBottom="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2"
tools:listitem="#layout/sell_variant_row_item" />
<androidx.constraintlayout.widget.ConstraintLayout>
...
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout>...</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
My Understanding
After debugging, I found out that the NestedScrollView height is lesser than the y co-ordinate of the desired item. Hence it scrolls to the bottom of the view instead of the desired item. My understanding could be completely wrong and if so, please correct me.
I resolved this with a really simple fix.
private fun scrollToPosition(position: Int) {
if (position in 0 until variantsAdapter.itemCount) {
val view = binding.variantsRecyclerView.getChildAt(position)
if (view != null) {
val target = binding.variantsRecyclerView.top + view.top
binding.nestedScrollView.scrollY = target
}
}
}
All I wanted was to get the desired item within the RecyclerView to the top of the screen.

ViewPager within a CoordinatorLayout (for sticky header purposes) leads to broken scrolling

Test app demonstrating the problem here: https://github.com/jstubing/ViewPagerTest
I'm using a CoordinatorLayout/AppBarLayout combo to achieve a sticky header within a scrollable page. At the very bottom of the page is a ViewPager2. When I swipe to a new ViewPager page, content loads asynchronously and populates a RecyclerView within the ViewPager. This all works well.
The problem occurs when I swipe to a new ViewPager page. At this point, the content loads and renders fine but I am no longer able to initiate a scroll outside of the ViewPager. In other words, swiping up and down in the "UPPER PAGE CONTENT" section (see XML) does nothing.
However, scrolling up and down does work within the ViewPager. If I scroll up or down in the ViewPager, or even just tap the ViewPager once, it fixes everything and I am again able to initiate a scroll from outside of the ViewPager.
I tried switching to the original ViewPager instead of ViewPager2 and it has definitely been more reliable, but the problem still happens every so often.
Any ideas how I can fix this? Ultimately I just want to be able to swipe between ViewPager pages and have the whole entire activity remain scrollable. I'm so confused as to why I lose scrolling ability in the first place, and why tapping on the ViewPager fixes it.
<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="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:elevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/background"
app:elevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:layout_scrollFlags="scroll">
<!-- UPPER PAGE CONTENT -->
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Sticky header -->
<include
android:id="#+id/sticky_header"
layout="#layout/sticky_header"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/events_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
The problem occurs when I swipe to a new ViewPager page. At this
point, the content loads and renders fine but I am no longer able to
initiate a scroll outside of the ViewPager.
I am again able to initiate a scroll from outside of the ViewPager.
So, the problem is now limited to the ViewPager. Most probably because the ViewPager2 supports nested scrolling through its inner RecyclerView (I don't mean the RecyclerView of the page, but the one that makes the ViewPager functions internally.
So, first thing we need to disable this nested-scrolling of the ViewPager2:
Kotlin:
viewPager.children.find { it is RecyclerView }?.let {
(it as RecyclerView).isNestedScrollingEnabled = false
}
Java:
for (int i = 0; i < viewPager.getChildCount(); i++) {
View child = viewPager.getChildAt(i);
if (child instanceof RecyclerView)
child.setNestedScrollingEnabled(false);
}
We can't completely disable the nested scrolling because this will badly affect the scrolling outside, so, we can manipulate this by a NestedScrollView that wraps the ViewPager:
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/events_pager"
android:layout_width="match_parent"
android:layout_height="match_parent />
</androidx.core.widget.NestedScrollView>

Efficient WebView resizing

I've implemented floating Toolbar in my app (hides with scroll down, shows on up) and now I see some flickering views inside WebView, to be precise these sticked to the bottom. I've noticed that this happens when I'm resizing WebView due to Toolbar offset change like this:
appBarLayout.addOnOffsetChangedListener(this);
#Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
boolean scrollFlagsSet = ((AppBarLayout.LayoutParams) getToolbar().getLayoutParams()).
getScrollFlags() != 0;
int bottom = (!scrollFlagsSet ? 0 : getToolbarHeightAttr()) + verticalOffset;
swipeRefreshLayout.setPadding(0, 0, 0, bottom);
swipeRefreshLayout.requestLayout();
}
swipeRefreshLayout is WebViews parent. Method causes proper resizing WebView and it's pretty efficient, no lags (EDIT: a bit laggy on older devices). But when WebView loads site with some views sticked to the bottom then these views are flickering when resizing occurs. I can even see text in gap between bottom WebViews edge (gap present when scrolling down, view are cutted on the bottom when scrolling up). I've catched this behavior on screens (blue - Toolbar, gray - WebView, light blue & orange - in-webview views sticked to bottom). It looks like this only for a couple of millis (one frame?) and get back to the bottom fixed position. How to fix this?
Same page loaded in Chrome or Firefox behaves correctly, these views are sticked to bottom and have fixed position, doesn't flicker with scroll/toolbar offset change
I'm using this NestedWebView. My app uses a lot of fragments loaded into Activities, which all are extending abstract BaseActivity in which I have CoordinatorLayout. WebViews belong to fragments and are placed in different containers, there is no option to place WebView straight into CoordinatorLayout
edit: XMLs
activity:
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/activity_main_cooridnator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<FrameLayout
android:id="#+id/activity_main_content_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white" />
fragment:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/fragment_webview_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<my.package.widgets.web.NestedWebView
android:id="#+id/fragment_webview_webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarStyle="outsideOverlay" />

RecyclerView inside CoordinatorLayout,AppBarLayout Scrolling issue

I have this xml code in fragment:
<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:id="#+id/coordinatorLayout" android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme"
app:elevation="0dp">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_scrollFlags="scroll"
android:id="#+id/collapsingToolbarLayout"
app:statusBarScrim="#color/bestColor">
<LinearLayout></LinearLayout> <!--this elements hide then appbar is collapsed-->
</android.support.design.widget.CollapsingToolbarLayout>
<LinearLayout>
<ImageButton>
android:id="#+id/profile_header_trophies"
</ImageButton><!-- this elements like a tab,visible if appbar collapsed-->
</LinearLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/profile_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
in Java Class on Item set ClickListener:
#OnClick(R.id.profile_header_trophies)
public void profile_header_trophies_clicked() {
if (myProfile != null) {
appBarLayout.setExpanded(false, false);
if (myProfile.getBests().size() == 0) {
profile_recyclerView.smoothScrollToPosition(3);
} else {
profile_recyclerView.smoothScrollToPosition(2 + 20);
}
}
When I click to ImageButton, my RecyclerView scrolls to position, everything looks fine.
But if I put finger on AppBarLayout section (ImageButton) which visible(sticky) on top, and drag to bottom I have a bad scrolling.
My appbar start expanded, while my Recycler have some elements on top (they are hidden when scrolled).
I think this problem is setting behavoir. Because if I scrolling recycler first, AppBar doesnt start expanding, while Recycler not rich top of elements.
Thanks for your answers.
The bad scrolling once happened to me, it was happening because I was using RecyclerView inside another RecyclerView.
So when I try to scroll contents in the main RecyclerView it gave me this issue.
To resolve that issue I added this to RecyclerView:
recyclerView.setNestedScrollingEnabled(false);
For doing this in XML you can use:
android:nestedScrollingEnabled="false"
with this, you tell it to "merge" the scrolling of the RecyclerView with the scrolling of its parent
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
if I well understood, you want to have the following scrolling behaviour:
if you are scrolling by touching outside the RecyclerView, it collapses the AppBar
if you are scrolling by touching inside it, it ignores the RecyclerView's scroll and collapses the AppBar, and once the AppBar is collapsed, it scrolls inside the RecyclerView
Could you confirm this is the desired behaviour please?
In such case, you may look at this answer, maybe it will help
I think you need wrap content in NestedScrollView and set app:layout_behavior="#string/appbar_scrolling_view_behavior" in NestedScrollView
If you are using RecyclerView inside ViewPager then add this line to ViewPager: android:nestedScrollingEnabled="false"
It will solve your problem.
It can be tricky and there's a few things you need to have in order for this to work.
You should use app:layout_scrollFlags="scroll|enterAlwaysCollapsed" in your CollapsingToolbarLayout instead of just scroll.
It's not clear where the tabs or buttons are in your XML layout, but if they are supposed to stay on screen then you need to pin them, so you would use app:layout_collapseMode="pin" for that element. Perhaps this is in the LinearLayout or ImageView?
If the LinearLayout holds something else then you should add some scroll flags to that too, probably best would be app:layout_scrollFlags="scroll|enterAlwaysCollapsed" if it is supposed to scroll off screen.
Lastly, make sure you are not disabling nested scrolling in your RecyclerView.

How to let only specific fragments scroll inside a ViewPager of CoordinatorLayout?

Background
I have a viewPager, with 3 fragments and tabs for them. Each fragment has an intro phase (of its own) that doesn't have any scrollable content.
After leaving the intro phase, there is a recyclerView that the user can scroll in.
The problem
I need to use the new design library, so that when scrolling (only via recyclerView), it will hide the actionBar and let the tabs still be shown.
When the user goes to a fragment that doesn't have a scrollable content yet, the actionBar should re-appear, similar to what "Google Play Newsstand" has. In fact, I would even be happy to have what they have: as soon as you start swiping left/right, re-show the action bar.
Thing is, if I follow the guidelines and samples, I have 2 issues:
The non-scrollable phase for fragments gets truncated at the bottom, as if it can get scrolled.
I can't find how to re-show the actionBar, and make it stuck there till I switch to a scrollable content (either by switching to another fragment, or when the content of the current fragment changes to a scrollable content).
What I've tried
Here's a short snippet of the current layout XML file of the activity:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.CoordinatorLayout
android:id="#+id/activity_main__coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/activity_main__appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:theme="?attr/actionBarTheme">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:layoutDirection="ltr"
android:theme="?attr/actionBarTheme"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
tools:ignore="UnusedAttribute"/>
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:tabGravity="fill"
app:tabIndicatorColor="#FFffffff"
app:tabIndicatorHeight="3dp"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<include layout="#layout/fabs"/>
</android.support.design.widget.CoordinatorLayout>
<include
layout="#layout/sliding_menu"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left"/>
</android.support.v4.widget.DrawerLayout>
The fragments have a layout of a ViewAnimator that just switches between phases, while one of them is the non-scrollable content, and the other is the RecyclerView.
I've tried to add a NestedScrollView/ScrollView the non-scrollable content , and force it to fill itself, using android:fillViewport="true" , but it didn't work. For ScrollView it didn't even allow to scroll.
EDIT: Another thing I've tried is to use addOnPageChangeListener on the viewPager, so that in onPageSelected I could set the flags for the toolbar :
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
params.setScrollFlags(!needScrolling? 0 : LayoutParams.SCROLL_FLAG_SNAP | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | LayoutParams.SCROLL_FLAG_SCROLL);
It works, but it has a issues too:
while scrolling horizontally, I can see the content of the non-scrollable fragment being truncated, and when going to the new fragment (stop touching the screen, to let it snap to the fragment), only then it shrinks its size to fit the correct space.
The toolbar doesn't get re-shown.
If the toolbar is hidden due to scrolling on another fragment, and I'm now on the non-scrollable fragment, it actually gets less space to fill than it's supposed to, so it has empty space at the bottom.
EDIT: one solution is to add an empty view of the same height of actionbar (layout_height="?actionBarSize") at the bottom of the non-scrollable fragments's content. However, when the action bar is hidden, I can see the view, so there is empty space. I still need to know how to re-show the actionbar on this case.
The question
How do I set a different behavior for the toolbar, so that it will re-show and stuck on certain states, yet be scrollable only when there is a RecyclerView shown on the current fragment?

Categories

Resources