效果
ScrollView滚动到最顶端和最底端时,还可以继续拉伸出一片空白区域,手松开后再回弹到正常状态
实现思路
- 到达顶端和底端后,还想继续拉伸,这个可以通过layout方法对Content重新定位就能实现
- 回弹效果,可以通过平移动画TranslateAnimation来实现
- 当内容没到达顶端或底端时,通过ScrollView默认的onTouchEvent处理来正常滚动即可
- 根据ScrollView的大小,ScrollView的滚动距离,ContentView的大小,可以判断控件是否滚动到顶端
核心代码
package com.easing.commons.android.ui.control.scroll;
import android.content.Context;
import android.content.res.TypedArray;
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;
import com.easing.commons.android.R;
import com.easing.commons.android.manager.Device;
import com.easing.commons.android.ui.dialog.TipBox;
@SuppressWarnings("all")
public class BounceScrollView extends ScrollView {
View contentView;
Rect recentNormalBound = new Rect();
float y;
TranslateAnimation animation;
boolean isAnimationFinished = true;
int maxWidth = Integer.MAX_VALUE;
int maxHeight = Integer.MAX_VALUE;
int widthRadix = 0;
int heightRadix = 0;
float maxScreenRatioX = 0F;
float maxScreenRatioY = 0F;
public BounceScrollView(Context context) {
this(context, null);
}
public BounceScrollView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
init(context, attributeSet);
}
protected void init(Context context, AttributeSet attributeSet) {
int screenWidth = Device.screenWidth();
int screenHeight = Device.screenHeight();
TypedArray attrs = context.obtainStyledAttributes(attributeSet, R.styleable.BounceScrollView);
maxWidth = (int) attrs.getDimension(R.styleable.BounceScrollView_maxWidth, Integer.MAX_VALUE);
maxHeight = (int) attrs.getDimension(R.styleable.BounceScrollView_maxHeight, Integer.MAX_VALUE);
widthRadix = (int) attrs.getDimension(R.styleable.BounceScrollView_widthDivision, 0);
heightRadix = (int) attrs.getDimension(R.styleable.BounceScrollView_heightDivision, 0);
maxScreenRatioX = attrs.getFloat(R.styleable.BounceScrollView_maxScreenRatioX, 0);
maxScreenRatioY = attrs.getFloat(R.styleable.BounceScrollView_maxScreenRatioY, 0);
if (maxScreenRatioX > 0)
if (maxScreenRatioX * screenWidth < maxWidth)
maxWidth = (int) (maxScreenRatioX * screenWidth);
if (maxScreenRatioY > 0)
if (maxScreenRatioY * screenHeight < maxHeight)
maxHeight = (int) (maxScreenRatioY * screenHeight);
}
@Override
protected void onFinishInflate() {
if (getChildCount() > 0)
contentView = getChildAt(0);
super.onFinishInflate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY)
widthSize = widthSize < maxWidth ? widthSize : maxWidth;
if (widthMode == MeasureSpec.UNSPECIFIED)
widthSize = widthSize < maxWidth ? widthSize : maxWidth;
if (widthMode == MeasureSpec.AT_MOST)
widthSize = widthSize < maxWidth ? widthSize : maxWidth;
if (widthRadix > 0) {
widthSize = widthSize - widthSize % widthRadix;
if (widthSize < widthRadix)
widthSize = widthRadix;
}
int maxWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY)
heightSize = heightSize < maxHeight ? heightSize : maxHeight;
if (heightMode == MeasureSpec.UNSPECIFIED)
heightSize = heightSize < maxHeight ? heightSize : maxHeight;
if (heightMode == MeasureSpec.AT_MOST)
heightSize = heightSize < maxHeight ? heightSize : maxHeight;
if (heightRadix > 0) {
heightSize = heightSize - heightSize % heightRadix;
if (heightSize < heightRadix)
heightSize = heightRadix;
}
int maxHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
super.onMeasure(maxWidthMeasureSpec, maxHeightMeasureSpec);
}
@Override
public boolean canScrollVertically(int direction) {
return true;
}
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN)
y = e.getY();
return super.dispatchTouchEvent(e);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
int action = e.getAction();
if (action == MotionEvent.ACTION_UP)
startRecoverAnimation();
if (action == MotionEvent.ACTION_MOVE) {
float preY = y;
float nowY = e.getY();
int dy = (int) (preY - nowY);
y = nowY;
if (!isAnimationFinished) {
dy = 0;
isAnimationFinished = true;
}
TipBox.tipInCenter(recentNormalBound.isEmpty());
boolean ifNeedRelayout = ifNeedRelayout();
if (ifNeedRelayout) {
if (recentNormalBound.isEmpty())
recentNormalBound.set(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom());
contentView.layout(contentView.getLeft(), contentView.getTop() - dy / 2, contentView.getRight(), contentView.getBottom() - dy / 2);
}
}
return super.onTouchEvent(e);
}
protected void startRecoverAnimation() {
if (recentNormalBound.isEmpty())
return;
animation = new TranslateAnimation(0, 0, contentView.getTop(), recentNormalBound.top);
animation.setDuration(200);
contentView.startAnimation(animation);
contentView.layout(recentNormalBound.left, recentNormalBound.top, recentNormalBound.right, recentNormalBound.bottom);
recentNormalBound.setEmpty();
isAnimationFinished = false;
}
protected boolean ifNeedRelayout() {
int invisibleHeight = contentView.getMeasuredHeight() - getHeight();
int scrollY = getScrollY();
if (scrollY <= 0 || scrollY >= invisibleHeight)
return true;
return false;
}
}
源码下载
带回弹效果的ScrollView
|