如图,此功能需要注意两个状态:
- 当手指滑动,并且不离开屏幕的时候PageCountView才显示;
- 当手指快速滑动,列表还在滑翔状态时,PageCountView也显示。
XML布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content_main"
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"/>
<TextView
android:id="@+id/tv_page_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_margin="15dp"
android:background="@drawable/bg_page_count"
android:text="1/140"
android:textColor="@android:color/white"
android:textSize="12sp" />
</RelativeLayout>
PageCountView其实就是一个TextView,它的背景是自己写的一个Drawable,即bg_page_count.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#66000000" />
<padding
android:bottom="2dp"
android:left="5dp"
android:right="5dp"
android:top="2dp" />
<corners android:radius="10dp" />
</shape>
核心代码
主要给 RecyclerView
添加滚动监听onScrollListener
,监听onScrolled
方法和onScrollStateChanged
方法。前一个方法处理页码值的计算和更新操作,后一个方法处理View
的显示和隐藏。
在动画显示的时候会定义一个回调方法ViewPropertyAnimatorListener
,记录onAnimationEnd
方法的状态,用于在IDLE
状态下并且显示动画执行完毕后,再执行隐藏动画。否则会出现类似闪烁的问题。
MyAdapter
的代码就不贴了,自己随意封装。
public class MainActivity extends AppCompatActivity {
private static final int PAGE_PER = 20;//每页显示数量
private static final int COUNT = 100;//总数量;实际开发中总数量是从网络获取的。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tvPageCount = (TextView) findViewById(R.id.tv_page_count);
//初始状态隐藏PageCountView。当然也可以在xml中控制
tvPageCount.post(new Runnable() {
@Override
public void run() {
AnimUtils.hide(tvPageCount);
}
});
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new MyAdapter());
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
int lastVisiblePosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
int page;
if (lastVisiblePosition % PAGE_PER == 0) {
page = lastVisiblePosition / PAGE_PER;
} else {
page = lastVisiblePosition / PAGE_PER + 1;
}
tvPageCount.setText(page + "/" + (COUNT / PAGE_PER));
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE && isAnimFinish) {
//如果是IDLE状态,并且显示动画执行完毕,再执行隐藏动画,避免出现动画闪烁
AnimUtils.hide(tvPageCount);
} else if (tvPageCount.getVisibility() != View.VISIBLE) {
AnimUtils.show(tvPageCount, listener);
}
}
});
}
private boolean isAnimFinish = false;//记录动画执行完成的状态
private ViewPropertyAnimatorListener listener = new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
isAnimFinish = false;
}
@Override
public void onAnimationEnd(View view) {
isAnimFinish = true;
}
@Override
public void onAnimationCancel(View view) {
}
};
}
动画工具类AnimUtils
public class AnimUtils {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
public static void hide(final View view) {
ViewCompat.animate(view)
.scaleX(0.0f)
.scaleY(0.0f)
.setInterpolator(INTERPOLATOR)
.setDuration(300)
.setListener(new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
}
@Override
public void onAnimationEnd(View view) {
view.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(View view) {
}
})
.start();
}
public static void show(final View view, ViewPropertyAnimatorListener listener) {
view.setVisibility(View.VISIBLE);
ViewCompat.animate(view)
.scaleX(1.0f)
.scaleY(1.0f)
.setInterpolator(INTERPOLATOR)
.setDuration(300)
.setListener(listener)
.start();
}
}
到此,基本效果已经实现。
但是,当快速滑动一小段距离,立马停止触摸屏幕的时候,View虽然显示了,但是不执行隐藏方法。可能导致的原因是动画执行一次需要300ms,如果在此时间内,回调方法onScrollStateChanged执行,处于IDLE状态,但是动画未执行完毕,isAnimFinish仍然为false,那么就不会隐藏。
完善代码
定义全局变量newState,在onScrollStateChanged记录newState的状态值,在显示动画监听器的onAnimationEnd方法中判断,如果newState是IDLE状态,再去执行隐藏动画。
//定义全局变量
private int newState;
//onScrollStateChanged方法
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
MainActivity.this.newState = newState;
if (newState == RecyclerView.SCROLL_STATE_IDLE && !isAnimFinish) {
//如果是IDLE状态,并且显示动画执行完毕,再执行隐藏动画,避免出现动画闪烁
//如果快速简短滑动,可能导致出现IDLE状态,但是动画未执行完成。因此无法执行隐藏动画。所以保存当前newState,在onAnimationEnd中增加判断。
AnimUtils.hide(tvPageCount);
} else if (tvPageCount.getVisibility() != View.VISIBLE) {
AnimUtils.show(tvPageCount, listener);
}
}
//onAnimationEnd方法
@Override
public void onAnimationEnd(View view) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
//IDLE状态下隐藏,其余状态保持View显示
AnimUtils.hide(view);
}
isAnimFinish = false;
}
工匠精神
作为一名Coder,代码如此杂乱是在是sou不鸟。我们可以封装一下动画监听器。
public class AnimListenerBuilder {
private int newState;
private boolean isAnimFinish = false;
private ViewPropertyAnimatorListener listener;
public AnimListenerBuilder() {
listener = new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
isAnimFinish = false;
}
@Override
public void onAnimationEnd(View view) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
//IDLE状态下隐藏,其余状态保持View显示
AnimUtils.hide(view);
}
isAnimFinish = true;
}
@Override
public void onAnimationCancel(View view) {
}
};
}
public ViewPropertyAnimatorListener build() {
return listener;
}
public void setNewState(int newState) {
this.newState = newState;
}
public boolean isAnimFinish() {
return isAnimFinish;
}
}
MainActivity则干净许多
public class MainActivity extends AppCompatActivity {
private static final int PAGE_PER = 20;//每页显示数量
private static final int COUNT = 100;//总数量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tvPageCount = (TextView) findViewById(R.id.tv_page_count);
//初始状态,隐藏View
tvPageCount.post(new Runnable() {
@Override
public void run() {
AnimUtils.hide(tvPageCount);
}
});
final AnimListenerBuilder builder = new AnimListenerBuilder();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new MyAdapter());
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
int lastVisiblePosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
int page;
if (lastVisiblePosition % PAGE_PER == 0) {
page = lastVisiblePosition / PAGE_PER;
} else {
page = lastVisiblePosition / PAGE_PER + 1;
}
tvPageCount.setText(page + "/" + (COUNT / PAGE_PER));
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
builder.setNewState(newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && builder.isAnimFinish()) {
//如果是IDLE状态,并且显示动画执行完毕,再执行隐藏动画,避免出现动画闪烁
//如果快速简短滑动,可能导致出现IDLE状态,但是动画未执行完成。因此无法执行隐藏动画。所以保存当前newState,在onAnimationEnd中增加判断。
AnimUtils.hide(tvPageCount);
} else if (tvPageCount.getVisibility() != View.VISIBLE) {
AnimUtils.show(tvPageCount, builder.build());
}
}
});
}
}