完整工程代碼在這:https://github.com/NashLegend/Auto-Hide-ListView
現在很多軟件都有這種滑動列表的時候自動隱藏頁面頭部和底部元素的功能,比如Google+。在剛剛進入Activity的時候,頁面是一個列表,底部有一個view,頭部一個view,當列表向上滑動的時候,隱藏頭尾元素,以顯示更多內容,當列表向下滑動的時候,再將頭尾元素拉出來。比如Google+。
剛剛進入時是這個樣子:
再把列表身上一拉,頭尾隱藏,成了這個樣子:
再往下拉,就會再變回第一張圖的樣子。
這個例子實現的就是這個功能
這個例子裏面,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是一個TextView,詳見上面的鏈接……
ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, R.layout.simple_layout, str);
listView.setAdapter(adapter);
//爲ListView添加一個Header,這個Header與ToolBar一樣高。這樣我們可以正確的看到列表中的第一個元素而不被遮住。
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設置點擊事件,點擊一次滾動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();
}
}
lastY = currentY;
}
}
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;//上次滾動到的第一個可見元素在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塊沒有必要
animateBack();
}
//這裏沒有判斷(firstVisibleItem == lastPosition && state == SCROLL_STATE_FLING)的情況,
//但是如果列表中的單個item如果很長的話還是要判斷的,只不過代碼又要多幾行
//但是可以取巧一下,在觸發滑動的時候拖動執行一下animateHide()或者animateBack()——本例中的話就寫在那個點擊事件裏就可以了)
//BTW,如果列表的滑動純是靠手滑動列表,而沒有類似於點擊一個按鈕滾到某個位置的話,只要第一個if就夠了……
}
lastPosition = firstVisibleItem;
}
};
}