android-Ultra-Pull-To-Refresh
2016-03-29 23:03
483 查看
本篇学习的内容主要参考文章:http://a.codekk.com/detail/Android/Grumoon/android-Ultra-Pull-To-Refresh%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90
一、主要特点:
android-Ultra-Pull-To-Refresh (简称
UltraPTR )是一个强大的 Andriod 下拉刷新框架。
1.继承自ViewGroup,Content可以包含任何View;
2.简洁完善的Header抽象,方便进行扩展,构建满足需求的Header;
对比于Android-PullToRefresh 来说,UltraPTR没有实现上拉加载,作者认为上拉加载于下拉刷新不是同一个层次的功能,上拉加载应该交由Content自己去实现;Googgle官方的SwipeRefreshLayout也是这样认为的,对比SwipeRefreshLayout,UltraPTR更加灵活,更容易扩展。
二、总体设计
UltraPTR首先抽象出两个接口,功能接口和UI接口
PtrHandler代表下拉刷新的功能接口,包含刷新功能回调方法以及判断是否可以下拉的方法,用户实现此接口来进行数据刷新工作。
PtrUIHandler代表下拉刷新的UI接口,包含准备下拉、下拉中、下拉完成、下拉重置以及下拉过程中的位置变化等回调方法。通常情况下,Header需要实现此接口,来处理下拉刷新过程中头部UI的变化。
整个项目围绕核心类PtrFrameLayout。
PtrFrameLayout代表了一个下拉刷新的自定义控件。
PtrFrameLayout继承自ViewGroup,有且只能有两个子View,头部Header和内容Content。通常情况下,Header会实现PtrUIHandler接口,Content可以为任意的View。
和所有的自定义控件一样,PtrFrameLayout通过重写onFinishInflate、onMeasure、onLayout来确定控件的大小和位置,通过重写dispatchTouchEvent来确定控件的下拉行为。
三、详细设计
1.核心类功能介绍
(1) PtrHandler.java
下拉刷新功能接口,对下拉刷新功能的抽象,包含以下两个方法:
[java] view
plain copy
public void onRefreshBegin(final PtrFrameLayout frame)
刷新开始回调方法,用户在这里写自己的后台数据刷新功能。
[java] view
plain copy
public boolean checkCanDoRefresh(final PtrFrameLayout frame, final View content, final View header)
判断是否可以下拉刷新方法,UltraPTR的content可以包含任意内容,用户在这里判断决定是否可以进入下拉刷新状态。
eg.
如果content是一个TextView,则可以直接返回true,表示可以进入下拉状态;
如果content是一个ListView,则当ListView的第一条Item在顶部的时候,返回true,表示可以进入下拉状态;
如果content是一个ScrollView,则当ScrollView滑动到顶部顶部的时候,返回true,表示可以进入下拉状态;
(2) PtrDefaultHandler.java
抽象类,实现了PtrHandler接口,给出了checkCanDoRefresh()的默认实现,给出了常见View是否可以下拉的判断方法。
这里我们看一下两个版本的演进:
老版代码:
[java] view
plain copy
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
return checkContentCanBePulledDown(frame, content, header);
}
public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
/**
* 如果 Content 不是 ViewGroup,返回 true,表示可以下拉</br>
* 例如:TextView,ImageView
*/
if (!(content instanceof ViewGroup)) {
return true;
}
ViewGroup viewGroup = (ViewGroup) content;
/**
* 如果 Content 没有子 View(内容为空)时候,返回 true,表示可以下拉
*/
if (viewGroup.getChildCount() == 0) {
return true;
}
/**
* 如果 Content 是 AbsListView(ListView,GridView),当第一个 item 不可见是,返回 false,不可以下拉。
*/
if (viewGroup instanceof AbsListView) {
AbsListView listView = (AbsListView) viewGroup;
if (listView.getFirstVisiblePosition() > 0) {
return false;
}
}
/**
* 如果 SDK 版本为 14 以上,可以用 canScrollVertically 判断是否能在竖直方向上,向上滑动</br>
* 不能向上,表示已经滑动到在顶部或者 Content 不能滑动,返回 true,可以下拉</br>
* 可以向上,返回 false,不能下拉
*/
if (Build.VERSION.SDK_INT >= 14) {
return !content.canScrollVertically(-1);
} else {
/**
* SDK 版本小于 14,如果 Content 是 ScrollView 或者 AbsListView,通过 getScrollY 判断滑动位置 </br>
* 如果位置为 0,表示在最顶部,返回 true,可以下拉
*/
if (viewGroup instanceof ScrollView || viewGroup instanceof AbsListView) {
return viewGroup.getScrollY() == 0;
}
}
/**
* 最终判断,判断第一个子 View 的 top 值</br>
* 如果第一个子 View 有 margin,则当 top==子 view 的 marginTop+content 的 paddingTop 时,表示在最顶部,返回 true,可以下拉</br>
* 如果没有 margin,则当 top==content 的 paddinTop 时,表示在最顶部,返回 true,可以下拉
*/
View child = viewGroup.getChildAt(0);
ViewGroup.LayoutParams glp = child.getLayoutParams();
int top = child.getTop();
if (glp instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) glp;
return top == mlp.topMargin + viewGroup.getPaddingTop();
} else {
return top == viewGroup.getPaddingTop();
}
}
bug:如果 Content 是 AbsListView(ListView,GridView),通过 getScrollY() 获取的值一直是 0,所以这段代码的判断,无效。
新版代码:
[java] view
plain copy
public static boolean canChildScrollUp(View view) {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (view instanceof AbsListView) {
final AbsListView absListView = (AbsListView) view;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return view.getScrollY() > 0;
}
} else {
return view.canScrollVertically(-1);
}
}
以上是对通用view的判断方式,如果是特殊的自定义view,需要使用者自己实现符合需求的判断。
(3)PtrUIHandler.java
下拉刷新UI接口,对下拉刷新UI变化的抽象。一般情况下,Header会实现此接口,处理下拉过程中的头部UI的变化。包含5个方法:
[java] view
plain copy
public void onUIReset(PtrFrameLayout frame);
Content重新回到顶部,Header消失,整个下拉刷新过程完全结束以后,重置View。
[java] view
plain copy
public void onUIRefreshPrepare(PtrFrameLayout frame);
准备刷新,Header将要出现时调用。
[java] view
plain copy
public void onUIRefreshBegin(PtrFrameLayout frame);
开始刷新,Header进入刷新状态之前调用。
[java] view
plain copy
public void onUIRefreshComplete(PtrFrameLayout frame);
刷新结束,Header开始向上移动之前调用。
[java] view
plain copy
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, int oldPosition, int currentPosition, float oldPercent, float currentPercent);
下拉刷新过程中,位置变化时回调方法。
(4) PtrUIHandlerHolder.java
UI接口PtrUIHandler的实现,封装了PtrUIHandler,并将其组织成链表的形式。之所以封装成链表的目的是作者希望调用者可以像Header一样去实现PtrUIHandler,能够重写onUIReset、onUIRefreshPrepare、onUIRefreshBegin、onUIRefreshComplete这几个方法去实现自己的逻辑和UI效果,而它们统一由PtrUIHandlerHolder来管理,你只需要通过addHandler方法加入到链表中即可,这一点的抽象为那些希望去做一些处理的开发者还是相当方便的。
(5) PtrFrameLayout.java
UltraPTR的核心类,自定义控件类。
有8个自定义属性:
ptr_header,设置头部id。
ptr_content,设置内容id。
ptr_resistance,阻尼系数,默认1.7f,该值越大,感觉下拉时越吃力。
ptr_ratio_of_header_height_to_refresh,触发刷新时移动的位置比例,默认1.2f,即下拉高度达到Header高度的1.2倍的时候出发刷新动作。
ptr_duration_to_close,回弹时延,默认200ms,放手刷新时,Header回弹到刷新高度所用的时间。
ptr_duration_to_close_header,头部回弹时延,默认1000ms,刷新结束时,Header回弹消失所用的时间。
ptr_pull_to_refresh,刷新时是否保持头部,默认为true。
ptr_keep_header_when_refresh,下拉刷新/释放刷新,默认为释放刷新。
从两个方面分析PtrFrameLayout:
显示(View绘制)
------>>>>>>重写onFinishInflate()方法来确定Header和Content。
可以通过setHeaderView()来设置Header,或者通过ptr_header、ptr_content两个属性来设置。
最终,将Header实例赋值给mHeaderView,Content实例赋值给mContent变量。
------>>>>>>重写onMeasure,测量Header和Content,将Header的高度赋值给mHeaderHeight,并据此计算出下拉刷新偏移量赋值给mOffsetToRefresh。
[java] view
plain copy
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
// 测量 Header
if (mHeaderView != null) {
measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0);
MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
mOffsetToRefresh = (int) (mHeaderHeight * mRatioOfHeaderHeightToRefresh);
...
}
// 测量 Content
if (mContent != null) {
measureContentView(mContent, widthMeasureSpec, heightMeasureSpec);
...
}
}
------>>>>>>重写onLayout()方法来确定子view的位置。
对于Header
[java] view
plain copy
final int top = paddingTop + lp.topMargin + offsetX - mHeaderHeight;
对于Content
[java] view
plain copy
final int top = paddingTop + lp.topMargin + offsetX;
在下拉的过程当中,不停的修改offsetX值,然后刷新视图。
行为(View事件)
ViewGroup事件处理,通常重写onInterceptTouchEvent()方法或者dispatchTouchEvent()方法,PtrFrameLayout是通过重写dispatchTouchEvent()方法来进行触摸事件处理。
处理流程:
![](http://img.blog.csdn.net/20160105152810173?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
a、ACTION_UP或者ACTION_CANCEL时候通过判断看是否执行onRelease()方法。
具体判断逻辑是:在tryToPerformRefresh()方法中,如果向下拉动的距离已经超过了下拉刷新偏移量mOffsetToRefresh值,并且当前的状态是PTR_STATUS_PREPARE状态,那么就执行onRelease()回调方法。
b、ACTION_MOVE中判断是否可以纵向move。
具体判断逻辑:ACTION_MOVE的方向向下,如果mPtrHandler不为null,并且mPtrHandler.checkCanDoRefresh返回值为true,则可以移动,Header和Content向下移动,否则,交给父类处理。
(6) PtrClassicDefaultHeader.java
经典下拉刷新的头部实现,实现了PtrUIHandler接口。可以作为我们实现自定义Header的参考。
[java] view
plain copy
@Override
public void onUIReset(PtrFrameLayout frame) {
resetView();
mShouldShowLastUpdate = true;
tryUpdateLastUpdateTime();
}
private void resetView() {
hideRotateView();
mProgressBar.setVisibility(INVISIBLE);
}
private void hideRotateView() {
mRotateView.clearAnimation();
mRotateView.setVisibility(INVISIBLE);
}
重写onUIReset()方法,隐藏滚动条,隐藏箭头,更新最后刷新时间。
[java] view
plain copy
@Override
public void onUIRefreshPrepare(PtrFrameLayout frame) {
mShouldShowLastUpdate = true;
tryUpdateLastUpdateTime();
mLastUpdateTimeUpdater.start();
mProgressBar.setVisibility(INVISIBLE);
mRotateView.setVisibility(VISIBLE);
mTitleTextView.setVisibility(VISIBLE);
if (frame.isPullToRefresh()) {
mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down_to_refresh));
} else {
mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down));
}
}
准备刷新,隐藏忙碌进度条,显示箭头view,显示文字,根据状态显示不同的文字提示。
[java] view
plain copy
@Override
public void onUIRefreshBegin(PtrFrameLayout frame) {
mShouldShowLastUpdate = false;
hideRotateView();
mProgressBar.setVisibility(VISIBLE);
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(R.string.cube_ptr_refreshing);
tryUpdateLastUpdateTime();
mLastUpdateTimeUpdater.stop();
}
开始刷新,隐藏箭头view,显示进度条,显示文字,更新最后刷新时间。
[java] view
plain copy
@Override
public void onUIRefreshComplete(PtrFrameLayout frame) {
hideRotateView();
mProgressBar.setVisibility(INVISIBLE);
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(getResources().getString(R.string.cube_ptr_refresh_complete));
// update last update time
SharedPreferences sharedPreferences = getContext().getSharedPreferences(KEY_SharedPreferences, 0);
if (!TextUtils.isEmpty(mLastUpdateTimeKey)) {
mLastUpdateTime = new Date().getTime();
sharedPreferences.edit().putLong(mLastUpdateTimeKey, mLastUpdateTime).commit();
}
}
刷新结束,隐藏箭头view,隐藏进度条,显示文字,写入最后刷新时间。
[java] view
plain copy
@Override
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, int lastPos, int currentPos, float oldPercent, float currentPercent) {
final int mOffsetToRefresh = frame.getOffsetToRefresh();
if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {
if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
crossRotateLineFromBottomUnderTouch(frame);
if (mRotateView != null) {
mRotateView.clearAnimation();
mRotateView.startAnimation(mReverseFlipAnimation);
}
}
} else if (currentPos > mOffsetToRefresh && lastPos <= mOffsetToRefresh) {
if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
crossRotateLineFromTopUnderTouch(frame);
if (mRotateView != null) {
mRotateView.clearAnimation();
mRotateView.startAnimation(mFlipAnimation);
}
}
}
}
下拉过程中位置变化回调。
PtrClassicFrameLayout添加PtrClassicDefaultHeader作为其头部视图。
(7) MaterialHeader.java
Material Design风格的头部实现
(8) StoreHouseHeader.java
StoreHouse风格的头部实现
(9) PtrLocalDisplay.java
显示相关工具类,用于获取用户屏幕宽度、高度以及屏幕密度。同时提供了dp和px的转化方法。
2.类关系图
![](http://img.blog.csdn.net/20160105164245532?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
3. UltraPTR的优点:
(1)对头部行为的抽象,可以很方便的使用户定制自己的下拉刷新头部,来实现各种效果。
(2)Content可以包含任意的view,都由UltraPTR来管理,简便,统一。
一、主要特点:
android-Ultra-Pull-To-Refresh (简称
UltraPTR )是一个强大的 Andriod 下拉刷新框架。
1.继承自ViewGroup,Content可以包含任何View;
2.简洁完善的Header抽象,方便进行扩展,构建满足需求的Header;
对比于Android-PullToRefresh 来说,UltraPTR没有实现上拉加载,作者认为上拉加载于下拉刷新不是同一个层次的功能,上拉加载应该交由Content自己去实现;Googgle官方的SwipeRefreshLayout也是这样认为的,对比SwipeRefreshLayout,UltraPTR更加灵活,更容易扩展。
二、总体设计
UltraPTR首先抽象出两个接口,功能接口和UI接口
PtrHandler代表下拉刷新的功能接口,包含刷新功能回调方法以及判断是否可以下拉的方法,用户实现此接口来进行数据刷新工作。
PtrUIHandler代表下拉刷新的UI接口,包含准备下拉、下拉中、下拉完成、下拉重置以及下拉过程中的位置变化等回调方法。通常情况下,Header需要实现此接口,来处理下拉刷新过程中头部UI的变化。
整个项目围绕核心类PtrFrameLayout。
PtrFrameLayout代表了一个下拉刷新的自定义控件。
PtrFrameLayout继承自ViewGroup,有且只能有两个子View,头部Header和内容Content。通常情况下,Header会实现PtrUIHandler接口,Content可以为任意的View。
和所有的自定义控件一样,PtrFrameLayout通过重写onFinishInflate、onMeasure、onLayout来确定控件的大小和位置,通过重写dispatchTouchEvent来确定控件的下拉行为。
三、详细设计
1.核心类功能介绍
(1) PtrHandler.java
下拉刷新功能接口,对下拉刷新功能的抽象,包含以下两个方法:
[java] view
plain copy
public void onRefreshBegin(final PtrFrameLayout frame)
刷新开始回调方法,用户在这里写自己的后台数据刷新功能。
[java] view
plain copy
public boolean checkCanDoRefresh(final PtrFrameLayout frame, final View content, final View header)
判断是否可以下拉刷新方法,UltraPTR的content可以包含任意内容,用户在这里判断决定是否可以进入下拉刷新状态。
eg.
如果content是一个TextView,则可以直接返回true,表示可以进入下拉状态;
如果content是一个ListView,则当ListView的第一条Item在顶部的时候,返回true,表示可以进入下拉状态;
如果content是一个ScrollView,则当ScrollView滑动到顶部顶部的时候,返回true,表示可以进入下拉状态;
(2) PtrDefaultHandler.java
抽象类,实现了PtrHandler接口,给出了checkCanDoRefresh()的默认实现,给出了常见View是否可以下拉的判断方法。
这里我们看一下两个版本的演进:
老版代码:
[java] view
plain copy
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
return checkContentCanBePulledDown(frame, content, header);
}
public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
/**
* 如果 Content 不是 ViewGroup,返回 true,表示可以下拉</br>
* 例如:TextView,ImageView
*/
if (!(content instanceof ViewGroup)) {
return true;
}
ViewGroup viewGroup = (ViewGroup) content;
/**
* 如果 Content 没有子 View(内容为空)时候,返回 true,表示可以下拉
*/
if (viewGroup.getChildCount() == 0) {
return true;
}
/**
* 如果 Content 是 AbsListView(ListView,GridView),当第一个 item 不可见是,返回 false,不可以下拉。
*/
if (viewGroup instanceof AbsListView) {
AbsListView listView = (AbsListView) viewGroup;
if (listView.getFirstVisiblePosition() > 0) {
return false;
}
}
/**
* 如果 SDK 版本为 14 以上,可以用 canScrollVertically 判断是否能在竖直方向上,向上滑动</br>
* 不能向上,表示已经滑动到在顶部或者 Content 不能滑动,返回 true,可以下拉</br>
* 可以向上,返回 false,不能下拉
*/
if (Build.VERSION.SDK_INT >= 14) {
return !content.canScrollVertically(-1);
} else {
/**
* SDK 版本小于 14,如果 Content 是 ScrollView 或者 AbsListView,通过 getScrollY 判断滑动位置 </br>
* 如果位置为 0,表示在最顶部,返回 true,可以下拉
*/
if (viewGroup instanceof ScrollView || viewGroup instanceof AbsListView) {
return viewGroup.getScrollY() == 0;
}
}
/**
* 最终判断,判断第一个子 View 的 top 值</br>
* 如果第一个子 View 有 margin,则当 top==子 view 的 marginTop+content 的 paddingTop 时,表示在最顶部,返回 true,可以下拉</br>
* 如果没有 margin,则当 top==content 的 paddinTop 时,表示在最顶部,返回 true,可以下拉
*/
View child = viewGroup.getChildAt(0);
ViewGroup.LayoutParams glp = child.getLayoutParams();
int top = child.getTop();
if (glp instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) glp;
return top == mlp.topMargin + viewGroup.getPaddingTop();
} else {
return top == viewGroup.getPaddingTop();
}
}
bug:如果 Content 是 AbsListView(ListView,GridView),通过 getScrollY() 获取的值一直是 0,所以这段代码的判断,无效。
新版代码:
[java] view
plain copy
public static boolean canChildScrollUp(View view) {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (view instanceof AbsListView) {
final AbsListView absListView = (AbsListView) view;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return view.getScrollY() > 0;
}
} else {
return view.canScrollVertically(-1);
}
}
以上是对通用view的判断方式,如果是特殊的自定义view,需要使用者自己实现符合需求的判断。
(3)PtrUIHandler.java
下拉刷新UI接口,对下拉刷新UI变化的抽象。一般情况下,Header会实现此接口,处理下拉过程中的头部UI的变化。包含5个方法:
[java] view
plain copy
public void onUIReset(PtrFrameLayout frame);
Content重新回到顶部,Header消失,整个下拉刷新过程完全结束以后,重置View。
[java] view
plain copy
public void onUIRefreshPrepare(PtrFrameLayout frame);
准备刷新,Header将要出现时调用。
[java] view
plain copy
public void onUIRefreshBegin(PtrFrameLayout frame);
开始刷新,Header进入刷新状态之前调用。
[java] view
plain copy
public void onUIRefreshComplete(PtrFrameLayout frame);
刷新结束,Header开始向上移动之前调用。
[java] view
plain copy
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, int oldPosition, int currentPosition, float oldPercent, float currentPercent);
下拉刷新过程中,位置变化时回调方法。
(4) PtrUIHandlerHolder.java
UI接口PtrUIHandler的实现,封装了PtrUIHandler,并将其组织成链表的形式。之所以封装成链表的目的是作者希望调用者可以像Header一样去实现PtrUIHandler,能够重写onUIReset、onUIRefreshPrepare、onUIRefreshBegin、onUIRefreshComplete这几个方法去实现自己的逻辑和UI效果,而它们统一由PtrUIHandlerHolder来管理,你只需要通过addHandler方法加入到链表中即可,这一点的抽象为那些希望去做一些处理的开发者还是相当方便的。
(5) PtrFrameLayout.java
UltraPTR的核心类,自定义控件类。
有8个自定义属性:
ptr_header,设置头部id。
ptr_content,设置内容id。
ptr_resistance,阻尼系数,默认1.7f,该值越大,感觉下拉时越吃力。
ptr_ratio_of_header_height_to_refresh,触发刷新时移动的位置比例,默认1.2f,即下拉高度达到Header高度的1.2倍的时候出发刷新动作。
ptr_duration_to_close,回弹时延,默认200ms,放手刷新时,Header回弹到刷新高度所用的时间。
ptr_duration_to_close_header,头部回弹时延,默认1000ms,刷新结束时,Header回弹消失所用的时间。
ptr_pull_to_refresh,刷新时是否保持头部,默认为true。
ptr_keep_header_when_refresh,下拉刷新/释放刷新,默认为释放刷新。
从两个方面分析PtrFrameLayout:
显示(View绘制)
------>>>>>>重写onFinishInflate()方法来确定Header和Content。
可以通过setHeaderView()来设置Header,或者通过ptr_header、ptr_content两个属性来设置。
最终,将Header实例赋值给mHeaderView,Content实例赋值给mContent变量。
------>>>>>>重写onMeasure,测量Header和Content,将Header的高度赋值给mHeaderHeight,并据此计算出下拉刷新偏移量赋值给mOffsetToRefresh。
[java] view
plain copy
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
// 测量 Header
if (mHeaderView != null) {
measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0);
MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
mOffsetToRefresh = (int) (mHeaderHeight * mRatioOfHeaderHeightToRefresh);
...
}
// 测量 Content
if (mContent != null) {
measureContentView(mContent, widthMeasureSpec, heightMeasureSpec);
...
}
}
------>>>>>>重写onLayout()方法来确定子view的位置。
对于Header
[java] view
plain copy
final int top = paddingTop + lp.topMargin + offsetX - mHeaderHeight;
对于Content
[java] view
plain copy
final int top = paddingTop + lp.topMargin + offsetX;
在下拉的过程当中,不停的修改offsetX值,然后刷新视图。
行为(View事件)
ViewGroup事件处理,通常重写onInterceptTouchEvent()方法或者dispatchTouchEvent()方法,PtrFrameLayout是通过重写dispatchTouchEvent()方法来进行触摸事件处理。
处理流程:
a、ACTION_UP或者ACTION_CANCEL时候通过判断看是否执行onRelease()方法。
具体判断逻辑是:在tryToPerformRefresh()方法中,如果向下拉动的距离已经超过了下拉刷新偏移量mOffsetToRefresh值,并且当前的状态是PTR_STATUS_PREPARE状态,那么就执行onRelease()回调方法。
b、ACTION_MOVE中判断是否可以纵向move。
具体判断逻辑:ACTION_MOVE的方向向下,如果mPtrHandler不为null,并且mPtrHandler.checkCanDoRefresh返回值为true,则可以移动,Header和Content向下移动,否则,交给父类处理。
(6) PtrClassicDefaultHeader.java
经典下拉刷新的头部实现,实现了PtrUIHandler接口。可以作为我们实现自定义Header的参考。
[java] view
plain copy
@Override
public void onUIReset(PtrFrameLayout frame) {
resetView();
mShouldShowLastUpdate = true;
tryUpdateLastUpdateTime();
}
private void resetView() {
hideRotateView();
mProgressBar.setVisibility(INVISIBLE);
}
private void hideRotateView() {
mRotateView.clearAnimation();
mRotateView.setVisibility(INVISIBLE);
}
重写onUIReset()方法,隐藏滚动条,隐藏箭头,更新最后刷新时间。
[java] view
plain copy
@Override
public void onUIRefreshPrepare(PtrFrameLayout frame) {
mShouldShowLastUpdate = true;
tryUpdateLastUpdateTime();
mLastUpdateTimeUpdater.start();
mProgressBar.setVisibility(INVISIBLE);
mRotateView.setVisibility(VISIBLE);
mTitleTextView.setVisibility(VISIBLE);
if (frame.isPullToRefresh()) {
mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down_to_refresh));
} else {
mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down));
}
}
准备刷新,隐藏忙碌进度条,显示箭头view,显示文字,根据状态显示不同的文字提示。
[java] view
plain copy
@Override
public void onUIRefreshBegin(PtrFrameLayout frame) {
mShouldShowLastUpdate = false;
hideRotateView();
mProgressBar.setVisibility(VISIBLE);
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(R.string.cube_ptr_refreshing);
tryUpdateLastUpdateTime();
mLastUpdateTimeUpdater.stop();
}
开始刷新,隐藏箭头view,显示进度条,显示文字,更新最后刷新时间。
[java] view
plain copy
@Override
public void onUIRefreshComplete(PtrFrameLayout frame) {
hideRotateView();
mProgressBar.setVisibility(INVISIBLE);
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(getResources().getString(R.string.cube_ptr_refresh_complete));
// update last update time
SharedPreferences sharedPreferences = getContext().getSharedPreferences(KEY_SharedPreferences, 0);
if (!TextUtils.isEmpty(mLastUpdateTimeKey)) {
mLastUpdateTime = new Date().getTime();
sharedPreferences.edit().putLong(mLastUpdateTimeKey, mLastUpdateTime).commit();
}
}
刷新结束,隐藏箭头view,隐藏进度条,显示文字,写入最后刷新时间。
[java] view
plain copy
@Override
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, int lastPos, int currentPos, float oldPercent, float currentPercent) {
final int mOffsetToRefresh = frame.getOffsetToRefresh();
if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {
if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
crossRotateLineFromBottomUnderTouch(frame);
if (mRotateView != null) {
mRotateView.clearAnimation();
mRotateView.startAnimation(mReverseFlipAnimation);
}
}
} else if (currentPos > mOffsetToRefresh && lastPos <= mOffsetToRefresh) {
if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
crossRotateLineFromTopUnderTouch(frame);
if (mRotateView != null) {
mRotateView.clearAnimation();
mRotateView.startAnimation(mFlipAnimation);
}
}
}
}
下拉过程中位置变化回调。
PtrClassicFrameLayout添加PtrClassicDefaultHeader作为其头部视图。
(7) MaterialHeader.java
Material Design风格的头部实现
(8) StoreHouseHeader.java
StoreHouse风格的头部实现
(9) PtrLocalDisplay.java
显示相关工具类,用于获取用户屏幕宽度、高度以及屏幕密度。同时提供了dp和px的转化方法。
2.类关系图
3. UltraPTR的优点:
(1)对头部行为的抽象,可以很方便的使用户定制自己的下拉刷新头部,来实现各种效果。
(2)Content可以包含任意的view,都由UltraPTR来管理,简便,统一。
相关文章推荐
- Android主题更换简单实践
- java.lang.ClassCastException: android.widget.ListView 替换控件出错 如自定义mlistview继承listview
- android 上传图片到服务器
- Android:Material系列:ActionBar-->Toolbar
- 我的博客之第一篇
- Android——滚动视图(ScrollView)图片视图(ImageView)、状态开关按钮(ToggleButton)、时钟
- Android屏幕适配全攻略(最权威的官方适配指导)
- Android中ActionBar的使用
- Android——selector背景选择器
- Service完全解析
- Service、IntentService
- Android中禁止SlidingPaneLayout的侧滑功能
- Android中禁止SlidingPaneLayout的侧滑功能
- Android组件之广播(Broadcast)
- [android] 安卓进程优先级&为什么使用服务
- Android 监听软键盘搜索键
- Android 监听软键盘搜索键
- Android Notification剖析
- gradle 基本配置介绍
- Android 内存优化学习