Making Your ActionBar Not Boring

        這篇文章轉自國外一個技術大牛的博客,首先感謝這位大牛的無私奉獻。html

        Android應用中有一名位 Google書報攤的應用,他實現了一種新的ActionBar風格。當用戶初始進入該界面的時候,爲一個透明的 ActiionBar ,這樣利用充分的空間顯示大圖片,若是用戶滾動頁面須要查看內容的時候,則大圖收縮到 ActionBar 中。java

 

 

這個的主要優點是使ActionBar和內容完美的結合在一塊兒,整個操做看起來渾然天成,給人一種新奇的感受。這篇文章將會講解ActionBar效果和 Ken Burns動畫效果的實現。android

 

 

The ActionBar trick

Styles:

 

第一步先製做合適的Style,這裏須要使用ActionBar的overlay模式並設置透明的ActionBar背景。git

 

[html]  view plain copy print ?
 
  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)。github

 

[html]  view plain copy print ?
 
  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。
ide


[html]  view plain copy print ?
 
  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佈局

 

[java]  view plain copy print ?
 
  1. mFakeHeader = getLayoutInflater().inflate(R.layout.fake_header, mListView, false);  
  2. mListView.addHeaderView(mFakeHeader);  



 

獲取Scroll位置

佈局文件搞定,須要計算出ListView的滾動位置post

 

[java]  view plain copy print ?
 
  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的高度爲邊界。動畫

 

[java]  view plain copy print ?
 
  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方法。

 

[java]  view plain copy print ?
 
  1. private TextView getActionBarTitleView() {  
  2.     int id = Resources.getSystem().getIdentifier("action_bar_title""id""android");  
  3.     return (TextView) findViewById(id);  
  4. }  


    而後設置初始的 alpha 值。

 

 

[java]  view plain copy print ?
 
  1. getActionBarTitleView().setAlpha(0f);  

 

 

在 ListView 滾動的時候,計算該 alpha 值。

 

 

[java]  view plain copy print ?
 
  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 值。


[java]  view plain copy print ?
 
  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 來提高性能。

 

 

[java]  view plain copy print ?
 
  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 上設置一個透明的圖標。

 

[java]  view plain copy print ?
 
  1. private ImageView getActionBarIconView() {  
  2.     return (ImageView) findViewById(android.R.id.home);  
  3. }  

[java]  view plain copy print ?
 
  1. ActionBar actionBar = getActionBar();  
  2. actionBar.setIcon(R.drawable.ic_transparent);  

 

 

    當 ListView 滾動時候,根據 header 的高度來移動和縮放圖標。該縮放和位移是根據兩個圖標的位置關係和大小關係來計算的。 代碼以下:


[java]  view plain copy print ?
 
  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/

相關文章
相關標籤/搜索