Android仿IOS阻尼效果,上下弹性滑动,改变背景颜色
之前在做项目的时候碰到过一个问题,废话不多说,先看我自己录制的一个效果
刚开始是想着百度搜索去看有没有合适的工具类,果不其然,搜到了很多,这里我贴上两个博客地址,大家可以先去看他们的再来看我的
https://blog.csdn.net/jky_yihuangxing/article/details/51981888
https://blog.csdn.net/zhangjg_blog/article/details/19193671
说的都很详细
可是应用到项目中会发现若我们的界面停留在顶部或底部,你再次去上拉下拉松手时就会导致回弹一部分,这是因为本身的效果就是可弹性滑动,还可回归原本位置。导致这一问题很影响用户体验,我就在此基础之上修改了他们的代码整合了起来。先贴出来如下
package com.sam.im.fddmall.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.LinearGradient;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.Shader;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.animation.TranslateAnimation;import android.widget.ScrollView;import com.sam.im.fddmall.R;import static com.sam.im.fddmall.app.App.selectedColor;import static com.sam.im.fddmall.mall.activity.MallCircleDetailActivity.linearHegiht;import static com.sam.im.fddmall.mall.fragment.MallCircleFragment.linearHegiht_Circle;import static com.sam.im.fddmall.mall.fragments.HomePageFragment.linearHegiht_Home;import static com.sam.im.fddmall.uis.fragments.SetTwoFragment.linearHegiht_setTwo;/** * 带滚动监听的scrollview,可以计算向上滑动的距离来判断标题栏的透明度颜色 */public class ObservableScrollView extends ScrollView { private static final String TAG = "ObservableScrollView"; // 当滑动的距离最小到10,才认为是上下滑动 private static final float MINI_DISTANCE = 10.0f; //移动因子, 是一个百分比, 比如手指移动了100px, 那么View就只移动50px //目的是达到一个延迟的效果 private static final float MOVE_FACTOR = 0.5f; //松开手指后, 界面回到正常位置需要的动画时间 private static final int ANIM_TIME = 200; //ScrollView的子View, 也是ScrollView的唯一一个子View private View contentView; //手指按下时的Y值, 用于在移动时计算移动距离 //如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值 private float startY; //用于记录正常的布局位置 private Rect originalRect = new Rect(); //手指按下时记录是否可以继续下拉 private boolean canPullDown = false; //手指按下时记录是否可以继续上拉 private boolean canPullUp = false; //在手指滑动的过程中记录是否移动了布局 private boolean isMoved = false; //用来判断背景颜色的处理 private int deltaY = 0; private int colorStart = 0;//下拉 private int colorEnd = 0;//上拉 public ObservableScrollView(Context context) { super(context); } public ObservableScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() > 0) { contentView = getChildAt(0); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (contentView == null) return; //ScrollView中的唯一子控件的位置信息, 这个位置信息在整个控件的生命周期中保持不变 originalRect.set(contentView.getLeft(), contentView.getTop(), contentView .getRight(), contentView.getBottom()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //获取View的宽高 int width = getWidth(); int height = getHeight();// Log.e(TAG, "ObservableScrollView:拿到首页的高度 " + linearHegiht_Home);// Log.e(TAG, "ObservableScrollView:拿到圈子的高度 " + linearHegiht_Circle);// Log.e(TAG, "ObservableScrollView:拿到子我的高度 " + linearHegiht_setTwo);// Log.e(TAG, "onDraw:拿到ScrollView的高度 " + height); if (selectedColor == 1) {//首页界面下拉拉伸的背景颜色 height = resultHeight(linearHegiht_Home, height); colorStart = getResources().getColor(R.color.background_white);//下拉 colorEnd = getResources().getColor(R.color.background_white);//上拉 }else if (selectedColor == 2) {//圈子界面下拉拉伸的背景颜色 height = resultHeight(linearHegiht_Circle,height); colorStart = getResources().getColor(R.color.background_white);//下拉 colorEnd = getResources().getColor(R.color.background_white);//上拉 } else if (selectedColor == 5) {//我的界面下拉拉伸的背景颜色 height = resultHeight(linearHegiht_setTwo,height); colorStart = getResources().getColor(R.color.colorPrimary);//下拉 colorEnd = getResources().getColor(R.color.background_white);//上拉 } else {//其他位置的,通常是出fragment的,但目前只有一个activity在使用,临时不做判断,直接归到else中 MallCircleDetailActivity.class height = resultHeight(linearHegiht, height); colorStart = getResources().getColor(R.color.colorPrimary);//下拉 colorEnd = getResources().getColor(R.color.background_white);//上拉 }// Log.e(TAG, "onDraw:拿到最后要使用的高度 " + height);//float[] position = {0f, 0.7f, 1.0f};//Log.e(TAG, "onDraw:开始打印y轴的数值 " + deltaY); //通过下拉来判断,大于0为下拉,小于0上拉 if (deltaY > 0) { //LinearGradient线性渐变,这种设置的比较细腻,可设置多种颜色渐变 Paint paint = new Paint(); LinearGradient backGradient = new LinearGradient(0, 0, 0, height, new int[]{colorStart, colorStart}, null, Shader.TileMode.CLAMP); paint.setShader(backGradient); canvas.drawRect(0, 0, width, height, paint); } else { //LinearGradient线性渐变,这种设置的比较细腻,可设置多种颜色渐变 Paint paint = new Paint(); LinearGradient backGradient = new LinearGradient(0, 0, 0, height, new int[]{colorEnd, colorEnd}, null, Shader.TileMode.CLAMP); paint.setShader(backGradient); canvas.drawRect(0, 0, width, height, paint); } } private int resultHeight(int linearHegiht, int height) { if (linearHegiht < height) { height = linearHegiht; } else { height = getHeight(); } return height; } /** * 在触摸事件中, 处理上拉和下拉的逻辑 */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (contentView == null) { return super.dispatchTouchEvent(ev); } int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: //判断是否可以上拉和下拉 canPullDown = isCanPullDown(); canPullUp = isCanPullUp(); //记录按下时的Y值 startY = ev.getY(); break; case MotionEvent.ACTION_MOVE: //在移动的过程中, 既没有滚动到可以上拉的程度, 也没有滚动到可以下拉的程度 if (!canPullDown && !canPullUp) { startY = ev.getY(); canPullDown = isCanPullDown(); canPullUp = isCanPullUp(); break; } //计算手指移动的距离 float nowY = ev.getY(); //纪录下滑动时Y,当滑动的距离小于MINI_DISTANCE 的时候则不认为是滑动 if (Math.abs(nowY - startY) < MINI_DISTANCE) break; deltaY = (int) (nowY - startY); boolean shouldMove = isSingleSliping(false, deltaY); if (shouldMove) { //计算偏移量 int offset = (int) (deltaY * MOVE_FACTOR); //写这段代码的目的 1.解决上拉下拉的回弹动画 2.解决根据上拉下拉数值来判断title透明度显示的bug,有时会半透明。在此记录 //先判断是在顶部、底部还是其他位置在通过下拉来判断,大于0为下拉,小于0上拉 //isCanPullDown() 顶部 isCanPullUp() 底部 if(isCanPullDown()){ //在顶部时,我们想要的上拉不想去有偏移量,因为偏移量会导致一个简单的回弹动画,有时候会影响圈子详情页的透明标题显示,并且本身也影响用户体验 //在顶部时判断上拉不增加偏移量,下拉依旧可以,因为仿ios阻尼效果 if (deltaY > 0) { //随着手指的移动而移动布局 contentView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset); } else { contentView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom ); } } else if(isCanPullUp()){ //在底部时,我们想要的下拉不想去有偏移量,因为偏移量会导致一个简单的回弹动画,并且本身也影响用户体验 //在底部时判断下拉不增加偏移量,上拉依旧可以,因为仿ios阻尼效果 if (deltaY > 0) { //随着手指的移动而移动布局 contentView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom); } else { contentView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset); } } else { //在其他位置时,也就是既不在顶部也不再底部,我们就不用考虑ios的阻尼效果,归置为普通滑动即可 //不设置这句应该也可以,没尝试..... contentView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom); } isMoved = true; //记录移动了布局 } break; case MotionEvent.ACTION_UP: if (!isMoved) break; //如果没有移动布局则跳过执行 // 开启动画 TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(), originalRect.top); anim.setDuration(ANIM_TIME); contentView.startAnimation(anim); // 设置回到正常的布局位置 contentView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom); //将标志位设回false canPullDown = false; canPullUp = false; isMoved = false; break; default: break; } return super.dispatchTouchEvent(ev); }// /**// * 是否可以向下拉// * ScrollView 是否在顶部// */// private boolean isCanPullDown() {//// return getScrollY() == 0;//// }////// /**// * 是否可以向上滑动// * ScrollView 是否在底部// * // * 说明:为什么要减去 10dp 主要原因是在UseInfoActivity ScrollView 中子View margin Parent View 10dp 可查看 R.layout.activity_user_info。// */// private boolean isCanPullUp() {//// return contentView.getHeight() <= getHeight() || contentView.getHeight() == getHeight() + getScrollY();//// } /** * 判断是否滚动到顶部 */ private boolean isCanPullDown() { return getScrollY() == 0 || contentView.getHeight() < getHeight() + getScrollY(); } /** * 判断是否滚动到底部 */ private boolean isCanPullUp() { return contentView.getHeight() <= getHeight() + getScrollY(); } /** * 设置是否单向滑动 * * @param isSingleSliping false 手势向上拖动,子View随着拖动而向上,之后不放开点击,在向下滑动使ScroView中的子View可以向下拖动。 * true 子View随着手势可以任意方向反复拖动 * @param distance isSingleSliping 为true 的情况下才有效 * @return 是否应该移动布局 true 应该 false 不滑动布局 */ private boolean isSingleSliping(boolean isSingleSliping, int distance) { if (!isSingleSliping) return canPullDown || canPullUp; else return (canPullDown && distance > 0) //可以下拉, 并且手指向下移动 || (canPullUp && distance < 0) //可以上拉, 并且手指向上移动 || (canPullUp && canPullDown); //既可以上拉也可以下拉(这种情况出现在ScrollView包裹的控件比ScrollView还小) } private ScrollViewListener scrollViewListener = null; public interface ScrollViewListener { void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy); } public void setScrollViewListener(ScrollViewListener scrollViewListener) { this.scrollViewListener = scrollViewListener; } @Override protected void onScrollChanged(int x, int y, int oldx, int oldy) { super.onScrollChanged(x, y, oldx, oldy); if (scrollViewListener != null) { scrollViewListener.onScrollChanged(this, x, y, oldx, oldy); } }}
这里作一下说明:
onScrollChanged 重写这个方法的原因是我自己的项目中要用到滑动,没有需要的可不用查看与此相关的接口变量等
onDraw 重写绘制这个方法是因为此类我在多处使用,而且多处使用的下拉上拉背景颜色不一致,我在此加的标志位来判断每个类中不同的背景颜色 有需要的话可以咨询我,我在这里不作解释
最重要的就是在触摸事件中,处理上下移动,我们可以这样判断
1.首先判断是在顶部、底部还是其他位置(isCanPullDown() 顶部 isCanPullUp() 底部,这两个方法在代码中都有可以自行查看)
2.在通过上拉还是下拉来判断(deltaY 这个变量即是上下拉的数值,大于0为下拉,小于0上拉,如果看过前两篇文章对此会很熟悉,一看就明白)
(1)开始顶部判断
在顶部时,我们想要的上拉不想去有偏移量,因为偏移量会导致一个简单的回弹动画 ,下拉就没有关系,因为下拉我们本来就想要弹性动画和回弹效果,所以不用去掉偏移量
(2)开始底部判断
在底部时,我们想要的下拉不想去有偏移量,因为偏移量会导致一个简单的回弹动画,相反,在此底部上拉就没有关系,我们依旧不做任何处理
说明:偏移量在代码中是 offset,就是因为有了这个偏移量我们的弹性上下拉和回弹动画才会完成
大家可以结合代码去查看自己需要的部分,也可以私我。都在代码里!
希望能帮助到大家
更多相关文章
- android 手机号码运营商判断
- Android(安卓)仿Facebook滑动菜单-支持android 2.2
- listview所带来的滑动冲突
- android自带的下拉刷新控件SwipeRefreshLayout
- Android(安卓)Viewpager与WebView轮播滑动冲突的解决方案
- Android(安卓)中判断一个程序是否为输入法程序
- Broadcast监听网络状态,ping判断网络是否可用
- android 判断摄像头是否可用(6.0以下 )
- Android(安卓)RecyclerView 监听滑动