简体   繁体   English

如何过度滚动自定义视图(surfaceview)

[英]How to overscroll a custom view (surfaceview)

I have a custom SurfaceView which is managed by a worker thread. 我有一个由工作线程管理的自定义SurfaceView。 I am using code very similar to the code in the following blog post to manage the SurfaceView: 我使用的代码非常类似于以下博客文章中的代码来管理SurfaceView:

http://android-coding.blogspot.com/2011/05/drawing-on-surfaceview.html 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. 我的自定义SurfaceView是可滚动的,因为我会监听触摸事件,将它们发送到手势检测器,并实现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. 我会跟踪某些成员变量(x轴和y轴)中的滚动距离,并在绘制到画布时将任何坐标转换为适当的量。 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. 问题是我想在我的自定义SurfaceView上显示标准的Android过度滚动效果。 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. 我尝试手动调用overScrollBy但它没有工作,我最好的猜测是因为我从工作线程中绘制视图,这意味着视图的onDraw永远不会被调用。

I found the following stackoverflow post about customizing the overscroll effects: 我发现以下有关自定义过卷效果的stackoverflow帖子:

How can I change the OverScroll color in Android 2.3.1? 如何在Android 2.3.1中更改OverScroll颜色?

That post is not intended for SurfaceViews but I could probably adapt the code. 该帖子不适用于SurfaceViews,但我可能会调整代码。 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: 好吧,我设法通过使用OverScroller在简单的View中汇总了一个简单的过度滚动示例:

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. 好的,要真正回答Android风格过度滚动的问题。 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. 您可以比我的上一个答案(代码)更进一步使用OverScroller通过绘图实现过度滚动后,您可以限制页面移动,因此页面不会过度滚动。 Instead, you can draw a resource for the Android style overscroll -- and that's what you call 'manual'. 相反,您可以为Android风格的过度滚动绘制资源 - 这就是您所谓的'手动'。

See the sample below, which is in addition to the code I posted yesterday. 请参阅下面的示例,该示例是我昨天发布的代码的补充。 Now, the 'onDraw' will not let the page visually overscroll. 现在,'onDraw'不会让页面在视觉上过度滚动。 Then 'displatchDraw' will draw a drawable resource to represent the Android style overscroll. 然后'displatchDraw'将绘制一个可绘制的资源来表示Android风格的过度滚动。

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初始化如下:

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: 过度滚动的图像资源发光,我在sdk中拍摄了两张图像,并将它们组合成一个可绘制的资源:

  • \\android-sdk\\platforms\\android-11\\data\\res\\drawable-hdpi\\overscroll_edge.png \\ Android的SDK \\平台\\机器人-11 \\数据\\水库\\抽拉-HDPI \\ overscroll_edge.png
  • \\android-sdk\\platforms\\android-11\\data\\res\\drawable-hdpi\\overscroll_glow.png \\ Android的SDK \\平台\\机器人-11 \\数据\\水库\\抽拉-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." 我查看了android视图的实现,在'onOverScrolled'里面只有注释“故意为空”。

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. 看看ListView实现,我可以看到他们通过获取drawables的'com.android.internal.R.styleable.ListView_overScrollHeader'和'com.android.internal.R.styleable.ListView_overScrollFooter'来手动实现它。从外面使用。 Unfortunately, I was not able to find those resources in the sources. 不幸的是,我无法在消息来源中找到这些资源。

as it seems we'll have to do overscroll manually... 因为看起来我们必须手动进行过度滚动...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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