Android scrollable tabs

Overview

logo

Maven Central


Scrollable is a library for an Android application to implement various scrolling technicks. It's all started with scrolling tabs, but now much more can be done with it. Scrollable supports all scrolling and non-scrolling views, including: RecyclerView, ScrollView, ListView, WebView, etc and any combination of those inside a ViewPager. Library is designed to let developer implement desired effect without imposing one solution. Library is small and has no dependencies.

Preview

All GIFs here are taken from sample application module.

colorful_sample custom_overscroll_sample dialog_sample

*Serving suggestion

Installation

compile 'ru.noties:scrollable:1.3.0'

Usage

To start using this library ScrollableLayout must be aded to your layout.

<?xml version="1.0" encoding="utf-8"?>
<ru.noties.scrollable.ScrollableLayout
    android:id="@+id/scrollable_layout"
    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"
    app:scrollable_autoMaxScroll="true"
    app:scrollable_defaultCloseUp="true">

    <ru.noties.scrollable.sample.SampleHeaderView
        style="@style/HeaderStyle"
        app:shv_title="@string/sample_title_fragment_pager"/>

    <ru.noties.scrollable.sample.TabsLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="@dimen/tabs_height"
        android:background="@color/md_teal_500"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="@dimen/tabs_height"/>

</ru.noties.scrollable.ScrollableLayout>

Please note, that ScrollableLayout positions its children like vertical LinearLayout, but measures them like a FrameLayout. It is crucial that scrolling content holder dimentions must be set to match_parent (minus possible sticky view that should be extracted from it, for example, by specifying android:layoutMarginTop="height_of_sticky_view").

Next, ScrollableLayout must be initialized in code:

final ScrollableLayout scrollableLayout = findView(R.id.scrollable_layout);

// this listener is absolute minimum that is required for `ScrollableLayout` to function
scrollableLayout.setCanScrollVerticallyDelegate(new CanScrollVerticallyDelegate() {
    @Override
    public boolean canScrollVertically(int direction) {
        // Obtain a View that is a scroll container (RecyclerView, ListView, ScrollView, WebView, etc)
        // and call its `canScrollVertically(int) method.
        // Please note, that if `ViewPager is used, currently displayed View must be obtained
        // because `ViewPager` doesn't delegate `canScrollVertically` method calls to it's children
        final View view = getCurrentView();
        return view.canScrollVertically(direction);
    }
});

Draggable View

This is a View, that can be dragged to change ScrollableLayout scroll state. For example, to expand header if tabs are dragged. To add this simply call:

// Please note that `tabsLayout` must be a child (direct or indirect) of a ScrollableLayout
scrollableLayout.setDraggableView(tabsLayout);

OnScrollChangedListener

In order to apply custom logic for different scroll state of a ScrollableLayout OnScrollChangedListener can be used (to change color of a header, create parallax effect, sticky tabs, etc)

scrollableLayout.addOnScrollChangedListener(new OnScrollChangedListener() {
    @Override
    public void onScrollChanged(int y, int oldY, int maxY) {

        // `ratio` of current scroll state (from 0.0 to 1.0)
        // 0.0 - means fully expanded
        // 1.0 - means fully collapsed
        final float ratio = (float) y / maxY;

        // for example, we can hide header, if we are collapsed
        // and show it when we are expanded (plus intermediate state)
        header.setAlpha(1.F - ratio);

        // to create a `sticky` effect for tabs this calculation can be used:
        final float tabsTranslationY;
        if (y < maxY) {
            // natural position
            tabsTranslationY = .0F;
        } else {
            // sticky position
            tabsTranslationY = y - maxY;
        }
        tabsLayout.setTranslationY(tabsTranslationY);
    }
});

OnFlingOverListener

To continue a fling event for a scrolling container OnFlingOverListener can be used.

scrollableLayout.setOnFlingOverListener(new OnFlingOverListener() {
    @Override
    public void onFlingOver(int y, long duration) {
        recyclerView.smoothScrollBy(0, y);
    }
});

OverScrollListener

To create custom overscroll handler (for example, like in SwipeRefreshLayout for loading, or to zoom-in header when cannot scroll further) OverScrollListener can be used

scrollableLayout.setOverScrollListener(new OverScrollListener() {
    @Override
    public void onOverScrolled(ScrollableLayout layout, int overScrollY) {

    }

    @Override
    public boolean hasOverScroll(ScrollableLayout layout, int overScrollY) {
        return false;
    }

    @Override
    public void onCancelled(ScrollableLayout layout) {

    }

    @Override
    public void clear() {

    }
});

OverScrollListener gives you full controll of overscrolling, but it implies a lot of handling. For a simple case OverScrollListenerBase can be used

scrollableLayout.setOverScrollListener(new OverScrollListenerBase() {
    @Override
    protected void onRatioChanged(ScrollableLayout layout, float ratio) {

    }
});

For example, this is onRatioChanged method from ZoomInHeaderOverScrollListener from sample application:

@Override
protected void onRatioChanged(ScrollableLayout layout, float ratio) {
    final float scale = 1.F + (.33F * ratio);
    mHeader.setScaleX(scale);
    mHeader.setScaleY(scale);

    final int headerHeight = mHeader.getHeight();
    mContent.setTranslationY(((headerHeight * scale) - headerHeight) / 2.F);
}

Scrolling Header

There is support for scrolling header. This means that if header can scroll, it will scroll first to the final position and only after that scroll event will be redirected. There are no extra steps to enable this feature if scrolling header is the first view in ScrollableLayout. Otherwise a XML attribute app:scrollable_scrollingHeaderId can be used, it accepts an id of a view.

Various customizations

CloseUpAlgorithm

In order to close-up ScrollableLayout (do not leave in intermediate state, allow only two scrolling states: collapsed & expanded, etc), CloseUpAlgorithm can be used. Its signature is as follows:

public interface CloseUpAlgorithm {

    int getFlingFinalY(ScrollableLayout layout, boolean isScrollingBottom, int nowY, int suggestedY, int maxY);

    int getIdleFinalY(ScrollableLayout layout, int nowY, int maxY);
}

And usage is like this:

scrollableLayout.setCloseUpAlgorithm(new MyCloseUpAlgorithm());

Library provides a DefaultCloseUpAlgorithm for a most common usage (to allow ScrollableLayout only 2 scrolling states: collapsed and expanded). It can be set via java code: scrollableLayout.setCloseUpAlgorithm(new DefaultCloseUpAlgorithm()) and via XML with app:scrollable_defaultCloseUp="true".

Also, there is an option to set duration after which CloseUpAlgorithm should be evaluated (idle state - no touch events). Java: scrollableLayout.setConsiderIdleMillis(100L) and XML: app:scrollable_considerIdleMillis="100". 100L is the default value and may be omitted.

If close-up need to have different animation times, CloseUpIdleAnimationTime can be used. Its signature:

public interface CloseUpIdleAnimationTime {
    long compute(ScrollableLayout layout, int nowY, int endY, int maxY);
}

If animation time is constant (do not depend on current scroll state), SimpleCloseUpIdleAnimationTime can be used. Java: scrollableLayout.setCloseUpIdleAnimationTime(new SimpleCloseUpIdleAnimationTime(200L)), XML: app:app:scrollable_closeUpAnimationMillis="200". 200L is default value and can be omitted.

If one want to get control of ValueAnimator that is used to animate between scroll states, CloseUpAnimatorConfigurator can be used. Its signature:

public interface CloseUpAnimatorConfigurator {
    // when called will already have a duration set
    void configure(ValueAnimator animator);
}

If only Interpolator must be configured, a InterpolatorCloseUpAnimatorConfigurator can be used. Java: scrollableLayout.setCloseAnimatorConfigurator(new InterpolatorCloseUpAnimatorConfigurator(interpolator)), XML: app:scrollable_closeUpAnimatorInterpolator="app:scrollable_closeUpAnimatorInterpolator="@android:interpolator/decelerate_cubic"

Auto Max Scroll

If you layout has a header with dynamic height, or it's height should be obtained at runtime, there is an option to automatically obtain it. Java: scrollableLayout.setAutoMaxScroll(true), XML: app:scrollable_autoMaxScroll="true". With this option there is no need manually set maxScrollY. Please note, that if not specified explicitly this option will be set to true if maxScroll option is not set (equals 0). Also, if at runtime called scrollableLayout.setMaxScroll(int), autoMaxScroll if set to true, will be set to false.

By default the first View will be used to calculate height, but if different one must be used, there is an option to specify id of this view. XML: app:scrollable_autoMaxScrollViewId="@id/header"

Disable Handling

If ScrollableLayout must not evaluate its scrolling logic (skip all touch events), scrollableLayout.setSelfUpdateScroll(boolean) can be used. Pass true to disable all handling, false to enable it.

Animate Scroll

To animate scroll state of a ScrollableLayout, animateScroll(int) can be used:

// returns ValueAnimator, that can be configured as desired
// `0` - expand fully
// `scrollableLayout.getMaxScroll()` - collapse
scrollableLayout.animateScroll(0)
        .setDuration(250L)
        .start();

Please note that ScrollableLayout caches returned ValueAnimator and reuses it. First of all because it doesn't make sense to have two different scrolling animations on one ScrollableLayout. So, it's advisable to clear all possible custom state before running animation (just like View handles ViewPropertyAnimator)

License

  Copyright 2015 Dimitry Ivanov ([email protected])

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
Comments
  • Issue scrolling the list/recyclview down

    Issue scrolling the list/recyclview down

    Whenever I scroll down, the header scrolls and becomes visible; but I have to release and scroll down again for the scrolling to continue.

    How do I change this behavior such that with one scroll down/drag down the header will show while the list will still continue to scroll down.

    opened by ayz4sci 12
  • bottom part of the fragment in the viewpager is clipped or cut of how to fix this

    bottom part of the fragment in the viewpager is clipped or cut of how to fix this

    hello

    i have a view pager with fragment in the scrollable , when i scroll down you can notics that the bottom part of the fragment in the viewpager is clipped or cut of how to fix this

    opened by xbahaa 6
  • Change viewpager item size dynamically with listview scroll

    Change viewpager item size dynamically with listview scroll

    I have a listview below a viewpager and in the initial state (when nothing has been scrolled), the viewpager shows only one item with a 10dp "preview" of the next and previous items (I have achieved this by setting a negative page margin:viewPager.setPageMargin(-48);). What I am trying to do is, on scrolling down the listview

    1. the listview should "push" the viewpager up, decreasing its height up to a certain point. On reaching that point (some minHeight for the viewpager), the listview should scroll normally with the smaller sized viewpager above it.

    2. The next and the previous items in the viewpager should pull inside (towards the central item) and in the final state, three items of the viewpager should be fully displayed. (Images below to illustrate this)

    Scrolling up the listview should do the opposite.

    Initial state (nothing scrolled)

    first

    Final state (minHeight of viewpager to be achieved)

    second

    I'm not able to do with Scrollable, Can anyone help me achieve this ?

    opened by mobilegeekkk 5
  • HeaderView Issue

    HeaderView Issue

    @noties : Header view is going inside the toolbar , i need to make header view below the toolbar with size variation as on scroll up size should get reduce and on scroll down size should get increase. review_screen

    opened by aamirmsw 5
  • How to add Toolbar in xml ?

    How to add Toolbar in xml ?

    if my app use theme

    <style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
    

    how to make sticky toolbar in xml ?

    opened by leelei 5
  • can't use viewpager in the header part instead of textview?

    can't use viewpager in the header part instead of textview?

    Hi, it is a awesome project, I just met a problem, I wanna replace the textview control of the header with a viewpager, but it seems being failed, and the listview move to the header, how to fix it?

    opened by jboeyin 4
  • Header height is not preserved after screen rotation

    Header height is not preserved after screen rotation

    Hi. I noticed that the sample app does not preserve the header view height after screen rotation.

    P.S. Thanks for the superb library, this is what I am just looking for! I am willing to contribute this library :)

    opened by h6ah4i 4
  • layout height problem with ViewPager

    layout height problem with ViewPager

    I use ScrollableLayout in a TabLayout+ViewPager,and ScrollableLayout has a LinearLayout(with Tablayout+ViewPager), if the ScrollableLayout in first page of tab,the height is ok,or else can see ScrollableLayout

    opened by Andecy 3
  • dude

    dude

    Sorry for the noob question, i have activity with tabs, in have 3 tabs(3 fragments), i the first two i have recycler view, i need to set this {canscroll vertical}, i follow your sample, but this not work...

    In fragments that i have recycler view, i implements CanScrollVerticallyDelegate, OnFlingOverListener methods:

    override fun canScrollVertically(direction: Int): Boolean { return recycler_view_relation.canScrollVertically(direction) }

    override fun onFlingOver(y: Int, duration: Long) {
            recycler_view_relation.smoothScrollBy(0, y)
    } 
    

    On activity i have: (nts top is my tabs) scrollable_layout.setDraggableView(nts_top)

    is correct? And how to collpse on click

    opened by xellDart 2
  • Disable all logs

    Disable all logs

    how to disable all logs D/InputEventConsistencyVerifier: TouchEvent: ACTION_MOVE contained 1 pointers but there are currently 0 pointers down. in ru.noties.scrollable.ScrollableLayout@b552f9f0 0: sent at 8776594648936, MotionEvent { action=ACTION_MOVE, id[0]=0, x[0]=128.0, y[0]=97.18901, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=1, eventTime=8776594, downTime=8776490, deviceId=0, source=0x1002 } -- recent events -- 1: sent at 8776594648936, (unhandled) MotionEvent { action=ACTION_CANCEL, id[0]=0, x[0]=128.0, y[0]=97.18901, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=1, eventTime=8776594, downTime=8776490, deviceId=0, source=0x1002 } 2: sent at 8776577982270, MotionEvent { action=ACTION_CANCEL, id[0]=0, x[0]=128.0, y[0]=90.14987, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=1, eventTime=8776577, downTime=8776490, deviceId=0, source=0x1002 } 3: sent at 8776561315604, MotionEvent { action=ACTION_MOVE, id[0]=0, x[0]=128.0, y[0]=84.162964, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=1, eventTime=8776561, downTime=8776490, deviceId=0, source=0x1002 } 4: sent at 8776490710000, MotionEvent { action=ACTION_MOVE, id[0]=0, x[0]=128.0, y[0]=82.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=8776490, downTime=8776490, deviceId=0, source=0x1002 } 5: sent at 8776490065000, MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=127.0, y[0]=79.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=8776490, downTime=8776490, deviceId=0, source=0x1002 }

    opened by GauravP123 2
  • Closeup Logic

    Closeup Logic

    Hi,

    Is it possible to implement CloseUpAlgorithm such that the header doesn't expand and hide automatically? What I mean is, when the header is fully expanded, if I move my finger upwards till the header is half of its original height and release my finger, the header doesn't collapse but remains as it is.

    I'm very new to development with regards to touch events so I'd be grateful if you could point me in the right direction.

    Thanks!

    opened by squeeish 2
  • Cannot collapsed header view programmatically

    Cannot collapsed header view programmatically

    I want to hide the header view when I first open the application until I scroll down. However, it seems that there is no function that allows me to do it. Thanks for any help!

    opened by khanguyen-appvity 2
  • Header reappears on top when returning to fragment that has been scrolled down.

    Header reappears on top when returning to fragment that has been scrolled down.

    Currently, in my project, I have a fragment that has a scrollable layout that hosts a header image and a recycler view. When scrolling down and hitting a button at the bottom of the recycler view a new fragment is added to the stack. When returning to the original fragment that has the scrollable layout the header appears at the top even though the recycler view is still scrolled to the bottom. What would be the best way to retain the headers state so when returning to the fragment the header is not shown if the recycler view is currently scrolled all the way down?

    bug 
    opened by stshelton 8
Releases(v1.3.0)
Owner
Dimitry
Small bits and big bytes
Dimitry
Bubbles for Android is an Android library to provide chat heads capabilities on your apps. With a fast way to integrate with your development.

Bubbles for Android Bubbles for Android is an Android library to provide chat heads capabilities on your apps. With a fast way to integrate with your

Txus Ballesteros 1.5k Jan 2, 2023
Android library used to create an awesome Android UI based on a draggable element similar to the last YouTube New graphic component.

Please switch to DragView, for the best support, thank you DraggablePanel Download allprojects { repositories { ... maven { url 'https://jitp

Hoàng Anh Tuấn 103 Oct 12, 2022
FixedHeaderTableLayout is a powerful Android library for displaying complex data structures and rendering tabular data composed of rows, columns and cells with scrolling and zooming features. FixedHeaderTableLayout is similar in construction and use as to Android's TableLayout

FixedHeaderTableLayout is a powerful Android library for displaying complex data structures and rendering tabular data composed of rows, columns and cells with scrolling and zooming features. FixedHeaderTableLayout is similar in construction and use as to Android's TableLayout

null 33 Dec 8, 2022
A wave view of android,can be used as progress bar.

WaveView ![Gitter](https://badges.gitter.im/Join Chat.svg) A wave view of android,can be used as progress bar. Screenshot APK demo.apk What can be use

Kai Wang 1.3k Dec 28, 2022
An Android Layout which has a same function like https://github.com/romaonthego/RESideMenu

ResideLayout An Android Layout which has a same function like https://github.com/romaonthego/RESideMenu. Can be used on Android 1.6(I haven't try it.)

Yang Hui 392 Oct 12, 2022
An Android library that help you to build app with swipe back gesture.

SwipeBackLayout An Android library that help you to build app with swipe back gesture. Demo Apk GooglePlay Requirement The latest android-support-v4.j

ike_w0ng 6.1k Dec 29, 2022
TileView is a subclass of android.view.ViewGroup that asynchronously displays, pans and zooms tile-based images. Plugins are available for features like markers, hotspots, and path drawing.

This project isn't maintained anymore. It is now recommended to use https://github.com/peterLaurence/MapView. MapView is maintained by Peter, one of o

Mike Dunn 1.5k Nov 21, 2022
Ultra Pull to Refresh for Android. Support all the views.

Welcome to follow me on GitHub or Twitter GitHub: https://github.com/liaohuqiu Twitter: https://twitter.com/liaohuqiu 中文版文档 Wanna auto-load-more? This

Huqiu Liao 9.6k Jan 5, 2023
SwipeBack is an android library that can finish a activity by using gesture.

SwipeBack SwipeBack is a android library that can finish a activity by using gesture. You can set the swipe direction,such as left,top,right and botto

Eric 1.7k Nov 21, 2022
A very simple arc layout library for Android

ArcLayout A very simple arc layout library for Android. Try out the sample application on the Play Store. Usage (For a working implementation of this

ogaclejapan 1.4k Dec 26, 2022
Android layout that simulates physics using JBox2D

PhysicsLayout Android layout that simulates physics using JBox2D. Simply add views, enable physics, and watch them fall! See it in action with the sam

John Carlson 689 Dec 29, 2022
Android component which presents a dismissible view from the bottom of the screen

BottomSheet BottomSheet is an Android component which presents a dismissible view from the bottom of the screen. BottomSheet can be a useful replaceme

Flipboard 4.5k Dec 28, 2022
This library provides a simple way to add a draggable sliding up panel (popularized by Google Music and Google Maps) to your Android application. Brought to you by Umano.

Note: we are not actively responding to issues right now. If you find a bug, please submit a PR. Android Sliding Up Panel This library provides a simp

Umano: News Read To You 9.4k Dec 31, 2022
Allows the easy creation of animated transition effects when the state of Android UI has changed

android-transition Android-Transition allows the easy creation of view transitions that reacts to user inputs. The library is designed to be general e

Kai 615 Nov 14, 2022
Stale Android Toasts made tasty.

FrenchToast Stale Android Toasts made tasty. Android Toasts are amazing, but they have a few major drawbacks: You cannot control when they show up as

py - Pierre Yves Ricau 367 Nov 15, 2022
waveview for android

中文介绍 WaveView A view to display wave effect. Screenshot Integration implementation 'com.gelitenight.waveview:waveview:1.0.0' Setter methods: setWaveS

NIGHT 1.6k Dec 21, 2022
An Android demo of a foldable layout implementation. Engineered by Vincent Brison.

Foldable Layout This code is a showcase of a foldable animation I created for Worldline. The code is fully written with java APIs from the Android SDK

Worldline 599 Dec 23, 2022
Flexbox for Android

FlexboxLayout FlexboxLayout is a library project which brings the similar capabilities of CSS Flexible Box Layout Module to Android. Installation Add

Google 17.7k Jan 2, 2023
A floating menu library for Android.

Hover Hover is a floating menu implementation for Android. Goals The goals of Hover are to: Provide an easy-to-use, out-of-the-box floating menu imple

Google 2.7k Dec 27, 2022