自定义阻尼下拉回弹布局

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的源代码,并不复杂。其它的参看源码吧。

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( );

    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 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" 时就可以实现隐藏头部下拉可见的效果了。

自定义阻尼下拉回弹布局

时间: 2024-07-29 09:56:39

自定义阻尼下拉回弹布局的相关文章

Android UI设计之&lt;十&gt;自定义ListView,实现QQ空间阻尼下拉刷新和渐变菜单栏效果

转载请注明出处:http://blog.csdn.net/llew2011/article/details/51559694 好久没有写有关UI的博客了,刚刚翻了一下之前的博客,最近一篇有关UI的博客是在2014年写的:Android UI设计之<七>自定义Dialog,实现各种风格效果的对话框,在那篇博客写完后由于公司封闭开发封网以及其它原因致使博客中断至今,中断这么久很是惭愧,后续我会尽量把该写的都补充出来.近来项目有个需求,要做个和QQ空间类似的菜单栏透明度渐变和下拉刷新带有阻尼回弹的效

Android自定义ListView下拉刷新

实现的目标是本地有数据并没有刷新.下拉数据及时刷新数据. 我在网上找了某位写的MyListView,这个东西的下拉核心部分还是没有弄明白.非常感谢这位作者. XML布局文件源代码: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layou

自定义SWT控件一之自定义单选下拉框

一.自定义下拉控件 自定义的下拉框,是自定义样式的,其中的下拉框使用的是独立的window,非复选框的下拉框双击单机其它区域或选择完之后,独立window构成的下拉框会自动消失. package com.view.control.select; import java.util.ArrayList; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite;

angular 实现自定义样式下拉菜单

自己闲着没事写了一个自定义的下拉菜单希望和大家交流一下!望能和大神们成为朋友. 下面上代码: <!doctype html> <html lang="en" ng-app='App'> <head> <meta charset="UTF-8"> <title>Document</title> <script src='../angular-1.3.9/angular.js'><

[k]自定义样式下拉菜单

自定义样式下拉菜单-1 1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title> 自定义样式下拉菜单1 </title> 6 <script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script> 7 <s

CSS自定义select下拉选择框的样式(不用其他标签模拟)

CSS自定义select下拉选择框的样式(不用其他标签模拟):http://www.jb51.net/css/148841.html CSS美化选择框:http://www.cnblogs.com/shishm/archive/2012/03/02/2376759.html 1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5

完美解决safari、微信浏览器下拉回弹效果。

完美解决safari.微信浏览器下拉回弹效果,只保留局部回弹效果. CSS代码 .box{ overflow: auto; -webkit-overflow-scrolling: touch; } HTML代码 <body class="box"> <div class="scroll" style="height:1500px"> </div> </body> JS代码 var overscrol

android之超级简单的下拉回弹--仿QQ个人主页

先看效果: 效果不错吧! 进入主题之前,先了解ImageView的scaleType的center_crop,网络上说的已经很清楚了 : 以下抄自网络: android:scaleType="centerCrop" 以填满整个ImageView为目的,将原图的中心对准ImageView的中心,等比例放大原图,直到填满ImageView为止(指的是ImageView的宽和高都要填满),原图超过ImageView的部分作裁剪处理. 均衡的缩放图像(保持图像原始比例),使图片的两个坐标(宽.

使用谷歌提供的SwipeRefreshLayout下拉控件,并自定义实现下拉加载的功能

package com.loaderman.swiperefreshdemo; import android.os.Bundle; import android.os.Handler; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.view.Gravity; import android.view.View;