這篇文章轉自國外一個技術大牛的博客,首先感謝這位大牛的無私奉獻。html
Android應用中有一名位 Google書報攤的應用,他實現了一種新的ActionBar風格。當用戶初始進入該界面的時候,爲一個透明的 ActiionBar ,這樣利用充分的空間顯示大圖片,若是用戶滾動頁面須要查看內容的時候,則大圖收縮到 ActionBar 中。java

這個的主要優點是使ActionBar和內容完美的結合在一塊兒,整個操做看起來渾然天成,給人一種新奇的感受。這篇文章將會講解ActionBar效果和 Ken Burns動畫效果的實現。android
The ActionBar trick
Styles:
第一步先製做合適的Style,這裏須要使用ActionBar的overlay模式並設置透明的ActionBar背景。git
- <resources>
-
- <style name="TransparentTheme" parent="@android:style/Theme.Holo.Light">
- <item name="android:windowBackground">@null</item>
- <item name="android:actionBarStyle">@style/ActionBarStyle.Transparent</item>
- <item name="android:windowActionBarOverlay">true</item>
- </style>
-
- <style name="ActionBarStyle.Transparent" parent="@android:Widget.ActionBar">
- <item name="android:background">@null</item>
- <item name="android:displayOptions">homeAsUp|showHome|showTitle</item>
- <item name="android:titleTextStyle">@style/ActionBarStyle.Transparent.TitleTextStyle</item>
- </style>
-
- <style name="ActionBarStyle.Transparent.TitleTextStyle" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
- <item name="android:textColor">@android:color/white</item>
- </style>
-
- </resources>
佈局結構
佈局結構是很是重要,主要的佈局是一個由ListView和另外一個的FrameLayout(即題圖)組成的FrameLayout。題圖包含兩個圖片,一個背景大圖(即header_picture),一個logo圖像(即header_logo)。github
- <?xml version="1.0" encoding="utf-8"?>
-
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".NoBoringActionBarActivity">
-
- <ListView
- android:id="@+id/listview"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/white" />
-
- <FrameLayout
- android:id="@+id/header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/header_height">
-
- <com.flavienlaurent.notboringactionbar.KenBurnsView
- android:id="@+id/header_picture"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:src="@drawable/picture0" />
-
- <ImageView
- android:id="@+id/header_logo"
- android:layout_width="@dimen/header_logo_size"
- android:layout_height="@dimen/header_logo_size"
- android:layout_gravity="center"
- android:src="@drawable/ic_header_logo" />
-
- </FrameLayout>
-
- </FrameLayout>
經過在 ListView 上添加一個高度和 題圖同樣高的 虛擬 header view 來實現該動畫。 能夠用一個佈局文件來做爲該虛擬 header 的 view。ide
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/header_height"
- android:orientation="vertical">
-
- </LinearLayout>
使用inflate添加上虛擬 header view佈局
- mFakeHeader = getLayoutInflater().inflate(R.layout.fake_header, mListView, false);
- mListView.addHeaderView(mFakeHeader);
獲取Scroll位置
佈局文件搞定,須要計算出ListView的滾動位置post
- public int getScrollY() {
- View c = mListView.getChildAt(0);
- if (c == null) {
- return 0;
- }
-
- int firstVisiblePosition = mListView.getFirstVisiblePosition();
- int top = c.getTop();
-
- int headerHeight = 0;
- if (firstVisiblePosition >= 1) {
- headerHeight = mPlaceHolderView.getHeight();
- }
-
- return -top + firstVisiblePosition * c.getHeight() + headerHeight;
- }<span style="font-family:SimSun;font-size:18px;">
- </span>
特別提示,若是listview第一個可視視圖位置大於1,須要計算虛擬視圖的高度。性能
移動題頭
伴隨着listview的滾動,你須要移動題頭,以跟蹤虛擬題頭的移動。這些移動以ActionBar的高度爲邊界。動畫
- mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- int scrollY = getScrollY();
-
- mHeader.setTranslationY(Math.max(-scrollY, mMinHeaderTranslation));
- }
- });
Title漸變
這裏的Title有個漸變效果,他是怎麼實現的呢,首先獲取到這個view,使用的Resources.getIdentifier方法。
- private TextView getActionBarTitleView() {
- int id = Resources.getSystem().getIdentifier("action_bar_title", "id", "android");
- return (TextView) findViewById(id);
- }
而後設置初始的 alpha 值。
- getActionBarTitleView().setAlpha(0f);
在 ListView 滾動的時候,計算該 alpha 值。
- mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- float ratio = clamp(mHeader.getTranslationY() / mMinHeaderTranslation, 0.0f, 1.0f);
-
- getActionBarTitleView().setAlpha(clamp(5.0F * ratio - 4.0F, 0.0F, 1.0F));
- }
- });
Alpha 值的變化方程式爲 f(x) = 5x-4。關於該方程式參考:Wikipedia

而關於標題的淡出Cyril Mottier提供了一個更好的方案。
在該方案中無需獲取 ActionBar title view。使用一個具備自定義 ForegroundColorSpan 的 SpannableString 。而後在該 SpannableString 上設置文字的 Alpha 值。
- public class AlphaForegroundColorSpan extends ForegroundColorSpan {
-
- private float mAlpha;
-
- public AlphaForegroundColorSpan(int color) {
- super(color);
- }
- […]
-
- @Override
- public void updateDrawState(TextPaint ds) {
- ds.setColor(getAlphaColor());
- }
-
- public void setAlpha(float alpha) {
- mAlpha = alpha;
- }
-
- public float getAlpha() {
- return mAlpha;
- }
-
- private int getAlphaColor() {
- int foregroundColor = getForegroundColor();
- return Color.argb((int) (mAlpha * 255), Color.red(foregroundColor), Color.green(foregroundColor), Color.blue(foregroundColor));
- }
- }
滾動的時候修改該 SpannableString 的 Alpha值並設置爲 Title,使用一樣的 AlphaForegroundColorSpan 和 SpannableString 避免頻繁 GC 來提高性能。
- mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- float ratio = clamp(mHeader.getTranslationY() / mMinHeaderTranslation, 0.0f, 1.0f);
-
- setTitleAlpha(clamp(5.0F * ratio – 4.0F, 0.0F, 1.0F));
- }
- });
-
- private void setTitleAlpha(float alpha) {
- mAlphaForegroundColorSpan.setAlpha(alpha);
- mSpannableString.setSpan(mAlphaForegroundColorSpan, 0, mSpannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- getActionBar().setTitle(mSpannableString);
- }
移動&縮放icon
先獲取該 圖標 View, 而後在 ActionBar 上設置一個透明的圖標。
- private ImageView getActionBarIconView() {
- return (ImageView) findViewById(android.R.id.home);
- }
- ActionBar actionBar = getActionBar();
- actionBar.setIcon(R.drawable.ic_transparent);
當 ListView 滾動時候,根據 header 的高度來移動和縮放圖標。該縮放和位移是根據兩個圖標的位置關係和大小關係來計算的。 代碼以下:
- mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- float ratio = clamp(mHeader.getTranslationY() / mMinHeaderTranslation, 0.0f, 1.0f);
-
-
- interpolation = mAccelerateDecelerateInterpolator.getInterpolation(ratio);
-
- View actionBarIconView = getActionBarIconView();
-
- getOnScreenRect(mRect1, mHeaderLogo);
- getOnScreenRect(mRect2, actionBarIconView);
-
- float scaleX = 1.0F + interpolation (mRect2.width() / mRect1.width() – 1.0F);
- float scaleY = 1.0F + interpolation (mRect2.height() / mRect1.height() – 1.0F);
- float translationX = 0.5F (interpolation (mRect2.left + mRect2.right – mRect1.left – mRect1.right));
- float translationY = 0.5F (interpolation (mRect2.top + mRect2.bottom – mRect1.top – mRect1.bottom));
-
- mHeaderLogo.setTranslationX(translationX);
- mHeaderLogo.setTranslationY(translationY – mHeader.getTranslationY());
- mHeaderLogo.setScaleX(scaleX);
- mHeaderLogo.setScaleY(scaleY);
- }
- });
注意你也能夠用 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/