设置ListView需要显示在第几页
当需要指定ListView具体显示的Item时,可以通过如下代码来实现:
mListView.setSelection(position);
但这个方法类似scrollTo,是瞬间完成的移动。除此之外,还可以使用如下代码来实现平滑移动。
mListView.smoothScrollToPosition(position);
mListView.smoothScrollByOffset(offset);
mListView.smoothScrollBy(distance, duration);
ListView滑动监听
ListView的滑动监听,是ListView中最重要的技巧,很多重写的ListView,基本上都是在滑动事件的处理上下功夫,通过判断滑动事件进行不同的逻辑处理。而为了更加精确地监听滑动事件,开发者通常还需要使用GestureDetector手势识别、VelocityTracker滑动速度检测等辅助类来完成更好的监听。一般有两种监听方式,一种是通过OnTouchListener来实现监听,另一种是使用OnScrollListener来实现监听。
OnTouchListener
通过ACTION_DOWN、ACTION_MOVE、ACTION_UP这三个事件发生时的坐标,就可以根据坐标判断用户滑动的方向,并在不同的事件中进行相应的逻辑处理,代码如下:
mListView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://触摸时操作
break;
case MotionEvent.ACTION_MOVE://移动时操作
break;
case MotionEvent.ACTION_UP://离开时操作
break;
}
return false;
}
});
OnScrollListener
OnScrollListener是AbsListView中的监听事件,它封装了很多与ListView相关的信息,使用也更加灵活。
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
private int lastVisibleItemPosition;//上次第一个可视的Item的ID
/**
* 当用户没有做手指抛动的状态时,这个方法只会回调2次,否则会回调3次,差别就是手指抛动的这个状态。
* 通常情况下,我们会在这个方法中通过不同的状态来设置一些标志Flag,来区分不同的滑动状态,供其它
* 方法处理。
* @param view
* @param scrollState
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
//滑动停止时
Log.d("Test", "SCROLL_STATE_IDLE");
break;
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
//正在滚动
Log.d("Test", "SCROLL_STATE_TOUCH_SCROLL");
break;
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
//手指抛动时,即手指用力滑动,在离开后ListView由于惯性继续滑动的状态
Log.d("Test", "SCROLL_STATE_FLING");
break;
}
}
/**
* 该方法在ListView滚动时会一直回调,通过该方法的后3个参数,可以很方便地进行一些判断
* @param view
* @param firstVisibleItem 当前能看见的第一个Item的ID(从0开始)
* @param visibleItemCount 当前看见的Item总数(包括没有显示完整的Item)
* @param totalItemCount 整个ListView的Item总数
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//滚动时一直调用
Log.d("Test", "onScroll");
//判断是否滚动到最后一行
if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) {
}
//判断滚动的方向
if (firstVisibleItem > lastVisibleItemPosition) {
//上滑
} else if (firstVisibleItem < lastVisibleItemPosition) {
//下滑
}
lastVisibleItemPosition = firstVisibleItem;
}
});
另外,ListView也给我们提供了一些封装的方法来获得当前可视的Item的位置信息:
//获取可视区域内最后一个Item的id
mListView.getLastVisiblePosition();
//获取可视区域内第一个Item的id
mListView.getFirstVisiblePosition();
具有弹性的ListView
在IOS系统中,列表都是具有弹性的,即滚动到底端或者顶端后会继续往下或者往上滑动一段距离。网上有很多通过重写ListView来实现弹性效果的方法,比如增加HeadView或者使用ScrollView进行嵌套,方法很多,不过可以使用一种非常简单的方法来实现这个效果。虽然不如那些方法可定制化高、效果丰富,但主要的目的是让我们学会如何从源代码中找到问题的解决方法,效果图如下。
我们在查看ListView的源代码时可以发现,ListView中有一个控制滑动到边缘的处理方法,如下所示:
protected boolean overScrollBy(int deltaX, int deltaY,
int scrollX, int scrollY,
int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY,
boolean isTouchEvent)
注意参数:maxOverScrollY,注释中写道——Number of pixels to overscroll by in either direction along the Y axis。由此可以发现,虽然它的默认值是0,但其实只要修改这个参数的值,就可以让ListView具有弹性了。代码如下:
package com.example.huangfei.myapplication;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.widget.ListView;
/**
* Created by huangfeihong on 2016/5/4.
* 具有弹性的ListView
*/
public class FlexibleListView extends ListView {
private int mMaxOverDistance = 50;
private Context mContext;
public FlexibleListView(Context context) {
super(context);
mContext = context;
init();
}
public FlexibleListView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public FlexibleListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
//让不同分辨率设备的弹性距离基本一致
private void init() {
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
float density = metrics.density;
mMaxOverDistance = (int) (density * mMaxOverDistance);
}
@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, maxOverScrollX, mMaxOverDistance, isTouchEvent);
}
}
自动显示、隐藏布局的ListView
若要实现当我们在ListView上滑动的时候,顶部的布局就会相应的隐藏或显示的效果,我们应该怎么办,效果图如下:
要让一个布局显示或者隐藏并带有动画效果,可以通过属性动画来很方便地实现,所以这个效果的关键在于根据ListView的滑动方向来判断是否需要显示或隐藏对应的布局。
布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:headerDividersEnabled="false" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@android:color/holo_blue_light"/>
</RelativeLayout>
代码如下:
package com.example.huangfei.myapplication;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
/**
* 自动显示、隐藏布局的ListView
*/
public class ScrollHideListViewActivity extends Activity {
private ListView mListView;
private Toolbar mToolbar;
private String[] data = new String[30];
private float mFirstY;
private float mCurrentY;
private int mDirection;
private int mTouchSlop;
private boolean mShow = true;
private ObjectAnimator mAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scroll_hide);
mListView = (ListView) findViewById(R.id.listview);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
for (int i = 0; i < data.length; i++) {
data[i] = "Item " + i;
}
/**
* 在开使判断滑动事件之前,要先给ListView增加一个HeaderView,避免第一个Item被Toolbar遮挡.
* 其中通过使用abc_action_bar_default_height_material属性获取系统Actionbar的高度
*/
View headerView = new View(this);
headerView.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, (int) getResources().getDimension(R.dimen.abc_action_bar_default_height_material)));
mListView.addHeaderView(headerView);
mListView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1, data));
//定义系统认为的最低滑动距离
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
ObjectAnimator mAnimator;
mListView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://触摸时操作
mFirstY = event.getY();
break;
case MotionEvent.ACTION_MOVE://移动时操作
mCurrentY = event.getY();
if (mCurrentY - mFirstY > mTouchSlop) {
mDirection = 0;//down
} else if (mFirstY - mCurrentY > mTouchSlop) {
mDirection = 1;//up
}
if (mDirection == 1) {
if (mShow) {
toolbarAnim(1);//hide
mShow = !mShow;
}
} else if (mDirection == 0) {
if (!mShow) {
toolbarAnim(0);//show
mShow = !mShow;
}
}
break;
case MotionEvent.ACTION_UP://离开时操作
break;
}
return false;
}
});
}
private void toolbarAnim(int flag) {
// if (mAnimator != null && mAnimator.isRunning()) {
// mAnimator.cancel();
// }
if (flag == 0) {
mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY", mToolbar.getTranslationY(), 0);
} else {
mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY", mToolbar.getTranslationY(), -mToolbar.getHeight());
}
mAnimator.start();
}
}
这里使用了Toolbar这样一个新控件,Google已经推荐它用来逐渐取代ActionBar了,因为它更灵活。但是在使用的时候,一定要注意使用的theme一定要是NoActionBar的,不然会引起冲突。