code小生,一个专注 Android 领域的技术平台
公众号回复 Android 加入我的安卓技术群
作者:colinWong链接:https://www.jianshu.com/p/739bb01eee80声明:本文已获
colinWong
授权发表,转发等请联系原作者授权
1563442819594.gif
设计思路
1.自定义个组件类似RelativeLayout
1.继承RelativeLayout
如果要从新写一个GroupView组件需要measure → layout → draw 很多细节要处理也不一定处理的好。所以直接用系统提供的RelativeLayout
public class MoreHeadLayout extends RelativeLayout {
...
}
2.内部子View 结构
![image.png 640?wx_fmt=other](https://i-blog.csdnimg.cn/blog_migrate/a8daae31b0183769ca9c96af09875f45.jpeg)
private View mHeadView;
private NewNestedScrollView mBodyView;
void init(){
mHeadView = findViewById(R.id.head_layout);//view为head
mBodyView = findViewById(R.id.body_layout);
}
xml中的代码
![image.png 640?wx_fmt=other](https://i-blog.csdnimg.cn/blog_migrate/461121f5d7e11de3891bbcd851f275f5.jpeg)
3.重写dispatchTouchEvent
这一步主要是做控制手指滑动跟随,就是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 的实现,代码就这么多点如何阅读代码(八点要记牢)
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~