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

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. JSP JSTL EL

    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> Html代码 复制代 ...

  2. swoole

    http://www.swoole.com/wiki/index/prid-1-p-project/road_map.html

  3. paip.简化字-手写参考二简字..共98个

    paip.简化字-手写参考二简字..共98个 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn.net/a ...

  4. 关闭Windows Update更新驱动程序

    关于Win10的更新配置,特别是自动更新驱动程序,经常会导致驱动安装错误而无法开机的问题. 此时只好开机时按F8进入高级模式恢复最后一次正确配置,或者在安全模式删除错误的驱动程序. 关于Win10的更 ...

  5. 无锁编程以及CAS

    无锁编程 / lock-free / 非阻塞同步 无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Sy ...

  6. 由1433端口入侵,浅谈sqlserver安全 (转)

    前几日笔者在家里的PC上安装了Windows7旗舰版的操作系统,顺便搭了sqlserver2008和vs2010的开发环境,本打算业余时 间可以方便开发.学习.可是不尽人意啊!用了不到两天,居然突然出 ...

  7. mac上使用终端生成RSA公钥和密钥

    首先确保你的电脑上安装了openssl,一般mac系统安装后都会自动安装!怎么安装??.....请百度...... 安装命令如下: sudo apt-get install openssl 在你的任何 ...

  8. Ques核心思想——CSS Namespace

    Facebook’s challenges are applicable to any very complex websites with many developers. Or any situa ...

  9. 在gradle 中使用ant 执行 “命令行”(CMD)不出日志解决方案

    因为gradle 好恶心,声明的任务,一定会事先运行一次,而任务追加的话就不会 例如: task hello(){ println "HelloWorld" } task hell ...

  10. 调用axis2开发的接口遇到的问题

    第1个异常 [org.apache.struts.actions.DispatchAction] – Dispatch[/myservice/NgCallServiceInfo]  to method ...