自定义behavior 仿支付宝首页
相信大家平时使用最多的应用应该就是支付宝和微信了吧,但是个人感觉从设计上看 支付宝的交互设计明显更胜一筹,要说到底是哪里强吧,我感觉就是支付宝的里面各种嵌套滑动给了应用更高的可玩性,不会显得那么单调.
于是,咱们就撸个支付宝首页玩吧! 哈哈哈
先摆上项目地址https://github.com/nokiafen/viewpro/tree/master/alihomepage
献上动图
是不是觉得我山寨了一个支付宝??? 冤枉!我只是偷了懒只实现了交互效果而已 其它区域都是截图啊 ,大兄弟!
怎么实现呢?其实只实现交互效果并不算太复杂,改bug倒是花了不少功夫 就一个类300来行就可以搞定了 不信看我截图
要达到这个效果最简单的就是自定义CoordinatorLayout.Behavior了 大概谈谈它的工作原理吧,CoordinatorLayout就是一个加强版的FrameLayout,它通过Behavior来与它的直接子控件进行通信,CoordinatorLayout会通过behavior来控制子控件的位置,以及统一分配滑动事件.
1.首先咋们定义一个behavior 给咱们布局里面最下面 NestSrollerView用(里面有一大堆可以滑动的东西)并且在布局文件里面NestScrollerView标签里面声明一下
image.png
这里的scroll_behavior 就是你自定义behavior的全包名
- 然后其它逻辑都在behavior了
2.1 构造函数得重写
public ScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
scroller = new Scroller(context);
scrollerRefresh = new Scroller(context);
handler = new Handler();
}
2.2 指定一个依赖,其实就是你想监听那个view的位置变化你就指定谁
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull NestedScrollView child,
@NonNull View dependency) {
if (dependency.getId() == R.id.function_area) {
dependy = new WeakReference<View>(dependency);
return true;
}
return super.layoutDependsOn(parent, child, dependency);
}
2.3 处理依赖位置变化 ,如果你想要依据依赖位置的变化做一些逻辑操作
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent,
@NonNull NestedScrollView child, @NonNull View dependency) {
child.setTranslationY(dependency.getTranslationY()*2);
float perchent = dependency.getTranslationY() / maxFunctionCollaped * 0.3f;
// parent.findViewById(R.id.title_bar).setAlpha(1-perchent);
if (dependency.getTranslationY() > -maxFunctionCollaped * 0.3f) {
parent.findViewById(R.id.title_bar).setBackgroundResource(R.mipmap.top_search_back);
} else {
parent.findViewById(R.id.title_bar).setBackgroundResource(R.mipmap.search_bar_collapsing);
}
return super.onDependentViewChanged(parent, child, dependency);
}
2.4 指定界面初始化时的布局位置,因为CoordinateLayout相当于帧布局 ,你要在这里指定一些位置关系(通常是view的上下关系,帧布局里面写比较费力,在代码里面直接码比较直接).
@Override
public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull NestedScrollView child,
int layoutDirection) {
View function_area = parent.findViewById(R.id.function_area);
View tigle_bar = parent.findViewById(R.id.title_bar);
View bottomLayout = parent.findViewById(R.id.bottom_layout);
child.layout(0, function_area.getBottom(), parent.getMeasuredWidth(),
function_area.getMeasuredHeight() + parent.getMeasuredHeight()+bottomLayout.getMeasuredHeight());
maxFunctionCollaped = (int) (function_area.getMeasuredHeight()*0.5f);
anim_root = child.findViewById(R.id.anim_root);
scroll_content = child.findViewById(R.id.scroll_content);
return true;
}
2.5 嵌套滑动第一步 ,判断是否进行嵌套滑动 ,每次嵌套滑动就是从这个方法开始的
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull NestedScrollView child, @NonNull View directTargetChild, @NonNull View target,
int axes, int type) {
return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
2.6 嵌套滑动第二步 如果第一步返回true 就会到这里来了
@Override
public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull NestedScrollView child, @NonNull View directTargetChild, @NonNull View target,
int axes, int type) {
scroller.abortAnimation();
scrollerRefresh.forceFinished(true);
isNestScrolling=false;
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, axes, type);
}
2.7 嵌套滑动第三步 开始分配滑动距离
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull NestedScrollView child, @NonNull View target, int dx, int dy,
@NonNull int[] consumed, int type) {
View dependView = dependy.get();
if (dy > 0 && dependView.getTranslationY() <= 0 && (anim_root.getTranslationY() == 0||anim_root.getTranslationY()==anim_root.getMeasuredHeight())) { //刷新布局未偏移 , 向上滑动 且 上部分折叠区未折叠或正在折叠
dy=(int)(Math.abs(dy));
int currentTranslation = (int) dependView.getTranslationY();
int targetDy = currentTranslation - dy;
int concorrect = targetDy < -maxFunctionCollaped ? -maxFunctionCollaped : targetDy;
dependView.setTranslationY(concorrect);
consumed[1] = currentTranslation - concorrect;
} else if (dy < 0 && dependView.getTranslationY() >= 0) {// 向下滑动 且上部分折叠区未折叠
int currentTranslationY = (int) anim_root.getTranslationY();
dy=-(int)(Math.abs(dy)*0.6f);
float calculate = currentTranslationY - dy > anim_root.getMeasuredHeight() ? anim_root.getMeasuredHeight() : currentTranslationY - dy ;
//calculate+=(calculate/anim_root.getMeasuredHeight())*dy; //阻尼效果
anim_root.setTranslationY(calculate);
scroll_content.setTranslationY(calculate);
consumed[1] = (int) (calculate - currentTranslationY);
} else if (dy > 0 && anim_root.getTranslationY() > 0) { //向上滑动 ,刷新布局已经划出来
int currentTranslationY = (int) anim_root.getTranslationY();
int calculate = currentTranslationY - dy >= 0 ? currentTranslationY - dy : 0;
anim_root.setTranslationY(calculate);
scroll_content.setTranslationY(calculate);
consumed[1] = dy;
}
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
这里dy表示y方向 感应到的滑动量 dy>0表示向上滑动 dy<0表示向下滑动,然后你可以根据dy以及当前控件的位置来决定用哪一个控件来消耗这个dy ,然后把你的消耗值传给consumed[1] ,如果你把dy消耗完了,那里面嵌套的可滑动控件NestScrollerview就不会滑动内部内容了,如果没用完,那么NestScrollerview会紧接着onNestedPreScroll你自己定义的滑动继续滑动NestScrollerview里面的内容
2.8 嵌套滑动第四步:可滑动控件NestScrollerview 滑动完了(前提是它有得滑)紧接着就会到 onNestedScroll方法
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull NestedScrollView child, @NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int type) {
if (dyUnconsumed < 0 && dependy.get().getTranslationY() < 0) {
View dependView = dependy.get();
int currentTranslation = (int) dependView.getTranslationY();
int targetDy = currentTranslation - dyUnconsumed;
int concorrect = targetDy > 0 ? 0 : targetDy;
dependView.setTranslationY(concorrect);
dyConsumed = currentTranslation - concorrect;
isNestScrolling=true;
}
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed, type);
}
如果可滑动控件NestScrollerview滑动(它是在滑动它里面的内容)完了 还有未消耗完的滑动量 你可以在这里拿到滑动量dyUnconsumed,继续定义紧接着NestScrollerview滑动完的其它滑动事件
2.9 惯性滑动处理 ---滑动结束的一种情况(不一定会调用)
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull NestedScrollView child, @NonNull View target, float velocityX, float velocityY) {
onRefreshEnd();
Log.d("onNestedStroll","fling");
return onDragEnd(velocityY);
}
一般就是快速滑动马上抬手就会出现,这里会返回滑动速度给你,参考这个数值来处理滑动view的最终停留状态。
2.10 滑动结束的回调,不同于上个方法这个 一定会触发的
@Override
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull NestedScrollView child, @NonNull View target, int type) {
Log.d("onNestedStroll","onStopNestedScroll");
onRefreshEnd();
onDragEnd(600);
isNestScrolling=false;
}
滑动结束后 必须确定View的最终位置(到底是展开还是缩起来),摆在中间不太好看吧,这里需要借助scroll开展平滑动画来将你的view从中间态移动到你最终希望它到达的位置,达到一种弹性效果。
onNestedPreFling onStopNestedScroll两个方法处理滑动结束的逻辑比较统一也相对简单,而且逻辑基本上都是一样的。麻烦点是动画播放时进行嵌套移动以及如何分配滑动量给子view
博文里只能简述基本流程,详情请移步仓库
https://github.com/nokiafen/viewpro/tree/master/alihomepage