Android实现回弹ScrollView的原理

本文实例为大家分享了Android实现回弹ScrollView的原理,供大家参考,具体内容如下
回弹的ScrollView 网上看到的通常是ElasticScrollView
有一个Bug:点击子控件滑动时,滑动无效,
所以针对此问题,我对ElasticScrollView做了改进。
原理图 Android实现回弹ScrollView的原理
文章图片

【Android实现回弹ScrollView的原理】Android实现回弹ScrollView的原理
文章图片

代码 我在注释中做了详细的说明

import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.TranslateAnimation; import android.widget.ScrollView; /** * Created by Tindle Wei. */public class ElasticScrollView extends ScrollView {/*** 手指抖动误差*/private static final int SHAKE_MOVE_VALUE = https://www.it610.com/article/8; /*** Scrollview内部的view*/private View innerView; /*** 记录innerView最初的Y位置*/private float startY; /*** 记录原始innerView的大小位置*/private Rect outRect = new Rect(); private boolean animationFinish = true; public ElasticScrollView(Context context) {super(context); }public ElasticScrollView(Context context, AttributeSet attrs) {super(context, attrs); }/*** 继承自View* 在xml的所有布局加载完之后执行*/@Overrideprotected void onFinishInflate() {if (getChildCount()> 0) {innerView = getChildAt(0); }}/*** 继承自ViewGroup* 返回true, 截取触摸事件* 返回false, 将事件传递给onTouchEvent()和子控件的dispatchTouchEvent()*/@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {// 判断 点击子控件 or 按住子控件滑动// 如果点击子控件,则返回 false, 子控件响应点击事件// 如果按住子控件滑动,则返回 true, 滑动布局switch (ev.getAction()) {case MotionEvent.ACTION_DOWN: {startY = ev.getY(); break; }case MotionEvent.ACTION_MOVE: {float currentY = ev.getY(); float scrollY = currentY - startY; // 是否返回 truereturn Math.abs(scrollY) > SHAKE_MOVE_VALUE; }}// 默认返回 falsereturn super.onInterceptTouchEvent(ev); }@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (innerView == null) {return super.onTouchEvent(ev); } else {myTouchEvent(ev); }return super.onTouchEvent(ev); }public void myTouchEvent(MotionEvent ev) {if (animationFinish) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:startY = ev.getY(); super.onTouchEvent(ev); break; case MotionEvent.ACTION_UP:startY = 0; if (isNeedAnimation()) {animation(); }super.onTouchEvent(ev); break; case MotionEvent.ACTION_MOVE:final float preY = startY == 0 ? ev.getY() : startY; float nowY = ev.getY(); int deltaY = (int) (preY - nowY); startY = nowY; // 当滚动到最上或者最下时就不会再滚动,这时移动布局if (isNeedMove()) {if (outRect.isEmpty()) {// 保存正常的布局位置outRect.set(innerView.getLeft(),innerView.getTop(), innerView.getRight(), innerView.getBottom()); }// 移动布局// 这里 deltaY/2 为了操作体验更好innerView.layout(innerView.getLeft(),innerView.getTop() - deltaY / 2,innerView.getRight(),innerView.getBottom() - deltaY / 2); } else {super.onTouchEvent(ev); }break; default:break; }}}/*** 开启移动动画*/public void animation() {TranslateAnimation ta = new TranslateAnimation(0, 0, 0, outRect.top - innerView.getTop()); ta.setDuration(400); // 减速变化 为了用户体验更好ta.setInterpolator(new DecelerateInterpolator()); ta.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {animationFinish = false; }@Overridepublic void onAnimationRepeat(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {innerView.clearAnimation(); // 设置innerView回到正常的布局位置innerView.layout(outRect.left, outRect.top, outRect.right, outRect.bottom); outRect.setEmpty(); animationFinish = true; }}); innerView.startAnimation(ta); }/*** 是否需要开启动画*/public boolean isNeedAnimation() {return !outRect.isEmpty(); }/*** 是否需要移动布局*/public boolean isNeedMove() {int offset = innerView.getMeasuredHeight() - getHeight(); offset = (offset < 0) ? 0: offset; int scrollY = getScrollY(); return (offset == 0 || scrollY == offset); }}

其他说明 1、下面是继承关系:
ElasticScrollView extends ScrollView
ScrollView extends FrameLayout
FrameLayout extends ViewGroup
ViewGroup extends View
2、解决子控件 截取滑动监听的代码在onInterceptTouchEvent() ,
通过监听Y的变化,来判断是点击子控件还是上拉下拉
3、getMeasuredHeight()返回的是原始测量高度,与屏幕无关,
getHeight()返回的是在屏幕上显示的高度。
实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别。当超出屏幕后,getMeasuredHeight()等于getHeight()加上屏幕之外没有显示的高度。
4、getScrollY()返回的是滑动View显示部分的顶部
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    推荐阅读