time

Working with the ScrollView in Android

Introduction

In this tutorial, you will learn to use Android ScrollView component in application. ScrollView is a different kind of layout than LinearLayout, RelativeLayout, etc. which is designed to view larger than its actual size. If the views inside ScrollView are more than the views that can be hold/contained within ScrollView then a scrollbar is automatically added.

ScrollView can hold only one direct child. This means that, if you have complex layout with more view controls, you must enclose them inside another standard layout like LinearLayout, TableLayout or RelativeLayout.

ScrollView only supports vertical scrolling. Use HorizontalScrollView for horizontal scrolling.

The android:fillViewport property defines whether the ScrollView should stretch its content to fill the viewport. You can set the same property by calling setFillViewport(boolean) method.

After creating the project, open the activity_main.xml file of your application from res/layout directory and replace the content with the content given below.

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="200dp"
            android:scaleType="centerCrop"
            android:src="@drawable/image" />        

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:padding="10dp"
            android:text="Android ScrollView Example"
            android:textStyle="bold" />    

    </LinearLayout>
</ScrollView>

Custom ScrollView

In android ScrollView there is no way or method to disable scrolling. But sometime it's necessary to disable scrolling of ScrollView while you are inside inner element, like if you want to disable ScrollView when you are inside Map view.

We can make our own ScrollView which have extra features for enable or disable scrolling.

public class ToggleScrollView extends ScrollView {
    private boolean enableScrolling = true;

    public ToggleScrollView(Context context) {
        super(context);
    }

    public ToggleScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ToggleScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public ToggleScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (scrollingEnabled()) {
            return super.onInterceptTouchEvent(ev);
        } else {
            return false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (scrollingEnabled()) {
            return super.onTouchEvent(ev);
        } else {
            return false;
        }
    }
    private boolean scrollingEnabled(){
        return enableScrolling;
    }
    public void setScrolling(boolean enableScrolling) {
        this.enableScrolling = enableScrolling;
    }
}

Also create a layout with new ToggleScrollView.

<me.proft.projectA.ToggleScrollView
    android:id="@+id/svToggle"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- child views in here -->
</me.proft.projectA.ToggleScrollView>

So now you have embedded your own ScrollView. To disable scrolling just call setScrolling() method and pass true value. As shown in following example:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ToggleScrollView svToggle = (ToggleScrollView) findViewById(R.id.svToggle);
        svToggle.setScrolling(false); // to disable scrolling
        svToggle.setScrolling(true); // to enable scrolling
    }
}

Horizontally Scrolling

In other cases, we want content to horizontally scroll in which case we need to use the HorizontalScrollView instead like this:

<HorizontalScrollView
  android:layout_width="match_parent"
  android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >   
        <!-- child views in here -->
    </LinearLayout>
</HorizontalScrollView>

and now you have a horizontally scrolling view.

Scrollable TextView

Note that a TextView doesn't require a ScrollView and if you just need a scrolling TextView simply set the scrollbars property and apply the correct MovementMethod:

<TextView
    android:id="@+id/tv_long"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:scrollbars = "vertical"
    android:text="@string/really_long_string">
</TextView>

and then in the activity:

TextView tvTitle = (TextView) findViewById(R.id.tvTitle);
tvTitle.setMovementMethod(new ScrollingMovementMethod());

Now the TextView will automatically scroll vertically.

Nested ScrollViews

Adding a ScrollView within another ScrollView can be difficult. Most of the times it won’t end well. You will end up adding few workarounds. Instead, use the NestedScrollView as outlined here.

How to detect when ScrollView stops scrolling and do some unscroll

public class MainActivity extends AppCompatActivity {
    NestedScrollView scrollView;
    Button btn1, btn2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ...
        scrollView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {

                switch (motionEvent.getAction()) {
                    case MotionEvent.ACTION_UP:
                        int scrolled = scrollView.getScrollY();
                        final int maxScroll = scrollView.getChildAt(0).getMeasuredHeight() - scrollView.getHeight();
                        int btn1Height = btn1.getHeight();

                        if (isIntersect(btn2, btn1) || (scrolled >= (maxScroll - btn1Height))) {
                            Handler handler = new Handler();
                            handler.postDelayed(new Runnable() {
                                public void run() {
                                    scrollView.smoothScrollTo(0, maxScroll);
                                }
                            }, 200);
                        }

                        break;
                }

                return false;
            }
        });        
    }

    public boolean isIntersect(View firstView, View secondView) {
        int[] firstPosition = new int[2];
        int[] secondPosition = new int[2];

        firstView.getLocationOnScreen(firstPosition);
        secondView.getLocationOnScreen(secondPosition);

        Rect rectFirstView = new Rect(firstPosition[0], firstPosition[1],
                firstPosition[0] + firstView.getMeasuredWidth(), firstPosition[1] + firstView.getMeasuredHeight());

        Rect rectSecondView = new Rect(secondPosition[0], secondPosition[1],
                secondPosition[0] + secondView.getMeasuredWidth(), secondPosition[1] + secondView.getMeasuredHeight());

        return rectFirstView.intersect(rectSecondView);
    }

}

How to programmatically scroll to end of screen

There are two possible solutions with pros and cons.

First. Use the method fullScroll(int) on NestedScrollView. NestedScrollView must be drawn before using this method and the focus will be lost on the View that gained it before.

nestedScrollView.post(new Runnable() {
    @Override
    public void run() {
        nestedScrollView.fullScroll(View.FOCUS_DOWN);
    }
});

Second. Use the method scrollBy(int,int)/smoothScrollBy(int,int). It requires much more code, but you will not lose the current focus:

nestedScrollView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        final int scrollViewHeight = nestedScrollView.getHeight();
        if (scrollViewHeight > 0) {
            nestedScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            final View lastView = nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1);
            final int lastViewBottom = lastView.getBottom() + nestedScrollView.getPaddingBottom();
            final int deltaScrollY = lastViewBottom - scrollViewHeight - nestedScrollView.getScrollY();
            /* If you want to see the scroll animation, call this. */
            nestedScrollView.smoothScrollBy(0, deltaScrollY);
            /* If you don't want, call this. */
            nestedScrollView.scrollBy(0, deltaScrollY);
        }
    }
});