http://blog.chengyunfeng.com/?p=497
在Play Music (v5.0) 版本中的音乐专辑详情界面引入了一个新的ActionBar效果。用户进入界面的时候ActionBar的背景为透明的,当用户向下滚动内容的时候,ActionBar背景逐渐有透明变为不透明。这种效果有两种明显的好处:
- Polish the UI:对于和当前操作同步的动画而言,用户满意度比较高,因为这种情况用户感觉该动画是对操作的反馈并且更加自然。这里ActionBar的动画是由于用户滚动内容区域而引发的,而不是界面启动时候的一次性动画。
- 充分利用屏幕优势:在遵守系统UI设计风格的同时,这种模式可以让用户进来后先关注界面内容而不是一些ActionBar上的操作。
下面来看看如何实现Play Music 5.0中的这种ActionBar动画效果。
App的样式/主题
如上图所示,这种模式要求ActionBar覆盖在内容之上,通过在style中设置android:windowActionBarOverlay属性即可实现这种效果。下面的代码显示如何使用自定义style:values/themes.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
resources
>
<
style
name
=
"Theme.TranslucentActionBar"
parent
=
"@android:style/Theme.Holo.Light.DarkActionBar"
>
<
item
name
=
"android:actionBarStyle"
>@style/Widget.ActionBar</
item
>
</
style
>
<
style
name
=
"Theme.TranslucentActionBar.ActionBar"
/>
<
style
name
=
"Theme.TranslucentActionBar.ActionBar.Overlay"
>
<
item
name
=
"android:actionBarStyle"
>@style/Widget.ActionBar.Transparent</
item
>
<
item
name
=
"android:windowActionBarOverlay"
>true</
item
>
</
style
>
</
resources
>
|
ActionBar的style定义到styles.xml文件中:
1
2
3
4
5
6
7
8
9
10
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
resources
>
<
style
name
=
"Widget.ActionBar"
parent
=
"@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse"
>
<
item
name
=
"android:background"
>@drawable/ab_background</
item
>
</
style
>
<
style
name
=
"Widget.ActionBar.Transparent"
>
<
item
name
=
"android:background"
>@android:color/transparent</
item
>
</
style
>
</
resources
>
|
然后就可以在Activity中使用前面定义的主题样式了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
package
=
"com.cyrilmottier.android.translucentactionbar"
android:versionCode
=
"1"
android:versionName
=
"1.0"
>
<
uses-sdk
android:minSdkVersion
=
"14"
android:targetSdkVersion
=
"17"
/>
<
application
android:allowBackup
=
"true"
android:icon
=
"@drawable/ic_launcher"
android:label
=
"@string/app_name"
android:theme
=
"@style/Theme.TranslucentActionBar"
>
<
activity
android:name
=
".HomeActivity"
android:theme
=
"@style/Theme.TranslucentActionBar.ActionBar.Overlay"
>
<
intent-filter
>
<
action
android:name
=
"android.intent.action.MAIN"
/>
<
category
android:name
=
"android.intent.category.LAUNCHER"
/>
</
intent-filter
>
</
activity
>
</
application
>
</
manifest
>
|
准备内容
如前面介绍的一样,ActionBar的动画和内容滚动操作同步。示例中使用了一个ScrollView控件,由于该控件默认没有提供监听滚动功能的接口,所以我们要自定义这个控件:NotifyingScrollView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package
com.cyrilmottier.android.translucentactionbar;
import
android.content.Context;
import
android.util.AttributeSet;
import
android.widget.ScrollView;
/**
* @author Cyril Mottier
*/
public
class
NotifyingScrollView
extends
ScrollView {
/**
* @author Cyril Mottier
*/
public
interface
OnScrollChangedListener {
void
onScrollChanged(ScrollView who,
int
l,
int
t,
int
oldl,
int
oldt);
}
private
OnScrollChangedListener mOnScrollChangedListener;
public
NotifyingScrollView(Context context) {
super
(context);
}
public
NotifyingScrollView(Context context, AttributeSet attrs) {
super
(context, attrs);
}
public
NotifyingScrollView(Context context, AttributeSet attrs,
int
defStyle) {
super
(context, attrs, defStyle);
}
@Override
protected
void
onScrollChanged(
int
l,
int
t,
int
oldl,
int
oldt) {
super
.onScrollChanged(l, t, oldl, oldt);
if
(mOnScrollChangedListener !=
null
) {
mOnScrollChangedListener.onScrollChanged(
this
, l, t, oldl, oldt);
}
}
public
void
setOnScrollChangedListener(OnScrollChangedListener listener) {
mOnScrollChangedListener = listener;
}
}
|
然后在布局XML文件中使用上面自定义的控件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
com.cyrilmottier.android.translucentactionbar.NotifyingScrollView
android:id
=
"@+id/scroll_view"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
>
<
LinearLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:orientation
=
"vertical"
>
<
ImageView
android:id
=
"@+id/image_header"
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:scaleType
=
"centerCrop"
android:src
=
"@drawable/daft_punk"
/>
<! -- Some long content -->
</
LinearLayout
>
</
com.cyrilmottier.android.translucentactionbar.NotifyingScrollView
>
|
ActionBar的渐隐/显动画
ActionBar背景的显示只需要根据内容滚动的距离来计算背景的透明度即可。注意下面代码中滚动距离的只不能小于0,否则会出现一些奇怪的效果。HomeActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
package
com.cyrilmottier.android.translucentactionbar;
import
android.app.Activity;
import
android.graphics.drawable.Drawable;
import
android.os.Build;
import
android.os.Bundle;
import
android.support.v4.view.GravityCompat;
import
android.support.v4.widget.DrawerLayout;
import
android.util.Log;
import
android.view.Menu;
import
android.widget.ScrollView;
public
class
HomeActivity
extends
Activity {
private
Drawable mActionBarBackgroundDrawable;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
mActionBarBackgroundDrawable = getResources().getDrawable(R.drawable.ab_background);
mActionBarBackgroundDrawable.setAlpha(
0
);
getActionBar().setBackgroundDrawable(mActionBarBackgroundDrawable);
((NotifyingScrollView) findViewById(R.id.scroll_view)).setOnScrollChangedListener(mOnScrollChangedListener);
}
private
NotifyingScrollView.OnScrollChangedListener mOnScrollChangedListener =
new
NotifyingScrollView.OnScrollChangedListener() {
public
void
onScrollChanged(ScrollView who,
int
l,
int
t,
int
oldl,
int
oldt) {
final
int
headerHeight = findViewById(R.id.image_header).getHeight() - getActionBar().getHeight();
final
float
ratio = (
float
) Math.min(Math.max(t,
0
), headerHeight) / headerHeight;
final
int
newAlpha = (
int
) (ratio *
255
);
mActionBarBackgroundDrawable.setAlpha(newAlpha);
}
};
}
|
由于JELLY_BEAN_MR1之前版本的Bug,上面的代码在JELLY_BEAN_MR1之前版本中无法正常使用。由于在之前版本中,ActionBar背景的Drawable没有注册自己为Drawable的callback,所以无法重绘自己。通过添加下面的Callback可以解决该问题:HomeActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private
Drawable.Callback mDrawableCallback =
new
Drawable.Callback() {
@Override
public
void
invalidateDrawable(Drawable who) {
getActionBar().setBackgroundDrawable(who);
}
@Override
public
void
scheduleDrawable(Drawable who, Runnable what,
long
when) {
}
@Override
public
void
unscheduleDrawable(Drawable who, Runnable what) {
}
};
//onCreate...
if
(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
mActionBarBackgroundDrawable.setCallback(mDrawableCallback);
}
|
增加ActionBar内容的对比度
为了避免ActionBar中的内容和内容颜色一样(比如ActionBar的文字颜色为白色,而内容图片最上面也为白色,这样在ActionBar背景透明的情况下,文字就看不清楚了),可以在内容最上方覆盖一个黑暗的半透明图层。这样即使内容颜色和ActionBar内容颜色一样的时候,也能保证ActionBar的内容可见。 唯一的缺点就是ActionBar下的内容一开始看起来比较黑暗,下面是实现方式:drawable/gradient.xml
1
2
3
4
5
6
7
8
9
10
11
12
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
android:shape
=
"rectangle"
>
<
size
android:height
=
"100dp"
/>
<
gradient
android:angle
=
"270"
android:startColor
=
"#8000"
android:endColor
=
"#0000"
/>
</
shape
>
|
半透明背景覆盖到图片上面(在布局文件中把ImageView用下面代码替换):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<
FrameLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
>
<
ImageView
android:id
=
"@+id/image_header"
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:scaleType
=
"centerCrop"
android:src
=
"@drawable/daft_punk"
/>
<
View
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:background
=
"@drawable/gradient"
/>
</
FrameLayout
>
|
避免过渡滚动
在Gingerbread (API 9)中,Android引入了一种新的方式来告诉用户可滚动内容已经滚动到头。在API 14中添加了一个EdgeEffect类并且还可以过渡滚动(和IPhone的滚动效果类似,不过没这么明显)。过渡滚动在一般情况下没什么问题,但是当内容区域和背景颜色反差比较大的时候,比较烦人。
通过快速的滑动可滚动区域可以复现这个效果,如果仔细看的话可以发现在当快速滑动到头的时候,图片上面会出现一个白色的滚动过渡的背景。
可以通过View#setOverScrollMode(int)函数并设置参数为View#OVER_SCROLL_NEVER来禁用过渡滚动来解决这个问题。 但是这样同时禁用了滚动效果。 另外一种比较好的方式是修改NotifyingScrollView类,在一些情况下设置最大过渡滚动的值为0:NotifyingScrollView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
private
boolean
mIsOverScrollEnabled =
true
;
public
void
setOverScrollEnabled(
boolean
enabled) {
mIsOverScrollEnabled = enabled;
}
public
boolean
isOverScrollEnabled() {
return
mIsOverScrollEnabled;
}
@Override
protected
boolean
overScrollBy(
int
deltaX,
int
deltaY,
int
scrollX,
int
scrollY,
int
scrollRangeX,
int
scrollRangeY,
int
maxOverScrollX,
int
maxOverScrollY,
boolean
isTouchEvent) {
return
super
.overScrollBy(
deltaX,
deltaY,
scrollX,
scrollY,
scrollRangeX,
scrollRangeY,
mIsOverScrollEnabled ? maxOverScrollX :
0
,
mIsOverScrollEnabled ? maxOverScrollY :
0
,
isTouchEvent);
}
|
From:http://cyrilmottier.com/2013/05/24/pushing-the-actionbar-to-the-next-level