10

I've just started writing a custom view in Android (for the first time) and I've realised that I need to implement a scrolling feature.

The custom view also uses a header containing some text (which should stay fixed and not scroll).

I've read through the documentation on GestureDetector.SimpleOnGestureListener and Scroller. I also read the documentation on Animating a Scroll Gesture but I found the examples difficult to understand. I've also looked at other questions on Stack Overflow which helped a little.

Using what I understood from the documentation with the Stack Overflow answer as a reference to guide me, I have added the following to my custom view:

Variables and fields:

private OverScroller mScroller;
private final GestureDetector mGestureDetector =
        new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                    float distanceX, float distanceY) {
                // Note 0 as the x-distance to prevent horizontal scrolling
                scrollBy(0, (int) distanceY);
                return true;
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2,
                                   float velocityX, float velocityY) {
                final int maxScrollX = 0;

                // wholeViewHeight is height of everything that is drawn
                int wholeViewHeight =  calculateWholeHeight(); 
                int visibleHeight = getHeight();
                final int maxScrollY = wholeViewHeight - visibleHeight;

                mScroller.forceFinished(true);

                mScroller.fling(0, // No startX as there is no horizontal scrolling
                        getScrollY(),
                        0, // No velocityX as there is no horizontal scrolling
                        - (int) velocityY,
                        0,
                        maxScrollX,
                        0,
                        maxScrollY);

                invalidate();

                return true;
            }

            @Override
            public boolean onDown(MotionEvent e) {
                if (!mScroller.isFinished()) {
                    mScroller.forceFinished(true);
                }
                return true;
            }
        });

Initialization of mScroller:

// Called from the constructor
private void init() {  
    mScroller = new OverScroller(getContext(), new FastOutLinearInInterpolator());

    ...
}

Stuff in onDraw():

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    ...

    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
    }
}

Stuff in onTouchEvent():

@Override
public boolean onTouchEvent(MotionEvent event) {
    return mGestureDetector.onTouchEvent(event);
}

The result of these additions is a custom view which can scroll vertically (and not horizontally), however there are a few issues:

  1. I can scroll further past what is drawn
  2. There is no edge glow effect as I reach the end of the custom view (I mean like a RecyclerView or ScrollView)
  3. All of the custom view scrolls as opposed to just a certain part of it
  4. I don't fully understand what is going on

Could someone explain how scrolling works in a custom view and how to implement it properly with these features?

Community
  • 1
  • 1
Farbod Salamat-Zadeh
  • 19,687
  • 20
  • 75
  • 125

1 Answers1

5

May I offer a simple layout that has a fixed header and vertically scrolling content that sounds like it will do what you want and does not require complex programming? It may save you from having to spend hours of research and programming in the long run:

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

    <FrameLayout
        android:id="@+id/header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:fillViewport="true">

        <FrameLayout
            android:id="@+id/scrolling_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </android.support.v4.widget.NestedScrollView>

</LinearLayout>

Using this layout, you would replace or insert inside the FrameLayout with ID "header" with your fixed header view. You would then put your scolling content inside of the FrameLayout with ID "scrolling_content". The NestedScrollingView would be located directly below your fixed header and automatically give you the scrolling behavior you want without any further code.

happydude
  • 3,869
  • 2
  • 23
  • 41
  • Thanks for your answer. Would I be able to replace the second `FrameLayout` with part of my custom view to enable it to scroll? – Farbod Salamat-Zadeh Sep 04 '16 at 18:18
  • Yes, but remember NestedScrollView can only have one direct child. As long as you don't try to put 2 views directly into NestedScrollView it will work fine. Also remember that any views inside a vertically scrolling NestedScrollView cannot have a layout_height of "match_parent", as if this value were honored it would create an infinitely long scroll. – happydude Sep 04 '16 at 18:37
  • Thanks for the clarification. I'll try using a `NestedScrollView` and see how that works for me. However, I would still like to know how it can be implemented (using `GestureDetector`, `OverScroller`, etc.) for a custom View. I've started a bounty for this, so if you have an alternative answer, do post it. :) – Farbod Salamat-Zadeh Sep 05 '16 at 21:51
  • Thanks, will see if I come up with anything. – happydude Sep 06 '16 at 01:29