Android实现网易严选标签栏滑动效果

发布时间 - 2026-01-11 02:24:03    点击率:

标签栏是一个非常常见的控件,似乎也是一个比较简单的控件,但如果在标签下方加个下划线的话,就还是可以玩出挺多花来的。

网易严选的标签栏就做的很不错,里面隐藏着诸多细节:

  • 手动滑动页面,下划线会跟着滑动。
  • 选择一个标签后,下划线会有滑动过去的动画。
  • 选择最左端或最右端的标签,标签栏会进行滑动,使得标签向中间靠拢(如果可以滑的话)。

仔细分析下,需要在简单标签栏的基础上实现以下逻辑:

  • 画出下划线。
  • 监听手动滑动页面事件,实时更新下划线位置。
  • 切换标签时,开始下划线滑动的动画,并判断是否要同时滑动标签栏。

我做了一个样例程序,其中的较难点在于计算下划线的位置,和下划线的动画效果。

// 根据当前选定的tab,得到indicator应该移动到的位置 
private Pair<Float, Float> getIndicatorTargetLeftRight(int position, float positionOffset) { 
  View tab = tabsContainer.getChildAt(position); 
  Pair<Float, Float> indicator = getIndicatorLeftRight(tab); 
  float targetLeft = indicator.first; 
  float targetRight = indicator.second; 
  // 如果positionOffset不为0,indicator正处于两个tab之间,需进行加权计算得到它的位置 
  if (positionOffset > 0f && position < tabCount - 1) { 
    View nextTab = tabsContainer.getChildAt(position + 1); 
    Pair<Float, Float> indicatorForNextTab = getIndicatorLeftRight(nextTab); 
    float left = indicatorForNextTab.first; 
    float right = indicatorForNextTab.second; 
    targetLeft = (positionOffset * left + (1f - positionOffset) * targetLeft); 
    targetRight = (positionOffset * right + (1f - positionOffset) * targetRight); 
  } 
  return new Pair<>(targetLeft, targetRight); 
} 
 
private Pair<Float, Float> getIndicatorLeftRight(View tab) { 
  float left = tab.getLeft(); 
  float right = tab.getRight(); 
  if (indicatorMode == IndicatorMode.WRAP && tab instanceof TextView) { 
    TextView tabTextView = (TextView) tab; 
    paint.setTextSize(tabTextView.getTextSize()); 
    float textLength = paint.measureText(tabTextView.getText().toString()); 
    float middle = (left + right) / 2f; 
    left = middle - textLength / 2f; 
    right = middle + textLength / 2f; 
  } 
  return new Pair<>(left, right); 
} 

上面是计算下划线位置的代码,通过传入在onPageScrolled()中获得的position和positionOffset,计算下划线是在某一个标签下,或者某两个标签之间的位置。需要注意的是,由于各标签的长度可能不一,所以下划线的长度在滑动中也可能发生变化,所以需分别计算下划线的left和right。

private boolean isAnimateRunning = false; 
private static final String TARGET_LEFT = "targetLeft"; 
private static final String TARGET_RIGHT = "targetRight"; 
 
private void startIndicatorAnimate(final float targetLeft, final float targetRight) { 
  // 在indicator超出屏幕范围时,让其从屏幕边界处开始移动 
  float move = 0; 
  if (indicatorCurrentRight < getScrollX()) { 
    move = getScrollX() - indicatorCurrentRight; 
  } else if (indicatorCurrentLeft > getScrollX() + DimenUtil.getScreenWidth(getContext())) { 
    move = getScrollX() + DimenUtil.getScreenWidth(getContext()) - indicatorCurrentLeft; 
  } 
  indicatorCurrentLeft += move; 
  indicatorCurrentRight += move; 
 
  PropertyValuesHolder valuesHolderLeft = PropertyValuesHolder.ofFloat( 
      TARGET_LEFT, indicatorCurrentLeft, targetLeft); 
  PropertyValuesHolder valuesHolderRight = PropertyValuesHolder.ofFloat( 
      TARGET_RIGHT, indicatorCurrentRight, targetRight); 
  ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(valuesHolderLeft, valuesHolderRight) 
      .setDuration(SCROLL_DURATION); 
  animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
    @Override 
    public void onAnimationUpdate(ValueAnimator animation) { 
      if (indicatorCurrentLeft != targetLeft) { 
        indicatorCurrentLeft = (float) animation.getAnimatedValue(TARGET_LEFT); 
      } 
      if (indicatorCurrentRight != targetRight) { 
        indicatorCurrentRight = (float) animation.getAnimatedValue(TARGET_RIGHT); 
      } 
      if (indicatorCurrentLeft == targetLeft && indicatorCurrentRight == targetRight) { 
        isAnimateRunning = false; 
      } 
      invalidate(); 
    } 
  }); 
  animator.start(); 
  isAnimateRunning = true; 
} 

这是切换标签时下划线运行滑动动画的代码,使用ValueAnimator实现,并且对下划线超出边界的情况做了特殊处理,以防止滑动距离过大时,滑动速度过快。

更多的细节,请见https://github.com/wlkdb/page_sliding

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# Android  # 标签栏  # 滑动  # Android Navigation TabBar控件实现多彩标签栏  # android配合viewpager实现可滑动的标签栏示例分享  # Android View教程之自定义验证码输入框效果  # Android自定义View实现课程表表格  # Android自定义view实现圆的扩散效果  # Android自定义view实现标签栏功能(只支持固定两个标签)  # 下划线  # 的是  # 这是  # 左端  # 是在  # 会有  # 基础上  # 很不错  # 网易  # 不为  # 中也  # 过大  # 过快  # 正处于  # 是一个非常  # 时下  # 画出  # 就做  # 需要注意  # 让其 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 如何基于云服务器快速搭建网站及云盘系统?  深圳网站制作平台,深圳市做网站好的公司有哪些?  网页制作模板网站推荐,网页设计海报之类的素材哪里好?  网站建设保证美观性,需要考虑的几点问题!  如何挑选高效建站主机与优质域名?  Python面向对象测试方法_mock解析【教程】  如何彻底卸载建站之星软件?  HTML 中如何正确使用模板变量为元素的 name 属性赋值  奇安信“盘古石”团队突破 iOS 26.1 提权  智能起名网站制作软件有哪些,制作logo的软件?  企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?  北京的网站制作公司有哪些,哪个视频网站最好?  Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解  关于BootStrap modal 在IOS9中不能弹出的解决方法(IOS 9 bootstrap modal ios 9 noticework)  高防服务器租用首荐平台,企业级优惠套餐快速部署  JavaScript如何实现错误处理_try...catch如何捕获异常?  Win11怎么设置默认图片查看器_Windows11照片应用关联设置  如何挑选优质建站一级代理提升网站排名?  三星网站视频制作教程下载,三星w23网页如何全屏?  Laravel如何使用Blade组件和插槽?(Component代码示例)  android nfc常用标签读取总结  Laravel如何实现用户角色和权限系统_Laravel角色权限管理机制  Laravel Eloquent关联是什么_Laravel模型一对一与一对多关系精讲  如何实现建站之星域名转发设置?  微信小程序 配置文件详细介绍  Laravel如何使用Laravel Vite编译前端_Laravel10以上版本前端静态资源管理【教程】  Laravel如何实现多语言支持_Laravel本地化与国际化(i18n)配置教程  php8.4header发送头信息失败怎么办_php8.4header函数问题解决【解答】  Laravel怎么判断请求类型_Laravel Request isMethod用法  Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例  Laravel API资源类怎么用_Laravel API Resource数据转换  Laravel N+1查询问题如何解决_Eloquent预加载(Eager Loading)优化数据库查询  Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】  移动端手机网站制作软件,掌上时代,移动端网站的谷歌SEO该如何做?  惠州网站建设制作推广,惠州市华视达文化传媒有限公司怎么样?  Laravel Docker环境搭建教程_Laravel Sail使用指南  如何在橙子建站上传落地页?操作指南详解  Laravel Blade模板引擎语法_Laravel Blade布局继承用法  为什么php本地部署后css不生效_静态资源加载失败修复技巧【技巧】  Laravel如何处理文件下载请求?(Response示例)  音乐网站服务器如何优化API响应速度?  Laravel怎么配置S3云存储驱动_Laravel集成阿里云OSS或AWS S3存储桶【教程】  Laravel 419 page expired怎么解决_Laravel CSRF令牌过期处理  Laravel如何实现密码重置功能_Laravel密码找回与重置流程  Laravel如何处理JSON字段_Eloquent原生JSON字段类型操作教程  Laravel中间件如何使用_Laravel自定义中间件实现权限控制  Android仿QQ列表左滑删除操作  简历在线制作网站免费版,如何创建个人简历?  如何在建站宝盒中设置产品搜索功能?  深圳网站制作的公司有哪些,dido官方网站?