自定義阻尼下拉回彈布局


overscroll功能真正的實現分別在ScrollView、AbsListView、HorizontalScrollView和WebView中各有一份。ScrollView實現阻尼回彈,但是是FrameLayout布局,有些場合不適用。listview和webview適用范圍也很有限。接下來,我們自定義一個LinearLayout的布局,帶有回彈效果。

   首先用到了OverScroller類,這個類相當於一個控制器。比如調用它的方法springBack( this.getScrollX( ), this.getScrollY( ), 0, 0, 0, 0)時,只要告訴當前的頁面偏移和頁面的目標偏移后,它會自動計算在回彈過程中每一個時間點的位置。它要配合computeScroll方法使用。為了易於控制滑屏過程,Android框架提供了 computeScroll()方法去控制這個流程。在繪制View時,會在draw()過程調用該方法。因此, 再配合使用Scroller實例,我們就可以獲得當前應該的偏移坐標,手動使View/ViewGroup偏移至該處。

  還有一個函數onOverScrolled,被overScrollBy(int, int, int, int, int, int, int, int, boolean)調用,來對一個over-scroll操作的結果進行響應。參見overScrollBy的源代碼,並不復雜。其它的參看源碼吧。

 

package com.zte.allowance.views;

import android.content.Context;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import android.widget.OverScroller;

public class CustomScrollView extends LinearLayout
{
    public static final int  OVERSCROLL_DISTANCE = 50;
    protected static final int  INVALID_POINTER_ID  = -1;

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

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

    public CustomScrollView(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 );
    }

    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) 
    {
        int action = ev.getAction();
        switch ( action & MotionEvent.ACTION_MASK )
        {
            case MotionEvent.ACTION_MOVE:
            {
                final float translateX = ev.getX( );
                final float translateY = ev.getY( );
           
                //距離小於5認為是單擊事件,傳遞給子控件
                if((firstX - translateX < -5) || (firstX - translateX > 5) ||
                    (firstY - translateY < -5) || (firstY - translateY > 5))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            case MotionEvent.ACTION_DOWN:
            {

                if ( !fScroller.isFinished( ) )
                    fScroller.abortAnimation( );

                
                final float x = ev.getX( );
                final float y = ev.getY( );
                firstX = x;
                firstY = y;
                fTranslateLastTouch.set( x, y );
                
                //記錄第一個手指按下時的ID
                fTranslatePointerId = ev.getPointerId( 0 );
            
                return false;
            }
            default:
            {
                return false;
            }
        }
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent 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 );
                
                //記錄第一個手指按下時的ID
                fTranslatePointerId = event.getPointerId( 0 );
                break;
            }

            case MotionEvent.ACTION_MOVE:
            {
                /**
                 * 取第一個觸摸點的位置
                 */
                final int pointerIndexTranslate = event.findPointerIndex( fTranslatePointerId );
                if ( pointerIndexTranslate >= 0 )
                {
                    float translateX = event.getX( pointerIndexTranslate );
                    float translateY = event.getY( pointerIndexTranslate );

                    //Log.i("com.zte.allowance", "fTranslatePointerId = " + fTranslatePointerId);
                    /**
                     * deltaX 將要在X軸方向上移動距離
                     * scrollX 滾動deltaX之前,x軸方向上的偏移
                     * scrollRangeX 在X軸方向上最多能滾動的距離
                     * maxOverScrollX 在x軸方向上,滾動到邊界時,還能超出的滾動距離
                     */
                    //Log.i("com.zte.allowance", "delta y = " + (fTranslateLastTouch.y - translateY));
                    this.overScrollBy(
                            (int) (fTranslateLastTouch.x - translateX),
                            (int) (fTranslateLastTouch.y - translateY)/4,
                            this.getScrollX( ),
                            this.getScrollY( ),
                            0,
                            0,
                            0,
                            OVERSCROLL_DISTANCE,
                            true );

                    fTranslateLastTouch.set( translateX, translateY );

                    this.invalidate( );
                }

                break;
            }

            case MotionEvent.ACTION_UP:
            {
                /**
                 * startX 回滾開始時x軸上的偏移
                 * minX 和maxX 當前位置startX在minX和manX之 間時就不再回滾  
                 * 
                 * 此配置表示X和Y上的偏移都必須復位到0
                 */
                if (fScroller.springBack( this.getScrollX( ), this.getScrollY( ), 0, 0, 0, 0))
                    this.invalidate( );

                fTranslatePointerId = INVALID_POINTER_ID;
                break;
            }
        }

        return true;
    }

    @Override
    public void computeScroll()
    {
        if ( fScroller != null && fScroller.computeScrollOffset( ) )
        {
            int oldX = this.getScrollX( );
            int oldY = this.getScrollY( );
            
            /**
             * 根據動畫開始及持續時間計算出當前時間下,view的X.Y方向上的偏移量
             * 參見OverScroller computeScrollOffset 的SCROLL_MODE
             */
            int x = fScroller.getCurrX( );
            int y = fScroller.getCurrY( );

            if ( oldX != x || oldY != y )
            {
                //Log.i("com.zte.allowance", oldY + "  " + y);
                this.overScrollBy(
                        x - oldX,
                        (y - oldY),
                        oldX,
                        oldY,
                        0,
                        0,
                        0,
                        OVERSCROLL_DISTANCE,
                        false );
            }

            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, 0, 0, 0);
            }
        }
        else
        {
            super.scrollTo( scrollX, scrollY );
        }
        awakenScrollBars( );
    }

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

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

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


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


}

 

當然,不僅僅可以是LinearLayout,還可以是別的布局。

layout文件如下:

<CustomScrollView
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/scroll_view"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:orientation="vertical"
      >
        <LinearLayout
            android:id="@+id/message_text"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_marginTop="-50px"
            android:background="@drawable/title_leaf"
            android:orientation="vertical">
            
            
        </LinearLayout>

    
    <RelativeLayout
      android:id="@+id/content_id"
      android:layout_width="match_parent"
      android:layout_height="fill_parent"
      android:background="@color/window_bg"
      android:padding="5dp">
        <Button 
            android:layout_width = "wrap_content"
            android:layout_height = "wrap_content"/>
    </RelativeLayout>
</CustomScrollView>

當把第一個textview設置成

android:layout_marginTop="-50px" 時就可以實現隱藏頭部下拉可見的效果了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM