Android L: ActionBar setHideOnContentScrollEnabled - android

I'm trying to use the setHideOnContentScrollEnabled and setHideOffset in the new L API. However, none of the mentioned functions seem to have any effect. Anyone else encountered the same issue?
My Activity's layout is a ScrollView with a TextView displaying a large amount of text, so there are def scrolling. I have also, as required by the documentation, added FEATURE_ACTION_BAR_OVERLAY
getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
setContentView(R.layout.main_activity);
getActionBar().setHideOnContentScrollEnabled(true);
getActionBar().setHideOffset(40);

Notice that:
If enabled, the action bar will scroll out of sight along with a
nested scrolling child view's content.
View.setNestedScrollingEnabled(boolean)

I was facing the same problem, using a RecyclerView, a Toolbar and trying to support API10+. I just could not get setHideOffset() or setHideOnContentScrollEnabled() on my SupportActionBar to work.
After a lot of different manual approaches on scrolling the toolbar, this is my current workaround:
I use a ScrollView only for my Toolbar. My Recycler handles its own scrolling which is being listened to.
my_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--The Recycler is in a RefreshLayout. This is optional.-->
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipe"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_alignParentTop="true">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical" />
</android.support.v4.widget.SwipeRefreshLayout>
<!--Draw the Recycler _below_ the Toolbar-->
<!--by defining it _before_ everything else.-->
<ScrollView
android:id="#+id/scroll_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentTop="true"
android:scrollbars="none">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
<!--Add a transparent View below the Toolbar
to give the ScrollView "something to scroll".
Make sure it is _not_ clickable.-->
<View
android:layout_width="match_parent"
android:layout_height="128dp"
android:clickable="false" />
</RelativeLayout>
</ScrollView>
In myActivity.class
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_room_list);
mToolbarScroller = (ScrollView) findViewById(R.id.scroll_toolbar);
mRecycler = (RecyclerView) findViewById(R.id.recycler_rooms);
// [...]
// Do not forget to give your Recycler a Layout before listening to scroll events.
mRecycler.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// Only handle scrolling further if there is at least one child element in the list.
if (recyclerView.getChildCount() == 0) {
mSwipeLayout.setEnabled(true);
return;
}
final boolean didReachTop = recyclerView.getChildAt(0).getTop() >= 0;
if (mToolbarScroller == null) return;
// Simply let the Toolbar follow the scrolling Recycler
// by passing on the scroll-down (positive) values of dy.
if (dy > 2) mToolbarScroller.scrollBy(0, dy);
// Let the Toolbar reappear immediately
// when scrolling up a bit or if the top has been reached.
else if (dy < -4 || didReachTop) mToolbarScroller.scrollTo(0, 0);
}
});
This leads to your Toolbar always overlapping the first element in your Recycler. If you want to avoid this, add an invisible View to your item layouts that has the size of the Toolbar. In your Adapter you simply set it to VISIBLE, if it is the first element in the list, or to GONE if it is any other element:
In myRecyclerItemAdapter.java (optional):
#Override
public void onBindViewHolder(RoomViewHolder viewHolder, Cursor cursor) {
// To compensate for the overlaying toolbar,
// offset the first element by making its spacer visible.
if (cursor.isFirst()) viewHolder.mSpacer.setVisibility(View.VISIBLE);
else viewHolder.mSpacer.setVisibility(View.GONE);
I am probably going to tweak the threshold dy values in the OnScrollListener. They are supposed to filter jittery scroll values, such as a rapid succession of -1, +1, -1, +1 that sometimes happen.
If anyone has a better way or thinks I am making huge mistakes, please let me know! I am always looking for better solutions.

Related

How to allow scrolling the last item of recyclerview above of the FAB?

I have a RecyclerView with a FAB on top at the right bottom.
When scrolling I would like to allow the user to scroll further up such that the last item is fully visible and not covered by the FAB. It would be ok to allow an additional 48px at the bottom showing the background.
I tried adding a transparent footer item which does the trick. However, after adding sorting to the list, the transparent footer item creates some UI glitches during sorting (the divider is shown below the normal items).
I tried adding margin, but then the space is wasted all the time. It is ok that the FAB covers the last item when user views the first items at the top. Only when that last item is important than the item must not be covered.
This is the layout xml:
<android.support.design.widget.CoordinatorLayout
android:id="#+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:clipToPadding="false"
android:scrollbars="vertical"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
style="#style/floating_action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/fab_margin"
android:src="#drawable/ic_add_white_24dp"
app:layout_anchorGravity="bottom|center"/>
</android.support.design.widget.CoordinatorLayout>
Update
Using item decoration is the correct answer here.
I think better solution should calculate scroll range and hide the fab, if content was scroll down.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy){
if (dy > 0) {
fab.hide();
return;
}
if (dy < 0) {
fab.show();
}
}
});
But if you really want to add margin at bottom you should use ItemDecoratorion.

Android - OnLoadMore in RecyclerView with wrap_content height

I have RecyclerView which its height is set to wrap_content. Now I need to implement OnLoadMore to it but there is a problem.
I used,
RecyclerView.OnScrollListener.onScrolled(RecyclerView recyclerView, int dx, int dy)
But it doesn't get invoked because my RecyclerView doesn't scroll. Its height is wrap_content.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root_rtl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<include
android:id="#+id/tool_bar"
layout="#layout/toolbar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/tool_bar">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context="com.yarima.msn.Activities.ProfileActivity">
<FrameLayout
android:id="#+id/FRAGMENT_PLACEHOLDER"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white">
<!-- Some Content That I want to scroll with recyclerview -->
</FrameLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview_posts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:bellow="#id/FRAGMENT_PLACEHOLDER"/>
</RelativeLayout>
</ScrollView>
</RelativeLayout>
So I need to use another approach for loading more pages to RecyclerView.
I think the best way to do this, is calling onLoadMore event when the last item of RecyclerView become visible. I already tried to do this from onBindViewHolder method in adapter, but all pages loaded altogether.
if(getItemCount()-position == 1 && onLoadMoreListener != null){
if (recyclerView != null) {
visibleItemCount = recyclerView.getChildCount();
totalItemCount = recyclerView.getLayoutManager().getItemCount();
firstVisibleItem = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
loading = true;
onLoadMoreListener.onLoadMore();
}
}
}
What is the alternative way to implement onLoadMore without using scroll events?
Update:
The RecyclerView works perfectly with android:layout_height:"wrap_content" and my ScrollView scrolls smoothly.
Update 2:
My problem is when your RecyclerView height is wrap_content, scroll events of RecyclerView cannot be invoked. So I need an alternative way to find out when my RecyclerView reaches to end of its list and implement OnLoadMore event that way.
Update 3
I simplified xml before I wrote it in question... In real xml, there is ViewPager instead of the RecyclerView. And I have 4 tabs in that ViewPager that each tab contains a RecyclerView with different contents.
Above of this ViewPager I have some information about user and I want to scroll all of them together. So I put this header and ViewPager in a ScrollView and set the height of RecyclerView to wrap_content.
You can take a look at profile page of instagram. I want to this page works like that.
It's not possible to show this information in header of RecyclerView because in this way, I should add this information in each RecyclerView in every tabs.
Edited
Could look for scroll events in scrollview
https://developer.android.com/reference/android/view/View.html#onScrollChanged(int, int, int, int)
You need to keep your ViewPager and your RecyclerView inside a NestedScrollView. The final xml should look like this.
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ViewPager/>
<RecyclerView/>
</android.support.v4.widget.NestedScrollView>
And set the height of your RecyclerView to match_parent.
You'll face another problem here. The page will automatically scroll to bottom when the RecyclerView will be loaded. But there's a solution to it too.
Adding android:descendantFocusability="blocksDescendants" to the child layout in NestedScrollView which will prevent the automatic scrolling to the bottom.
If the above doesn't work, try setting nestedScrollView.scrollTo(0, 0); from your code after the RecyclerView is loaded with items.

Prevent RecyclerView from scrolling under AppBarLayout before AppBarLayout is collapsed

I'm creating a RecyclerView with header where the header collapses as you scroll up the RecyclerView. I can achieve this very closely with the layout below, with a transparent AppBarLayout, and MyCoolView which is the header. The parallax effect works great.
However, if the header is still visible and I fling the RecyclerView, the RV scrolls slowly to the top and some of the items are under the Toolbar until the RV reaches the top of the view. I've been playing around with the scrollFlags but haven't achieved a desirable result. Any suggestions on how to improve the fling experience so the items don't get clipped?
View the video and watch when its flinged --- https://www.dropbox.com/s/jppd6m7zo41k23z/20160609_151309.mp4?dl=0
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout
android:background="#00000000">
<android.support.design.widget.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<com.android.myapp.MyCoolView
app:layout_collapseMode="parallax"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView/>
</android.support.design.widget.CoordinatorLayout>
Possible solution (untested). Add an OnOffsetChangedListener to your AppBarLayout, and keep note of the offset value. First, declare this field:
private boolean shouldScroll = false;
Then, onCreate:
AppBarLayout appbar = findViewById(...);
appbar.addOnOffsetChangedListener(new OnOffsetChangedListener() {
#Override
void onOffsetChanged(AppBarLayout appbar, int offset) {
// Allow recycler scrolling only if we started collapsing.
this.shouldScroll = offset != 0;
}
});
Now, add a scroll listener to your RecyclerView. Whenever it tries to scroll, revert the scroll if the AppBarLayout is still expanded:
RecyclerView recycler = findViewById(...);
recycler.addOnScrollListener(new OnScrollListener() {
#Override
void onScrolled(RecyclerView recycler, int dx, int dy) {
// If AppBar is fully expanded, revert the scroll.
if (!shouldScroll) {
recycler.scrollTo(0,0);
}
}
});
This might need some tweaking though. I see two issues:
Possible stack overflow if scrollTo() calls onScrolled() back. Can be solved with a boolean or by removing/adding the scroll listener
Possibly you want to prevent scrolling not only when AppBarLayout is fully expanded, but more generally when AppBarLayout is not collapsed. This means you don’t have to check for offset != 0, but rather for offset == appBarLayout.getTotalScrollRange(). I think.
Maybe you can add layout_behavior="#string/appbar_scrolling_view_behavior" to your RecylerView like this.
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" app:layout_behavior="#string/appbar_scrolling_view_behavior" />
Wrapping the RecyclerView in a FrameLayout solves this problem.
You also need move the appbar_scrolling_view_behavior from the RecyclerView to the FrameLayout so it will be positioned below the AppBarLayout properly.
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout
android:background="#00000000">
<android.support.design.widget.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<com.android.myapp.MyCoolView
app:layout_collapseMode="parallax"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- BEGIN SOLUTION -->
<!-- the layout behavior needs to be set on the FrameLayout, not the RecyclerView -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
<!--This RecyclerView MUST be wrapped in a FrameLayout-->
<!--This prevents the RecyclerView from going behind the AppBarLayout-->
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
<!-- END SOLUTION -->
</android.support.design.widget.CoordinatorLayout>

Weird behavior with CoordinatorLayout+SwipeRefreshLayout+Endless RecyclerView

I'm trying to implement a CoordinatorLayout with a ToolBar that collapses. I already have a SwipeRefreshLayout with a RecyclerView inside. This recycler view also has an onScrollListener to load more content and a custom adapter to show a loading ViewHolder when it's loading more content.
Everything was working fine before I tried adding the CoordinatorLayout. Now I have two problems:
When loading items for the first time, the loading ViewHolder shows up and it's well placed (below the ToolBar). When it finishes, the loading is removed and the items are added. The problem is that the first item is hidden. It's like the second item is actually the first one. It's completely impossible to see the first item even when it is bigger than the ToolBar. But when I use swipe to refresh, the item gets placed properly. I have no idea why this happens.
When I'm using swipe to refresh, it loads items two times. The first time is the normal load and the second is a load for more items because of the onScrollListener. However the scroll is still on the top of the list. The recycler view items stay invisible until I scroll (I think this is because I only notify the adapter of the new items but we are still at the top of the list).
However, I don't know what to change in the listener to fix this.
Here's the listener:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(!adapter.isLoading()) {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
//position starts at 0
if (layoutManager.findLastCompletelyVisibleItemPosition() >= layoutManager.getItemCount() - 2) {
loadSubmissions(false);
}
}
}
});
My activity layout:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="#+id/toolbar"
layout="#layout/toolbar" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:scrollbars="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout>
My toolbar:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
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/colorPrimary"
android:elevation="4dp"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
Both problems are fixed. For the first one, when I was removing the loading ViewHolder I was actually doing notifyItemInserted instead of notifyItemRemoved.
For the second problem it's because I'm calling notifyDatasetChanged when I'm using swipe to refresh.
#Override public void onRefresh() {
adapter.clear();
adapter.notifyDataSetChanged();
loadSubmissions(true);
}
When I clear the adapter with the swipe to refresh, I'm swiping so it means that the onScrollListener will be called.
I changed the listener and added a verification to don't load more if there are no items.
mRecyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(linearLayoutManager) {
#Override
public void onLoadMore(int current_page) {
// Doesnt load multiples times with the same scroll and doesnt load if there are no items
if(!adapter.isLoading() && adapter.getItemCount() > 0) {
loadSubmissions(false);
}
}
});

Android 5.0 - Implement the Google Play Movies Material design "My movies" screen

I'd like to implement that screen from the new GPlay Movies material version :
If you haven't tried the app yet, the best way to see what I mean is to check it here.
So I have a RecyclerView coupled with a GridLayoutManager for the grid. And I thought about adding a header view with a red background to the grid. Note that I'd also like to add the same parallax effect on this header view than on the Movies app.
For now, I don't really need the support of the tabs (I want to make it work with a RecyclerView, not a ViewPager).
I used to make this kind of screen work with a solution based on this tuto this tuto by Cyrill Mottier and the source code of this lib by Flavien Laurent. But these solutions worked for ListViews or ScrollViews, but are not (yet) adapted to the RecyclerView.
Would you have some idea on how to make this work?
Thanks in advance,
VieuMa
There's quite a bit going on when scrolling in in the My Movies screen (or in the Google Play Music app). Here's how thing basically work:
General Idea
Create a simple colored view with your actionbar color behind your RecylcerView
Add your tabs (or dummy tab, in your case) to the Toolbar container view.
Add an OnScrollListener to your RecyclerView (the more difficult part). Use the dy (delta in y-direction) to
parallax-scroll your colored view. If scrolling down, translate
the toolbar container towards the top of the screen. If scolling
up, show the toolbar container (unless it's already visible)
Code
In your activity.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Actual content (could also be in a fragment -->
<View
android:id="#+id/coloredView"
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="#CC0000" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="12dp"
android:layout_marginLeft="12dp"
android:paddingTop="120dp"
android:clipToPadding="false" />
<!-- Toolbar (container) -->
<LinearLayout
android:id="#+id/toolbar_container"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="#dimen/abc_action_bar_default_height_material"
android:layout_gravity="top"
android:gravity="center_vertical"
android:background="#CC0000"
android:minHeight="0dp"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
style="#style/Toolbar" />
<!-- Fake tab ;) -->
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Dummy tab"
android:background="#CC0000"
android:padding="16dp"
android:textAllCaps="true"
android:textColor="#fff" />
</LinearLayout>
</FrameLayout>
In onCreate() of your MainActivity.java:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Init your recyclerview here.
final View toolbarContainer = findViewById(R.id.toolbar_container);
final View coloredView = findViewById(R.id.coloredView);
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy < 0) {
onScrollUp(dy);
} else {
onScrollDown(dy);
}
}
private void onScrollDown(int dy) {
coloredView.setTranslationY(coloredView.getTranslationY() - dy / 3);
toolbarContainer.setTranslationY(toolbarContainer.getTranslationY() - dy);
}
private void onScrollUp(int dy) {
coloredView.setTranslationY(coloredView.getTranslationY() - dy / 3);
if (toolbarContainer.getTranslationY() < 0) {
showToolbar();
}
}
private void showToolbar() {
toolbarContainer.animate()
.translationY(0)
.setDuration(200)
.setInterpolator(new DecelerateInterpolator())
.start();
}
});
Result
(I've left out little details like the toolbar shadow or the toolbar showing its full height if it wasn't fully hidden.)
Hope that helps!
PS: The title of your question is very specific. Maybe you should change it to something like "Hiding toolbar on recyclerview scroll like in Google Play/Play Music" so that this thread is easier to find ;)

Categories

Resources