转载请注明出处,点击此处 查看更多精彩内容。

QQ空间相信大家都用过,是否觉得它的下拉刷新很酷呢?今天就来自己实现这个控件。

本文主要是讲思想和一些api,想要使用此效果到项目中的同学请点击这里带动画的下拉刷新RecyclerView

###效果图:

对实现过程不感兴趣的童鞋可以直接到文章底部粘帖代码,代码中有详细注释。

要实现这样的效果,需要重写ListView控件,并在ListView中处理下拉事件。

首先我们进行ListView最基础的操作,就是设置适配器显示头部布局和一个列表出来,这些操作相信大家都会写,直接贴出代码:
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.sch.headzoomlistviewdemo.HeadZoomListViewandroid:id="@+id/list_view"android:layout_width="match_parent"android:layout_height="wrap_content" /></RelativeLayout>

上面这个是Activity的内容布局,其中包含一个自定义的ListView控件com.example.sch.headzoomlistviewdemo.HeadZoomListView
list_view_header.xml

<?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="wrap_content"><ImageViewandroid:id="@+id/iv_hander"android:scaleType="centerCrop"android:layout_width="match_parent"android:layout_height="162dp"android:src="@mipmap/banner1" /></RelativeLayout>

此布局做为ListView的头部,里面只包含一个ImageView,可以看到给ImageView设置了android:scaleType="centerCrop"属性,表示按比例扩大此ImageView的图片资源的size居中显示,使得图片长(宽)等于或大于View的长(宽) ,但是此属性只有在ImageView使用android:src=""属性或者setImageBitmap()或者setImageResource()方法设置图片时才有效,使用background设置背景是无效的。

android:scaleType的各种值的含义可以参考 ImageView.ScaleType设置图解
MainActivity.java

package com.example.sch.headzoomlistviewdemo;import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;import java.util.ArrayList;
import java.util.List;/*** Created by shichaohui on 2015/7/31 0031.*/
public class MainActivity extends AppCompatActivity {private HeadZoomListView mListView;private View headerView;private List<String> datas;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initData();mListView = (HeadZoomListView) this.findViewById(R.id.list_view);headerView = LayoutInflater.from(this).inflate(R.layout.list_view_header, null);mListView.addHeaderView(headerView);mListView.setAdapter(new ArrayAdapter<>(this,android.R.layout.simple_expandable_list_item_1, datas));}/*** 初始化数据*/private void initData() {datas = new ArrayList<>();for (int i = 0; i < 3; i++) {datas.add("条目  " + (i + 1));}}}

接着就是我们的重头戏自定义ListView了,首先回顾我们需要实现的效果:

  • 在顶部继续下拉时头部拉伸;
  • 拉伸之后手指上推减小拉伸高度;
  • 拉伸时即时更改背景图的透明度;
  • 松手后自动弹回原位置。

根据要实现的效果,我们可以推出需要的参数如下:

private ImageView headerImage;
private int headerImageHeight = -1; // 默认高度
private int headerImageMaxHeight = -1; // 最大高度
private float scaleRatio = 1.5f; // 最大拉伸比例
private int headerImageScaleHeight = -1; // 被拉伸的高度
private float headerImageMinAlpha = 0.5f; // 拉伸到最高时头部的透明度
private long durationMillis = 1000; // 头部恢复动画的执行时间

由于ListView中并不能直接获取Header,所以我们需要定义一个函数,由调用者传入头部的背景ImageView,并计算相关属性:

/*** 设置头部图片** @param headerImage 头部中的背景ImageView*/
public void setHeaderImage(ImageView headerImage) {this.headerImage = headerImage;headerImageHeight = headerImage.getHeight();headerImageMaxHeight = (int) (headerImageHeight * scaleRatio);// 防止第一次拉伸的时候headerImage.getLayoutParams().height = 0headerImage.getLayoutParams().height = headerImageHeight;
}

为了计算的准确性,我们需要在View显示出来后才调用setHeaderImage(),因此需要重写MainActivity的onWindowFocusChanged()方法:

@Override
public void onWindowFocusChanged(boolean hasFocus) {super.onWindowFocusChanged(hasFocus);mListView.setHeaderImage((ImageView) headerView.findViewById(R.id.iv_hander));
}

为了增加扩展性,还增加以下几个方法:

/*** 设置头部的最大拉伸倍率,默认1.5f** @param scaleRatio 头部的最大拉伸倍率,必须大于1,小于1则默认为1.5f*/
public void setScaleRatio(float scaleRatio) {this.scaleRatio = scaleRatio;
}/*** 设置拉伸到最高时头部的透明度,默认0.5f** @param headerImageMinAlpha 拉伸到最高时头部的透明度,0.0~1.0*/
public void setHeaderImageMinAlpha(float headerImageMinAlpha) {this.headerImageMinAlpha = headerImageMinAlpha;
}/*** 设置头部恢复动画的执行时间,默认1000毫秒** @param durationMillis 头部恢复动画的执行时间,单位:毫秒*/
public void setHeaderImageDurationMillis(long durationMillis) {this.durationMillis = durationMillis;
}

接下来重写ListView的overScrollBy()方法处理下拉/上拉过度事件:

@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,int scrollRangeX, int scrollRangeY, int maxOverScrollX,int maxOverScrollY, boolean isTouchEvent) {// deltaY为拉伸过度时每毫秒拉伸的距离,正数表示向上拉伸多度,负数表示向下拉伸过度if (deltaY < 0 && headerImage.getLayoutParams().height < headerImageMaxHeight|| deltaY > 0 && headerImage.getLayoutParams().height > headerImageHeight) {// 修改宽高headerImage.getLayoutParams().height -= deltaY;// 重新设置View的宽高headerImage.requestLayout();}return true;
}

当ListView到达边界并继续拉的时候(这里称为"下拉/上拉过度")就会触发此方法,其中参数deltaY表示每毫秒拉动的距离,下拉时此参数是负数,上拉过度时是正数。
因此,满足条件deltaY < 0 && headerImage.getLayoutParams().height < headerImageMaxHeight(下拉且没有到达最大高度)的时候,需要增大headerImage的宽度,但是此时deltaY是负数,因此使用"-=“修改高度。条件deltaY > 0 && headerImage.getLayoutParams().height > headerImageHeight表示上拉过度且已被拉伸的时候,需要减小headerImage的宽度,但是此时deltaY是正数,因此也使用”-="修改高度。
headerImage.requestLayout();会重新测量View的宽高,不调用此方法上面的修改也就不会更新到界面上。

实现下拉一段高度后上推减小headerImage的拉伸高度效果,需要重写onScrollChanged()方法,重写onTouchEvent()也可以,只是太难控制,且效果不太好:

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);if (headerImage == null) {return;}View view = (View) headerImage.getParent();// 上推的时候减小高度至默认高度if (view.getTop() < 0 && headerImage.getLayoutParams().height > headerImageHeight) {headerImage.getLayoutParams().height += view.getTop();// 重新计算尺寸布局view.layout(view.getLeft(), 0, view.getRight(), view.getBottom());headerImage.requestLayout();}}

view.layout(view.getLeft(), 0, view.getRight(), view.getBottom());遍历视图树,重新测量并设置头部的高度和子布局的位置。

重新测量的时候,View使用requestLayout()方法,ViewGroup使用layout()方法,layout()方法中的四个参数前两个表示ViewGroup左上角坐标和右下角坐标。

接着实现松手时的动画:

@Override
public boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if (headerImage.getLayoutParams().height > headerImageHeight) {  // 使用动画恢复默认高度  headerImage.clearAnimation();  headerImage.startAnimation(new ResetAnimaton());  return true;  }}return super.onTouchEvent(ev);
}
/*** 自定义恢复时的动画*/
class ResetAnimaton extends Animation {public ResetAnimaton() {setDuration(durationMillis);// 计算开始动画时的拉伸高度headerImageScaleHeight = headerImage.getLayoutParams().height - headerImageHeight;}@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {// interpolatedTime从动画开始到结束,由0.0~1.0if (headerImage.getLayoutParams().height - headerImageHeight > 0) {// 计算新高度headerImage.getLayoutParams().height -= headerImageScaleHeight * interpolatedTime;// 计算新拉伸高度headerImageScaleHeight -= headerImageScaleHeight * interpolatedTime;// 重新布局headerImage.requestLayout();}}
}

最后加入拉伸时透明度的变化:

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);...updateHeaderAlpha();}/*** 更新头部的透明度*/
private void updateHeaderAlpha() {// 当前拉伸高度int scallHeight = headerImage.getLayoutParams().height - headerImageHeight;if (scallHeight > 0) {// 新的透明度(1 - 当前拉伸高度 / 最大拉伸高度 * (1 - 目标透明度))headerImage.setAlpha(1 - (float) scallHeight/ (headerImageMaxHeight - headerImageHeight) * (1 - headerImageMinAlpha));}
}

贴完整代码

MainActivity.java

package com.example.sch.headzoomlistviewdemo;import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;import java.util.ArrayList;
import java.util.List;/*** Created by shichaohui on 2015/7/31 0031.*/
public class MainActivity extends AppCompatActivity {private HeadZoomListView mListView;private View headerView;private List<String> datas;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initData();mListView = (HeadZoomListView) this.findViewById(R.id.list_view);headerView = LayoutInflater.from(this).inflate(R.layout.list_view_header, null);mListView.addHeaderView(headerView);mListView.setScaleRatio(2.0f);mListView.setHeaderImageDurationMillis(800);mListView.setHeaderImageMinAlpha(0.3f);mListView.setAdapter(new ArrayAdapter<>(this,android.R.layout.simple_expandable_list_item_1, datas));}@Overridepublic void onWindowFocusChanged(boolean hasFocus) {super.onWindowFocusChanged(hasFocus);mListView.setHeaderImage((ImageView) headerView.findViewById(R.id.iv_hander));}/*** 初始化数据*/private void initData() {datas = new ArrayList<>();for (int i = 0; i < 3; i++) {datas.add("条目  " + (i + 1));}}}

HeadZoomListView.java

package com.example.sch.headzoomlistviewdemo;import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.ImageView;
import android.widget.ListView;/*** 下拉头部缩放ListView* <br/>* Created by shichaohui on 2015/7/31 0031.*/
public class HeadZoomListView extends ListView {private ImageView headerImage;private int headerImageHeight = -1; // 默认高度private int headerImageMaxHeight = -1; // 最大高度private int headerImageScaleHeight = -1; // 被拉伸的高度private float scaleRatio = 1.5f; // 最大拉伸比例private float headerImageMinAlpha = 0.5f; // 拉伸到最高时头部的透明度private long durationMillis = 1000; // 头部恢复动画的执行时间public HeadZoomListView(Context context) {super(context);}public HeadZoomListView(Context context, AttributeSet attrs) {super(context, attrs);}public HeadZoomListView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 设置头部图片** @param headerImage 头部中的背景ImageView*/public void setHeaderImage(ImageView headerImage) {this.headerImage = headerImage;headerImageHeight = headerImage.getHeight();headerImageMaxHeight = (int) (headerImageHeight * scaleRatio);// 防止第一次拉伸的时候headerImage.getLayoutParams().height = 0headerImage.getLayoutParams().height = headerImageHeight;}/*** 设置头部的最大拉伸倍率,默认1.5f** @param scaleRatio 头部的最大拉伸倍率,必须大于1,小于1则默认为1.5f*/public void setScaleRatio(float scaleRatio) {this.scaleRatio = scaleRatio;}/*** 设置拉伸到最高时头部的透明度,默认0.5f** @param headerImageMinAlpha 拉伸到最高时头部的透明度,0.0~1.0*/public void setHeaderImageMinAlpha(float headerImageMinAlpha) {this.headerImageMinAlpha = headerImageMinAlpha;}/*** 设置头部恢复动画的执行时间,默认1000毫秒** @param durationMillis 头部恢复动画的执行时间,单位:毫秒*/public void setHeaderImageDurationMillis(long durationMillis) {this.durationMillis = durationMillis;}@Overrideprotected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,int scrollRangeX, int scrollRangeY, int maxOverScrollX,int maxOverScrollY, boolean isTouchEvent) {// deltaY为拉伸过度时每毫秒拉伸的距离,正数表示向上拉伸多度,负数表示向下拉伸过度if (deltaY < 0 && headerImage.getLayoutParams().height < headerImageMaxHeight|| deltaY > 0 && headerImage.getLayoutParams().height > headerImageHeight) {// 修改宽高headerImage.getLayoutParams().height -= deltaY;// 重新设置View的宽高headerImage.requestLayout();}return true;}@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);if (headerImage == null) {return;}View view = (View) headerImage.getParent();// 上推的时候减小高度至默认高度if (view.getTop() < 0 && headerImage.getLayoutParams().height > headerImageHeight) {headerImage.getLayoutParams().height += view.getTop();// 重新计算尺寸布局view.layout(view.getLeft(), 0, view.getRight(), view.getBottom());headerImage.requestLayout();}updateHeaderAlpha();}@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if (headerImage.getLayoutParams().height > headerImageHeight) {  // 使用动画恢复默认高度  headerImage.clearAnimation();  headerImage.startAnimation(new ResetAnimaton());  return true;  }}return super.onTouchEvent(ev);}/*** 更新头部的透明度*/private void updateHeaderAlpha() {// 当前拉伸高度int scallHeight = headerImage.getLayoutParams().height - headerImageHeight;if (scallHeight > 0) {// 新的透明度(1 - 当前拉伸高度 / 最大拉伸高度 * (1 - 目标透明度))headerImage.setAlpha(1 - (float) scallHeight/ (headerImageMaxHeight - headerImageHeight) * (1 - headerImageMinAlpha));}}/*** 自定义恢复时的动画*/class ResetAnimaton extends Animation {public ResetAnimaton() {setDuration(durationMillis);// 计算开始动画时的拉伸高度headerImageScaleHeight = headerImage.getLayoutParams().height - headerImageHeight;}@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {// interpolatedTime从动画开始到结束,由0.0~1.0if (headerImage.getLayoutParams().height - headerImageHeight > 0) {// 计算新高度headerImage.getLayoutParams().height -= headerImageScaleHeight * interpolatedTime;// 计算新拉伸高度headerImageScaleHeight -= headerImageScaleHeight * interpolatedTime;// 重新布局headerImage.requestLayout();}}}}

布局文件在文章开头有贴出,这里就不重复了。

END

欢迎评论吐槽…

打造QQ空间头部视差ListView相关推荐

  1. iOS传感器集锦、飞机大战、开发调试工具、强制更新、Swift仿QQ空间头部等源码

    iOS精选源码 飞机大作战 MUPhotoPreview -简单易用的图片浏览器 image LLDebugTool是一款针对开发者和测试者的调试工具,它可以帮... image 多个UIScroll ...

  2. RecyclerView实现QQ空间和微信朋友圈头部刷新效果

    RecyclerView实现QQ空间和微信朋友圈头部刷新效果 老规矩先上图 本篇主要讲RecyclerView实现QQ空间和微信朋友圈头部刷新效果,如果想了解ListView如何实现,请查看上篇:Li ...

  3. 安卓项目SimpleQQ——QQ空间说说时光轴设计

    完整项目介绍看这里:安卓项目SimpleQQ概述,可下载源码. 单条说说:qzone_time_item.xml <?xml version="1.0" encoding=& ...

  4. 仿QQ空间打造可拉伸头部组件

    仿QQ空间打造可拉伸头部组件 本章没有什么难点和技术点,主要是大晚上的没啥事写着玩 -.- 首先先上一个效果图 全代码文件不超过150行 所以说超简单~ ~(当然前提是布局简单 只写了一个类似的效果而 ...

  5. Android自定义控件:打造自己的QQ空间主页

    前面已经实现过仿QQ的List抽屉效果以及仿QQ未读消息拖拽效果,具体请见: Android自定义控件:类QQ抽屉效果 Android自定义控件:类QQ未读消息拖拽效果 趁热打铁,这次我们实现QQ空间 ...

  6. android仿qq动态视频播放,Android 自定义ListView实现QQ空间界面(说说内包含图片、视频、点赞、评论、转发功能)...

    前端时间刚好需要做一个类似于QQ空间的社区分享功能,说说内容包含文字(话题.内容).视频.图片,还需包含点赞,评论,位置信息等功能. 就采用LIstview做了一个,先来看下效果,GIF太大,CSDN ...

  7. android listview左右滑动动画效果,Android基于ListView实现类似QQ空间的滚动翻页与滚动加载效果...

    本文实例讲述了Android基于ListView实现类似QQ空间的滚动翻页与滚动加载效果.分享给大家供大家参考,具体如下: 1. 滚动加载 listView.setOnScrollListener(n ...

  8. Android ListView实现QQ空间界面(说说内包含图片、视频、点赞、评论、转发功能),结尾附源码

    前段时间刚好需要做一个类似于QQ空间的社区分享功能,说说内容包含文字(话题.内容).视频.图片,还需包含点赞,评论,位置信息等功能. 就采用LIstview做了一个,先来看下效果,GIF太大,CSDN ...

  9. 【Android 仿微信通讯录 导航分组列表-上】使用ItemDecoration为RecyclerView打造带悬停头部的分组列表

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 转载请标明出处: http://blog.csdn.net/zxt0601/article/details/52355199 本文 ...

最新文章

  1. UIButton下面添加滑动的线
  2. IDC最新报告:阿里语音AI登顶中国No.1
  3. c怎么调用matlab dll,matlab和c++调用DLL方法(最新整理)
  4. Observer 模式在eHR中的应用
  5. 实践:使用FLANN.LSH进行检索
  6. 前端学习(3251):样式的模块化
  7. c语言二进制数怎么表示_搞懂这些公式和原理,二级C语言对你来说肯定会简单很多!...
  8. C和C++混合编程(__cplusplus 与 external c 的使用)
  9. 下载IEEE 论文模板的方法
  10. IEC62304-2006解读
  11. hive根据出生日期算年龄-粗略版
  12. Android网络:开发浏览器(五)——功能完善之保存图片实现
  13. 中序和后序(前序和中序)序列确定一颗二叉树
  14. 推荐系统--基于图的推荐算法
  15. 黑客入侵微软邮件服务器部署勒索软件、惠普更新打印机漏洞|12月2日全球网络安全热点
  16. psycopg2 , pymysql 连接数据库 操作
  17. 国际移动设备识别码IMEI
  18. 国内人工智能行业全梳理
  19. 计算机网络专业函授,函授计算机专业都考什么课程
  20. 三层开发我的一家之言

热门文章

  1. java要学mysql 吗_做JAVA开发需要把数据库学习到何种程度
  2. 个人亲历64位win7硬盘安装ubuntu实录
  3. 华为云普惠AI:把人工智能变成“水电煤气”
  4. Excel---将文本转变表格中
  5. java.net.UnknownHostException: www.xxx.com: Name or service not known的某种情况
  6. ESP32(IDF)EC11旋转编码器使用总结
  7. oracle全集实战视频教程,oracle入门到精通教程下载
  8. 【STM32CubeMX】教程二_IIC驱动0.96oled屏幕(SSD1306)
  9. 新酒店图集 | 南京丽思卡尔顿、武汉光谷万豪、福州梧桐君澜温泉度假酒店开业...
  10. python几何拼贴画_想要了解拼贴画这篇就够了,最全的种类和技法全在这里!