该效果是一名国外工程师(johannilsson)的代码,拿来研究了下,自己整合了一下,现在拿出来,跟大家一起分享。
再次感谢这位国外工程师(johannilsson),谢谢!
新浪微博,和QQ空间里面,都有那个下拉刷新的效果,另很多人眼前一亮,细细分析,原理原来如此。
在原作者的基础上,写了一些注释,和帮助大家更好的阅读理解,(可能其中有些地方注释不准,欢迎指正,谢谢)
下面,就亮出关键代码:
**** 自定义 listivew (关键代码)
public class PullToRefreshListView extends ListView implements OnScrollListener {
private static final int TAP_TO_REFRESH = 1; // 初始状态
private static final int PULL_TO_REFRESH = 2; //拉动刷新
private static final int RELEASE_TO_REFRESH = 3; //释放刷新
private static final int REFRESHING = 4; //正在刷新
private static final String TAG = "PullToRefreshListView";
//刷新接口
private OnRefreshListener mOnRefreshListener;
//箭头图片
private static int REFRESHICON = R.drawable.goicon;
/**
* listview 滚动监听器
*/
private OnScrollListener mOnScrollListener;
//视图索引器
private LayoutInflater mInflater;
/**
* 头部视图 内容 -- start
*/
private RelativeLayout mRefreshView;
private TextView mRefreshViewText;
private ImageView mRefreshViewImage;
private ProgressBar mRefreshViewProgress;
private TextView mRefreshViewLastUpdated;
/**
* 头部视图 内容 -- end
*/
//当前listivew 的滚动状态
private int mCurrentScrollState;
//当前listview 的刷新状态
private int mRefreshState;
//动画效果
//变为向下的箭头
private RotateAnimation mFlipAnimation;
//变为逆向的箭头
private RotateAnimation mReverseFlipAnimation;
//头视图的高度
private int mRefreshViewHeight;
//头视图 原始的 top padding 属性值
private int mRefreshOriginalTopPadding;
//
private int mLastMotionY;
//是否反弹
private boolean mBounceHack;
public PullToRefreshListView(Context context) {
super(context);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
// Load all of the animations we need in code rather than through XML
//初始化动画
//
mFlipAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mFlipAnimation.setInterpolator(new LinearInterpolator());
mFlipAnimation.setDuration(250);
mFlipAnimation.setFillAfter(true);
mReverseFlipAnimation = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
mReverseFlipAnimation.setDuration(250);
mReverseFlipAnimation.setFillAfter(true);
mInflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mRefreshView = (RelativeLayout) mInflater.inflate(R.layout.pull_to_refresh_header, this, false);//(R.layout.pull_to_refresh_header, null);
mRefreshViewText =
(TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);
mRefreshViewImage =
(ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);
mRefreshViewProgress =
(ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);
mRefreshViewLastUpdated =
(TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);
mRefreshViewImage.setMinimumHeight(50);
mRefreshView.setOnClickListener(new OnClickRefreshListener());
mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();
mRefreshState = TAP_TO_REFRESH;
addHeaderView(mRefreshView);
super.setOnScrollListener(this);
measureView(mRefreshView);
mRefreshViewHeight = mRefreshView.getMeasuredHeight(); //获取头文件的测量高度
}
@Override
protected void onAttachedToWindow() {
setSelection(1);
}
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
setSelection(1);
}
/**
* Set the listener that will receive notifications every time the list
* scrolls.
*
* @param l The scroll listener.
*/
@Override
public void setOnScrollListener(AbsListView.OnScrollListener l) {
mOnScrollListener = l;
}
/**
* Register a callback to be invoked when this list should be refreshed.
*
* @param onRefreshListener The callback to run.
*/
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
mOnRefreshListener = onRefreshListener;
}
/**
* Set a text to represent when the list was last updated.
* @param lastUpdated Last updated at.
*/
public void setLastUpdated(CharSequence lastUpdated) {
if (lastUpdated != null) {
mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
mRefreshViewLastUpdated.setText(lastUpdated);
} else {
mRefreshViewLastUpdated.setVisibility(View.GONE);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//当前手指的Y值
final int y = (int) event.getY();
//Log.i(TAG, "触屏的Y值"+y);
mBounceHack = false; //不反弹
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
//将垂直滚动条设置为可用状态
if (!isVerticalScrollBarEnabled()) {
setVerticalScrollBarEnabled(true);
}
//如果头部刷新条出现,并且不是正在刷新状态
if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
if ((mRefreshView.getBottom() >= mRefreshViewHeight
|| mRefreshView.getTop() >= 0)
&& mRefreshState == RELEASE_TO_REFRESH) { //如果头部视图处于拉离顶部的情况
// Initiate the refresh
mRefreshState = REFRESHING; //将标量设置为,正在刷新
prepareForRefresh(); //准备刷新
onRefresh(); //刷新
} else if (mRefreshView.getBottom() < mRefreshViewHeight
|| mRefreshView.getTop() <= 0) {
// Abort refresh and scroll down below the refresh view
// 停止刷新,并且滚动到头部刷新视图的下一个视图
resetHeader();
setSelection(1); //定位在第二个列表项
}
}
break;
case MotionEvent.ACTION_DOWN:
mLastMotionY = y; //跟踪手指的Y值
break;
case MotionEvent.ACTION_MOVE:
//更行头视图的toppadding 属性
applyHeaderPadding(event);
break;
}
return super.onTouchEvent(event);
}
/****
* 不断的头部的top padding 属性
* @param ev
*/
private void applyHeaderPadding(MotionEvent ev) {
//获取累积的动作数
int pointerCount = ev.getHistorySize();
// Log.i(TAG, "获取累积的动作数"+pointerCount);
for (int p = 0; p < pointerCount; p++) {
if (mRefreshState == RELEASE_TO_REFRESH) { //如果是释放将要刷新状态
if (isVerticalFadingEdgeEnabled()) {
setVerticalScrollBarEnabled(false);
}
//历史累积的高度
int historicalY = (int) ev.getHistoricalY(p);
//Log.i(TAG, "单个动作getHistoricalY值:"+historicalY);
// Calculate the padding to apply, we divide by 1.7 to
// simulate a more resistant effect during pull.
int topPadding = (int) (((historicalY - mLastMotionY)
- mRefreshViewHeight) / 1.7);
mRefreshView.setPadding(
mRefreshView.getPaddingLeft(),
topPadding,
mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
}
}
/**
* Sets the header padding back to original size.
* 使头部视图的 toppadding 恢复到初始值
*/
private void resetHeaderPadding() {
mRefreshView.setPadding(
mRefreshView.getPaddingLeft(),
mRefreshOriginalTopPadding,
mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
/**
* Resets the header to the original state.
* 初始化头部视图 状态
*/
private void resetHeader() {
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshState = TAP_TO_REFRESH; //初始刷新状态
//使头部视图的 toppadding 恢复到初始值
resetHeaderPadding();
// Set refresh view text to the pull label
//将文字初始化
mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);
// Replace refresh drawable with arrow drawable
//设置初始图片
mRefreshViewImage.setImageResource(REFRESHICON);
// Clear the full rotation animation
// 清除动画
mRefreshViewImage.clearAnimation();
// Hide progress bar and arrow.
//隐藏头视图
mRefreshViewImage.setVisibility(View.GONE);
//隐藏进度条
mRefreshViewProgress.setVisibility(View.GONE);
}
}
//测量视图的高度
private void measureView(View child) {
//获取头部视图属性
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0,
0 + 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
//不懂MeasureSpec------------------------------------------------------------------------------------------
if (lpHeight > 0) { //如果视图的高度大于0
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
//不懂MeasureSpec------------------------------------------------------------------------------------------
}
/****
* 滑动事件
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// When the refresh view is completely visible, change the text to say
// "Release to refresh..." and flip the arrow drawable.
if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL //如果是接触滚动状态,并且不是正在刷新的状态
&& mRefreshState != REFRESHING) {
if (firstVisibleItem == 0) { //如果显示出来了第一个列表项
//显示刷新图片
mRefreshViewImage.setVisibility(View.VISIBLE);
if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20
|| mRefreshView.getTop() >= 0)
&& mRefreshState != RELEASE_TO_REFRESH) { //如果下拉了listiview,则显示上拉刷新动画
mRefreshViewText.setText(R.string.pull_to_refresh_release_label);
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mFlipAnimation);
mRefreshState = RELEASE_TO_REFRESH;
Log.i(TAG, "现在处于下拉状态");
} else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
&& mRefreshState != PULL_TO_REFRESH) { //如果没有到达,下拉刷新距离,则回归原来的状态
mRefreshViewText.setText(R.string.pull_to_refresh_pull_label);
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mReverseFlipAnimation);
Log.i(TAG, "现在处于回弹状态");
}
mRefreshState = PULL_TO_REFRESH;
}
} else {
mRefreshViewImage.setVisibility(View.GONE); //隐藏刷新图片
resetHeader(); //初始化,头部
}
} else if (mCurrentScrollState == SCROLL_STATE_FLING //如果是自己滚动状态+ 第一个视图已经显示 + 不是刷新状态
&& firstVisibleItem == 0
&& mRefreshState != REFRESHING) {
setSelection(1);
mBounceHack = true; //状态为回弹
Log.i(TAG, "现在处于自由滚动到顶部的状态");
} else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
setSelection(1);
Log.i(TAG, "现在处于自由滚动到顶部的状态");
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem,
visibleItemCount, totalItemCount);
}
}
//滚动状态改变
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
if (mCurrentScrollState == SCROLL_STATE_IDLE) { //如果滚动停顿
mBounceHack = false;
}
if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
//准备刷新
public void prepareForRefresh() {
resetHeaderPadding(); //初始化,头部文件
mRefreshViewImage.setVisibility(View.GONE);
// We need this hack, otherwise it will keep the previous drawable.
mRefreshViewImage.setImageDrawable(null);
mRefreshViewProgress.setVisibility(View.VISIBLE);
// Set refresh view text to the refreshing label
mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);
mRefreshState = REFRESHING;
}
//刷新
public void onRefresh() {
Log.d(TAG, "执行刷新");
if (mOnRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
}
/**
* 刷新完成 的回调函数
* Resets the list to a normal state after a refresh.
* @param lastUpdated Last updated at.
*/
public void onRefreshComplete(CharSequence lastUpdated) {
setLastUpdated(lastUpdated);
onRefreshComplete();
}
/**
* 刷新完成回调函数
* Resets the list to a normal state after a refresh.
*/
public void onRefreshComplete() {
Log.d(TAG, "onRefreshComplete");
resetHeader();
// If refresh view is visible when loading completes, scroll down to
// the next item.
if (mRefreshView.getBottom() > 0) {
invalidateViews(); //重绘视图
setSelection(1);
}
}
/**
* Invoked when the refresh view is clicked on. This is mainly used when
* there's only a few items in the list and it's not possible to drag the
* list.
*/
private class OnClickRefreshListener implements OnClickListener {
@Override
public void onClick(View v) {
if (mRefreshState != REFRESHING) {
//准备刷新
prepareForRefresh();
//刷新
onRefresh();
}
}
}
/**
* 刷新方法接口
*/
public interface OnRefreshListener {
public void onRefresh();
}
* 如果你还是没有弄明白的话,那就点击下面的链接,来下载整个demo项目:
http://download.csdn.net/detail/zjl5211314/3775209
原作者:johannilsson
选自:https://github.com/johannilsson/android-pulltorefresh
分享到:
相关推荐
本文将深入探讨如何实现这种Android ListView的下拉回弹刷新功能。 首先,我们需要理解这个效果的基本原理。下拉回弹刷新通常涉及到两个主要部分:手势检测和动画效果。当用户在ListView顶部下拉时,系统会检测到这...
"android listview上拉刷新下拉加载"这个主题涉及到ListView的两个关键功能扩展:上拉刷新(Pull-to-Refresh)和下拉加载更多(Load-more)。这两个特性使得用户可以在滚动到列表底部时加载更多数据,而在顶部时刷新...
在Android开发中,"下拉回弹刷新效果列表"是一种常见的用户交互设计,它使得用户在滚动列表到顶部时能够触发数据的更新或加载更多内容。这种效果在许多社交应用如QQ空间、新浪微博中被广泛采用,提高了用户体验的...
这个"Android listView下拉刷新上拉刷新带阻尼效果"的源码Demo是几年前的一个示例,旨在帮助学生理解和实现Android应用中的下拉刷新和上拉加载更多功能,同时加入了阻尼效果,提升用户体验。阻尼效果是指在用户滑动...
本教程将指导你在Android Studio中为ListView实现下拉刷新功能。 1. **下拉刷新概念** 下拉刷新(Pull-to-Refresh)是一种常见的UI设计模式,允许用户通过从列表顶部向下拉动来触发数据的更新。这种功能常见于新闻...
SwipeRefreshLayout和腾讯的X5内核的WebView相结合的下拉刷新,亲测华为荣耀android6.0 测试通过,由于百度某些页面单独处理过,开启了滚动条,否则下拉和下拉刷新冲突..2016年9月7日19:40:50
在Android开发中,为了提供与iOS类似的用户体验,开发者经常需要实现一种称为“下拉回弹”(Pull-to-Refresh)的效果。这种效果通常应用于ListView、RecyclerView等滚动视图,允许用户下拉列表顶部来刷新数据。本文...
在Android开发中,下拉刷新是一项常见的功能,它允许用户通过在列表顶部向下拉动来刷新内容。Android系统自API 19(KitKat)开始引入了一个名为`SwipeRefreshLayout`的原生控件,用于实现这一交互。这个控件通常与`...
在Android开发中,"下拉刷新"是一种常见的用户体验设计,让用户可以轻松地更新应用程序中的数据。这个开源项目特别针对Eclipse IDE,意味着开发者无需切换到Android Studio也能利用此功能。以下将详细介绍这个开源...
在Android开发中,上拉加载和下拉刷新是常见的组件功能,用于提升用户体验,使得用户在滚动列表到顶部时能够方便地获取更多数据,而在滚动到底部时加载更多内容。本示例“Android自定义上拉加载下拉刷新控件”提供了...
当列表项很少(比如只有2项) 先上拉增加Footer的高度 再下拉增加Header的高度 放开手指时能看到Header和Footer同时回弹的效果 有BUG或新idea可以联系我 fakedream http: blog csdn net fakedream ">代码特点:...
首先,我们要引入SwipeRefreshLayout库,它是Android SDK提供的一个下拉刷新框架。在`build.gradle`文件中添加以下依赖: ```groovy dependencies { implementation 'com.android.support:support-v4:版本号' } ``...
总结,下拉刷新和上拉加载是Android应用中常见的功能,通过SwipeRefreshLayout和OnScrollListener可以方便地实现。自定义ListView则允许开发者更深入地控制列表的行为和视觉效果,以提供更加个性化的用户体验。在...
这个"Android应用源码 ListView下拉刷新 Demo"提供了一个实际的例子,帮助开发者了解如何在ListView中实现这一功能。 1. **SwipeRefreshLayout**:Android SDK 提供了一个名为SwipeRefreshLayout的布局容器,它是...
Android自定义控件实战——下拉刷新控件终结者:PullToRefreshLayout http://blog.csdn.net/zhongkejingwang/article/details/38340701 3.PullToRefreshDemo.zip(与第一个几乎相同,但使用方便简单,唯一...
在Android开发中,"android list下拉刷新"指的是在ListView或者RecyclerView等列表组件上实现下拉刷新的功能。这种功能让用户可以动态更新列表数据,通常应用于显示实时信息的应用,如新闻、社交媒体或者邮件应用。...
本Demo "Android ListView下拉刷新 Demo.rar" 主要是为了展示如何在ListView中实现下拉刷新功能,帮助开发者更好地理解和实践这一功能。 首先,我们要理解下拉刷新的基本概念。下拉刷新,顾名思义,是指用户在...
"Android 阻尼下拉刷新列表的实现方法" Android 阻尼下拉刷新列表是一个非常实用的功能,它可以提供给用户一个更加流畅的体验。下面我们将详细介绍 Android 阻尼下拉刷新列表的实现方法。 首先,我们需要了解 ...
本示例中的“Android 利用CoordinatorLayout实现高仿抖音个人中心 下拉越界回弹 图片放大”就是一个很好的案例,它展示了如何通过 CoordinatorLayout 结合其他组件来构建类似抖音个人中心的下拉回弹以及图片放大效果...