ScrollView反弹效果的实现

Posted cxchanpin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ScrollView反弹效果的实现相关的知识,希望对你有一定的参考价值。

发现非常多APP的界面都能够滑动,QQ。微信等等,自己琢磨了下。效果例如以下:

技术分享


代码:ScrollView

package com.wsj.wsjdemo;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;

/**
 * ScrollView反弹效果的实现
 */
public class VScrollView extends ScrollView {
	private View inner;// 孩子View
	private float y;// 点击时y坐标
	private int mLastMotionX;
	private int mLastMotionY;
	// 默认支持反弹效果
	private boolean isAllowRebound = true;
	private Rect normal = new Rect();// 矩形(这里仅仅是个形式。仅仅是用于推断是否须要动画.)

	private boolean isCount = false;// 是否開始计算

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

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

	public VScrollView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	/***
	 * 依据 XML 生成视图工作完毕.该函数在生成视图的最后调用,在全部子视图加入完之后. 即使子类覆盖了 onFinishInflate
	 * 方法,也应该调用父类的方法,使该方法得以运行.
	 */
	@Override
	protected void onFinishInflate() {
		if (getChildCount() > 0) {
			inner = getChildAt(0);
		}
	}

	public void setInnerView(View view) {
		this.inner = view;
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent e) {
		int x = (int) e.getRawX();
		int y = (int) e.getRawY();
		switch (e.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mLastMotionX = x;
			mLastMotionY = y;
		case MotionEvent.ACTION_MOVE:
			int deltaX = x - mLastMotionX;
			int deltaY = y - mLastMotionY;
			if (Math.abs(deltaX) > 10 && Math.abs(deltaY) < 45) {
				return false;
			}
		}
		return super.onInterceptTouchEvent(e);
	}

	/***
	 * 监听touch
	 */
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (inner != null && getAllowRebound()) {
			commOnTouchEvent(ev);
		}
		return super.onTouchEvent(ev);
	}

	/***
	 * 触摸事件
	 * 
	 * @param ev
	 */
	public void commOnTouchEvent(MotionEvent ev) {
		int action = ev.getAction();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			break;
		case MotionEvent.ACTION_UP:
			// 手指松开.
			if (isNeedAnimation()) {
				animation();
				isCount = false;
			}
			break;
		/***
		 * 排除出第一次移动计算,由于第一次无法得知y坐标。 在MotionEvent.ACTION_DOWN中获取不到,
		 * 由于此时是MyScrollView的touch事件传递到到了LIstView的孩子item上面.所以从第二次计算開始.
		 * 然而我们也要进行初始化,就是第一次移动的时候让滑动距离归0. 之后记录准确了就正常运行.
		 */
		case MotionEvent.ACTION_MOVE:
			final float preY = y;// 按下时的y坐标
			float nowY = ev.getY();// 时时y坐标
			int deltaY = (int) (preY - nowY);// 滑动距离
			if (!isCount) {
				deltaY = 0; // 在这里要归0.
			}

			y = nowY;
			// 当滚动到最上或者最下时就不会再滚动,这时移动布局
			if (isNeedMove()) {
				// 初始化头部矩形
				if (normal.isEmpty()) {
					// 保存正常的布局位置
					normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom());
				}

				// 移动布局
				inner.layout(inner.getLeft(), inner.getTop() - deltaY / 2, inner.getRight(), inner.getBottom() - deltaY / 2);
			}
			isCount = true;
			break;

		default:
			break;
		}
	}

	/***
	 * 回缩动画
	 */
	public void animation() {
		// 开启移动动画
		TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(), normal.top);
		ta.setDuration(250);
		inner.startAnimation(ta);
		// 设置回到正常的布局位置
		inner.layout(normal.left, normal.top, normal.right, normal.bottom);

		normal.setEmpty();

	}

	// 是否须要开启动画
	public boolean isNeedAnimation() {
		return !normal.isEmpty();
	}

	/***
	 * 是否须要移动布局 inner.getMeasuredHeight():获取的是控件的总高度
	 * 
	 * getHeight():获取的是屏幕的高度
	 * 
	 * @return
	 */
	public boolean isNeedMove() {
		int offset = inner.getMeasuredHeight() - getHeight();
		int scrollY = getScrollY();
		// 0是顶部,后面那个是底部
		if (scrollY == 0 || scrollY == offset) {
			return true;
		}
		return false;
	}

	public void scrollToTop() {
		// this.setScrollY(0);
		this.scrollTo(this.getLeft(), 0);
	}

	public void scrollToBottom() {
		// this.setScrollY(this.getHeight());
		this.scrollTo(this.getLeft(), this.getHeight());
	}

	public void setAllowRebound(boolean isAllowRebound) {
		this.isAllowRebound = isAllowRebound;
	}

	public boolean getAllowRebound() {
		return this.isAllowRebound;
	}

}

Main方法:什么都没有

package com.wsj.wsjdemo;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

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


  
}


仅仅须要在布局仅仅用自己定义ScrollView包括内容就可以:

<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" >

    <com.wsj.wsjdemo.VScrollView
        android:id="@+id/sv_mycenter"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:scrollbars="none" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />
        </LinearLayout>
    </com.wsj.wsjdemo.VScrollView>

</LinearLayout>

结束









以上是关于ScrollView反弹效果的实现的主要内容,如果未能解决你的问题,请参考以下文章

AndroidAndroid开发实现带有反弹效果,仿IOS反弹scrollview详解教程

Android -- 仿ios上下反弹效果

ScrollView反弹效果 仿小米私密短信效果

对于 UIScrollview,我如何使用自己的 panGestureRecognizer 但允许 scrollView 使用其识别器进行反弹?

水平 ScrollView 在 SwiftUI 中在更改时垂直反弹

与 Geometry Reader 结合使用时,ScrollView 无法正确滚动(反弹)