Making Your ActionBar Not Boring
2016-05-16 10:27
417 查看
这篇文章转自国外一个技术大牛的博客,首先感谢这位大牛的无私奉献。
Android应用中有一名位 Google书报摊的应用,他实现了一种新的ActionBar风格。当用户初始进入该界面的时候,为一个透明的 ActiionBar ,这样利用充分的空间显示大图片,如果用户滚动页面需要查看内容的时候,则大图收缩到 ActionBar 中。
这个的主要优势是使ActionBar和内容完美的结合在一起,整个操作看起来浑然天成,给人一种新奇的感觉。这篇文章将会讲解ActionBar效果和 Ken
Burns动画效果的实现。
第一步先制作合适的Style,这里需要使用ActionBar的overlay模式并设置透明的ActionBar背景。
<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)。
<?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。
<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);
布局文件搞定,需要计算出ListView的滚动位置
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();
//sticky actionbar
mHeader.setTranslationY(Math.max(-scrollY, mMinHeaderTranslation));
}
});
这里的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);
//actionbar title alpha
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);
//actionbar title alpha
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);
}
先获取该 图标 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);
//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);
}
});
注意你也可以用 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/
Android应用中有一名位 Google书报摊的应用,他实现了一种新的ActionBar风格。当用户初始进入该界面的时候,为一个透明的 ActiionBar ,这样利用充分的空间显示大图片,如果用户滚动页面需要查看内容的时候,则大图收缩到 ActionBar 中。
这个的主要优势是使ActionBar和内容完美的结合在一起,整个操作看起来浑然天成,给人一种新奇的感觉。这篇文章将会讲解ActionBar效果和 Ken
Burns动画效果的实现。
The ActionBar trick
Styles:
第一步先制作合适的Style,这里需要使用ActionBar的overlay模式并设置透明的ActionBar背景。<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)。<?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。
<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的滚动位置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();
//sticky actionbar
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);
//actionbar title alpha
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);
//actionbar title alpha
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);
//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);
}
});
注意你也可以用 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/
相关文章推荐
- 苹果个人开发者账号如何升级成公司账号
- nanopc的uboot移植,越跑越深(1)
- 【转】【漫画解读】HDFS存储原理
- [HDOJ4027]Can you answer these queries?(线段树,特殊成段更新,成段查询)
- CSS 3 过渡-transition
- 北漂程序员的笑与泪
- GTK学习心得
- utilities(matlab)—— 图像分块(image2cols、cols2image)
- 前台页面验证码如何生成
- free -m 内存使用分析
- cocos converToWordSpace
- Android 记录和恢复ListView滚动的位置的方法
- maven依赖本机的jar包。无法打包到war中问题
- Axure RP 7.0注册码
- 浅谈Android Location
- 多线程编程--两个城市去旅游
- MySQL中因为unique key 非空唯一索引存在导致修改主键失败案例
- 判定一个年份是否为闰年
- 无密码通过ssh执行rsync
- java配置dbcp连接池(数据库连接池)示例分享