小飒的成长史原创作品:窥视懒人的秘密---android下拉刷新开启手势的新纪元转载请注明出处
*****************************************************************
前言:窥视懒人那些不为人知的秘密
*****************************************************************
作为一个程序员,哪有不勤奋的道理,当我们都在为技术奋不顾身的时候。偏偏懒人创造了世界。
有的时候真心没有办法理解,为什么?
为什么懒人什么都不做,却能创造出一个又一个的奇迹,创造出一个又一个的经典。
很早都听过一句话:懒人创造世界。
因为懒得记住那些复杂的DOS命令,于是,比尔盖茨继承图形界面的设计,完成了一次图形界面与机器的完美结合,当全世界的电脑都一模一样的是,他也成为了世界首富;
因为懒得整天带个随身听听音乐,于是,乔布斯发布了iPod,不仅改变了人们欣赏音乐的方式,还改变了整个音乐产业;
因为懒得开每天电脑,于是移动互联网应运而生;因为懒得点击那些那些难过的按钮,开发者们不断的改进科技,一次来迎合越来越懒的人的需求。
是的,说到这里,我们仿佛明白了一个道理。所谓懒惰不是真的懒,而是为了,他们在满足我们高效快速的生活同时,极力让产品更加的人性化,让产品更能服务懒人的生活。懒人们的福音,而创造世界的人,恰恰看到了这一点,他们是为懒人思考的懒人。
当众多的开发框架,开源项目在网上激起一波又一波的项目模仿浪潮,我们可以看到的是,我们的周围充斥着一样的东西。那就是懒人逻辑,模仿懒人逻辑。
说了这么多,都没有引出我们今天的主题。不是因为我没有料,而是,在引出料的同时,希望自己能过有更多的思考。那些料到底是为了迎合谁的胃口而被调试出来的。是的,我们就是为了满足不同人的口味,而在不断的调试着一种一种的料。以求这种料在投入市场的时候,能做出适合更多人的产品。
在一次小的面试中,面试老师问道了我一个问题,像QQ一样的软件,都有一个功能叫下拉刷新,它是怎么实现的。
这个我们起初在回答的时候,肯定都是只停留在去解释它的实现代码的含义。却不清楚为什么要这样的实现,又为什么有这样的实现。
在看了几天的开源项目后,在此,我想对自己的理解做一次总结。
******************************************************************************
面对一个东西,我们总要问了:为什么?
******************************************************************************
1.为什么有滑动这样的实现效果?
下拉刷新技术的发明者是Twitter曾经最好的第三方客户端Tweetie的创始人洛伦.布里切特(Loren Brichter)。Twitter于2010年收购该公司时就已获取该项技术的专利权。
在《宅男成功:28岁的应用设计教父》一文中,我们可以看到这样的设计师出自什么情况下。随着简约和设计在竞争异常激烈的应用程序行业变得越加重要,布里切特也正与凭着各种点子成为众所周知在应用设计领域有影响力的人。
原文摘录:
Mr. Brichter, whose design aesthetic is inspired by information theorists like Edward Tufte, a proponent of minimizing extraneous information in graphic designs, says he thinks up new features for apps based on how people move objects in the real world.
布里切特的设计美学受到了爱德华?塔夫特(Edward Tufte)等信息理论家的启发,塔夫特主张在图形设计里将无关的信息减少到最小程度。布里切特说他是根据人们在现实世界里移动物体的方式构思出应用程序的新功能的。 'Everything should come from somewhere and go somewhere,' he says, adding that he's irked by apps that have menus that pop up or collapse on themselves because the interactions aren't real. 'The most important thing is obviousness. The problem is overdesign.'
他说:“任何事物都应该有来源有去处。”他还说自己很不喜欢那些菜单自己弹出或者收起的应用程序,因为那种互动是不真实的。“最重要的事情是要显而易见。问题出在过度设计上。”
在这片文章中,我们看到这样的话,仿佛知道了些什么。在设计下拉刷新的过程中,他的设计美学受到了爱德华 塔夫特(Edward Tufte)等信息理论家的启发,塔夫特主张在图形设计里将无关的信息减少到最小程度。布里特说他是人们在线式世界里移动物体的方式构思出应用程序的新功能的。而正是这样希望用更少的动作,去实现在懒人们看来没有必要太多的动作。形成的效果设计,满足的人机交互中最简单的一个理论:减少人们的记忆符合。
为什么这么说呢?因为,软件最大的特点就是与机器进行交流,而人要与机器交流就要通过软件。如果软件中操作步骤过多,就是容易阻碍人的使用感。那么用户体验就会因为的开发者的疏忽而变的糟糕。当下拉刷新出现在GitHub,一度成为软件中最流行的效果。当然,发现最新技术的人都是对技术有着灵敏的嗅觉,随时探测技术中的流行趋势。
2.为什么要这样实现?
下拉刷新,我们常见的效果应该是如下图所示:这种事QQ界面中的下拉刷新
在GitHub中总共有十种下拉刷新的效果,分别是:
------ ListView
------ ExpandableListView
------ GridView
------ WebView
------ ScrollView
------ Horizontal ScrollView
------ ViewPager
------ ListView Fragment
------ WebView Advanced
------ ListView in ViewPager
下面,我先介绍一下,怎么获取得到下拉刷新的开源项目:
首先,在GitHub搜索Android-PullToRefresh-master
其次,下载文件,解压
将项目添加到eclipse中后,有很多的问题出现。比如library会一直报错。这样的话,就要重新加载一下,library的引用库文件。这个项目是通过项目与项目之间的关联性,整合到一起的。
为什么要这样实现?一来是因为,下拉刷新简单方便,实用具体。二来对于开发者来说也是一项新的改变。改变以前按钮点击的时代。从简单的按钮监听,用户看不到刷新的过程,带下拉刷新,用户参与到整个刷新的过程,连续的动画,让这种效果瞬间风靡移动互联网应用也就是情理之中的事了。
3.为什么android功能代码要这样写?
在开始抛析功能代码之前,我们先想想它的实现场景。
当用户进行下拉刷新操作时,设计效果经过的三个步骤:
第一步:当手指下拉的时候,在listView的上方有出现一个向下的箭头,和pull to refresh。。。的字样。(可见当手指下拉的时候,箭头的imageView,和textView从隐藏到显示)
第二步:在手指下拉超过了一定的高度时,箭头方向反转向上,和release to refresh 。。。的字样。(可见当用户在操作出现过度的时候要实施进行提醒,而此时,箭头通过一个反转的动画,使整个效果看起来精美,连贯)
第三步:在手指松开时箭头消失,出现ProgressBar旋转加载图标和Loading的字样。(可见加载的过程摆在用户面前。自己的简化了操作的复杂性,同时保持了用户在操作中的参与度。)
第四步:当刷新完成的时候,UI恢复到刷新前的状态,头部填充布局都消失。(这样就悄无声息的实现了刷新的整个过程,如此简单的设计,却有着不简单的思考)
就这样,我们开始分析代码的实现过程吧。
对于android项目来说,这三步都被封装在一个类中,就是PullToRefreshListView自定义布局中。通过对布局的声明和调用,即可实现这样的一个过程。
我们都知道,在listView中,可以在该控件的上端添加布局。如图所示:
对于功能代码的解析,我们分为两个方面
一方面:要先建立一个头部布局的layout文件
1.建立布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pull_to_refresh_header"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFFFF"
android:gravity="center"
android:paddingBottom="15dip"
android:paddingTop="10dip" > <ProgressBar
android:id="@+id/pull_to_refresh_progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="30dip"
android:layout_marginRight="20dip"
android:layout_marginTop="10dip"
android:indeterminate="true"
android:visibility="gone" /> <ImageView
android:id="@+id/pull_to_refresh_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="30dip"
android:layout_marginRight="20dip"
android:gravity="center"
android:src="@drawable/arrow"
android:visibility="gone" /> <TextView
android:id="@+id/pull_to_refresh_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#000000"
android:gravity="center"
android:paddingTop="5dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold" /> <TextView
android:id="@+id/pull_to_refresh_updated_at"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/pull_to_refresh_text"
android:layout_gravity="center"
android:gravity="center"
android:textAppearance="?android:attr/textAppearanceSmall"
android:visibility="gone" /> </RelativeLayout>
2.对建立的头部布局进行注册,以及填充到listView中
/**
* 初始化控件和箭头动画
* @param context
*/
private void init(Context context) {
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);
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);
mRefreshOriginalTopPadding = mRefreshView.getPaddingTop(); mRefreshState = TAP_TO_REFRESH;
//将上述布局文件以及动画效果 加入ListView的头部
addHeaderView(mRefreshView); super.setOnScrollListener(this); measureView(mRefreshView);
mRefreshViewHeight = mRefreshView.getMeasuredHeight(); }
另一方面:通过代码控制实现效果的转换
1.监听手势的变化
/**
* 重写的一个触摸事件处理
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
final int y = (int) event.getY();
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) {
//将状态设置为 正在刷新
mRefreshState = REFRESHING;
//准备刷新
prepareForRefresh();
//刷新
onRefresh();
//如果取消拖动 或者 拖的距离不够
} else if (mRefreshView.getBottom() < mRefreshViewHeight
|| mRefreshView.getTop() <= 0) {
//终止刷新
resetHeader();
setSelection(1);
}
}
break;
case MotionEvent.ACTION_DOWN:
//获取按下的y轴的位置
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
//计算边距
applyHeaderPadding(event);
break;
}
return super.onTouchEvent(event);
}
2.获取headerView的边距和高度
/**
* 获取headerView的边距
* @param ev
*/
private void applyHeaderPadding(MotionEvent ev) { int pointerCount = ev.getHistorySize(); for (int p = 0; p < pointerCount; p++) {
if (mRefreshState == RELEASE_TO_REFRESH) {
if (isVerticalFadingEdgeEnabled()) {
setVerticalScrollBarEnabled(false);
} int historicalY = (int) ev.getHistoricalY(p);
//控制下拉的程度 拉动效果
int topPadding = (int) (((historicalY - mLastMotionY) - mRefreshViewHeight) / 1.7); mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
topPadding, mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
}
}
/**
* 将HeaderView的边距 重置为初始的数值
*/
private void resetHeaderPadding() {
mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
mRefreshOriginalTopPadding, mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
/**
* 将整个HeaderView重置为下拉之前的状态
*/
private void resetHeader() {
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshState = TAP_TO_REFRESH; resetHeaderPadding();
//将图片重新换成箭头
mRefreshViewImage
.setImageResource(R.drawable.arrow);
//清除动画效果
mRefreshViewImage.clearAnimation();
//隐藏图标以及进度条
mRefreshViewImage.setVisibility(View.GONE);
mRefreshViewProgress.setVisibility(View.GONE);
}
}
/**
* 估算headView的宽和高
* @param child
*/
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;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
3.通过手势移动的位置,计算高度,并对滑动过程中效果的变化进行更新操作
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//在headerView完全可见的时候 将文字设置为"松开加载..." 同时翻转箭头
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) {
mRefreshViewText.setText("松开加载...");
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mFlipAnimation);
mRefreshState = RELEASE_TO_REFRESH;
} else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
&& mRefreshState != PULL_TO_REFRESH) {
mRefreshViewText.setText("下拉刷新...");
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mReverseFlipAnimation);
}
mRefreshState = PULL_TO_REFRESH;
}
} else {
mRefreshViewImage.setVisibility(View.GONE);
resetHeader();
}
} else if (mCurrentScrollState == SCROLL_STATE_FLING
&& firstVisibleItem == 0 && mRefreshState != REFRESHING) {
setSelection(1);
mBounceHack = true;
} else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
setSelection(1);
} if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem,
visibleItemCount, totalItemCount);
} //
if (mCurrentScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
//获取最后一条数据的索引
lastVisibleIndex = firstVisibleItem + visibleItemCount - 1;
//当所有的数据 都已经加载出来了(所有的条目数等于最大条目数) 移除掉底部的footerView
if (totalItemCount == MaxDateNum + 1) {
removeFooterView(moreView);
Toast.makeText(context, "数据全部加载完成,没有更多数据!", Toast.LENGTH_LONG)
.show();
}
}
}
/**
* 滚动变化监听器
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState; if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(view, scrollState);
}
//当滑动到底部的时候 执行自动加载功能
if (mCurrentScrollState == OnScrollListener.SCROLL_STATE_IDLE
&& lastVisibleIndex == this.getAdapter().getCount()) {
//、、、、、、、、、 异步加载数据的代码
pg.setVisibility(View.VISIBLE);
bt.setVisibility(View.GONE);
}
}
4.定义一系列的调用方法,以便上层代码进行调用。
/**
* 准备刷新
*/
public void prepareForRefresh() {
resetHeaderPadding();//恢复HeaderView的边距
//将图片隐藏
mRefreshViewImage.setVisibility(View.GONE);
//将图片置为null
mRefreshViewImage.setImageDrawable(null);
mRefreshViewProgress.setVisibility(View.VISIBLE); mRefreshViewText.setText("加载中..."); mRefreshState = REFRESHING;
} public void onRefresh() { if (mOnRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
}
/**
* 当ListView加载完 可以调用该方法 设置最后更新时间
* @param lastUpdated
*/
public void onRefreshComplete(CharSequence lastUpdated) {
setLastUpdated(lastUpdated);
onRefreshComplete();
}
/**
*当ListView加载完 可以调用该方法 但是没有设置最后更新时间
*/
public void onRefreshComplete() { resetHeader(); // 如果refreshview在加载结束后可见,下滑到下一个条目
if (mRefreshView.getBottom() > 0) {
invalidateViews();
setSelection(1);
}
}
/**
* 刷新监听器接口
* @author ChnAdo
*
*/
public interface OnRefreshListener {
/**
* 需要刷新时调用该方法
*/
public void onRefresh();
}
完整的功能代码如下:
public class PullToRefreshListView extends ListView implements OnScrollListener { private Context context; 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 OnRefreshListener mOnRefreshListener; private OnScrollListener mOnScrollListener;
private LayoutInflater mInflater; private RelativeLayout mRefreshView;
private TextView mRefreshViewText;
private ImageView mRefreshViewImage;
private ProgressBar mRefreshViewProgress;
private TextView mRefreshViewLastUpdated; private int mCurrentScrollState; private int mRefreshState; private RotateAnimation mFlipAnimation;
private RotateAnimation mReverseFlipAnimation; private int mRefreshViewHeight;
private int mRefreshOriginalTopPadding;
private int mLastMotionY; private boolean mBounceHack; private int MaxDateNum;
private View moreView;
public Button bt;
private ProgressBar pg;
private int lastVisibleIndex;
public boolean isclick; public PullToRefreshListView(Context context) {
super(context);
init(context);
this.context = context;
} public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
this.context = context;
} public PullToRefreshListView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init(context);
this.context = context;
}
/**
* 初始化控件和箭头动画
* @param context
*/
private void init(Context context) {
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);
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);
mRefreshOriginalTopPadding = mRefreshView.getPaddingTop(); mRefreshState = TAP_TO_REFRESH;
//将上述布局文件以及动画效果 加入ListView的头部
addHeaderView(mRefreshView); super.setOnScrollListener(this); measureView(mRefreshView);
mRefreshViewHeight = mRefreshView.getMeasuredHeight(); //给ListView加载一个FooterView
MaxDateNum = 20;
moreView = LayoutInflater.from(context)
.inflate(R.layout.moredata, null);
bt = (Button) moreView.findViewById(R.id.bt_load);
pg = (ProgressBar) moreView.findViewById(R.id.pg);
addFooterView(moreView);
//给底部的按钮实现一个监听事件
bt.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
//点这个按钮 让进度条可见 按钮自身隐藏
pg.setVisibility(View.VISIBLE);
bt.setVisibility(View.GONE);
isclick = true;
}
});
} @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setSelection(1);
} @Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter); setSelection(1);
}
/**
* 设置一个滚动(滑动)监听器
*/
@Override
public void setOnScrollListener(AbsListView.OnScrollListener l) {
mOnScrollListener = l;
}
/**
* 当ListView的列表需要刷新的时候 重新回调的一个监听器
* @param onRefreshListener
*/
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
mOnRefreshListener = onRefreshListener;
}
/**
* 设置文字标题显示 例如可以显示最近刷新时间等等
* @param lastUpdated
*/
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) {
final int y = (int) event.getY();
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) {
//将状态设置为 正在刷新
mRefreshState = REFRESHING;
//准备刷新
prepareForRefresh();
//刷新
onRefresh();
//如果取消拖动 或者 拖的距离不够
} else if (mRefreshView.getBottom() < mRefreshViewHeight
|| mRefreshView.getTop() <= 0) {
//终止刷新
resetHeader();
setSelection(1);
}
}
break;
case MotionEvent.ACTION_DOWN:
//获取按下的y轴的位置
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
//计算边距
applyHeaderPadding(event);
break;
}
return super.onTouchEvent(event);
}
/**
* 获取headerView的边距
* @param ev
*/
private void applyHeaderPadding(MotionEvent ev) { int pointerCount = ev.getHistorySize(); for (int p = 0; p < pointerCount; p++) {
if (mRefreshState == RELEASE_TO_REFRESH) {
if (isVerticalFadingEdgeEnabled()) {
setVerticalScrollBarEnabled(false);
} int historicalY = (int) ev.getHistoricalY(p);
//控制下拉的程度 拉动效果
int topPadding = (int) (((historicalY - mLastMotionY) - mRefreshViewHeight) / 1.7); mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
topPadding, mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
}
}
/**
* 将HeaderView的边距 重置为初始的数值
*/
private void resetHeaderPadding() {
mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
mRefreshOriginalTopPadding, mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
/**
* 将整个HeaderView重置为下拉之前的状态
*/
private void resetHeader() {
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshState = TAP_TO_REFRESH; resetHeaderPadding();
//将图片重新换成箭头
mRefreshViewImage
.setImageResource(R.drawable.arrow);
//清除动画效果
mRefreshViewImage.clearAnimation();
//隐藏图标以及进度条
mRefreshViewImage.setVisibility(View.GONE);
mRefreshViewProgress.setVisibility(View.GONE);
}
}
/**
* 估算headView的宽和高
* @param child
*/
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;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
} @Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//在headerView完全可见的时候 将文字设置为"松开加载..." 同时翻转箭头
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) {
mRefreshViewText.setText("松开加载...");
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mFlipAnimation);
mRefreshState = RELEASE_TO_REFRESH;
} else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
&& mRefreshState != PULL_TO_REFRESH) {
mRefreshViewText.setText("下拉刷新...");
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mReverseFlipAnimation);
}
mRefreshState = PULL_TO_REFRESH;
}
} else {
mRefreshViewImage.setVisibility(View.GONE);
resetHeader();
}
} else if (mCurrentScrollState == SCROLL_STATE_FLING
&& firstVisibleItem == 0 && mRefreshState != REFRESHING) {
setSelection(1);
mBounceHack = true;
} else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
setSelection(1);
} if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem,
visibleItemCount, totalItemCount);
} //
if (mCurrentScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
//获取最后一条数据的索引
lastVisibleIndex = firstVisibleItem + visibleItemCount - 1;
//当所有的数据 都已经加载出来了(所有的条目数等于最大条目数) 移除掉底部的footerView
if (totalItemCount == MaxDateNum + 1) {
removeFooterView(moreView);
Toast.makeText(context, "数据全部加载完成,没有更多数据!", Toast.LENGTH_LONG)
.show();
}
}
} /**
* 滚动变化监听器
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState; if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(view, scrollState);
}
//当滑动到底部的时候 执行自动加载功能
if (mCurrentScrollState == OnScrollListener.SCROLL_STATE_IDLE
&& lastVisibleIndex == this.getAdapter().getCount()) {
//、、、、、、、、、 异步加载数据的代码
pg.setVisibility(View.VISIBLE);
bt.setVisibility(View.GONE);
}
}
/**
* 准备刷新
*/
public void prepareForRefresh() {
resetHeaderPadding();//恢复HeaderView的边距
//将图片隐藏
mRefreshViewImage.setVisibility(View.GONE);
//将图片置为null
mRefreshViewImage.setImageDrawable(null);
mRefreshViewProgress.setVisibility(View.VISIBLE); mRefreshViewText.setText("加载中..."); mRefreshState = REFRESHING;
} public void onRefresh() { if (mOnRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
}
/**
* 当ListView加载完 可以调用该方法 设置最后更新时间
* @param lastUpdated
*/
public void onRefreshComplete(CharSequence lastUpdated) {
setLastUpdated(lastUpdated);
onRefreshComplete();
}
/**
*当ListView加载完 可以调用该方法 但是没有设置最后更新时间
*/
public void onRefreshComplete() { resetHeader(); // 如果refreshview在加载结束后可见,下滑到下一个条目
if (mRefreshView.getBottom() > 0) {
invalidateViews();
setSelection(1);
}
}
/**
* 刷新监听器接口
* @author ChnAdo
*
*/
public interface OnRefreshListener {
/**
* 需要刷新时调用该方法
*/
public void onRefresh();
} /**
* onMoreComplete() 刷新
*/
public void onMoreComplete() {
bt.setVisibility(View.GONE);//设置按钮消失
pg.setVisibility(View.VISIBLE);//设置加载滚动条显示
isclick = false;
}
//结束进度条
public void dismissProgress() {
bt.setVisibility(View.GONE);
pg.setVisibility(View.INVISIBLE); }
}
在使用该自定义listView的Activity中,要实现两个方法。
/**
* 实现blogslist上滑加载
*/
blogslist.setOnScrollListener(new OnScrollListener() { @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE
&& !blogslist.isclick
&& visiableItemCount == visiableLastIndex) {
addArrayList();
}
} @Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
AllBlogsActivity.visiableItemCount = totalItemCount;
visiableLastIndex = firstVisibleItem + visibleItemCount;
}
});
至此,我们下拉刷新的例子就完成了。其实,很多代码,每一个程序员都会的就是学习,利用和改造。但是,在学习他们的成果的时候,自己是否也在想,我为什么没有这样的想法。看起来如此之简单,实现起来如此之容易,使用起来如此之爽。偏偏,我们总是后知后觉。没错,思考。。。
在生活中,我们不缺乏的就是经验,但是将经验之谈转换通过思考转化成简单的逻辑,在由简单的逻辑去创造出懒人们的产品,是的,
这句话没错:懒人创造世界。