一、普通控件带阻尼回弹效果;
先上图:
再上代码:
/**
* ================================
* Des: 阻尼回弹 DampingReboundFrameLayout
* Created by kele on 2021/2/22.
* E-mail:984127585@qq.com
* ================================
*/
public class DampingReboundFrameLayout extends FrameLayout {
private View mPrinceView;// 太子View
private int mInitTop, mInitBottom, mInitLeft, mInitRight;// 太子View初始时上下坐标位置(相对父View,
// 即当前ReboundEffectsView)
private boolean isEndwiseSlide;// 是否纵向滑动
private float mVariableY;// 手指上下滑动Y坐标变化前的Y坐标值
private float mVariableX;// 手指上下滑动X坐标变化前的X坐标值
private int orientation;//1:竖向滚动 2:横向滚动
public DampingReboundFrameLayout(Context context) {
this(context, null);
}
public DampingReboundFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DampingReboundFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.setClickable(true);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DampingReboundFrameLayout);
orientation = ta.getInt(R.styleable.DampingReboundFrameLayout_orientation, 1);
ta.recycle();
}
/**
* Touch事件
*/
@Override
public boolean onTouchEvent(MotionEvent e) {
if (null != mPrinceView) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
onActionDown(e);
break;
case MotionEvent.ACTION_MOVE:
return onActionMove(e);
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
onActionUp(e);// 当ACTION_UP一样处理
break;
}
}
return super.onTouchEvent(e);
}
/**
* 手指按下事件
*/
private void onActionDown(MotionEvent e) {
mVariableY = e.getY();
mVariableX = e.getX();
/**
* 保存mPrinceView的初始上下高度位置
*/
mInitTop = mPrinceView.getTop();
mInitBottom = mPrinceView.getBottom();
mInitLeft = mPrinceView.getLeft();
mInitRight = mPrinceView.getRight();
}
/**
* 手指滑动事件
*/
private boolean onActionMove(MotionEvent e) {
float nowY = e.getY();
float diffY = (nowY - mVariableY) / 2;
if (orientation == 1 && Math.abs(diffY) > 0) {// 上下滑动
// 移动太子View的上下位置
mPrinceView.layout(mPrinceView.getLeft(), mPrinceView.getTop() + (int) diffY,
mPrinceView.getRight(), mPrinceView.getBottom() + (int) diffY);
mVariableY = nowY;
isEndwiseSlide = true;
return true;// 消费touch事件
}
float nowX = e.getX();
float diffX = (nowX - mVariableX) / 5;//除数越大可以滑动的距离越短
if (orientation == 2 && Math.abs(diffX) > 0) {// 左右滑动
// 移动太子View的左右位置
mPrinceView.layout(mPrinceView.getLeft() + (int) diffX, mPrinceView.getTop(),
mPrinceView.getRight() + (int) diffX, mPrinceView.getBottom());
mVariableX = nowX;
isEndwiseSlide = true;
return true;// 消费touch事件
}
return super.onTouchEvent(e);
}
/**
* 手指释放事件
*/
private void onActionUp(MotionEvent e) {
if (isEndwiseSlide) {// 是否为纵向滑动事件
// 是纵向滑动事件,需要给太子View重置位置
if (orientation==1){
resetPrinceViewV();
}else if (orientation==2){
resetPrinceViewH();
}
isEndwiseSlide = false;
}
}
/**
* 回弹,重置太子View初始的位置
*/
private void resetPrinceViewV() {
TranslateAnimation ta = new TranslateAnimation(0, 0, mPrinceView.getTop() - mInitTop, 0);
ta.setDuration(600);
mPrinceView.startAnimation(ta);
mPrinceView.layout(mPrinceView.getLeft(), mInitTop, mPrinceView.getRight(), mInitBottom);
}
private void resetPrinceViewH() {
TranslateAnimation ta = new TranslateAnimation(mPrinceView.getLeft() - mInitLeft, 0, 0, 0);
ta.setDuration(600);
mPrinceView.startAnimation(ta);
mPrinceView.layout(mInitLeft, mPrinceView.getTop(), mInitRight, mPrinceView.getBottom());
}
/**
* XML布局完成加载
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 0) {
mPrinceView = getChildAt(0);// 获得子View,太子View
}
}
}
res-values-attr.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DampingReboundFrameLayout">
<attr name="orientation">
<enum name="portrait" value="1" />
<enum name="landscape" value="2" />
</attr>
</declare-styleable>
</resources>
二、NestesScrollView带阻尼回弹效果:
先上图:
再上代码:
/**
* ================================
* Des: 阻尼回弹 DampingReboundNestedScrollView
* Created by kele on 2021/2/22.
* E-mail:984127585@qq.com
* ================================
*/
public class DampingReboundNestedScrollView extends NestedScrollView {
// y方向上当前触摸点的前一次记录位置
private int previousY = 0;
// y方向上的触摸点的起始记录位置
private int startY = 0;
// y方向上的触摸点当前记录位置
private int currentY = 0;
// y方向上两次移动间移动的相对距离
private int deltaY = 0;
// 第一个子视图
private View childView;
// 用于记录childView的初始位置
private Rect topRect = new Rect();
//水平移动搞定距离
private float moveHeight;
public DampingReboundNestedScrollView(Context context) {
this(context,null);
}
public DampingReboundNestedScrollView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public DampingReboundNestedScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFillViewport(true);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 0) {
childView = getChildAt(0);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (null == childView) {
return super.dispatchTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = (int) event.getY();
previousY = startY;
// 记录childView的初始位置
topRect.set(childView.getLeft(), childView.getTop(),
childView.getRight(), childView.getBottom());
moveHeight = 0;
break;
case MotionEvent.ACTION_MOVE:
currentY = (int) event.getY();
deltaY = currentY - previousY;
previousY = currentY;
//判定是否在顶部或者滑到了底部
if((!childView.canScrollVertically(-1)&&(currentY-startY)>0)||(!childView.canScrollVertically(1)&&(currentY-startY)<0)){
//计算阻尼
float distance = currentY - startY;
if (distance < 0) {
distance *= -1;
}
float damping = 0.5f;//阻尼值
float height = getHeight();
if (height != 0) {
if (distance > height) {
damping = 0;
} else {
damping = (height - distance) / height;
}
}
if (currentY - startY < 0) {
damping = 1 - damping;
}
//阻力值限制再0.3-0.5之间,平滑过度
damping *= 0.25;
damping += 0.25;
moveHeight = moveHeight + (deltaY * damping);
childView.layout(topRect.left, (int) (topRect.top + moveHeight), topRect.right,
(int) (topRect.bottom + moveHeight));
}
break;
case MotionEvent.ACTION_UP:
if (!topRect.isEmpty()) {
//开始回移动画
upDownMoveAnimation();
// 子控件回到初始位置
childView.layout(topRect.left, topRect.top, topRect.right,
topRect.bottom);
}
//重置一些参数
startY = 0;
currentY = 0;
topRect.setEmpty();
break;
}
return super.dispatchTouchEvent(event);
}
// 初始化上下回弹的动画效果
private void upDownMoveAnimation() {
TranslateAnimation animation = new TranslateAnimation(0.0f, 0.0f,
childView.getTop(), topRect.top);
animation.setDuration(600);
animation.setFillAfter(true);
//设置阻尼动画效果
animation.setInterpolator(new DampInterpolator());
childView.setAnimation(animation);
}
public class DampInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
//没看过源码,猜测是input是时间(0-1),返回值应该是进度(0-1)
//先快后慢,为了更快更慢的效果,多乘了几次,现在这个效果比较满意
return 1 - (1 - input) * (1 - input) * (1 - input) * (1 - input) * (1 - input);
}
}
}
参考链接:
https://my.oschina.net/u/1462828/blog/1553433
https://blog.csdn.net/u012230055/article/details/80000887