完全工程代码在这:https://github.com/NashLegend/Auto-Hide-ListView
现在很多软件都有这类滑动列表的时候自动隐藏页面头部和底部元素的功能,比如Google+。在刚刚进入Activity的时候,页面是1个列表,底部有1个view,头部1个view,当列表向上滑动的时候,隐藏头尾元素,以显示更多内容,当列表向下滑动的时候,再将头尾元素拉出来。比如Google+。
刚刚进入时是这个模样:
再把列表身上1拉,头尾隐藏,成了这个模样:
再往下拉,就会再变回第1张图的模样。
这个例籽实现的就是这个功能
这个例子里面,MainActivity的布局以下,ToolBar是顶部元素,Button为底部元素。
<RelativeLayout 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=".MainActivity">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:headerDividersEnabled="false" />
<android.support.v7.widget.Toolbar
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@android:color/holo_blue_light" />
<Button
android:id="@+id/footer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="@string/ScrollDown" />
</RelativeLayout>
public class MainActivity extends ActionBarActivity {
ListView listView;
Toolbar toolbar;
View header;
View footer;
int touchSlop = 10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
touchSlop = (int) (ViewConfiguration.get(MainActivity.this).getScaledTouchSlop() * 0.9);//转动过量少距离后才开始计算是不是隐藏/显示头尾元素。这里用了默许touchslop的0.9倍。
listView = (ListView) findViewById(R.id.list_view);
footer = findViewById(R.id.footer);
toolbar = (Toolbar) findViewById(R.id.action_bar);
// 下面这句将这个ToolBar设置为ActionBar,在这个例子里面,这句其实用不着,但是如果用了这句,就得把Theme设置为NoActionBar了,无关这里要说的,具体见上面的链接中的Style
setSupportActionBar(toolbar);
//为这个ListView填充元素。
String[] str = new String[64];
for (int i = 0; i < str.length; i++) {
str[i] = "Android " + i;
}
//R.layout.simple_layout是1个TextView,详见上面的链接……
ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, R.layout.simple_layout, str);
listView.setAdapter(adapter);
//为ListView添加1个Header,这个Header与ToolBar1样高。这样我们可以正确的看到列表中的第1个元素而不被遮住。
header = new View(MainActivity.this);
header.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) getResources().getDimension(R.dimen.abc_action_bar_default_height_material)));
header.setBackgroundColor(Color.parseColor("#00000000"));
listView.addHeaderView(header);
//为ListView设置触摸事件和转动事件,这是核心
listView.setOnTouchListener(onTouchListener);
listView.setOnScrollListener(onScrollListener);
footer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//为button设置点击事件,点击1次转动10个item
listView.smoothScrollByOffset(10);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
AnimatorSet backAnimatorSet;//这是显示头尾元素使用的动画
private void animateBack() {
//先清除其他动画
if (hideAnimatorSet != null && hideAnimatorSet.isRunning()) {
hideAnimatorSet.cancel();
}
if (backAnimatorSet != null && backAnimatorSet.isRunning()) {
//如果这个动画已在运行了,就不管它
} else {
backAnimatorSet = new AnimatorSet();
//下面两句是将头尾元素放回初始位置。
ObjectAnimator headerAnimator = ObjectAnimator.ofFloat(toolbar, "translationY", toolbar.getTranslationY(), 0f);
ObjectAnimator footerAnimator = ObjectAnimator.ofFloat(footer, "translationY", footer.getTranslationY(), 0f);
ArrayList<Animator> animators = new ArrayList<>();
animators.add(headerAnimator);
animators.add(footerAnimator);
backAnimatorSet.setDuration(300);
backAnimatorSet.playTogether(animators);
backAnimatorSet.start();
}
}
AnimatorSet hideAnimatorSet;//这是隐藏头尾元素使用的动画
private void animateHide() {
//先清除其他动画
if (backAnimatorSet != null && backAnimatorSet.isRunning()) {
backAnimatorSet.cancel();
}
if (hideAnimatorSet != null && hideAnimatorSet.isRunning()) {
//如果这个动画已在运行了,就不管它
} else {
hideAnimatorSet = new AnimatorSet();
ObjectAnimator headerAnimator = ObjectAnimator.ofFloat(toolbar, "translationY", toolbar.getTranslationY(), -toolbar.getHeight());//将ToolBar隐藏到上面
ObjectAnimator footerAnimator = ObjectAnimator.ofFloat(footer, "translationY", footer.getTranslationY(), footer.getHeight());//将Button隐藏到下面
ArrayList<Animator> animators = new ArrayList<>();
animators.add(headerAnimator);
animators.add(footerAnimator);
hideAnimatorSet.setDuration(200);
hideAnimatorSet.playTogether(animators);
hideAnimatorSet.start();
}
}
View.OnTouchListener onTouchListener = new View.OnTouchListener() {
float lastY = 0f;
float currentY = 0f;
//下面两个表示滑动的方向,大于0表示向下滑动,小于0表示向上滑动,等于0表示未滑动
int lastDirection = 0;
int currentDirection = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = event.getY();
currentY = event.getY();
currentDirection = 0;
lastDirection = 0;
break;
case MotionEvent.ACTION_MOVE:
if (listView.getFirstVisiblePosition() > 0) {
//只有在listView.getFirstVisiblePosition()>0的时候才判断是不是进行显隐动画。由于listView.getFirstVisiblePosition()==0时,
//ToolBar――也就是头部元素必须是可见的,如果这时候候隐藏了起来,那末占位置用了headerview就被用户发现了
//但是当用户将列表向下拉露出列表的headerview的时候,应当要让头尾元素再次出现才对――这个判断写在了后面onScrollListener里面……
float tmpCurrentY = event.getY();
if (Math.abs(tmpCurrentY - lastY) > touchSlop) {//滑动距离大于touchslop时才进行判断
currentY = tmpCurrentY;
currentDirection = (int) (currentY - lastY);
if (lastDirection != currentDirection) {
//如果与上次方向不同,则履行显/隐动画
if (currentDirection < 0) {
animateHide();
} else {
animateBack();
}
}
}
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
//手指抬起的时候要把currentDirection设置为0,这样下次不管向哪拉,都与当前的不同(其实在ACTION_DOWN里写了以后这里就用不着了……)
currentDirection = 0;
lastDirection = 0;
break;
}
return false;
}
};
AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() {
//这个Listener实际上是用来对付当用户的手离开列表后列表依然在滑动的情况,也就是SCROLL_STATE_FLING
int lastPosition = 0;//上次转动到的第1个可见元素在listview里的位置――firstVisibleItem
int state = SCROLL_STATE_IDLE;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//记录当前列表状态
state = scrollState;
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == 0) {
animateBack();
}
if (firstVisibleItem > 0) {
if (firstVisibleItem > lastPosition && state == SCROLL_STATE_FLING) {
//如果上次的位置小于当前位置,那末隐藏头尾元素
animateHide();
}
//================================
if (firstVisibleItem < lastPosition && state == SCROLL_STATE_FLING) {
//如果上次的位置大于当前位置,那末显示头尾元素,其实本例中,这个if没用
//如果是滑动ListView触发的,那末,animateBack()肯定已履行过了,所以没有必要
//如果是点击按钮啥的触发转动,那末根据设计原则,按钮肯定是头尾元素之1,所以也不需要animateBack()
//所以这个if块是不需要的
animateBack();
}
//这里没有判断(firstVisibleItem == lastPosition && state == SCROLL_STATE_FLING)的情况,
//但是如果列表中的单个item如果很长的话还是要判断的,只不过代码又要多几行
//但是可以取巧1下,在触发滑动的时候拖动履行1下animateHide()或animateBack()――本例中的话就写在那个点击事件里就能够了)
//BTW,如果列表的滑动纯是靠手滑动列表,而没有类似于点击1个按钮滚到某个位置的话,只要第1个if就够了…
}
lastPosition = firstVisibleItem;
}
};
}