在第一部分中,咱们学会了如何实现Google+应用中隐藏Toolbar的效果,今天咱们来实现Play Store中的效果。html
在开始以前,我先讲讲这一部分对 项目 结构的一点改动。原有的activity被分割成了两个:PartOneActivity
和PartTwoActivity,他们都是被
javaMainActivity
所调用。
译者注:
在阅读本文的同时,最好先实际操做一下play store应用,即使你大体知道效果是怎样也建议操做一下,否则下面的计算有点很差理解。其实这些都是很细微的东西,要一眼带过,估计什么也看不出来。android
开始
build.gradle
文件和第一部分是同样的,再也不赘述,咱们从建立Activity
的布局开始:git
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<FrameLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
>
<android.support.v7.widget.RecyclerView
android:id=
"@+id/recyclerView"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:paddingTop=
"?attr/actionBarSize"
android:clipToPadding=
"false"
/>
<android.support.v7.widget.Toolbar
android:id=
"@+id/toolbar"
android:layout_width=
"match_parent"
android:layout_height=
"?attr/actionBarSize"
android:background=
"?attr/colorPrimary"
/>
</FrameLayout>
|
只有一个RecyclerView
和一个Toolbar
(后面咱们还会添加Tabs
)。注意此次咱们使用的是 上篇文章 中提到的第二种方法(添加padding到RecyclerView)
。list item的布局文件和上次同样,直接跳过,RecyclerAdapter
一样如此(这里 是其代码-一个不带header的adapter),跳过 ,咱们直接进入PartTwoActivity
的代码:github
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
|
public class PartTwoActivity extends ActionBarActivity {
private Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppThemeGreen);
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_part_two);
initToolbar();
initRecyclerView();
}
private void initToolbar() {
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
setTitle(getString(R.string.app_name));
mToolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
}
private void initRecyclerView() {
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(
new
LinearLayoutManager(
this
));
RecyclerAdapter recyclerAdapter =
new
RecyclerAdapter(createItemList());
recyclerView.setAdapter(recyclerAdapter);
recyclerView.setOnScrollListener(
new
HidingScrollListener(
this
));
}
private List<String> createItemList() {
List<String> itemList =
new
ArrayList<>();
for
(int i=0;i<20;i++) {
itemList.add(
"Item "
+i);
}
return
itemList;
}
}
|
只是RecyclerView
和Toolbar
基本的初始化操做,注意第27行OnScrollListener
的设置。api
最有趣的部分是HidingScrollListener
,让咱们建立一个。app
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
|
public abstract class HidingScrollListener extends RecyclerView.OnScrollListener {
private int mToolbarOffset = 0;
private int mToolbarHeight;
public HidingScrollListener(Context context) {
mToolbarHeight = Utils.getToolbarHeight(context);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super
.onScrolled(recyclerView, dx, dy);
clipToolbarOffset();
onMoved(mToolbarOffset);
if
((mToolbarOffset <mToolbarHeight && dy>0) || (mToolbarOffset >0 && dy<0)) {
mToolbarOffset += dy;
}
}
private void clipToolbarOffset() {
if
(mToolbarOffset > mToolbarHeight) {
mToolbarOffset = mToolbarHeight;
}
else
if
(mToolbarOffset < 0) {
mToolbarOffset = 0;
}
}
public abstract void onMoved(int distance);
}
|
若是你读了前面一篇文章,这段代码应该很眼熟(实际上此次还更简单了)。这里只有一个比较重要的变量-mToolbarOffset
,它表示相对于Toolbar高度的
滚动偏移量。为了简便起见,咱们只追踪0到Toolbar
高度之间的值:ide
1
2
3
|
if
((mToolbarOffset <mToolbarHeight && dy>0) || (mToolbarOffset >0 && dy<0)) {
mToolbarOffset += dy;
}
|
当向上滚动的时候(注意在第一篇文章中咱们对于向上滚动的解释)这个值将增长(可是咱们并不但愿这个值大于Toolbar
的高度),而向下滚动的时候这个值将减少(一样,咱们也不但愿减少到小于0),你很快会知道为何咱们要做此限制的缘由。虽然上面的代码已经有了限制,
布局可是在很短的时间内(好比fling的时候),仍是有可能超过这个区间,
所以须要调用clipToolbarOffset()方法来作二次限制,确保mToolbarOffset
在0到Toolbar
高度的范围内,不然会出现抖动闪烁的状况。咱们还定义了一个在scroll期间被调用的抽象方法onMoved()
。
让咱们回到
测试PartTwoActivity
,同时实现scroll listener中的onMoved()
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private void initRecyclerView() {
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(
new
LinearLayoutManager(
this
));
RecyclerAdapter recyclerAdapter =
new
RecyclerAdapter(createItemList());
recyclerView.setAdapter(recyclerAdapter);
recyclerView.setOnScrollListener(
new
HidingScrollListener(
this
) {
@Override
public void onMoved(int distance) {
mToolbarContainer.setTranslationY(-distance);
}
});
}
|
好了,咱们看看如今是什么效果:
很是不错,Toolbar
随着列表的滚动而滚动,而且能在消失以后再次随着反向的滚动而滚回来,这和咱们的预期是一致的。这要归功于咱们对mToolbarOffset
的限制。若是咱们省略检查mToolbarOffset
是否大于0且小于mToolbarHeight
,那么当咱们向上滚动(这里指手指向上,也许是做者疏忽吧,先后的意思不一致)时,Toolbar
将会远远超出屏幕的范围,想再次看到Toolbar
须要等列表滚回到0的位置才行。而如今最多才滚动mToolbarHeight
的距离,所以Toolbar
始终紧挨着列表的最上面,所以向下滚动(这里也是指手指向下)的时候,能当即看到Toolbar
。
虽然目前看起来还不错,但并不是我想要的。若是在滚动一半的时候忽然中止,Toolbar
将是部分可见的,这看起来很奇怪。实际上Google Play Games就是这种效果,但我以为这是个bug。
正确是效果是Toolbar
应该像Google Play store中那样自动平滑的过分到该有的位置。
下面来看看新的HidingScrollListener:
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
public abstract class HidingScrollListener extends RecyclerView.OnScrollListener {
private static final float HIDE_THRESHOLD = 10;
private static final float SHOW_THRESHOLD = 70;
private int mToolbarOffset = 0;
private boolean mControlsVisible =
true
;
private int mToolbarHeight;
public HidingScrollListener(Context context) {
mToolbarHeight = Utils.getToolbarHeight(context);
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super
.onScrollStateChanged(recyclerView, newState);
if
(newState == RecyclerView.SCROLL_STATE_IDLE) {
if
(mControlsVisible) {
if
(mToolbarOffset > HIDE_THRESHOLD) {
setInvisible();
}
else
{
setVisible();
}
}
else
{
if
((mToolbarHeight - mToolbarOffset) > SHOW_THRESHOLD) {
setVisible();
}
else
{
setInvisible();
}
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super
.onScrolled(recyclerView, dx, dy);
clipToolbarOffset();
onMoved(mToolbarOffset);
if
((mToolbarOffset <mToolbarHeight && dy>0) || (mToolbarOffset >0 && dy<0)) {
mToolbarOffset += dy;
}
}
private void clipToolbarOffset() {
if
(mToolbarOffset > mToolbarHeight) {
mToolbarOffset = mToolbarHeight;
}
else
if
(mToolbarOffset < 0) {
mToolbarOffset = 0;
}
}
private void setVisible() {
if
(mToolbarOffset > 0) {
onShow();
mToolbarOffset = 0;
}
mControlsVisible =
true
;
}
private void setInvisible() {
if
(mToolbarOffset < mToolbarHeight) {
onHide();
mToolbarOffset = mToolbarHeight;
}
mControlsVisible =
false
;
}
public abstract void onMoved(int distance);
public abstract void onShow();
public abstract void onHide();
}
|
比之前复杂了点,可是也不用怕,咱们只是重写了RecyclerView.OnScrollListener的第二个方法onScrollStateChanged(),下面是onScrollStateChanged中所作的事情:
1.检查列表是否处于RecyclerView.SCROLL_STATE_IDLE
状态,这个状态下列表没有滚动(由于若是在滚动,咱们是像之前同样主动移动Toolbar
的Y值)。
2.若是咱们放开了手指而且列表中止滚动(这是就是RecyclerView.SCROLL_STATE_IDLE
状态),咱们须要检查当前Toolbar
是否可见,若是是可见的,意味着在mToolbarOffset大于
的时候显示它。HIDE_THRESHOLD
的时候隐藏它,而在mToolbarOffset
小于SHOW_THRESHOLD
1
2
3
4
5
6
7
|
if
(mControlsVisible) {
if
(mToolbarOffset > HIDE_THRESHOLD) {
setInvisible();
}
else
{
setVisible();
}
}
|
若是Toolbar
是不可见的,咱们要作相反的事情-当mToolbarOffset
(如今是从顶部计算因此是mToolbarHeight - mToolbarOffset
)大于SHOW_THRESHOLD
显示,当小于IDE_THRESHOLD
再次隐藏:
1
2
3
4
5
6
7
|
else
{
// it's not visible
if
((mToolbarHeight - mToolbarOffset) > SHOW_THRESHOLD) {
setVisible();
}
else
{
setInvisible();
}
}
|
ps:实话是说我没看懂。。。
onScrolled()
方法和以前保持一致,最后剩下的事情是在PartTwoActivity
中实现两个新的抽象方法:
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
|
private void initRecyclerView() {
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(
new
LinearLayoutManager(
this
));
RecyclerAdapter recyclerAdapter =
new
RecyclerAdapter(createItemList());
recyclerView.setAdapter(recyclerAdapter);
recyclerView.setOnScrollListener(
new
HidingScrollListener(
this
) {
@Override
public void onMoved(int distance) {
mToolbarContainer.setTranslationY(-distance);
}
@Override
public void onShow() {
mToolbarContainer.animate().translationY(0).setInterpolator(
new
DecelerateInterpolator(2)).start();
}
@Override
public void onHide() {
mToolbarContainer.animate().translationY(-mToolbarHeight).setInterpolator(
new
AccelerateInterpolator(2)).start();
}
});
}
|
如今来看看编译运行的结果:
看起来还比较顺利!
等等,不是说要作成play store的效果吗,还差了个tab吧,你可能会以为添加tab会让事情变得复杂不少,让我来告诉你。其实不是那么回事。
须要修改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
|
<FrameLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
>
<android.support.v7.widget.RecyclerView
android:id=
"@+id/recyclerView"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:clipToPadding=
"false"
/>
<LinearLayout
android:id=
"@+id/toolbarContainer"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"vertical"
>
<android.support.v7.widget.Toolbar
android:id=
"@+id/toolbar"
android:layout_width=
"match_parent"
android:layout_height=
"?attr/actionBarSize"
android:background=
"?attr/colorPrimary"
/>
<include layout=
"@layout/tabs"
/>
</LinearLayout>
</FrameLayout>
|
其中tabs.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
25
26
27
28
29
30
31
32
|
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"match_parent"
android:layout_height=
"@dimen/tabsHeight"
android:background=
"?attr/colorPrimary"
>
<FrameLayout
android:layout_width=
"0dp"
android:layout_height=
"match_parent"
android:layout_weight=
"1"
>
<TextView
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:text=
"@string/tab_1"
android:gravity=
"center"
style=
"@style/Base.TextAppearance.AppCompat.Body2"
android:textColor=
"@android:color/white"
android:background=
"@android:color/transparent"
/>
<View
android:layout_width=
"match_parent"
android:layout_height=
"6dp"
android:layout_gravity=
"bottom"
android:background=
"@android:color/white"
/>
</FrameLayout>
<TextView
android:layout_width=
"0dp"
android:layout_height=
"match_parent"
android:layout_weight=
"1"
android:text=
"@string/tab_2"
android:gravity=
"center"
style=
"@style/Base.TextAppearance.AppCompat.Body2"
android:textColor=
"@android:color/white"
android:background=
"@android:color/transparent"
/>
</LinearLayout>
|
能够看到,我并无添加一个真正意义上的Tab,而是一个长得像tab的布局。但这并不会改变什么,这里能够是任意的view,原理都是同样的,至于材料设计风格的tab,github上有一些实现,你能够用来替换。
添加
Tab意味着列表再次被挡住一部分空间,所以须要增长padding的值。考虑到灵活性,此次咱们再也不使用xml来设置padding,而是在代码中设置:
1
2
3
4
5
6
7
8
9
|
private void initRecyclerView() {
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
int paddingTop = Utils.getToolbarHeight(PartTwoActivity.
this
) + Utils.getTabsHeight(PartTwoActivity.
this
);
recyclerView.setPadding(recyclerView.getPaddingLeft(), paddingTop, recyclerView.getPaddingRight(), recyclerView.getPaddingBottom());
recyclerView.setLayoutManager(
new
LinearLayoutManager(
this
));
// ...
}
|
很简单,咱们将padding设置成Toolbar
和Tab
高度之和,运行看看正确与否:
看来是正确的列表的第一个item能够彻底显示,那么咱们继续,实际上,HidingScrollListener
类中的代码彻底不变,只需呀变动下PartTwoActivity
:
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
public class PartTwoActivity extends ActionBarActivity {
private LinearLayout mToolbarContainer;
private int mToolbarHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppThemeGreen);
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_part_two);
mToolbarContainer = (LinearLayout) findViewById(R.id.toolbarContainer);
initToolbar();
initRecyclerView();
}
private void initToolbar() {
Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
setTitle(getString(R.string.app_name));
mToolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
mToolbarHeight = Utils.getToolbarHeight(
this
);
}
private void initRecyclerView() {
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
int paddingTop = Utils.getToolbarHeight(PartTwoActivity.
|