首页 > 代码库 > 实现一个与内容合二为一的ActionBar动画效果

实现一个与内容合二为一的ActionBar动画效果


实现一个与内容合二为一的ActionBar动画效果,让你的actionbar更生动。下面是效果图:

这种效果的好处是让actionbar也成为了内容的一部分,实际应用的效果比图片展示的效果要好,除了actionbar渐渐出现的效果外,背景图片还有一种称之为 Ken Burns effect 的动态效果。


下面讲解实现过程。

设置actionbar的样式,我们需要如下两点:

1.actionbar是透明的。

2.开启overlay mode模式

<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>

布局

布局是实现的关键,FrameLayout中包含一个ListView以及另外一个FrameLayout(头部,显示图片的地方,这里我们称之为header),header中包含了两个ImageView,一个用于显示背景图片,一个用于显示logo。这个logo会跟着listView的滚动而变化,并最终跑到actionbar中。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <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">
        <ImageView
            android:id="@+id/header_picture"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src=http://www.mamicode.com/"@drawable/picture0" />>这里的技巧是给Listview添加一个伪造的header,并且将它的高度设置成真实header的高度伪造header的xml代码
<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后添加到ListView中:
mFakeHeader = getLayoutInflater().inflate(R.layout.fake_header, mListView, false);
mListView.addHeaderView(mFakeHeader);

获得滚动位置,这段代码是从stackOverflow上摘抄的:
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;
}

移动header的位置

当ListView滚动的时候,你必须移动header的位置让他和伪造的ListView header保持同步,注意移动到了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();
            //sticky actionbar
            mHeader.setTranslationY(Math.max(-scrollY, mMinHeaderTranslation));
        }
    });

标题的渐变

actionbar标题文字的出现是渐变的,而标题文字所在的TextView控件可以通过Resource的getIdentifier方法获得。

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

初始化的时候将它的透明度设置为0:
getActionBarTitleView().setAlpha(0f);

ListView滚动的时候这个透明度变化的依据是header移动距离的一个比值ratio:
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);
            //actionbar title alpha
            getActionBarTitleView().setAlpha(clamp(5.0F * ratio - 4.0F, 0.0F, 1.0F));
        }
    });

Alpha values: f(x) = 5x-4

clamp是一个基本的数学计算公式:


public static float clamp(float value, float max, float min) {
    return Math.max(Math.min(value, min), max);
}

应用图标和logo的移动和缩放

首先你要获得图标所在的ImageView:

private ImageView getActionBarIconView() {
    return (ImageView) findViewById(android.R.id.home);
}

设置一个透明的图标
ActionBar actionBar = getActionBar();
actionBar.setIcon(R.drawable.ic_transparent);

然后在ListView滑动的过程中根据header的移动比率对应用图标和logo移动与缩放。在这个过程中通过获得应用图标和header中的logo在屏幕上的矩形区域来判断该如何移动与缩放。
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);
            //move & scale
            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);
        }
    });

注:对上面的代码,我自己的理解是应用图标至始至终都没有显示,只是他的位置被header中的logo占据了。

代码下载:https://github.com/flavienlaurent/NotBoringActionBar


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

实现一个与内容合二为一的ActionBar动画效果