ScrollView的顶部下拉和底部上拉回弹效果

摘要:
/***ASimpleReboundScrollView*@authorDenluoyia*/publicclassReboundScroolViewextendsScrollView{privatebooleanmEnableTopRebound=true;privateboolean mEnableBottomRebound=true;

要实现ScrollView的回弹效果,需要对其进行触摸事件处理。先来看一下简单的效果:

ScrollView的顶部下拉和底部上拉回弹效果第1张

根据Android的View事件分发处理机制,下面对dispatchTouchEvent进行详细分析:

在加载布局完成之后,获取ScrollView的第一个子元素,保存它的参数,left top right bottom参数,根据顶部下拉操作和底部上拉操作进行子View的布局参数根据滑动距离改变,ACTION_UP的时候判断是否存在回弹,如果需要则进行动画回弹到原来的位置,可以添加一个回弹结束监听,比如监听回弹处理跳转到其他的页面的操作等。

具体的实现如下,添加了是否禁用顶部和底部回弹的参数设置,以及回弹效果结束监听。

/**
 * A Simple Rebound ScrollView
 * @author Denluoyia
 */
public class ReboundScrollView extends ScrollView{

    private boolean mEnableTopRebound = true;
    private boolean mEnableBottomRebound = true;
    private OnReboundEndListener mOnReboundEndListener;
    private View mContentView;
    private Rect mRect = new Rect();

    public ReboundScrollView(Context context) {
        super(context);
    }

    public ReboundScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ReboundScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /** after inflating view, we can get the width and height of view */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentView = getChildAt(0);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (mContentView == null) return;
        // to remember the location of mContentView
        mRect.set(mContentView.getLeft(), mContentView.getTop(), mContentView.getRight(), mContentView.getBottom());
    }

    public ReboundScrollView setOnReboundEndListener(OnReboundEndListener onReboundEndListener){
        this.mOnReboundEndListener = onReboundEndListener;
        return this;

    }

    public ReboundScrollView setEnableTopRebound(boolean enableTopRebound){
        this.mEnableTopRebound = enableTopRebound;
        return this;
    }

    public ReboundScrollView setEnableBottomRebound(boolean mEnableBottomRebound){
        this.mEnableBottomRebound = mEnableBottomRebound;
        return this;
    }

    private int lastY;
    private boolean rebound = false;
    private int reboundDirection = 0; //<0 表示下部回弹  >0 表示上部回弹 0表示不回弹

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mContentView == null){
            return super.dispatchTouchEvent(ev);
        }
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastY = (int) ev.getY();
                break;

            case MotionEvent.ACTION_MOVE:
                if (!isScrollToTop() && !isScrollToBottom()){
                    lastY = (int) ev.getY();
                    break;
                }
                //处于顶部或者底部
                int deltaY = (int) (ev.getY() - lastY);
                //deltaY > 0 下拉  deltaY < 0 上拉


                //disable top or bottom rebound
                if ((!mEnableTopRebound && deltaY > 0) || (!mEnableBottomRebound && deltaY < 0)){
                    break;
                }

                int offset = (int) (deltaY * 0.48);
                mContentView.layout(mRect.left, mRect.top + offset, mRect.right, mRect.bottom + offset);
                rebound = true;
                break;

            case MotionEvent.ACTION_UP:
                if (!rebound) break;
                reboundDirection = mContentView.getTop() - mRect.top;
                TranslateAnimation animation = new TranslateAnimation(0, 0, mContentView.getTop(), mRect.top);
                animation.setDuration(300);
                animation.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        if (mOnReboundEndListener != null){
                           if (reboundDirection > 0){
                               mOnReboundEndListener.onReboundTopComplete();
                           }
                           if (reboundDirection < 0){
                               mOnReboundEndListener.onReboundBottomComplete();
                           }
                           reboundDirection = 0;
                        }
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }
                });
                mContentView.startAnimation(animation);
                mContentView.layout(mRect.left, mRect.top, mRect.right, mRect.bottom);
                rebound = false;
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public void setFillViewport(boolean fillViewport) {
        super.setFillViewport(true); //默认是填充ScrollView 或者再XML布局文件中设置fillViewport属性
    }

    /**
     * 判断当前ScrollView是否处于顶部
     */
    private boolean isScrollToTop(){
        return getScrollY() == 0;
    }

    /**
     * 判断当前ScrollView是否已滑到底部
     */
    private boolean isScrollToBottom(){
        return mContentView.getHeight() <= getHeight() + getScrollY();
    }

    /**
     * listener for top and bottom rebound
     * do your implement in the following methods
     */
    public interface OnReboundEndListener{

        void onReboundTopComplete();

        void onReboundBottomComplete();
    }
}

 使用:

直接在XML布局文件中把ScrollView替换成ReboundScrollView就可以了。还可以拓展把回弹顶部和底部添加其他的动画效果(之后再拓展试下)。

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

    <com.denluoyia.dtils.widget.ReboundScrollView
        android:id="@+id/reboundScrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#eefade"
            android:padding="16dp">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:textSize="15sp"
                android:lineSpacingExtra="5dp"
                android:text="@string/content"/>
        </LinearLayout>
    </com.denluoyia.dtils.widget.ReboundScrollView>

</LinearLayout>

 如果需要禁用回弹,可以直接设置enableTopRebound和enableBottomRebound参数,同样设置回弹结束(或开始)监听。

public class TestActivity extends AppCompatActivity {

    private ReboundScrollView reboundScrollView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        reboundScrollView = findViewById(R.id.reboundScrollView);
        //reboundScrollView.setEnableTopRebound(false);
        //reboundScrollView.setEnableBottomRebound(false);
        reboundScrollView.setOnReboundEndListener(new ReboundScrollView.OnReboundEndListener() {
            @Override
            public void onReboundTopComplete() {
                Toast.makeText(TestActivity.this, "顶部回弹", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onReboundBottomComplete() {
                Toast.makeText(TestActivity.this, "底部回弹", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

免责声明:文章转载自《ScrollView的顶部下拉和底部上拉回弹效果》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇opencv3.1线性可分svm例子及函数分析git push到Gitee的时候上传不成功,可能是本地文件夹与远程仓库不同步下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

SqlBulkCopy 批量插入数据库

    /// <summary> /// 批量插入 注:DT的tableName为要更新的数据库表名,DT的列名和数据库一致 /// </summary> /// <param name="dt"></param> /// <rem...

maven pom 属性介绍

maven pom属性 内置属性(预定义,可直接使用) ${basedir} 表示项目根目录,即包含pom.xml文件的目录; ${version} 表示项目版本; ${project.basedir}同${basedir}; ${maven.build.timestamp} 表示项目构件开始时间; ${maven.build.timestamp.for...

python练习题3--for

题目:一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少? 程序分析: 假设该数为 x。 1、则:x + 100 = n2, x + 100 + 168 = m2 2、计算等式:m2 - n2 = (m + n)(m - n) = 168 3、设置: m + n = i,m - n = j,i * j =168,i 和...

SSH三大框架整合步骤

Struts2:需要整合的第一个框架: 1.创建一个动态web项目 2.导入struts2必须的jar 放到 lib目录下 ,再 build path 添加web工程中 3.配置struts2的核心配置文件:struts.xml 4.在web.xml文件中添加struts2的核心过滤器 5.添加测试页面 6.导入tomcat jar包 对j2e...

Avue-curd通用模板(二)

目录 Avue-curd通用模板 1、增加路由菜单 2、通用模板 3、踩坑 4、表格空数据样式 Avue-curd通用模板 上一篇已经把所有使用Avue的准备工作做好了,下面就通过一个简单的例子来体验一下avue能带给我们的便利。 1、增加路由菜单 在 src/router/index.js,增加一个路由(由于这只是用来测试学习的,所以放在了...

Python——截取web网页长图

# -*- coding: utf8 -*-import timeimport xlrdfrom selenium import webdriverdef read_excel(filename): data = xlrd.open_workbook(filename) # 打开xls文件 sheet = data.sheets()[0]...