安卓ListView原设定并没有办法实现类似IOS那种上下拉弹性的效果,这样体验起来难免会有些生硬。主要源于于ListView的一个方法overScrollBy(),当中“maxOverScrollY”在垂直方向上可超出拉升距离默认值为0,若想实现弹性的效果只要设定该值大于0即可。
@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, maxOverScrollY, isTouchEvent);
}
想要实现导航栏的滑动隐藏或者显示必然得先获取列表的滚动方向,获取滚动方向有两种方法,一种是通过setOnScrollListener();中的onScroll();判断,其判断原理主要是通过获取滚动列表前第一个可见项Item的索引与随后滑动置顶第一个Item的索引带下关系获取滚动方向。
int lastVisibleItem;
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem > lastVisibleItem) {
// 上滑
} else if (firstVisibleItem < lastVisibleItem){
// 下滑
}
lastVisibleItem = firstVisibleItem;
}
另外的一种方法即使通过setOnTouchListener();记录触控点的位置与滑动位置的比较以获得ListView的滚动方向。其中”mMinSlopDistance”为获取默认最小滑动距离。
mMinSlopDistance = ViewConfiguration.get(context).getScaledScrollBarSize();
OnTouchListener mOnTouch = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float currentY = event.getY();
if (mStartY - currentY > mMinSlopDistance) {
// 上滑
} else if (currentY - mStartY > mMinSlopDistance) {
// 下滑
}
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
};
准确获取到ListView的滑动方向之后,我们就可以根据滑动方向来控制导航栏的显示状态了。其中有个需要注意的地方是,我们要为ListView添加一个高度与导航栏一致的HeaderView才行,这样才能避免导航栏出现挡住ListView首项。另外导航栏的显示/隐藏动画主要通过ObjectAnimation这个动画对象控制。下面把自定义ListView源码贴下,以及最终效果(弹性效果还需要继续完善)。
public class StretchListView extends ListView {
// 上下文
private Context mContext;
// 最大拉伸
private int mMaxOverDistance;
// 是否启动弹性
private boolean mIsSetOver;
// 对象动画
private ObjectAnimator mAnimator;
// 隐藏导航栏的最低滑动距离限制
private int mMinSlopDistance;
// 起始滑动Y轴触控点
private float mStartY;
// 需要控制现实/隐藏的View
private View mToolbar;
// 是否可启动隐藏动画
private boolean mIsShow;
public StretchListView(Context context) {
super(context);
this.mContext = context;
initView();
}
public StretchListView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initView();
}
public StretchListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initView();
}
private void initView() {
this.mMaxOverDistance = getStretchDistance(mContext, 30);
this.mMinSlopDistance = ViewConfiguration.get(mContext).getScaledScrollBarSize();
}
/** * @param isSetOver 是否启动列表滑动弹性功能 */
public void setScrollOver(boolean isSetOver) {
this.mIsSetOver = isSetOver;
}
/** * @return 获取视图控件高度 */
private int getNavHeight(View v) {
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
v.measure(w, h);
return v.getMeasuredHeight();
}
/** * @param v 需要被隐藏的导航视图 */
public void setHideHeader(View v) {
if (v == null) return;
this.mToolbar = v;
// 先为ListView添加一个HeaderView,避免第一个Item被toolbar挡住
View headerV = new View(mContext);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, getNavHeight(mToolbar));
headerV.setLayoutParams(params);
this.addHeaderView(headerV);
this.setOnTouchListener(mOnTouch);
}
/** * @param context 上下文 * @param maxOver 自定义最大弹射距离 * @return 最大拉伸 */
protected int getStretchDistance(Context context, int maxOver) {
// DisplayMetrics 类提供了一种关于显示的通用信息,如显示大小,分辨率和字体
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
// 获取密度
float dy = metrics.density;
return (int) dy * maxOver;
}
/** * @param flag 方向 */
private void toolbarAnim(int flag) {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY", mToolbar.getTranslationY(),
flag == 0 ? 0 : -mToolbar.getHeight());
mAnimator.start();
}
OnTouchListener mOnTouch = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float currentY = event.getY();
if (mStartY - currentY > mMinSlopDistance && mIsShow) {
toolbarAnim(1);// 向上
mIsShow = !mIsShow;
} else if (currentY - mStartY > mMinSlopDistance && !mIsShow) {
toolbarAnim(0);// 向下
mIsShow = !mIsShow;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
};
@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, mIsSetOver ? mMaxOverDistance : maxOverScrollY, isTouchEvent);
}
}