美文网首页Android开发高级UIAndroid动画
自定义behavior 仿支付宝首页

自定义behavior 仿支付宝首页

作者: halo_0b54 | 来源:发表于2019-08-23 02:21 被阅读0次
    相信大家平时使用最多的应用应该就是支付宝和微信了吧,但是个人感觉从设计上看 支付宝的交互设计明显更胜一筹,要说到底是哪里强吧,我感觉就是支付宝的里面各种嵌套滑动给了应用更高的可玩性,不会显得那么单调.
    于是,咱们就撸个支付宝首页玩吧! 哈哈哈

    先摆上项目地址https://github.com/nokiafen/viewpro/tree/master/alihomepage

    献上动图

    ali_sc_shot.gif
    是不是觉得我山寨了一个支付宝??? 冤枉!我只是偷了懒只实现了交互效果而已 其它区域都是截图啊 ,大兄弟!

    怎么实现呢?其实只实现交互效果并不算太复杂,改bug倒是花了不少功夫 就一个类300来行就可以搞定了 不信看我截图

    image.png
    要达到这个效果最简单的就是自定义CoordinatorLayout.Behavior了 大概谈谈它的工作原理吧,CoordinatorLayout就是一个加强版的FrameLayout,它通过Behavior来与它的直接子控件进行通信,CoordinatorLayout会通过behavior来控制子控件的位置,以及统一分配滑动事件.

    1.首先咋们定义一个behavior 给咱们布局里面最下面 NestSrollerView用(里面有一大堆可以滑动的东西)并且在布局文件里面NestScrollerView标签里面声明一下


    image.png

    这里的scroll_behavior 就是你自定义behavior的全包名

    1. 然后其它逻辑都在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

    相关文章

      网友评论

        本文标题:自定义behavior 仿支付宝首页

        本文链接:https://www.haomeiwen.com/subject/dwuasctx.html