Android 高仿QQ的下拉刷新 ListView
来源:互联网 发布:php读取zip文件 编辑:程序博客网 时间:2024/06/05 17:54
最近工程需要使用下拉刷新,但是使用网上流传的各种版本均有或多或少的bug,或者效果不完美的地方。在使用QQ的时候,在消息列表界面的下拉刷新,个人感觉效果比较棒,就做了一个高仿版,效果与QQ的基本保持一致,有不足之处,欢迎指正。
源码下载地址:
http://download.csdn.net/detail/yutou58nian/6708851
又重构了一下代码,目前接近完美版!
仅有的一个小问题是,滑开头部,在下拉刷新和松手立即刷新状态来回切换时,滑动的弹性效果会变小。原因是在一直滑动的过程中,根据手指滑动的距离,一直setPadding时,会导致头部的paddingTop值跟实际显示在界面上的效果不一致,暂时还不知道怎么解决。
效果图如下:
主要实现的特殊效果如下:
1. 下拉时缩减手指滑动距离,实现越拉越难的效果
2. 加载状态时,上推界面遮挡部分头部,头部自动收回
3. 加载状态时,界面依然可以下拉,松手自动收回,只显示头部
4. 加载完成后,有加载完成的状态,停留1秒之后自动收回
5. ListView中数据长度没有充满屏幕时,可以下拉刷新
6. ListView中没有数据时,可以下拉刷新
7. 所有的下拉,回弹均有动画效果
具体的实现思路跟网上的是一样的,就是给ListView添加HeadView,默认隐藏,通过监听OnTouch、OnScroll事件实现滑动时的各种效果。
只说说在实现时遇到的几个问题是怎么解决的:
1. 实现越拉越难的效果
在实现拉动越来越难的效果时,通过监听MotionEvent.ACTION_MOVE事件,取手指滑动距离的1/3,代码如下:
if (currentHeaderState != REFRESH_BACED) {mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), -mHeaderHeight+ (int) ((mMoveY - mDownY) / 3),mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());} else if (currentHeaderState == REFRESH_BACED&& headVisible == true) {mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),(int) ((mMoveY - mDownY) / 3),mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());} else if (currentHeaderState == REFRESH_BACED&& headVisible == false) {mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), -mHeaderHeight+ (int) ((mMoveY - mDownY) / 3),mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());}
下拉时一共有三种状态:A. 正常状态、B. 加载状态且头部隐藏、C.加载状态且头部显示。其中A状态和B状态处理方式一样,直接从手指滑动开始计算,拉开滑动距离1/3的效果
C状态时,滑动时位置减去头部的高度,再开始滑动。
直接通过setPadding来实现滑开的效果,且滑动距离缩减1/3,如果手指不松开,来回滑动的话,会导致距离计算不正确,所以在设置回弹效果的时候,要做处理,不然会导致界面收回之后,List中的部分条目也被遮挡。
2. 实现回弹动画
这个因为ListView也是在主界面的线程中,所以可以使用Handler.postDelayed()来实现,每次缩减剩余高度的1/4,5毫秒刷新一次即可。
这里主要实现了两个动画,一个是头部隐藏动画,用于未达到刷新状态,和遮挡部分加载中的头部时的动画
另外一个是头部收回动画,用于下拉高度超出头部高度时,头部的松手回弹动画,代码如下:
Runnable headHideAnimation = new Runnable() {public void run() {if (mHeaderLinearLayout.getBottom() > 0) {int paddingTop = (int) (-mHeaderHeight * 0.25f + mHeaderLinearLayout.getPaddingTop() * 0.75f) - 1;if (paddingTop < -mHeaderHeight) {paddingTop = -mHeaderHeight;}mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), paddingTop,mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());handler.postDelayed(headHideAnimation, 5);} else {handler.removeCallbacks(headHideAnimation);mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), -mHeaderHeight,mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());setSelection(1);headVisible = false;}}};Runnable headBackAnimation = new Runnable() {public void run() {if (mHeaderLinearLayout.getPaddingTop() > 1) {mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),(int) (mHeaderLinearLayout.getPaddingTop() * 0.75f),mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());handler.postDelayed(headBackAnimation, 5);} else {headVisible = true;handler.removeCallbacks(headBackAnimation);}}};3. 实现ListView中数据没有充满屏幕时的下拉当ListView中的数据没有充满屏幕的时候,滑动ListView没有内容的部分,监听不到onScrollStateChanged()事件,只能监听到onTouchEvent()、onScroll()这两个事件,如果不做特殊处理的话,会导致下拉之后状态不改变。
所以在onTouchEvent()中的Move事件中将界面的状态由静止改为滑动,即可解决问题。
全部的代码实现如下:
public class RefreshListView extends ListView implements OnScrollListener {private float mDownY;private float mMoveY;private int mHeaderHeight;private int mCurrentScrollState;private final static int NONE_PULL_REFRESH = 0; // 正常状态private final static int ENTER_PULL_REFRESH = 1; // 进入下拉刷新状态private final static int OVER_PULL_REFRESH = 2; // 进入松手立即刷新状态// 加载状态下拉private final static int PUSH_REFRESHING = 3; // 加载状态中,隐藏部分正在加载private final static int OVER_PULL_REFRESHING = 4; // 加载状态中,滑开超出titlebar高度private int mPullRefreshState = 0; // 记录当前滑动状态// 松手后,界面状态private final static int REFRESH_BACED = 1; // 反弹结束,刷新中private final static int REFRESH_RETURN = 2; // 没有达到刷新界限,返回private final static int REFRESH_DONE = 3; // 加载数据结束private final static int REFRESH_ORIGINAL = 4; // 最初的状态public int currentHeaderState = -1; // 记录当前数据加载状态private boolean headVisible = false;private LinearLayout mHeaderLinearLayout = null;private TextView mHeaderTextView = null;private ImageView mHeaderPullDownImageView = null;private ImageView mHeaderProgressImage = null;private ImageView mHeaderRefreshOkImage = null;private RefreshListener mRefreshListener = null;private RotateAnimation animation;private RotateAnimation reverseAnimation;private boolean isBack = false;private Handler handler = new Handler();public void setOnRefreshListener(RefreshListener refreshListener) {this.mRefreshListener = refreshListener;}public RefreshListView(Context context) {this(context, null);}public RefreshListView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public void init(final Context context) {mHeaderLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_header, null);addHeaderView(mHeaderLinearLayout);mHeaderTextView = (TextView) findViewById(R.id.refresh_list_header_text);mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down);mHeaderProgressImage = (ImageView) findViewById(R.id.refresh_list_header_loading);mHeaderRefreshOkImage = (ImageView) findViewById(R.id.refresh_list_header_success);setSelection(1);setOnScrollListener(this);measureView(mHeaderLinearLayout);mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),-mHeaderHeight, mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());animation = new RotateAnimation(0, 180,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);animation.setInterpolator(new LinearInterpolator());animation.setDuration(150);animation.setFillAfter(true);// 箭头翻转动画reverseAnimation = new RotateAnimation(180, 0,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);reverseAnimation.setInterpolator(new LinearInterpolator());reverseAnimation.setDuration(150);reverseAnimation.setFillAfter(true);// 箭头反翻转动画}@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:mDownY = ev.getY();handler.removeCallbacks(headHideAnimation);handler.removeCallbacks(headBackAnimation);break;case MotionEvent.ACTION_MOVE:mMoveY = ev.getY();if (mCurrentScrollState == SCROLL_STATE_IDLE) {mCurrentScrollState = SCROLL_STATE_TOUCH_SCROLL;}if (currentHeaderState != REFRESH_BACED) {mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), -mHeaderHeight+ (int) ((mMoveY - mDownY) / 3),mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());} else if (currentHeaderState == REFRESH_BACED&& headVisible == true) {mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),(int) ((mMoveY - mDownY) / 3),mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());} else if (currentHeaderState == REFRESH_BACED&& headVisible == false) {mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), -mHeaderHeight+ (int) ((mMoveY - mDownY) / 3),mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());}break;case MotionEvent.ACTION_UP:if (mPullRefreshState == OVER_PULL_REFRESH) {currentHeaderState = REFRESH_BACED;handler.postDelayed(headBackAnimation, 5);refreshViewByState();} else if (mPullRefreshState == ENTER_PULL_REFRESH) {currentHeaderState = REFRESH_RETURN;handler.postDelayed(headHideAnimation, 5);refreshViewByState();} else if (mPullRefreshState == PUSH_REFRESHING) {handler.postDelayed(headHideAnimation, 5);} else if (mPullRefreshState == OVER_PULL_REFRESHING) {handler.postDelayed(headBackAnimation, 5);}break;}return super.onTouchEvent(ev);}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {if (currentHeaderState != REFRESH_BACED) {if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL&& firstVisibleItem == 0&& (mHeaderLinearLayout.getBottom() >= 0 && mHeaderLinearLayout.getBottom() < mHeaderHeight)) {mPullRefreshState = ENTER_PULL_REFRESH;mHeaderTextView.setText(R.string.app_list_header_refresh_down);mHeaderPullDownImageView.setVisibility(View.VISIBLE);mHeaderRefreshOkImage.setVisibility(View.GONE);if (isBack) {isBack = false;mHeaderPullDownImageView.clearAnimation();mHeaderPullDownImageView.startAnimation(reverseAnimation);}} else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL&& firstVisibleItem == 0&& (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {isBack = true;if (mPullRefreshState == ENTER_PULL_REFRESH|| mPullRefreshState == NONE_PULL_REFRESH) {mPullRefreshState = OVER_PULL_REFRESH;mHeaderTextView.setText(R.string.app_list_header_refresh);mHeaderPullDownImageView.clearAnimation();mHeaderPullDownImageView.startAnimation(animation);}} else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL&& firstVisibleItem != 0) {if (mPullRefreshState == ENTER_PULL_REFRESH) {mPullRefreshState = NONE_PULL_REFRESH;}}} else {if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL&& firstVisibleItem == 0&& (mHeaderLinearLayout.getBottom() >= 0 && mHeaderLinearLayout.getBottom() < mHeaderHeight)) {mPullRefreshState = PUSH_REFRESHING;} else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL&& firstVisibleItem == 0&& (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {mPullRefreshState = OVER_PULL_REFRESHING;}}if (mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0) {setSelection(1);}}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {mCurrentScrollState = scrollState;}@Overridepublic void setAdapter(ListAdapter adapter) {super.setAdapter(adapter);setSelection(1);}private void measureView(View child) {ViewGroup.LayoutParams p = child.getLayoutParams();if (p == null) {p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);int lpHeight = p.height;int childHeightSpec;if (lpHeight > 0) {childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,MeasureSpec.EXACTLY);} else {childHeightSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);}child.measure(childWidthSpec, childHeightSpec);}public void refreshViewByState() {switch (currentHeaderState) {case REFRESH_BACED:mHeaderTextView.setText(R.string.app_list_loading);mHeaderProgressImage.setVisibility(View.VISIBLE);mHeaderPullDownImageView.clearAnimation();mHeaderPullDownImageView.setVisibility(View.GONE);mPullRefreshState = NONE_PULL_REFRESH;isBack = false;if (mRefreshListener != null) {mRefreshListener.refreshing();}break;case REFRESH_RETURN:mPullRefreshState = NONE_PULL_REFRESH;currentHeaderState = REFRESH_ORIGINAL;break;case REFRESH_DONE:mHeaderTextView.setText(R.string.app_list_refresh_done);mHeaderProgressImage.setVisibility(View.INVISIBLE);mHeaderRefreshOkImage.setVisibility(View.VISIBLE);mPullRefreshState = NONE_PULL_REFRESH;currentHeaderState = REFRESH_ORIGINAL;mCurrentScrollState = SCROLL_STATE_IDLE;handler.postDelayed(headHideAnimation, 700);break;default:break;}}Runnable headHideAnimation = new Runnable() {public void run() {if (mHeaderLinearLayout.getBottom() > 0) {int paddingTop = (int) (-mHeaderHeight * 0.25f + mHeaderLinearLayout.getPaddingTop() * 0.75f) - 1;if (paddingTop < -mHeaderHeight) {paddingTop = -mHeaderHeight;}mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), paddingTop,mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());handler.postDelayed(headHideAnimation, 5);} else {handler.removeCallbacks(headHideAnimation);mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(), -mHeaderHeight,mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());setSelection(1);headVisible = false;}}};Runnable headBackAnimation = new Runnable() {public void run() {if (mHeaderLinearLayout.getPaddingTop() > 1) {mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),(int) (mHeaderLinearLayout.getPaddingTop() * 0.75f),mHeaderLinearLayout.getPaddingRight(),mHeaderLinearLayout.getPaddingBottom());handler.postDelayed(headBackAnimation, 5);} else {headVisible = true;handler.removeCallbacks(headBackAnimation);}}};public interface RefreshListener {// 正在下拉刷新public void refreshing();}}
头部的布局文件如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@android:color/black" android:gravity="center" android:orientation="horizontal" > <RelativeLayout android:layout_width="fill_parent" android:layout_height="50dp" > <ImageView android:id="@+id/refresh_list_header_loading" android:layout_width="wrap_content" android:layout_height="50dp" android:layout_marginLeft="@dimen/refresh_title_margin_left" android:contentDescription="@string/app_image_helper" android:src="@drawable/refresh_loading" android:visibility="gone" > </ImageView> <ImageView android:id="@+id/refresh_list_header_pull_down" android:layout_width="wrap_content" android:layout_height="50dp" android:layout_marginLeft="@dimen/refresh_title_margin_left" android:contentDescription="@string/app_image_helper" android:src="@drawable/refresh_arrow" /> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" > <ImageView android:id="@+id/refresh_list_header_success" android:layout_width="15dp" android:layout_height="15dp" android:layout_centerVertical="true" android:contentDescription="@string/app_image_helper" android:src="@drawable/header_refresh_success" android:visibility="gone" > </ImageView> <TextView android:id="@+id/refresh_list_header_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="4dp" android:layout_toRightOf="@id/refresh_list_header_success" android:text="@string/app_list_header_refresh_down" android:textColor="@android:color/white" android:textSize="15sp" /> </RelativeLayout> </RelativeLayout></LinearLayout>
代码中注释不多,敬请谅解。
- Android 高仿QQ的下拉刷新 ListView
- Android高仿QQ下拉刷新
- 高仿QQ聊天消息列表的下拉刷新效果
- 高仿QQ下拉刷新之LoadView
- android listView & 下拉刷新 & 仿通讯录的alpha
- android listView & 下拉刷新 & 仿通讯录的alpha
- android listView & 下拉刷新 & 仿通讯录的alpha
- android listView & 下拉刷新 & 仿通讯录的alpha .
- android listView & 下拉刷新 & 仿通讯录的alpha .
- Android 仿网易新闻 listView下拉刷新
- 高仿QQ消息,可以下拉刷新带小红点
- 安卓listView实现下拉刷新上拉加载滑动仿QQ的删除功能
- 仿QQ实现ListView中item的左右滑动同时实现ListView的上拉刷新和下拉加载更多
- android listview 的下拉刷新
- Android listView的下拉刷新
- Android ListView的下拉刷新
- Android仿苹果版QQ下拉刷新实现(一) ——打造简单平滑的通用下拉刷新控件
- RecyclerView仿ListView下拉刷新
- cisco2960交换机 简单配置
- 【C语言】10-字符和字符串常用处理函数
- c/s客户端---功能测试总结(一)
- 交叉编译器及常见编译错误
- 作用域链(执行上下文) 原型链(对象)
- Android 高仿QQ的下拉刷新 ListView
- 今年暑假不ac,贪心
- sqlserver服务器 急救
- OC内存管理小记
- struts1.3中<html:errors>不显示问题
- 一代 nexus 7 4.4刷机+ROOT教程(官方4.4镜像线刷)
- linux 文件权限修改
- 小波变换和motion信号处理(一)
- 关于XP系统下nexus7 连接PC后MTP无法识别的解决方法