• Making Your ActionBar Not Boring


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

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

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

    The ActionBar trick

    Styles:

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

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



    布局结构

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

    1. <?xml version="1.0" encoding="utf-8"?>  
    2.   
    3. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    4.     xmlns:tools="http://schemas.android.com/tools"  
    5.     android:layout_width="match_parent"  
    6.     android:layout_height="match_parent"  
    7.     tools:context=".NoBoringActionBarActivity">  
    8.   
    9.     <ListView  
    10.         android:id="@+id/listview"  
    11.         android:layout_width="match_parent"  
    12.         android:layout_height="match_parent"  
    13.         android:background="@android:color/white" />  
    14.   
    15.     <FrameLayout  
    16.         android:id="@+id/header"  
    17.         android:layout_width="match_parent"  
    18.         android:layout_height="@dimen/header_height">  
    19.   
    20.         <com.flavienlaurent.notboringactionbar.KenBurnsView  
    21.             android:id="@+id/header_picture"  
    22.             android:layout_width="match_parent"  
    23.             android:layout_height="match_parent"  
    24.             android:src="@drawable/picture0" />  
    25.   
    26.         <ImageView  
    27.             android:id="@+id/header_logo"  
    28.             android:layout_width="@dimen/header_logo_size"  
    29.             android:layout_height="@dimen/header_logo_size"  
    30.             android:layout_gravity="center"  
    31.             android:src="@drawable/ic_header_logo" />  
    32.   
    33.     </FrameLayout>  
    34.   
    35. </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.   
    6. </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.   
    7.     int firstVisiblePosition = mListView.getFirstVisiblePosition();  
    8.     int top = c.getTop();  
    9.   
    10.     int headerHeight = 0;  
    11.     if (firstVisiblePosition >= 1) {  
    12.         headerHeight = mPlaceHolderView.getHeight();  
    13.     }  
    14.   
    15.     return -top + firstVisiblePosition * c.getHeight() + headerHeight;  
    16. }<span style="font-family:SimSun;font-size:18px;">  
    17. </span>  


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

    移动题头

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

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



    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.   
    6.         @Override  
    7.         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {  
    8.             float ratio = clamp(mHeader.getTranslationY() / mMinHeaderTranslation, 0.0f, 1.0f);  
    9.             //actionbar title alpha  
    10.             getActionBarTitleView().setAlpha(clamp(5.0F * ratio - 4.0F, 0.0F, 1.0F));  
    11.         }  
    12.     });  



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



     

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

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


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



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

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


    移动&缩放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.   
    6.         @Override  
    7.         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {  
    8.             float ratio = clamp(mHeader.getTranslationY() / mMinHeaderTranslation, 0.0f, 1.0f);  
    9.             //move & scale  
    10.   
    11.             interpolation = mAccelerateDecelerateInterpolator.getInterpolation(ratio);  
    12.   
    13.             View actionBarIconView = getActionBarIconView();  
    14.   
    15.             getOnScreenRect(mRect1, mHeaderLogo);  
    16.             getOnScreenRect(mRect2, actionBarIconView);  
    17.   
    18.             float scaleX = 1.0F + interpolation  (mRect2.width() / mRect1.width() – 1.0F);  
    19.             float scaleY = 1.0F + interpolation  (mRect2.height() / mRect1.height() – 1.0F);  
    20.             float translationX = 0.5F  (interpolation  (mRect2.left + mRect2.right – mRect1.left – mRect1.right));  
    21.             float translationY = 0.5F  (interpolation  (mRect2.top + mRect2.bottom – mRect1.top – mRect1.bottom));  
    22.   
    23.             mHeaderLogo.setTranslationX(translationX);  
    24.             mHeaderLogo.setTranslationY(translationY – mHeader.getTranslationY());  
    25.             mHeaderLogo.setScaleX(scaleX);  
    26.             mHeaderLogo.setScaleY(scaleY);  
    27.         }  
    28.     });  

        注意你也可以用 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/

  • 相关阅读:
    安卓系统源码编译系列(六)——单独编译内置浏览器WebView教程
    android4.0浏览器在eclipse中编译的步骤
    android.net.wifi的简单使用方法
    android:sharedUserId 获取系统权限
    android 常见死机问题--log分析
    android anr分析方法
    android的logcat详细用法
    Android 设计模式 之 观察者模式
    Android 设计模式 之 单例模式
    android 自定义控件 使用declare-styleable进行配置属性(源码角度)
  • 原文地址:https://www.cnblogs.com/xyzlmn/p/3684814.html
Copyright © 2020-2023  润新知