自定义ScrollView实现弹性效果
来源:互联网 发布:台湾生活水平知乎 编辑:程序博客网 时间:2024/06/11 19:58
弹性效果包括过度拉伸效果和反弹效果。
实现思路请看这篇文章,我基于他的实现进行了一些优化。
一、实现思路
这个问题的本质是控制ScrollView装载的View(不妨叫它innerView)的显示位置。而innerView显示位置的改变可以通过两种方式实现。
- 改变ScrollView的mScrollY
- 调用innerView的layout(l, t, r, b)函数
前者就是ScrollView实现正常滚动的方式。而后者就是我们拿来实现反弹效果的。也就是说,在ScrollView正常滚动达到极限的时候,我们调用innerView的layout(l, t, r, b)函数继续"滚动"innerView。手指抬起时,意味着一次滑动的结束,这个时候,我们要判断是否要"反弹"。所以这里面有两个关键问题。
- 如何判断ScrollView的滚动已经达到极限
- 如何判断是否需要反弹
public boolean needOverScroll(float deltaY) { final int offset = innerView.getMeasuredHeight() - getHeight(); final float scrollY = getScrollY(); return (scrollY == 0 && deltaY > 0)|| (scrollY == offset && deltaY < 0); }
ScrollView的滚动已经达到极限分两种情况。
第一种情况是手指往下滑动无法移动innerView了。我们知道,手指往下滑动,ScrollView的mScrollY减小,直至为0。ScrollView的mScrollY减为0之后(scrollY == 0),再向下滑动(deltaY > 0),mScrollY就不再变化了(不会变为负数),innerView也就不会移动了。这个时候,就需要我们调用innerView的layout(l, t, r, b)函数继续"滚动"innerView。
第二种情况是手指往上滑动无法移动innerView了。我们知道,手指往上滑动,ScrollView的mScrollY增大,直至为innerView.getMeasuredHeight() - getHeight()(即mScrollY最大值为ScrollView装载的View的测量高度与ScrollView提供给其显示的高度的差值)。ScrollView的mScrollY增大为innerView.getMeasuredHeight() - getHeight()之后(scrollY == offset),再向上滑动(deltaY < 0),mScrollY就不再变化了,innerView也就不会移动了。这个时候,也需要我们调用innerView的layout(l, t, r, b)函数继续"滚动"innerView。
这里相对于我参考博文的那种实现,增加了对deltaY的考虑。这么做,可以避免本可以利用ScrollView的本身滚动的时候,使用layout(l, t, r, b)进行滚动。是对初始时就向上滑动的情况的优化。如果不考虑deltaY的话,一开始向上滑动也会调用layout(l, t, r, b)移动布局。虽然根据打印的日志,只移动了几个像素(之后因为mScrollY不再为0就不再调用layout(l, t, r, b)),但是既然能避免还是应该避免。
对于问题二,参照以下代码。
public boolean needRebound() { return getInnerViewActualTop() > 0 || getInnerViewActualBottom() < getHeight(); } public int getInnerViewActualTop(){ return innerView.getTop() - getScrollY(); } public int getInnerViewActualBottom(){ return innerView.getBottom() - getScrollY(); }是否需要反弹也分两种情况
第一种情况是需要向上反弹,即getInnerViewActualTop() > 0。
第二种情况是需要向下反弹,即getInnerViewActualBottom() < getHeight()。
说明一下getInnerViewActualTop()函数。我在调试的时候发现,使用原生的ScrollView时,无论你如何滚动,innerView.getTop()都是0。而视觉上,以ScrollView为参考系,innerView的上边界是不断变化的。(我推想,应该是在onDraw()函数中,getTop()-getScrollY()来确定上边界,所以getTop()可以一直不变,改变mScrollY就行了)所以,我写了getInnerViewActualTop()函数,来获得innerView相对于ScrollView视觉上的上边界。
这样实现有一个好处,就是当你过度拉伸,不松手,再往回滑动,让innerView重新显示到合理的位置上(没有过度拉伸,没有空白的区域),抬手,innerView不会反弹。而我参考博文的那种实现,只要你过度拉伸过,抬手就会反弹,体验不是太好。
二、源码
package com.example.ligang.demo_autopullrefreshlistview;import android.content.Context;import android.graphics.Rect;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.animation.TranslateAnimation;import android.widget.ScrollView;public class OverScrollView extends ScrollView { private static final float OVER_SCROLL_RATIO = 0.5f; private static final int REBOUND_DURATION = 200; private View innerView; private float lastY = -1; private Rect originalRect = new Rect(); public OverScrollView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { if (getChildCount() > 0) { innerView = getChildAt(0); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (innerView != null) { handleTouchEvent(ev); } return super.onTouchEvent(ev); } public void handleTouchEvent(MotionEvent ev) { final float currY = ev.getY(); if (lastY == -1) { lastY = currY; } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: lastY = currY; break; case MotionEvent.ACTION_MOVE: final float deltaY = (currY - lastY) * OVER_SCROLL_RATIO; lastY = currY; if (needOverScroll(deltaY)) { if (originalRect.isEmpty()) { originalRect.set(innerView.getLeft(), innerView.getTop(), innerView.getRight(), innerView.getBottom()); } if (deltaY > 0) { innerView.layout(innerView.getLeft(), innerView.getTop() + (int) (Math.ceil(deltaY)), innerView.getRight(), innerView.getBottom() + (int) (Math.ceil(deltaY))); } else { innerView.layout(innerView.getLeft(), innerView.getTop() + (int) (Math.floor(deltaY)), innerView.getRight(), innerView.getBottom() + (int) (Math.floor(deltaY))); } } break; case MotionEvent.ACTION_UP: if (needRebound()) { rebound(); } reset(); break; default: reset(); break; } } private void reset() { lastY = -1; } public void rebound() { TranslateAnimation translateAnimation = new TranslateAnimation(0, 0, innerView.getTop(), originalRect.top); translateAnimation.setDuration(REBOUND_DURATION); innerView.startAnimation(translateAnimation); innerView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom); originalRect.setEmpty(); } public boolean needRebound() { return getInnerViewActualTop() > 0 || getInnerViewActualBottom() < getHeight(); } public int getInnerViewActualTop(){ return innerView.getTop() - getScrollY(); } public int getInnerViewActualBottom(){ return innerView.getBottom() - getScrollY(); } public boolean needOverScroll(float deltaY) { final int offset = innerView.getMeasuredHeight() - getHeight(); final float scrollY = getScrollY(); return (scrollY == 0 && deltaY > 0)|| (scrollY == offset && deltaY < 0); }}
- 自定义ScrollView实现弹性效果
- 自定义ScrollView实现弹性ScrollView
- 自定义ScrollView实现图片下拉放大(弹性效果)+ 悬浮框
- 自定义ScrollView实现图片下拉放大(弹性效果)+ 悬浮框
- Android自定义弹性ScrollView
- 自定义弹性的ScrollView
- 自定义弹性的ScrollView
- 自定义scrollview弹性布局
- 自定义scrollview实现吸附效果
- 自定义ViewGroup实现弹性滑动效果
- 自定义Behavior实现AppBarLayout越界弹性效果
- 弹性scrollview的实现
- Android自定义ScrollView实现反弹效果
- Android自定义ScrollView实现反弹效果
- 安卓自定义Scrollview,实现卷帘效果
- Android自定义ScrollView实现反弹效果
- Android自定义ScrollView实现上下反弹效果
- Android自定义View--ScrollView实现回弹效果
- 第五章思维导图
- c#抽象类与接口
- Android系统中添加一个产品----图文详解
- 第六章思维导图
- 第七章思维导图
- 自定义ScrollView实现弹性效果
- 第八章思维导图
- django cpu监控之七-----使用wmi获取CPU数据
- coi 2013-2014 round 5 trokuti
- zookeeper安装部署
- 抽象类与抽象方法和接口
- 1.面向对象
- django cpu监控之八-----查看属性指标
- iOS fmdb 主键自增一 方法