这篇文章转自国外一个技术大牛的博客,首先感谢这位大牛的无私奉献。

Android应用中有一名位 Google书报摊的应用,他实现了一种新的ActionBar风格。当用户初始进入该界面的时候,为一个透明的 ActiionBar ,这样利用充分的空间显示大图片,如果用户滚动页面需要查看内容的时候,则大图收缩到 ActionBar 中。

这个的主要优势是使ActionBar和内容完美的结合在一起,整个操作看起来浑然天成,给人一种新奇的感觉。这篇文章将会讲解ActionBar效果和 Ken Burns动画效果的实现。

The ActionBar trick

Styles:

第一步先制作合适的Style,这里需要使用ActionBar的overlay模式并设置透明的ActionBar背景。

  1. <resources>
  2. <style name="TransparentTheme" parent="@android:style/Theme.Holo.Light">
  3. <item name="android:windowBackground">@null</item>
  4. <item name="android:actionBarStyle">@style/ActionBarStyle.Transparent</item>
  5. <item name="android:windowActionBarOverlay">true</item>
  6. </style>
  7. <style name="ActionBarStyle.Transparent" parent="@android:Widget.ActionBar">
  8. <item name="android:background">@null</item>
  9. <item name="android:displayOptions">homeAsUp|showHome|showTitle</item>
  10. <item name="android:titleTextStyle">@style/ActionBarStyle.Transparent.TitleTextStyle</item>
  11. </style>
  12. <style name="ActionBarStyle.Transparent.TitleTextStyle" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
  13. <item name="android:textColor">@android:color/white</item>
  14. </style>
  15. </resources>

布局结构

布局结构是非常重要,主要的布局是一个由ListView和另一个的FrameLayout(即题图)组成的FrameLayout。题图包含两个图片,一个背景大图(即header_picture),一个logo图像(即header_logo)。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. tools:context=".NoBoringActionBarActivity">
  7. <ListView
  8. android:id="@+id/listview"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"
  11. android:background="@android:color/white" />
  12. <FrameLayout
  13. android:id="@+id/header"
  14. android:layout_width="match_parent"
  15. android:layout_height="@dimen/header_height">
  16. <com.flavienlaurent.notboringactionbar.KenBurnsView
  17. android:id="@+id/header_picture"
  18. android:layout_width="match_parent"
  19. android:layout_height="match_parent"
  20. android:src="@drawable/picture0" />
  21. <ImageView
  22. android:id="@+id/header_logo"
  23. android:layout_width="@dimen/header_logo_size"
  24. android:layout_height="@dimen/header_logo_size"
  25. android:layout_gravity="center"
  26. android:src="@drawable/ic_header_logo" />
  27. </FrameLayout>
  28. </FrameLayout>

通过在 ListView 上添加一个高度和 题图一样高的 虚拟 header view 来实现该动画。 可以用一个布局文件来作为该虚拟 header 的 view。

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="@dimen/header_height"
  4. android:orientation="vertical">
  5. </LinearLayout>

使用inflate添加上虚拟 header view

  1. mFakeHeader = getLayoutInflater().inflate(R.layout.fake_header, mListView, false);
  2. mListView.addHeaderView(mFakeHeader);

获取Scroll位置

布局文件搞定,需要计算出ListView的滚动位置

  1. public int getScrollY() {
  2. View c = mListView.getChildAt(0);
  3. if (c == null) {
  4. return 0;
  5. }
  6. int firstVisiblePosition = mListView.getFirstVisiblePosition();
  7. int top = c.getTop();
  8. int headerHeight = 0;
  9. if (firstVisiblePosition >= 1) {
  10. headerHeight = mPlaceHolderView.getHeight();
  11. }
  12. return -top + firstVisiblePosition * c.getHeight() + headerHeight;
  13. }<span style="font-family:SimSun;font-size:18px;">
  14. </span>

特别提示,如果listview第一个可视视图位置大于1,需要计算虚拟视图的高度。

移动题头

伴随着listview的滚动,你需要移动题头,以跟踪虚拟题头的移动。这些移动以ActionBar的高度为边界。

  1. mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
  2. @Override
  3. public void onScrollStateChanged(AbsListView view, int scrollState) {
  4. }
  5. @Override
  6. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  7. int scrollY = getScrollY();
  8. //sticky actionbar
  9. mHeader.setTranslationY(Math.max(-scrollY, mMinHeaderTranslation));
  10. }
  11. });

Title渐变

这里的Title有个渐变效果,他是怎么实现的呢,首先获取到这个view,使用的Resources.getIdentifier方法。

  1. private TextView getActionBarTitleView() {
  2. int id = Resources.getSystem().getIdentifier("action_bar_title", "id", "android");
  3. return (TextView) findViewById(id);
  4. }

然后设置初始的 alpha 值。

  1. getActionBarTitleView().setAlpha(0f);

在 ListView 滚动的时候,计算该 alpha 值。

  1. mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
  2. @Override
  3. public void onScrollStateChanged(AbsListView view, int scrollState) {
  4. }
  5. @Override
  6. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  7. float ratio = clamp(mHeader.getTranslationY() / mMinHeaderTranslation, 0.0f, 1.0f);
  8. //actionbar title alpha
  9. getActionBarTitleView().setAlpha(clamp(5.0F * ratio - 4.0F, 0.0F, 1.0F));
  10. }
  11. });

Alpha 值的变化方程式为 f(x) = 5x-4。关于该方程式参考:Wikipedia

而关于标题的淡出Cyril Mottier提供了一个更好的方案。

在该方案中无需获取 ActionBar title view。使用一个具有自定义 ForegroundColorSpan 的  SpannableString  。然后在该  SpannableString  上设置文字的 Alpha 值。

  1. public class AlphaForegroundColorSpan extends ForegroundColorSpan {
  2. private float mAlpha;
  3. public AlphaForegroundColorSpan(int color) {
  4. super(color);
  5. }
  6. […]
  7. @Override
  8. public void updateDrawState(TextPaint ds) {
  9. ds.setColor(getAlphaColor());
  10. }
  11. public void setAlpha(float alpha) {
  12. mAlpha = alpha;
  13. }
  14. public float getAlpha() {
  15. return mAlpha;
  16. }
  17. private int getAlphaColor() {
  18. int foregroundColor = getForegroundColor();
  19. return Color.argb((int) (mAlpha * 255), Color.red(foregroundColor), Color.green(foregroundColor), Color.blue(foregroundColor));
  20. }
  21. }

滚动的时候修改该  SpannableString  的 Alpha值并设置为 Title,使用同样的 AlphaForegroundColorSpan 和 SpannableString 避免频繁 GC 来提升性能。

  1. mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
  2. @Override
  3. public void onScrollStateChanged(AbsListView view, int scrollState) {
  4. }
  5. @Override
  6. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  7. float ratio = clamp(mHeader.getTranslationY() / mMinHeaderTranslation, 0.0f, 1.0f);
  8. //actionbar title alpha
  9. setTitleAlpha(clamp(5.0F * ratio – 4.0F, 0.0F, 1.0F));
  10. }
  11. });
  12. private void setTitleAlpha(float alpha) {
  13. mAlphaForegroundColorSpan.setAlpha(alpha);
  14. mSpannableString.setSpan(mAlphaForegroundColorSpan, 0, mSpannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  15. getActionBar().setTitle(mSpannableString);
  16. }

移动&缩放icon

先获取该 图标 View, 然后在 ActionBar 上设置一个透明的图标。

  1. private ImageView getActionBarIconView() {
  2. return (ImageView) findViewById(android.R.id.home);
  3. }
  1. ActionBar actionBar = getActionBar();
  2. actionBar.setIcon(R.drawable.ic_transparent);

当 ListView 滚动时候,根据 header 的高度来移动和缩放图标。该缩放和位移是根据两个图标的位置关系和大小关系来计算的。 代码如下:

  1. mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
  2. @Override
  3. public void onScrollStateChanged(AbsListView view, int scrollState) {
  4. }
  5. @Override
  6. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  7. float ratio = clamp(mHeader.getTranslationY() / mMinHeaderTranslation, 0.0f, 1.0f);
  8. //move & scale
  9. interpolation = mAccelerateDecelerateInterpolator.getInterpolation(ratio);
  10. View actionBarIconView = getActionBarIconView();
  11. getOnScreenRect(mRect1, mHeaderLogo);
  12. getOnScreenRect(mRect2, actionBarIconView);
  13. float scaleX = 1.0F + interpolation  (mRect2.width() / mRect1.width() – 1.0F);
  14. float scaleY = 1.0F + interpolation  (mRect2.height() / mRect1.height() – 1.0F);
  15. float translationX = 0.5F  (interpolation  (mRect2.left + mRect2.right – mRect1.left – mRect1.right));
  16. float translationY = 0.5F  (interpolation  (mRect2.top + mRect2.bottom – mRect1.top – mRect1.bottom));
  17. mHeaderLogo.setTranslationX(translationX);
  18. mHeaderLogo.setTranslationY(translationY – mHeader.getTranslationY());
  19. mHeaderLogo.setScaleX(scaleX);
  20. mHeaderLogo.setScaleY(scaleY);
  21. }
  22. });

注意你也可以用 AccelerateDecelerateInterpolator 来让动画看起来更平滑一些。

在该示例代码中还包含了一个 Ken Burns 动画,使题图可以移动,可以参考其实现:KenBurnsView.java

完整示例项目代码.下载

 

总结:

View的同步Scroll总有他的相似之处,大家要多思考。
       As it’s said here, it’s always (with a few different details) the same trick called synchronized scrolling. The true genius of this effect is to have thought about it!

参考:

http://flavienlaurent.com/blog/2013/11/20/making-your-action-bar-not-boring/

Making Your ActionBar Not Boring的更多相关文章

  1. Android中通过ActionBar为标题栏添加搜索以及分享视窗

    在Android3.0之后,Google对UI导航设计上进行了一系列的改革,其中有一个非常好用的新功能就是引入的ActionBar,他用于取代3.0之前的标题栏,并提供更为丰富的导航效果.Action ...

  2. Android 添加ActionBar Buttons

    一.在res/menu文件夹下创建Xml文件 跟标签为menu,设置item <?xml version="1.0" encoding="utf-8"?& ...

  3. mono for android 自定义titleBar Actionbar 顶部导航栏 修改 样式 学习

    以前的我是没有做笔记的习惯的,学习了后觉得自己能记住,但是最近发现很多学的东西都忘记了,所有现在一有新的知识,就记下来吧. 最近又做一个mono for android 的项目 这次调整比较大,上次做 ...

  4. Xamarin.Android之ActionBar与菜单

    一.选项卡 如今很多应用都会使用碎片以便在同一个活动中能够显示多个不同的视图.在Android 3.0 以上的版本中,我们已经可以使用ActionBar提供的Tab来实现这种效果,而不需要我们自己去实 ...

  5. 自定义ActionBar标题与菜单中的文字样式

    自定义标题文字样式 标题样式是ActionBar样式的一部分,所以要先定义ActionBar的样式 <style name="AppTheme" parent="A ...

  6. ActionBar设置自定义setCustomView()留有空白的问题

    先来看问题,当我使用ActionBar的时候,设置setCustomView时,会留有空白的处理 网上很多朋友说可以修改V7包到19,结果处理的效果也是不理想的. 下面贴出我觉得靠谱的处理代码 pub ...

  7. Android ActionBar 初探

    1.指南,例子,个人感觉 首先上官网指南链接http://developer.android.com/guide/topics/ui/actionbar.html 参考了官网上的例子http://de ...

  8. Menu与ActionBar的爱恨情仇

    最近在开发一款音乐播放器,在开发过程中遇到了一点小麻烦,通过android API搞清楚了Menu与ActionBar的爱恨情仇,写了个小Demo祭奠一下那些年我们陷进去的坑,有不对的地方请大神们批评 ...

  9. ANDROID中去掉ACTIONBAR或TABWIDGET的分隔线

    在android中,有时需要对ActionBar或者TabWidget的分隔线进行定制,如取消,相关的属性设置为android:divider 以TabWidget为例,取消对应的函数: tabWid ...

随机推荐

  1. 2-MSP430按键输入检测

    为了写一篇文章做铺垫--提醒着自己,,,,,, P1.0的电平,随着P1.1引脚输入的电平变化而变化 #include "io430.h" void delay(void) { u ...

  2. 隐藏路由器的WIFI信号,防蹭网

    进入路由器的管理界面 最后别忘了 现在信号隐藏了,现在说一下如何用手机连接隐藏的路由器

  3. Yii2中系统定义的常用路径别名,如果获取web的url

    下面这些别名都是在Yii2里面系统定义的,可以直接拿来就用 '@yii' => '@yii/swiftmailer' => string 'C:\wamp\www\advanced\ven ...

  4. IoC实践--用Unity实现MVC5.0的IoC控制反转方法

    在MVC中,控制器依赖于模型对数据进行处理,也可以说执行业务逻辑.我们可以使用依赖注入(DI)在控制层分离模型层,这边要用到Repository模式,在领域驱动设计(DDD)中,Repository翻 ...

  5. 不会用ant打包、部署项目的工程师,不是一个好程序员(测试)

    副标题:利用ant脚本 自动构建svn增量/全量 系统程序升级包 首先请允许我这样说,作为开发或测试,你一定要具备这种本领.你可以手动打包.部署你的工程,但这不是最好的方法.最好的方式就是全自动化的方 ...

  6. apache的hadoop升级到CDH hadoop2.0时遇到的问题及解决

    1:引入的jar包 1.X版本有hadoop-core包:而2.x没有 如果你需要hdfs就引入\share\hadoop\common\lib + hadoop-common-2.0.0-cdh4. ...

  7. 将 Book-Crossing Dataset 书籍推荐算法中 CVS 格式测试数据集导入到MySQL数据库

    本文内容 最近看<写给程序员的数据挖掘指南>,研究推荐算法,书中的测试数据集是 Book-Crossing Dataset 提供的亚马逊用户对书籍评分的真实数据.推荐大家看本书,写得不错, ...

  8. asp.net mvc 动态显示不同的部分视图

    首先是AJAX请求 //第一次打开 默认s单行文本 $.ajax({ type: "GET", url: "/Admin/Field/ChoiceType4Edit&qu ...

  9. Oracle 一次生产分库,升级,迁移

    今天完成了一个负载较高的中央数据库的分库操作, 并实现了oracle的滚动升级(10.2.0.1->10.2.0.4), 业务中断仅15分钟. 平台: RHEL AS 4 + Oracle 10 ...

  10. 通过微信查找SAP TCODE代码

    输入T-CODE查询作用: (包含了16000+ 个SAP T-CODE),扫码关注后可以体验效果 再也不用去记那么多T-CODE用途了 还不试试看 输入关键词:"利润中心" &q ...