老规矩,还是先上效果图

github地址

前面我也写过一篇关于UC浏览器首页滑动动画效果的文章UC浏览器首页滑动动画实现,只不过这篇文章是通过自定义View的方式实现这个滑动效果。最近在看Behavior相关的东西,所以使用Behavior又实现了一次UC浏览器主页的滑动效果,使用Behavior实现相比较自定义View的实现方式还是要简单方便很多。

View结构分析

UC首页滑动过程中可以分为四个View在参与滑动,具体的分析流程可以参见UC浏览器首页滑动动画实现这篇文章的分析,这里简要罗列下:
1. UCViewTitle:首页标题栏视图(UC首页显示UC头条)
2. UCViewHeader:首页头部导航视图(UC首页显示各个网站ICON入口)
3. UCViewContent:首页内容视图(UC首页显示新闻内容的列表)
4. UCViewTab:首页内容Tab导航视图(UC首页显示新闻分类的View)

Behavior

既然已经决定通过Behavior实现此效果,那下面几个概念就必须要弄清楚:
1. Behavior必须作用于CoordinatorLayout直接子View才会生效
2. Behavior其实是对嵌套滑动的应用,因为CoordinatorLayout其实是实现嵌套滑动,最终对嵌套滑动的执行交给Behavior来实现,所以Behavior的滑动处理必须要有能触发嵌套滑动的子View触发才会起作用

关于嵌套滑动

  1. Android实现嵌套滑动只需要实现NestedScrollingParentNestedScrollingChild这两个接口即可
  2. 在嵌套滑动过程中子View(实现NestedScrollingChild接口)会将自身的滑动情况通知父View(实现NestedScrollingParent接口),不一定是直接父View父View做完相关动作之后再通知子View,也就是子View其实是整个嵌套滑动的发起者
  3. CoordinatorLayout实现了NestedScrollingParent接口作为嵌套滑动的父View,因此如果要处理Behavior中对于滑动的相关处理,就需要有一个嵌套滑动的子View来触发这个Behavior

实现

  1. 上面分析UC首页时发现有个显示新闻的列表,因此我们可以用RecyclerView作为列表,因为RecyclerView实现了NestedScrollingChild接口,可以作为嵌套滑动的子View
  2. 因为是多个视图的同时滑动处理,所以在实现Behavior时需要选择一个依赖,这里我选择前面说过的UCViewHeader作为其他视图Behavior的依赖
  3. 在看了AppBarLayout的源码之后,发现其子类ScrollingViewBehavior继承至HeaderScrollingViewBehavior,在查看源码之后发现如下几个类可以抽出来为我们所用HeaderScrollingViewBehavior,ViewOffsetBehavior,ViewOffsetHelper
    1. HeaderScrollingViewBehavior:继承该类后,应用此BehaviorView布局时会自动在其依赖View的下方
    2. ViewOffsetBehavior:继承该类后,应用此BehaviorView在布局时会自动进行移动处理

UCViewTitleBehavior实现

UCViewTitle在初始时是不可见的,我采用设置其TopMargin为其-height让其不可见,然后在滑动过程中再慢慢滑动到可见,当其完全可见时滑动结束,因此滑动结束时UCViewTitle滑动的距离为UCViewTitle的高度值

下面的代码中涉及到Behavior在处理滑动过程中一些函数的实现及作用这里就不再说明,不清楚滑动过程中各个函数的作用可以参考我的上篇文章自定义Behavior实现快速返回效果,这篇文章里有介绍Behavior中各个函数的作用

同时UCViewTitle与其依赖UCViewHeader为反向滑动,关键代码如下:

public class UCViewTitleBehavior extends ViewOffsetBehavior<View> {...@Overridepublic boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {//因为UCViewTitle默认是在屏幕外不可见,所以在UCViewTitle进行布局的时候设置其topMargin让其不可见((CoordinatorLayout.LayoutParams) child.getLayoutParams()).topMargin = -child.getMeasuredHeight();return super.onLayoutChild(parent, child, layoutDirection);}@Overridepublic boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {return isDependOn(dependency);}@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {//因为UCViewTitle与UCViewHeader的滑动方向相反//所以当依赖UCViewHeader发生变化时,只需要时设置反向的translationY即可child.setTranslationY(-dependency.getTranslationY());return false;}private boolean isDependOn(View dependency) {//确定UCViewHeader作为依赖return dependency != null && dependency.getId() == R.id.news_view_header_layout;}
}

UCViewTabBehavior实现

上面已经说了,当UCViewTitle完全可见时即代表整个滑动结束。因此在这个过程中UCViewTab整个滑动的距离即为UCViewHeader的高度减去UCViewTitle的高度。而且因为是同向滑动,所以在依赖位置发生变化时,我们只需要根据依赖视图因滑动而产生的translationY计算出UCViewTitletranslationY即可。计算方式见下面代码和注释:

public class UCViewTabBehavior extends HeaderScrollingViewBehavior {private int mTitleViewHeight = 0;... @Overrideprotected void layoutChild(CoordinatorLayout parent, View child, int layoutDirection) {//UCViewTitle高度mTitleViewHeight = parent.findViewById(R.id.news_view_title_layout).getMeasuredHeight();super.layoutChild(parent, child, layoutDirection);}@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {//UCViewTab要滑动的距离为Header的高度减去TitleView的高度float offsetRange = mTitleViewHeight - dependency.getMeasuredHeight();//当Header向上滑动mTitleViewHeight高度后,即滑动完成int headerOffsetRange = -mTitleViewHeight;if(dependency.getTranslationY() == headerOffsetRange) {//Header已经上滑结束child.setTranslationY(offsetRange);} else if(dependency.getTranslationY() == 0) {//下滑结束,也是初始化的状态child.setTranslationY(0);} else {//UCViewTab与UCViewHeader为同向滑动//根据依赖UCViewHeader的滑动比例计算当前UCViewTab应该要滑动的值translationY,依赖的translationY为正值则其也为正值反之亦然child.setTranslationY(dependency.getTranslationY() / (headerOffsetRange * 1.0f) * offsetRange);}return false;}...
}

UCViewContentBehavior实现

UCViewTab一样,UCViewContent与依赖UCViewHeader也是同向滑动,其滑动过程中translationY的计算方式也是一样的。只是滑动过程中UCViewContent的滑动总距离为依赖UCViewHeader的高度减去UCViewTab的高度和UCViewTitle高度,其对应的Behavior实现关键代码如下:

public class UCViewContentBehavior extends HeaderScrollingViewBehavior {private int mTitleViewHeight = 0;private int mTabViewHeight = 0;...@Overrideprotected void layoutChild(CoordinatorLayout parent, View child, int layoutDirection) {mTitleViewHeight = parent.findViewById(R.id.news_view_title_layout).getMeasuredHeight();mTabViewHeight = parent.findViewById(R.id.news_view_tab_layout).getMeasuredHeight();super.layoutChild(parent, child, layoutDirection);}@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {int headerOffsetRange = -mTitleViewHeight;//因为UCViewContent与依赖UCViewHeader为同向滑动//所以UCViewHeader向上滑即translationY为负数时,UCViewContent也向上滑其translationY也为负数//所以UCViewHeader向上滑即translationY为正数时,UCViewContent也向上滑其translationY也为正数//而headerOffsetRange为负数,getScrollRange(dependency)为正数,所以最前面要加上一个负号//计算方式与UCViewTab的计算方式一样child.setTranslationY(-dependency.getTranslationY() / (headerOffsetRange * 1.0f) * getScrollRange(dependency));return false;}@Overrideprotected int getScrollRange(View dependency) {if(isDependency(dependency)) {//UCViewHeader的高度,减去UCViewTab和UCViewTitle的高度就是UCViewContent要滑动的高度return dependency.getMeasuredHeight() - mTitleViewHeight - mTabViewHeight;}return super.getScrollRange(dependency);}...
}

UCViewHeaderBehavior实现

UCViewHeader需要处理好何时滑动结束,何时可以滑动,松开手指时该如何处理

在我的实现中以每次实际滑动距离的1/4作为UCViewHeader的滑动值,而在松开手指时如果滑动达到整个滑动距离的1/4则会自动滑动到结束,否则则会自动滑动到初始位置,下面是该Behavior的完整代码

public class UCViewHeaderBehavior extends ViewOffsetBehavior<View> {private int mTitleViewHeight = 0;private OverScroller mOverScroller;private WeakReference<View> mChild;public static final int STATE_OPENED = 0;public static final int STATE_CLOSED = 1;public static final int DURATION_SHORT = 300;public static final int DURATION_LONG = 600;private int mCurState = STATE_OPENED;public UCViewHeaderBehavior() {super();}public UCViewHeaderBehavior(Context context, AttributeSet attrs) {super(context, attrs);mOverScroller = new OverScroller(context);}@Overrideprotected void layoutChild(CoordinatorLayout parent, View child, int layoutDirection) {super.layoutChild(parent, child, layoutDirection);mTitleViewHeight = parent.findViewById(R.id.news_view_title_layout).getMeasuredHeight();mChild = new WeakReference<>(child);}@Overridepublic boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {//开始滑动的条件,垂直方向滑动,滑动未结束return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL && canScroll(child, 0) && !isClosed(child);}/*** 当前是否可以滑动* @param child* @param pendingDy     Y轴方向滑动的translationY* @return*/private boolean canScroll(View child, float pendingDy) {int pendingTranslationY = (int) (child.getTranslationY() - pendingDy);if (pendingTranslationY >= getHeaderOffsetRange() && pendingTranslationY <= 0) {return true;}return false;}@Overridepublic void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);//开始滑动之前的逻辑处理//dy>0 向上滑//dy<0 向下滑float halfOfDis = dy / 4.0f;//每次以滑动的1/4作为滑动距离进行滑动if (!canScroll(child, halfOfDis)) {//滑动结束if(halfOfDis > 0) {child.setVisibility(View.GONE);//滑动结束后,隐藏此视图child.setTranslationY(getHeaderOffsetRange());} else {child.setTranslationY(0);}} else {//滑动未结束if(halfOfDis <= 0) {child.setVisibility(View.VISIBLE);}//滑动child.setTranslationY(child.getTranslationY() - halfOfDis);}//消耗掉当前垂直方向上的滑动距离consumed[1] = dy;}/*** 向上滑动过程时translationY的最小值* @return*/private int getHeaderOffsetRange() {return -mTitleViewHeight;}@Overridepublic boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {if(ev.getAction() == MotionEvent.ACTION_UP) {//对松开手指时进行处理,如果松开时滑动滑动了1/4则自动滑动到结束,否则则回归原位handlerActionUp(child);}return super.onInterceptTouchEvent(parent, child, ev);}private void handlerActionUp(View child) {if (mFlingRunnable != null) {child.removeCallbacks(mFlingRunnable);mFlingRunnable = null;}mFlingRunnable = new FlingRunnable(child);if (child.getTranslationY() < getHeaderOffsetRange() / 4.0f) {mFlingRunnable.scrollToClosed(DURATION_SHORT);} else {mFlingRunnable.scrollToOpen(DURATION_SHORT);}}private void onFlingFinished(View layout) {boolean isClosed = isClosed(layout);mCurState = isClosed ? STATE_CLOSED : STATE_OPENED;if(isClosed) {layout.setVisibility(View.GONE);}}/*** 是否滑动结束* @param child* @return*/private boolean isClosed(View child) {return child.getTranslationY() == getHeaderOffsetRange();}public boolean isClosed() {return mCurState == STATE_CLOSED;}public void openPager() {openPager(DURATION_LONG);}/*** @param duration open animation duration*/public void openPager(int duration) {View child = mChild.get();if (isClosed() && child != null) {if(child.getVisibility() == View.GONE) {child.setVisibility(View.VISIBLE);}if (mFlingRunnable != null) {child.removeCallbacks(mFlingRunnable);mFlingRunnable = null;}mFlingRunnable = new FlingRunnable(child);mFlingRunnable.scrollToOpen(duration);}}public void closePager() {closePager(DURATION_LONG);}/*** @param duration close animation duration*/public void closePager(int duration) {View child = mChild.get();if (!isClosed()) {if (mFlingRunnable != null) {child.removeCallbacks(mFlingRunnable);mFlingRunnable = null;}mFlingRunnable = new FlingRunnable(child);mFlingRunnable.scrollToClosed(duration);}}private FlingRunnable mFlingRunnable;private class FlingRunnable implements Runnable {private final View mLayout;FlingRunnable(View layout) {mLayout = layout;}public void scrollToClosed(int duration) {float curTranslationY = ViewCompat.getTranslationY(mLayout);float dy = getHeaderOffsetRange() - curTranslationY;mOverScroller.startScroll(0, Math.round(curTranslationY - 0.1f), 0, Math.round(dy + 0.1f), duration);start();}public void scrollToOpen(int duration) {float curTranslationY = ViewCompat.getTranslationY(mLayout);mOverScroller.startScroll(0, (int) curTranslationY, 0, (int) -curTranslationY, duration);start();}private void start() {if (mOverScroller.computeScrollOffset()) {mFlingRunnable = new FlingRunnable(mLayout);ViewCompat.postOnAnimation(mLayout, mFlingRunnable);} else {onFlingFinished(mLayout);}}@Overridepublic void run() {if (mLayout != null && mOverScroller != null) {if (mOverScroller.computeScrollOffset()) {ViewCompat.setTranslationY(mLayout, mOverScroller.getCurrY());ViewCompat.postOnAnimation(mLayout, this);} else {onFlingFinished(mLayout);}}}}
}

上面就是涉及到的四个视图对应的Behavior,从上面代码的介绍可以看出使用Behavior实现该效果比自定义View实现该效果要简单省事很多而且难度也不大,可见掌握Behavior对开发者来说是很有必要的。

完整代码戳这里


大家如果有问题可以加QQ群交流:106510493

Behavior实现UC浏览器首页动画效果相关推荐

  1. UC浏览器首页滑动动画实现

    UC浏览器首页滑动动画实现 我们先来看下UC浏览器首页的滑动动画和我最终实现的动画效果 使用方式 <cn.ittiger.ucpage.view.UCIndexViewxmlns:android ...

  2. android 高仿UC浏览器首页上拉面板效果

    最近在项目中,产品经理看见uc浏览器首页的上拉面板的效果做的非常不错,于是希望我们的项目的首页也做成这样的效果.于是经过思考后,实现了一个仿uc浏览器的上拉面板效果. 接下来说一下实现的思路吧 . 首 ...

  3. android仿微信红包动画、Kotlin综合应用、Xposed模块、炫酷下拉视觉、UC浏览器滑动动画等源码...

    Android精选源码 仿微信打开红包旋转动画 使用Kotlin编写的Android应用,内容你想象不到 Android手机上的免Root Android系统日志Viewer 一个能让微信 Mater ...

  4. android仿微信红包动画、Kotlin综合应用、Xposed模块、炫酷下拉视觉、UC浏览器滑动动画等源码

    Android精选源码 仿微信打开红包旋转动画 使用Kotlin编写的Android应用,内容你想象不到 Android手机上的免Root Android系统日志Viewer 一个能让微信 Mater ...

  5. 11.1011.首页动画效果和书架的实现

    Chapter:11.前端页面开发 11.10&11.首页动画效果和书架的实现 实现思路 再写一个书架模块 index-shelf.html ,并且调整首页元素的位置,将该模块与首页 商城 对 ...

  6. android 语音搜索动画,Android自定义控件实现UC浏览器语音搜索效果

    最近项目上要实现语音搜索功能,界面样式要模仿一下UC浏览器的样式,UC浏览器中有一个控件,会随着声音大小浮动,然后寻思偷个懒,百度一下,结果也没有找到类似的,只能自己动手了. 先上图看我实现的效果: ...

  7. 仿菁优网首页动画效果

    原文链接 1.菁优网首页动画效果图 2.动画效果分析 2.1.动画效果一定是UIView动画,因为核心动画是CALayer的动画效果给我们的位移假象,视图的真实位置并没有发生变化.在首页的动画中,按钮 ...

  8. uc如何HTML编辑,UC浏览器首页这些快捷网址导航如何编辑,怎么修..._网络编辑_帮考网...

    在UC浏览器打开下拉菜单后,找到"设置"这个按钮,点击打开,在"常规"里面有一个"浏览器主页保护"找到这个,你不管它当时是怎么设置的,你现在 ...

  9. animation 在 电视版的 UC 浏览器没动画

    公司配了个电视版的大屏幕,放一个后台数据首页面大屏: 在高德地图里的点加入闪闪的动画,发现在电脑上好好的动画,到了那都不会动了, 下载了个电脑版的 UC 发现是好的,想到可能是电视版本的的 UC 版本 ...

最新文章

  1. SQL*Plus 系统变量之15 - DESC[RIBE]
  2. pandas处理mysql 展现wpf_Python:用Pandas读CSV文件写到MySQL
  3. springmvc框架介绍_Java修行第071天 ---SpringMVC(上)
  4. 六元均匀直线阵的各元间距为_实验二 均匀直线阵
  5. 操作系统(6)-协程
  6. Windows 7 建立 ×××网络
  7. Ubuntu18.04没有WiFi怎么解决(图文详解)
  8. 贪吃蛇程序 php,PHP下利用PHPMailer Web程序【tofacebook.com】 - 贪吃蛇
  9. Flask初级(三)flash使用模板
  10. clamwin + 拖拽查毒+右键查毒
  11. python中如何查一个函数的用法_Python常见内置函数用法(三)
  12. HelloWorld之jetty运行
  13. Blender基础:多边形建模中F命令和J命令的区别
  14. 来了!新一代 App 视觉增强辅助方案它真的来了!
  15. [笔记分享] [Camera] MTK Camera基础知识二
  16. 基于统计复用的分组交换网络拥塞控制的科普解释
  17. 高德地图API和百度地图API哪个更适合开发者?
  18. 简单的php+Mysql管理系统
  19. 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(Finchley版本)
  20. 六年级计算机课学什么意思,六年级信息技术上册《第一单元第2课与计算机交朋友》教案及教学反思...

热门文章

  1. 互联网日报 | 百度推出企业查询工具“爱企查”;滴滴货运宣布再开6城;京东健康推出家庭医生服务...
  2. bnuoj 50394 Censor
  3. 前端js实现拼图小游戏
  4. 【Visual Studio Code】VS Code在Linux/Mac/Windows中向前、向后定位的快捷键及修改方法
  5. 机械师T58-V加装机械硬盘
  6. 【云服务器】小bai(白):我想上云,但是怕不安全和浪费资金,怎么办呢
  7. MCMChybrid GP_5.4 R语言
  8. 华为hicar是鸿蒙系统,鸿蒙之后,华为官宣HiCar智慧车载系统
  9. 五、移植u-boot-2016.03到Jz2440之修改代码支持NOR Flash
  10. STM8L051 GPIO PC0 PC1无法上拉的问题