Android开发高级UIAndroid动画

自定义behavior 仿支付宝首页

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

先摆上项目地址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

上一篇下一篇

猜你喜欢

热点阅读