code小生,一个专注 Android 领域的技术平台
公众号回复 Android 加入我的安卓技术群
image.png
image.png
作者:colinWong链接:https://www.jianshu.com/p/739bb01eee80 声明:本文已获
colinWong授权发表,转发等请联系原作者授权
1563442819594.gif
设计思路
1.自定义个组件类似RelativeLayout2.可以内部放子View,然后就是滑动主体在前,小程序View在后3.重写dispatchTouchEvent 控制这两个子View的位置4.加上临界点回弹动画5.手势判断(惯性效果)
1.继承RelativeLayout
如果要从新写一个GroupView组件需要measure → layout → draw 很多细节要处理也不一定处理的好。所以直接用系统提供的RelativeLayout
public class MoreHeadLayout extends RelativeLayout { ...}
2.内部子View 结构就两个,如下图
private View mHeadView; private NewNestedScrollView mBodyView; void init(){ mHeadView = findViewById(R.id.head_layout);//view为head mBodyView = findViewById(R.id.body_layout); }
xml中的代码
3.重写dispatchTouchEvent
这一步主要是做控制手指滑动跟随,就是bodyView跟随你的手指滑动先写一个方法,就是控制bodyView纵坐标的位置
private void setMarginTop(int offY) { RelativeLayout.LayoutParams paramsBody = (LayoutParams) mBodyView.getLayoutParams(); int marginTop = paramsBody.topMargin - offY; if (marginTop < 0) { marginTop = 0; } else if (marginTop > mHeadView.getHeight()) { marginTop = mHeadView.getHeight(); } paramsBody.topMargin = marginTop; mBodyView.setLayoutParams(paramsBody); }
再计算滑动距离,然后调用setMarginTop 方法
@Override public boolean dispatchTouchEvent(MotionEvent ev) { int y = (int) ev.getRawY(); int offY = lastY - y; lastY = y; switch (ev.getAction()) { ... case MotionEvent.ACTION_MOVE: if (isBodyTop && headViewVisible()) { setMarginTop((int) (offY * damp)); return true; } if (isBodyTop && offY < 0) { setMarginTop((int) (offY * damp)); return true; } break; ... }
这样bodyView 就可以跟随手指动了,若需要一些阻尼效果可以添加系数damp,就是bodyView移动的距离等于手指滑动的距离乘以系数damp。
4.临界点与回弹
先写一个动画方法,该动画方法就是bodyView 从开始的高度移动到结束的高度。
private ValueAnimator mAnim; /** * 收起或打开动画 * * @param start 开始高度 * @param end 结束高度 * @param time 动画时间 */ public void startAnimator(int start, int end, long time) { if (mAnim != null && mAnim.isRunning()) { mAnim.cancel(); } mAnim = ValueAnimator.ofInt(start, end); mAnim.setDuration(time); mAnim.setTarget(mBodyView); mAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int currentValue = (Integer) animation.getAnimatedValue(); RelativeLayout.LayoutParams params = (LayoutParams) mBodyView.getLayoutParams(); params.topMargin = currentValue; mBodyView.setLayoutParams(params); } }); mAnim.start(); }
然后设置一个触发的临界点
private float closeOrOpen = 0.4f;//动画关闭还是打开的点(相对于头部高度的比例)
触发时机例如手指抬起事件
case MotionEvent.ACTION_UP: ... if (mBodyView.getTop() < mHeadView.getHeight() * closeOrOpen) { startAnimator(mBodyView.getTop(), 0);//收起动画 } else { startAnimator(mBodyView.getTop(), mHeadView.getMeasuredHeight());//打开动画 }
5.手势判断(惯性效果)
添加手势判断会有更好的使用体验效果,方法如下
private GestureDetector mGestureDetector; mGestureDetector = new GestureDetector(getContext(), new MoreGestureListener()); class MoreGestureListener implements GestureDetector.OnGestureListener { @Override public boolean onDown(MotionEvent e) { mIsHaveScrolled = false; return false; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { mIsHaveScrolled = true; return false; } @Override public void onLongPress(MotionEvent e) { } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //当头部显示并且向上fling时候关闭头部(惯性视觉) if (mBodyView.getTop() > 0 && velocityY < 0) { startAnimator(mBodyView.getTop(), 0); } return false; } }
在dispatchTouchEvent 方法里每个事件下面把事件传进去
mGestureDetector.onTouchEvent(ev);//为什么不放在最前面,因为会比关闭/打开动画先触发
其他
大概的雏形就是这样可以扩展其他功能如,三个小点或者震动、headView跟随bodyView 移动等
附上用例地址https://github.com/collinWong/wx_drawer
推荐阅读仿简书动态 searchview 的实现,代码就这么多点如何阅读代码(八点要记牢)