应用场景:QQ 空间,微信朋友圈,微博,需要快速定位的列表效果图
自定义ParallaxListView 继承ListView
public class ParallaxListView extends ListView {
public ParallaxListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ParallaxListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ParallaxListView(Context context) {
super(context);
}
}
填充ListView 的数据
public class Cheeses {
public static final String[] NAMES = new String[]{"宋江", "卢俊义", "吴用",
"公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智 深",
"武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", " 穆弘",
"雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", " 解珍",
" 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪 ",
"魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方 ",
"郭盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", " 项充",
"李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿 ",
"陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永 ", "施恩",
"周通", "李忠", "杜兴", "汤隆", "邹渊", "邹润", "朱富", "朱贵", "蔡福", "蔡庆", " 李立",
"李云", "焦挺", "石勇", "孙新", "顾大嫂", "张青", "孙二娘", " 王定六", "郁保四", " 白胜",
"时迁", "段景柱"};
}
activity_main.xml
<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" >
<com.example.parallax.widget.ParallaxListView
android:id="@+id/plv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
RelativeLayout>
MainActivity 填充数据
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
plv = (ParallaxListView) findViewById(R.id.plv);
plv.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, Cheeses.NAMES));
}
创建header 布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_header"
android:layout_width="match_parent"
android:layout_height="160dp"
android:contentDescription="@null"
android:scaleType="centerCrop"
android:src="@drawable/parallax_img"/>
RelativeLayout>
第10 行scaleType 为图片的填充模式
MainActivity.java 中添加头布局
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
plv = (ParallaxListView) findViewById(R.id.plv);
View headerView = View.inflate(this,R.layout.layout_header, null);
//添加头布局,需要在setAdapter 前添加
plv.addHeaderView(headerView);
plv.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1,Cheeses.NAMES));
}
ParallaxListView 需要重写overScrollBy()方法,api 要求9 以上
/**
* 滑动到ListView 两端才会调用
*/
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
int scrollY, int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
//deltaY 竖直方向滑动的瞬时变化量,顶部下拉为-,底部上拉为+
//scrollY 两端滑动超出的距离,顶部为-,底部为+
//scrollRangeY 竖立方向滑动的范围
//maxOverScrollY 竖立方向最大的滑动位置
//isTouchEvent 是否是用户触摸拉动,true 表示用户手指触摸拉动,false 表示惯性
System.out.println("deltaY:" + deltaY + " scrollY:" + scrollY
+ " scrollRangeY:" + scrollRangeY + " maxOverScrollY:"
+ maxOverScrollY + " isTouchEvent:" + isTouchEvent);
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
理解deltaY 及isTouchEvent 参数的含义
ParallaxListView 添加setParallaxImage()方法
private ImageView headerImage;
public void setParallaxImage(ImageView imageView){
headerImage = imageView;
}
MainActivity 调用ParallaxListView 的setParallaxImage()方法
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
plv = (ParallaxListView) findViewById(R.id.plv);
View headerView = View.inflate(this,R.layout.layout_header, null);
final ImageView headerImage = (ImageView) headerView.findViewById(R.id.iv_header);
//等view 的树状结构渲染完毕时,再将headerImage 设置到plv 中
headerImage.getViewTreeObserver().addOnGlobalLayoutListener(newOnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//宽高已经测量完毕
plv.setParallaxImage(headerImage);
//移除监听,避免下次渲染时还调用
headerImage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
//添加头布局,需要在setAdapter 前添加
plv.addHeaderView(headerView);
plv.setAdapter(new ArrayAdapter(this,android.R.layout.simple_list_item_1, Cheeses.NAMES));
}
第7-18 行view 树状结构渲染完毕时,再将头布局中ImageView 设置到ParallaxListView 中,这样在
ParallaxListView 的setParallaxImage()方法中才能获取到ImageView 的宽高
获取头部ImageView 的高度
private int orignalHeight;
private int drawableHeight;
public void setParallaxImage(ImageView imageView){
headerImage = imageView;
//ImageView 初始高度
orignalHeight = imageView.getHeight();
//图片原始高度
drawableHeight = imageView.getDrawable().getIntrinsicHeight();
}
顶部下拉时动态设置头部ImageView 的高度
/**
* 滑动到ListView 两端才会调用
*/
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
int scrollY, int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
//deltaY 竖直方向滑动的瞬时变化量,顶部下拉为-,底部上拉为+
//scrollY 两端滑动超出的距离,顶部为-,底部为+
//scrollRangeY 竖立方向滑动的范围
//maxOverScrollY 竖立方向最大的滑动位置
//isTouchEvent 是否是用户触摸拉动,true 表示用户手指触摸拉动,false 表示惯性
System.out.println("deltaY:" + deltaY + " scrollY:" + scrollY
+ " scrollRangeY:" + scrollRangeY + " maxOverScrollY:"
+ maxOverScrollY + " isTouchEvent:" + isTouchEvent);
//顶部下拉,用户触摸时,将deltaY 累加给Header
if(deltaY < 0 && isTouchEvent){
int newHeight = headerImage.getHeight()+Math.abs(deltaY);
//新高度小于图片原始高度才允许累加变化量
if(newHeight <= drawableHeight){
//让新的值生效
headerImage.getLayoutParams().height = newHeight;
headerImage.requestLayout();
}
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
第16-24 行动态设置头部ImageView 的高度
ParallaxListView 重写onTouchEvent()方法
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
//松手时,把currentHeight 恢复到orignalHeight
int currentHeight = headerImage.getHeight();
//300->160 300,299,280,250,200,...160 随时间生成300 到160 间的值
ValueAnimator animator = ValueAnimator.ofInt(currentHeight,orignalHeight);
animator.setDuration(500);
//动画更新的监听
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取随时间变化得到的currentHeight 到orignalHeight 间的值
int value = (Integer) animation.getAnimatedValue();
//让新的值生效
headerImage.getLayoutParams().height = value;
headerImage.requestLayout();
}
});
//设置插值器实现回弹效果
animator.setInterpolator(new OvershootInterpolator(2));
animator.start();
break;
default:
break;
}
return super.onTouchEvent(ev);
}
第8-23 行手指抬起时,用属性动画实现headerImage 恢复到初始高度