Android自定义View实现竖向滑动回弹效果
作者:DwView 发布时间:2021-08-03 12:19:00
标签:Android,View,滑动回弹
本文实例为大家分享了Android自定义View实现滑动回弹的具体代码,供大家参考,具体内容如下
前言
Android 页面滑动的时候的回弹效果
一、关键代码
public class UniversalBounceView extends FrameLayout implements IPull {
private static final String TAG = "UniversalBounceView";
//default.
private static final int SCROLL_DURATION = 200;
private static final float SCROLL_FRACTION = 0.4f;
private static final int VIEW_TYPE_NORMAL = 0;
private static final int VIEW_TYPE_ABSLISTVIEW = 1;
private static final int VIEW_TYPE_SCROLLVIEW = 2;
private static float VIEW_SCROLL_MAX = 720;
private int viewHeight;
private AbsListView alv;
private OnBounceStateListener onBounceStateListener;
private View child;
private Scroller scroller;
private boolean pullEnabled = true;
private boolean pullPaused;
private int touchSlop = 8;
private int mPointerId;
private float downY, lastDownY, tmpY;
private int lastPointerIndex;
private float moveDiffY;
private boolean isNotJustInClickMode;
private int moveDelta;
private int viewType = VIEW_TYPE_NORMAL;
public UniversalBounceView(Context context) {
super(context);
init(context);
}
public UniversalBounceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public UniversalBounceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
scroller = new Scroller(context, new CustomDecInterpolator());
touchSlop = (int) (ViewConfiguration.get(context).getScaledTouchSlop() * 1.5);
}
class CustomDecInterpolator extends DecelerateInterpolator {
public CustomDecInterpolator() {
super();
}
public CustomDecInterpolator(float factor) {
super(factor);
}
public CustomDecInterpolator(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public float getInterpolation(float input) {
return (float) Math.pow(input, 6.0 / 12);
}
}
private void checkCld() {
int cnt = getChildCount();
if (1 <= cnt) {
child = getChildAt(0);
} else if (0 == cnt) {
pullEnabled = false;
child = new View(getContext());
} else {
throw new ArrayIndexOutOfBoundsException("child count can not be less than 0.");
}
}
@Override
protected void onFinishInflate() {
checkCld();
super.onFinishInflate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewHeight = h;
VIEW_SCROLL_MAX = h * 1 / 3;
}
private boolean isTouch = true;
public void setTouch(boolean isTouch) {
this.isTouch = isTouch;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!isTouch) {
return true;
} else {
try {
if (isPullEnable()) {
if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
if (Math.abs(ev.getY() - tmpY) < touchSlop) {
return super.dispatchTouchEvent(ev);
} else {
tmpY = Integer.MIN_VALUE;
}
}
return takeEvent(ev);
}
} catch (IllegalArgumentException | IllegalStateException e) {
e.printStackTrace();
}
if (getVisibility() != View.VISIBLE) {
return true;
}
return super.dispatchTouchEvent(ev);
}
}
private boolean takeEvent(MotionEvent ev) {
int action = ev.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
mPointerId = ev.getPointerId(0);
downY = ev.getY();
tmpY = downY;
scroller.setFinalY(scroller.getCurrY());
setScrollY(scroller.getCurrY());
scroller.abortAnimation();
pullPaused = true;
isNotJustInClickMode = false;
moveDelta = 0;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
pullPaused = false;
smoothScrollTo(0);
if (isNotJustInClickMode) {
ev.setAction(MotionEvent.ACTION_CANCEL);
}
postDelayed(new Runnable() {
@Override
public void run() {
if (getScrollY() == 0 && onBounceStateListener != null) {
onBounceStateListener.overBounce();
}
}
}, 200);
break;
case MotionEvent.ACTION_MOVE:
lastPointerIndex = ev.findPointerIndex(mPointerId);
lastDownY = ev.getY(lastPointerIndex);
moveDiffY = Math.round((lastDownY - downY) * getScrollFraction());
downY = lastDownY;
boolean canStart = isCanPullStart();
boolean canEnd = isCanPullEnd();
int scroll = getScrollY();
float total = scroll - moveDiffY;
if (canScrollInternal(scroll, canStart, canEnd)) {
handleInternal();
break;
}
if (Math.abs(scroll) > VIEW_SCROLL_MAX) {
return true;
}
if ((canStart && total < 0) || (canEnd && total > 0)) {
if (moveDelta < touchSlop) {
moveDelta += Math.abs(moveDiffY);
} else {
isNotJustInClickMode = true;
}
if (onBounceStateListener != null) {
onBounceStateListener.onBounce();
}
scrollBy(0, (int) -moveDiffY);
return true;
}
// else if ((total > 0 && canStart) || (total < 0 && canEnd)) {
// if (moveDelta < touchSlop) {
// moveDelta += Math.abs(moveDiffY);
// } else {
// isNotJustInClickMode = true;
// }
// scrollBy(0, -scroll);
// return true;
// }
break;
case MotionEvent.ACTION_POINTER_UP:
handlePointerUp(ev, 1);
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
private boolean canScrollInternal(int scroll, boolean canStart, boolean canEnd) {
boolean result = false;
if ((child instanceof RecyclerView) || (child instanceof AbsListView) || child instanceof ScrollView) {
viewType = VIEW_TYPE_ABSLISTVIEW;
result = canStart && canEnd;
} else if (child instanceof ScrollView || child instanceof NestedScrollView) {
viewType = VIEW_TYPE_SCROLLVIEW;
} else {
return false;
}
if (result) {
isNotJustInClickMode = true;
if (moveDelta < touchSlop) {
moveDelta += Math.abs(moveDiffY);
return true;
}
return false;
}
if (((scroll == 0 && canStart && moveDiffY < 0) || (scroll == 0 && canEnd && moveDiffY > 0) || (!canStart && !canEnd))) {
return true;
}
if (moveDelta < touchSlop) {
moveDelta += Math.abs(moveDiffY);
return true;
} else {
isNotJustInClickMode = true;
}
return false;
}
private void handleInternal() {
}
private void handlePointerUp(MotionEvent event, int type) {
int pointerIndexLeave = event.getActionIndex();
int pointerIdLeave = event.getPointerId(pointerIndexLeave);
if (mPointerId == pointerIdLeave) {
int reIndex = pointerIndexLeave == 0 ? 1 : 0;
mPointerId = event.getPointerId(reIndex);
// 调整触摸位置,防止出现跳动
downY = event.getY(reIndex);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
private void smoothScrollTo(int value) {
int scroll = getScrollY();
scroller.startScroll(0, scroll, 0, value - scroll, SCROLL_DURATION);
postInvalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if (!pullPaused && scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}
private float getScrollFraction() {
float ratio = Math.abs(getScrollY()) / VIEW_SCROLL_MAX;
ratio = ratio < 1 ? ratio : 1;
float fraction = (float) (-2 * Math.cos((ratio + 1) * Math.PI) / 5.0f) + 0.1f;
return fraction < 0.10f ? 0.10f : fraction;
}
@Override
public boolean isPullEnable() {
return pullEnabled;
}
@Override
public boolean isCanPullStart() {
if (child instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) child;
return !recyclerView.canScrollVertically(-1);
}
if (child instanceof AbsListView) {
AbsListView lv = (AbsListView) child;
return !lv.canScrollVertically(-1);
}
if (child instanceof RelativeLayout
|| child instanceof FrameLayout
|| child instanceof LinearLayout
|| child instanceof WebView
|| child instanceof View) {
return child.getScrollY() == 0;
}
return false;
}
@Override
public boolean isCanPullEnd() {
if (child instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) child;
return !recyclerView.canScrollVertically(1);
}
if (child instanceof AbsListView) {
AbsListView lv = (AbsListView) child;
int first = lv.getFirstVisiblePosition();
int last = lv.getLastVisiblePosition();
View view = lv.getChildAt(last - first);
if (null == view) {
return false;
} else {
return (lv.getCount() - 1 == last) &&
(view.getBottom() <= lv.getHeight());
}
}
if (child instanceof ScrollView) {
View v = ((ScrollView) child).getChildAt(0);
if (null == v) {
return true;
} else {
return child.getScrollY() >= v.getHeight() - child.getHeight();
}
}
if (child instanceof NestedScrollView) {
View v = ((NestedScrollView) child).getChildAt(0);
if (null == v) {
return true;
} else {
return child.getScrollY() >= v.getHeight() - child.getHeight();
}
}
if (child instanceof WebView) {
return (((WebView) child).getContentHeight() * ((WebView) child).getScale()) - (((WebView) child).getHeight() + ((WebView) child).getScrollY()) <= 10;
}
if (child instanceof RelativeLayout
|| child instanceof FrameLayout
|| child instanceof LinearLayout
|| child instanceof View) {
return (child.getScrollY() == 0);
}
return false;
}
/**
* 通过addView实现效果回弹效果
*
* @param replaceChildView 需要替换的View
*/
public void replaceAddChildView(View replaceChildView) {
if (replaceChildView != null) {
removeAllViews();
child = replaceChildView;
addView(replaceChildView);
}
}
public void setPullEnabled(boolean enable) {
pullEnabled = enable;
}
public interface OnBounceStateListener {
public void onBounce();
public void overBounce();
}
public void setOnBounceStateListener(OnBounceStateListener onBounceStateListener) {
this.onBounceStateListener = onBounceStateListener;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
try {
return super.dispatchKeyEvent(event);
} catch (IllegalArgumentException | IllegalStateException e) {
e.printStackTrace();
}
return false;
}
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
try {
super.dispatchWindowFocusChanged(hasFocus);
} catch (IllegalArgumentException | IllegalStateException e) {
e.printStackTrace();
}
}
}
二、注意要点
滑动结束的时候要防止动画抖动
private void handlePointerUp(MotionEvent event, int type) {
int pointerIndexLeave = event.getActionIndex();
int pointerIdLeave = event.getPointerId(pointerIndexLeave);
if (mPointerId == pointerIdLeave) {
int reIndex = pointerIndexLeave == 0 ? 1 : 0;
mPointerId = event.getPointerId(reIndex);
// 调整触摸位置,防止出现跳动
downY = event.getY(reIndex);
}
}
来源:https://blog.csdn.net/DwView/article/details/108369481
0
投稿
猜你喜欢
- 大致分为以下几个方面:一些查询指令整理使用SQL语句进行特殊查询检测表字段是否存在数据库升级数据库表字段赋初始值一、查询指令整理1.链式执行
- 引言最近,因为开发的时候经改动依赖的库,所以,我想对 Gradle 脚本做一个调整,用来动态地将依赖替换为源码。这里以 android-mv
- 目标效果: 点击动画按钮之后每张牌各自旋转 散开到屏幕上半部分的任意位置之后回到初始位置 比较像LOL男刀的技能动画 : )1: 创建卡牌对
- 现在的项目越来越多的都是打包成jar运行尤其是springboot项目,这时候配置文件如果一直放在项目中,每次进行简单的修改时总会有些不方便
- 随机数的定义为:产生的所有数字毫无关系.在实际应用中很多地方会用到随机数,比如需要生成唯一的订单号.在C#中获取随机数有三种方法:一.Ran
- RestAPI中, 经常需要操作json字符串, 需要把json字符串"反序列化"成一个对象, 也需要把一个
- 上文我们讨论了一种最简单的线性结构——顺序表,这节我们要讨论另一种线性结构——链表。什么是链表了,不要求逻辑上相邻的数据元素在物理存储位置上
- 1、锁优化在JDK6之前,通过synchronized来实现同步效率是很低的,被synchronized包裹的代码块经过javac编译后,会
- 在网站开发中经常遇到级联数据的展示,比如选择城市的时候弹出的省市县选择界面。很多前端制作人员习惯于从JSON中而不是从数据库中获
- 代码入下:import java.io.*; public class Practice { publ
- 一、迭代key&value第一种方式:迭代entrySet1.方法一/** * entrySet集合for-each循环(
- CXF简介CXF是一个开源的WebService框架。Apache CXF = Celtix + XFire,开始叫 Apache Celt
- 从主线程发送消息到子线程(准确地说应该是非UI线程)package com.zhuozhuo;import android.app.Acti
- 一. 简介 俩个数据库db1,db2, db1数据库的map
- 很多童鞋反应在吧项目导入到eclipse(myeclipse)时中文会有乱码,修改了编码格式后还是乱码,这里给大家介绍一下关于中文乱码时修改
- 前言Spring Boot常用注解整理提示:以下是本篇文章正文内容,下面案例可供参考一、@SpringBootApplication此注解是
- 本文实例讲述了Java使用Thread和Runnable的线程实现方法。分享给大家供大家参考,具体如下:一 使用Thread实现多线程模拟铁
- 概述关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 于某个对象的。也就是说,既
- 最近在公司的功能需求中,需要实现可以签到的日历,签到后在签到过的日期做标志。本功能参考了网上一些大神的日历控件,在此基础上进行修改,已满足本
- 示例代码:<%@ Page Language="C#" AutoEventWireup="true&qu