简体   繁体   中英

How to overscroll a custom view (surfaceview)

I have a custom SurfaceView which is managed by a worker thread. I am using code very similar to the code in the following blog post to manage the SurfaceView:

http://android-coding.blogspot.com/2011/05/drawing-on-surfaceview.html

My custom SurfaceView is scrollable in the sense that I listen for touch events, send them to a gesture detector, and implement onScroll. I keep track of the scroll distances in some member variables (both x-axis and y-axis) and translate any coordinates by the appropriate amounts when drawing to the canvas. I also clamp the scroll distances and can easily calculate and store any overscroll amounts when clamping.

This all works fine.

The problem is that I want to show the standard Android overscroll effects on my custom SurfaceView. I tried calling overScrollBy manually but it didn't work and my best guess is because I am drawing the view from a worker thread which means the view's onDraw is never called.

I found the following stackoverflow post about customizing the overscroll effects:

How can I change the OverScroll color in Android 2.3.1?

That post is not intended for SurfaceViews but I could probably adapt the code. That said, is there a better way? I want to show the exact same overscroll effects that are shown elsewhere. Making copies of the overscroll drawables and attempting to duplicate the overscroll logic seems... ugly.

Well, i managed to put together a simple example of overscroll in simple View by using OverScroller:

package net.project.experimental;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.OverScroller;

public class WorksheetView extends View
{
    protected static final int  OVERSCROLL_DISTANCE = 10;
    protected static final int  INVALID_POINTER_ID  = -1;

    private int                 fWorksheetWidth     = 2000;
    private int                 fWorksheetHeight    = 2000;

    private OverScroller        fScroller;
    private VelocityTracker     fVelocityTracker    = null;
    private int                 fMinimumVelocity;

    // The ‘active pointer’ is the one currently moving our object.
    private int                 fTranslatePointerId = INVALID_POINTER_ID;
    private PointF              fTranslateLastTouch = new PointF( );

    private boolean             fInteracting        = false;

    public WorksheetView(Context context, AttributeSet attrs)
    {
        super( context, attrs );
        this.initView( context, attrs );
    }

    public WorksheetView(Context context, AttributeSet attrs, int defStyle)
    {
        super( context, attrs, defStyle );
        this.initView( context, attrs );
    }

    protected void initView(Context context, AttributeSet attrs)
    {
        fScroller = new OverScroller( this.getContext( ) );

        this.setOverScrollMode( OVER_SCROLL_ALWAYS );

        final ViewConfiguration configuration = ViewConfiguration.get( getContext( ) );
        //fTouchSlop = configuration.getScaledTouchSlop( );
        fMinimumVelocity = configuration.getScaledMinimumFlingVelocity( );
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        if ( fVelocityTracker == null )
        {
            fVelocityTracker = VelocityTracker.obtain( );
        }
        fVelocityTracker.addMovement( event );

        final int action = event.getAction( );
        switch ( action & MotionEvent.ACTION_MASK )
        {
            case MotionEvent.ACTION_DOWN:
            {
                if ( !fScroller.isFinished( ) )
                    fScroller.abortAnimation( );

                final float x = event.getX( );
                final float y = event.getY( );

                fTranslateLastTouch.set( x, y );
                fTranslatePointerId = event.getPointerId( 0 );
                this.startInteracting( );
                break;
            }

            case MotionEvent.ACTION_MOVE:
            {
                final int pointerIndexTranslate = event.findPointerIndex( fTranslatePointerId );
                if ( pointerIndexTranslate >= 0 )
                {
                    float translateX = event.getX( pointerIndexTranslate );
                    float translateY = event.getY( pointerIndexTranslate );

                    this.overScrollBy(
                            (int) (fTranslateLastTouch.x - translateX),
                            (int) (fTranslateLastTouch.y - translateY),
                            this.getScrollX( ),
                            this.getScrollY( ),
                            fWorksheetWidth - this.getWidth( ),
                            fWorksheetHeight - this.getHeight( ),
                            OVERSCROLL_DISTANCE,
                            OVERSCROLL_DISTANCE,
                            true );

                    fTranslateLastTouch.set( translateX, translateY );

                    this.invalidate( );
                }

                break;
            }

            case MotionEvent.ACTION_UP:
            {
                final VelocityTracker velocityTracker = fVelocityTracker;
                velocityTracker.computeCurrentVelocity( 1000 );
                //velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialXVelocity = (int) velocityTracker.getXVelocity( );
                int initialYVelocity = (int) velocityTracker.getYVelocity( );

                if ( (Math.abs( initialXVelocity ) + Math.abs( initialYVelocity ) > fMinimumVelocity) )
                {
                    this.fling( -initialXVelocity, -initialYVelocity );
                }
                else
                {
                    if ( fScroller.springBack( this.getScrollX( ), this.getScrollY( ), 0, fWorksheetWidth - this.getWidth( ), 0, fWorksheetHeight - this.getHeight( ) ) )
                        this.invalidate( );

                    this.stopInteracting( );
                }

                if ( fVelocityTracker != null )
                {
                    fVelocityTracker.recycle( );
                    fVelocityTracker = null;
                }


                fTranslatePointerId = INVALID_POINTER_ID;
                break;
            }

            case MotionEvent.ACTION_POINTER_DOWN:
            {
                break;
            }

            case MotionEvent.ACTION_POINTER_UP:
            {
                final int pointerIndex = (event.getAction( ) & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final int pointerId = event.getPointerId( pointerIndex );
                if ( pointerId == fTranslatePointerId )
                {
                    // This was our active pointer going up. Choose a new
                    // active pointer and adjust accordingly.
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    fTranslateLastTouch.set( event.getX( newPointerIndex ), event.getY( newPointerIndex ) );
                    fTranslatePointerId = event.getPointerId( newPointerIndex );
                }

                break;
            }

            case MotionEvent.ACTION_CANCEL:
            {
                if ( fScroller.springBack( this.getScrollX( ), this.getScrollY( ), 0, fWorksheetWidth - this.getWidth( ), 0, fWorksheetHeight - this.getHeight( ) ) )
                    this.invalidate( );

                fTranslatePointerId = INVALID_POINTER_ID;
                break;
            }
        }

        return true;
    }

    private void fling(int velocityX, int velocityY)
    {
        int x = this.getScrollX( );
        int y = this.getScrollY( );

        this.startInteracting( );
        //fScroller.setFriction( ViewConfiguration.getScrollFriction( ) );
        fScroller.fling( x, y, velocityX, velocityY, 0, fWorksheetWidth - this.getWidth( ), 0, fWorksheetHeight - this.getHeight( ) );

        this.invalidate( );
    }

    private void startInteracting()
    {
        fInteracting = true;
    }

    private void stopInteracting()
    {
        fInteracting = false;
    }

    @Override
    public void computeScroll()
    {
        if ( fScroller != null && fScroller.computeScrollOffset( ) )
        {
            int oldX = this.getScrollX( );
            int oldY = this.getScrollY( );
            int x = fScroller.getCurrX( );
            int y = fScroller.getCurrY( );

            if ( oldX != x || oldY != y )
            {
                this.overScrollBy(
                        x - oldX,
                        y - oldY,
                        oldX,
                        oldY,
                        fWorksheetWidth - this.getWidth( ),
                        fWorksheetHeight - this.getHeight( ),
                        OVERSCROLL_DISTANCE,
                        OVERSCROLL_DISTANCE,
                        false );
            }

            if ( fScroller.isFinished( ) )
                this.stopInteracting( );

            this.postInvalidate( );
        }
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)
    {
        // Treat animating scrolls differently; see #computeScroll() for why.
        if ( !fScroller.isFinished( ) )
        {
            super.scrollTo( scrollX, scrollY );

            if ( clampedX || clampedY )
            {
                fScroller.springBack( this.getScrollX( ), this.getScrollY( ), 0, fWorksheetWidth - this.getWidth( ), 0, fWorksheetHeight - this.getHeight( ) );
            }
        }
        else
        {
            super.scrollTo( scrollX, scrollY );
        }
        awakenScrollBars( );
    }

    @Override
    protected int computeHorizontalScrollExtent()
    {
        return this.getWidth( );
    }

    @Override
    protected int computeHorizontalScrollRange()
    {
        return fWorksheetWidth;
    }

    @Override
    protected int computeHorizontalScrollOffset()
    {
        return this.getScrollX( );
    }

    @Override
    protected int computeVerticalScrollExtent()
    {
        return this.getHeight( );
    }

    @Override
    protected int computeVerticalScrollRange()
    {
        return fWorksheetHeight;
    }

    @Override
    protected int computeVerticalScrollOffset()
    {
        return this.getScrollY( );
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        canvas.drawColor( Color.BLACK );

        Paint paint = new Paint( );

        if ( fInteracting )
            ;

        paint.setColor( Color.WHITE );
        canvas.drawRect( 0, 0, fWorksheetWidth, fWorksheetHeight, paint );

        paint.setColor( Color.RED );
        for (int i = 0; i < 1500; i += 10)
        {
            canvas.drawLine( i, 0, i + 100, 500, paint );
        }

        canvas.drawRect( fWorksheetWidth - 50, 0, fWorksheetWidth, fWorksheetHeight, paint );
        canvas.drawRect( 0, fWorksheetHeight - 50, fWorksheetWidth, fWorksheetHeight, paint );
    }
}

Ok, to really answer the question of Android style overscroll. You can go a step further than my last answer (the code) After using OverScroller to implement overscrolling through drawing, you can restrict the page movement, so the page wont overscroll. Instead, you can draw a resource for the Android style overscroll -- and that's what you call 'manual'.

See the sample below, which is in addition to the code I posted yesterday. Now, the 'onDraw' will not let the page visually overscroll. Then 'displatchDraw' will draw a drawable resource to represent the Android style overscroll.

Note, i'm a memory freak, so I use the same resource for all four corners and rotate it manually through matrices when rendering on the canvas.

    private void compensateForOverscroll(Canvas canvas)
    {
        int x = this.getScrollX( );
        int y = this.getScrollY( );

        Matrix matrix = canvas.getMatrix( );

        int maxX = fWorksheetWidth - this.getWidth( );
        int maxY = fWorksheetHeight - this.getHeight( );

        if ( x < 0 || x > maxX || y < 0 || y > maxY )
        {
            if ( x < 0 )
                matrix.postTranslate( x, 0 );
            else if ( x > maxX )
                matrix.postTranslate( (x - maxX), 0 );

            if ( y < 0 )
                matrix.postTranslate( 0, y );
            else if ( y > maxY )
                matrix.postTranslate( 0, (y - maxY) );

            canvas.setMatrix( matrix );
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas)
    {
        int width = this.getWidth( );
        int height = this.getHeight( );
        int maxX = fWorksheetWidth - width;
        int maxY = fWorksheetHeight - height;

        int x = this.getScrollX( );
        int y = this.getScrollY( );


        if ( x < 0 || x > maxX )
        {
            canvas.save( );
            Matrix canvasMatrix = canvas.getMatrix( );

            if ( x < 0 )
            {
                fOverScrollDrawable.setBounds( 0, x, height, x - x );
                canvasMatrix.preRotate( -90 );
                canvasMatrix.preTranslate( - y - height, 0 );
            }
            else if ( x > maxX )
            {
                fOverScrollDrawable.setBounds( 0, maxX, height, x );
                canvasMatrix.preRotate( 90 );
                canvasMatrix.preTranslate( y, - x - fWorksheetWidth );
            }

            canvas.setMatrix( canvasMatrix );
            fOverScrollDrawable.draw( canvas );

            canvas.restore( );
        }

        if ( y < 0 || y > maxY )
        {
            canvas.save( );
            Matrix canvasMatrix = canvas.getMatrix( );

            if ( y < 0 )
            {
                fOverScrollDrawable.setBounds( x, y, x + width, y - y );
            }
            else if ( y > maxY )
            {
                fOverScrollDrawable.setBounds( 0, maxY, width, y );
                canvasMatrix.preRotate( 180 );
                canvasMatrix.preTranslate( - x - width, - y - fWorksheetHeight );
            }

            canvas.setMatrix( canvasMatrix );
            fOverScrollDrawable.draw( canvas );

            canvas.restore( );
        }
    }

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

        this.compensateForOverscroll( canvas );

        canvas.drawColor( Color.BLACK );        

        Paint paint = new Paint( );

        if ( fInteracting )
            ;

        paint.setColor( Color.WHITE );
        canvas.drawRect( 0, 0, fWorksheetWidth, fWorksheetHeight, paint );

        paint.setColor( Color.RED );
        for (int i = 0; i < 1500; i += 10)
        {
            canvas.drawLine( i, 0, i + 100, 500, paint );
        }

        canvas.drawRect( fWorksheetWidth - 50, 0, fWorksheetWidth, fWorksheetHeight, paint );
        canvas.drawRect( 0, fWorksheetHeight - 50, fWorksheetWidth, fWorksheetHeight, paint );

        canvas.restore( );
    }

The drawable is initialized like that:

Drawable fOverScrollDrawable;

...

Resources rsr = context.getResources( );
fOverScrollDrawable = rsr.getDrawable( R.drawable.overscroll_glow );

The image resource for the overscroll glow, i've taken two images in the sdk and combined them into a single drawable resource:

  • \\android-sdk\\platforms\\android-11\\data\\res\\drawable-hdpi\\overscroll_edge.png
  • \\android-sdk\\platforms\\android-11\\data\\res\\drawable-hdpi\\overscroll_glow.png

Well, i have the same exactly problem.

I looked at the implementation of the android view, and inside 'onOverScrolled' there is only the comment "Intentionally empty."

Looking at the ListView implementation, I can see that they've implemented it manually by getting the drawables 'com.android.internal.R.styleable.ListView_overScrollHeader' and 'com.android.internal.R.styleable.ListView_overScrollFooter' which cannot be used from outside. Unfortunately, I was not able to find those resources in the sources.

as it seems we'll have to do overscroll manually...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM