最近看了动脑学院一些视频资料,讲的是通过自定义View实现一个头部可伸缩的ListView,觉得实现不难,而且思路挺有启发性的,于是就根据资料自己写了相关代码实现了一个。
因为添加gif失败,就添加图片吧。。
正常状态:
手指下拉状态:
这里的思路首先是给ListView添加一个Header,一般是一个渲染一个包含一张图片的布局的View,然后通过检测手指过度下拉的尺寸取改变Header的属性使得图片缩放。
完成这一过程主要要解决这几个问题:
1.如何使得图片缩放:
这里巧妙运用了ImageVIew的ScaleType中的centerCrop属性(图片中心在ImageView的中心,图片铺满ImageView,即图片最小边至少和ImageView一样大),于是可以通过改变ImageView的高度来改变图片的大小。
2.如何检测过度下拉:
首先什么是过度下(上)拉?它就是当ListView的内容已经全部展开的时候,即ListView内容顶部到达ListView控件顶部或者内容底部没有超过ListView控件底部的时候,再去分别下拉和上拉就属于过度下上拉。
安卓系统View已经有方法去检测,当ListView过度上下拉的时候,系统会回调该方法,将上下拉的距离作为参数传进来,官方的注释是。
Scroll the view with standard behavior for scrolling beyond the normal
* content boundaries. Views that call this method should override
* {@link #onOverScrolled(int, int, boolean, boolean)} to respond to the
* results of an over-scroll operation.
*
* Views can use this method to handle any touch or fling-based scrolling.
这里ListView已经重写好了onOverScrolled实现滑动,所以这里要做的就是重写该方法,通过传进来的deltaY进行处理Header图片大小。
3.如何在不过度上下拉的情况下改变图片大小(比如过度下拉后假如此时ListView内容底部已经超过ListView控件底部时候,手指往上滑):
其实和2一样,只是重写的是onScrollChanged方法,该方法是当ListView内部内容滑动的时候回调。
4.如何在手指下拉之后离开屏幕让ListView自动滑动返回原来位置:
这里使用自定义动画,重写onTouchEvent方法,检测到手指离开屏幕则让ImageVeiw做动画恢复原来大小。
上面是基本思路,下面给出具体的代码:
public class ExtendHeaderListView extends ListView {
private ImageView mImageView;
private static int mImageViewHeight = 400;
public ExtendHeaderListView(Context context) {
super(context);
}
public ExtendHeaderListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExtendHeaderListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 监听下拉过度,注意,只有在ListView顶部到屏幕顶部继续下拉或者底部到屏幕底部继续上推才会调用
*/
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX
, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
Log.d("overScrollBy","deltaY:" + deltaY + " scrollY:" + scrollY + " scrollRangeY:" + scrollRangeY + " maxOverScrollY:" + maxOverScrollY);
if (mImageView != null){
if (deltaY < 0){
//过度下拉
mImageView.getLayoutParams().height = mImageView.getHeight() - deltaY;
mImageView.requestLayout();
}else {
//过度上拉。注意这里是当图片已经因下拉而放大的时候上滑,而因为ListView的底部还未超过屏幕底部时候,此时上拉属于过度上拉
if (mImageView.getHeight() > mImageViewHeight){
mImageView.getLayoutParams().height = mImageView.getHeight() - deltaY;
mImageView.requestLayout();
}
}
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY
, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
/**
* 当过度下拉之后再用手指滑回去
*
* 注意,当ListView内容全部展开的时候,即往上滑(此时属于过度上拉)不动了,就不会回调这个方法了
* 该方法只在ListView内部内容滑动的时候回调
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
View header = (View) mImageView.getParent();
if (mImageView.getHeight() > mImageViewHeight){
mImageView.getLayoutParams().height = mImageView.getHeight() + header.getTop();
header.layout(header.getLeft(),0,header.getRight(),header.getHeight());
mImageView.requestLayout();
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action){
//手指离开屏幕,图片恢复原来的大小
case MotionEvent.ACTION_UP:
ShrinkAnimation animation = new ShrinkAnimation(mImageView,mImageView.getHeight(),mImageViewHeight);
animation.setDuration(500);
mImageView.startAnimation(animation);
break;
}
return super.onTouchEvent(ev);
}
public void setImageView(ImageView imageView) {
mImageView = imageView;
}
/**
* 松手缩回动画
*/
private class ShrinkAnimation extends Animation{
private ImageView mImageView;
private int originHeight;
private int targetHeight;
public ShrinkAnimation(ImageView imageView, int originHeight, int targetHeight) {
mImageView = imageView;
this.originHeight = originHeight;
this.targetHeight = targetHeight;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mImageView.getLayoutParams().height = (int) (originHeight - (originHeight - targetHeight)*interpolatedTime);
mImageView.requestLayout();
}
}
}
有了思路看懂代码不难,重点的代码也写了注释。
项目代码都在这里