Android源代码学习笔记:android-Ultra-Pull-To-Refresh-master
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android源代码学习笔记:android-Ultra-Pull-To-Refresh-master相关的知识,希望对你有一定的参考价值。
学习要点:下拉刷新
这个小应用包含了在使用到GridView,FrameLayout,TextView,ListView等等控件时的所有下拉刷新效果下拉刷新具有极为强大的使用空间,几乎所有的应用都会用到。
源码解析文档: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
public interface PtrHandler { // 刷新回调函数,用户在这里写自己的刷新功能实现,处理业务数据的刷新。 public boolean checkCanDoRefresh(final PtrFrameLayout frame, final View content, final View header); /*判断是否可以下拉刷新。 UltraPTR 的 Content 可以包含任何内容,用户在这里判断决定是否可以下拉。 例如,如果 Content 是 TextView,则可以直接返回 true,表示可以下拉刷新。 如果 Content 是 ListView,当第一条在顶部时返回 true,表示可以下拉刷新。 如果 Content 是 ScrollView,当滑动到顶部时返回 true,表示可以刷新*/ public void onRefreshBegin(final PtrFrameLayout frame); }
在PtrDefaultHandler中会实现这两个接口。
@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(); } }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);
}
}public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
return !canChildScrollUp(content);
}
以上代码是用来判断是否可以滑动的,给出了AbsListView和非AbsListView的判断方法。
/* 用于实现UI界面的更新 */ public interface PtrUIHandler { /** * When the content view has reached top and refresh has been completed, view will be reset. * * @param frame */ public void onUIReset(PtrFrameLayout frame); /** * prepare for loading * * @param frame */ public void onUIRefreshPrepare(PtrFrameLayout frame); /** * perform refreshing UI */ public void onUIRefreshBegin(PtrFrameLayout frame); /** * perform UI after refresh */ public void onUIRefreshComplete(PtrFrameLayout frame); public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator); }
PtrFrameLayout
在自定义空间PtrFrameLayout中,定义了mHeaderView和 mContent,表示头部信息和内容信息。
在PtrFrameLayout中实现了view的onFinishInflate 类,onFinishInflate 是在当View中所有的子控件均被映射成xml后触发,例如MyView mv = (MyView)View.inflate (context,R.layout.my_view,null);当加载完成xml后,就会执行那个方法。在这个函数中,通过viewgroup函数getChild函数,获得了两个view,从而赋值给了
header与content两个函数。
/*在这个函数中,通过viewgroup函数getChild函数,获得了两个view,从而赋值给了 header与content两个函数。 */ protected void onFinishInflate() { final int childCount = getChildCount(); if (childCount > 2) { throw new IllegalStateException("PtrFrameLayout only can host 2 elements"); } else if (childCount == 2) { if (mHeaderId != 0 && mHeaderView == null) { mHeaderView = findViewById(mHeaderId); } if (mContainerId != 0 && mContent == null) { mContent = findViewById(mContainerId); } // not specify header or content if (mContent == null || mHeaderView == null) { View child1 = getChildAt(0); View child2 = getChildAt(1); if (child1 instanceof PtrUIHandler) { mHeaderView = child1; mContent = child2; } else if (child2 instanceof PtrUIHandler) { mHeaderView = child2; mContent = child1; } else { // both are not specified if (mContent == null && mHeaderView == null) { mHeaderView = child1; mContent = child2; } // only one is specified else { if (mHeaderView == null) { mHeaderView = mContent == child1 ? child2 : child1; } else { mContent = mHeaderView == child1 ? child2 : child1; } } } } } else if (childCount == 1) { mContent = getChildAt(0); } else { TextView errorView = new TextView(getContext()); errorView.setClickable(true); errorView.setTextColor(0xffff6600); errorView.setGravity(Gravity.CENTER); errorView.setTextSize(20); errorView.setText("The content view in PtrFrameLayout is empty. Do you forget to specify its id in xml layout file?"); mContent = errorView; addView(mContent); } if (mHeaderView != null) { mHeaderView.bringToFront(); } super.onFinishInflate(); }
view的描绘流程:
onMeasure:计算空间的长宽 ——》 onLayout:计算子控件的位置 ——》 onDraw:进行绘制
重写 onMeasure,测量 Header 和 Content,将 Header 的高度赋值给 mHeaderHeight
变量,将计算出的下拉刷新偏移量赋值给 mOffsetToRefresh
变量。
重写 onLayout 方法来确定子 View 的位置。
对于 Headerf:inal int top = paddingTop + lp.topMargin + offsetX - mHeaderHeight;
对于 Content:final int top = paddingTop + lp.topMargin + offsetX;
这就回答了,如何制造出下拉时显示header的效果。在 LonLayout中调用了layoutChildren():
private void layoutChildren() { int offsetX = mPtrIndicator.getCurrentPosY(); int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); if (mHeaderView != null) { MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams(); final int left = paddingLeft + lp.leftMargin; final int top = paddingTop + lp.topMargin + offsetX - mHeaderHeight; final int right = left + mHeaderView.getMeasuredWidth(); final int bottom = top + mHeaderView.getMeasuredHeight(); mHeaderView.layout(left, top, right, bottom); if (DEBUG && DEBUG_LAYOUT) { PtrCLog.d(LOG_TAG, "onLayout header: %s %s %s %s", left, top, right, bottom); } } if (mContent != null) { if (isPinContent()) { offsetX = 0; } MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams(); final int left = paddingLeft + lp.leftMargin; final int top = paddingTop + lp.topMargin + offsetX; final int right = left + mContent.getMeasuredWidth(); final int bottom = top + mContent.getMeasuredHeight(); if (DEBUG && DEBUG_LAYOUT) { PtrCLog.d(LOG_TAG, "onLayout content: %s %s %s %s", left, top, right, bottom); } mContent.layout(left, top, right, bottom); } }
通过 mHeaderView.layout(left, top, right, bottom);设置了mHeaderView在viewgrop中的位置。
事件传输
1、基础知识 (1) 所有 Touch 事件都被封装成了 MotionEvent 对象,包括 Touch 的位置、时间、历史记录以及第几个手指(多指触摸)等。 (2) 事件类型分为 ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每个事件都是以 ACTION_DOWN 开始 ACTION_UP 结束。 (3) 对事件的处理包括三类,分别为传递——dispatchTouchEvent()函数、拦截——onInterceptTouchEvent()函数、消费——onTouchEvent()函数和 OnTouchListener 2、传递流程 (1) 事件从 Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的 View(ViewGroup)开始一直往下(子 View)传递。子 View 可以通过 onTouchEvent()对事件进行处理。 (2) 事件由父 View(ViewGroup)传递给子 View,ViewGroup 可以通过 onInterceptTouchEvent()对事件做拦截,停止其往下传递。 (3) 如果事件从上往下传递过程中一直没有被停止,且最底层子 View 没有消费事件,事件会反向往上传递,这时父 View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到 Activity 的 onTouchEvent()函数。 (4) 如果 View 没有对 ACTION_DOWN 进行消费,之后的其他事件不会传递过来。 (5) OnTouchListener 优先于 onTouchEvent()对事件进行消费。 上面的消费即表示相应函数返回值为 true。
public boolean dispatchTouchEvent(MotionEvent e) { if (!isEnabled() || mContent == null || mHeaderView == null) { return dispatchTouchEventSupper(e); } int action = e.getAction(); switch (action) { /*功能上,通过执行 tryToPerformRefresh 方法,如果向下拉动的位移已经超过了触发下拉刷新的偏移量 mOffsetToRefresh,并且当前状态是 PTR_STATUS_PREPARE,执行刷新功能回调。 行为上,如果没有达到触发刷新的偏移量,或者当前状态为 PTR_STATUS_COMPLETE,或者刷新过程中不保持头部位置,则执行向上的位置回复动作。*/ case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mPtrIndicator.onRelease(); if (mPtrIndicator.hasLeftStartPosition()) { if (DEBUG) { PtrCLog.d(LOG_TAG, "call onRelease when user release"); } onRelease(false); if (mPtrIndicator.hasMovedAfterPressedDown()) { sendCancelEvent(); return true; } return dispatchTouchEventSupper(e); } else { return dispatchTouchEventSupper(e); } case MotionEvent.ACTION_DOWN: mHasSendCancelEvent = false; mPtrIndicator.onPressDown(e.getX(), e.getY()); mScrollChecker.abortIfWorking(); mPreventForHorizontal = false; // The cancel event will be sent once the position is moved. // So let the event pass to children. // fix #93, #102 dispatchTouchEventSupper(e); return true; /*2.ACTION_MOVE 中判断是否可以纵向 move。 ACTION_MOVE 的方向向下,如果 mPtrHandler 不为空,并且 mPtrHandler.checkCanDoRefresh 返回值为 true,则可以移动, Header 和 Content 向下移动,否则,事件交由父类处理。 ACTION_MOVE 的方向向上,如果当前位置大于起始位置,则可以移动,Header 和 Content 向上移动,否则,事件交由父类处理。*/ case MotionEvent.ACTION_MOVE: mLastMoveEvent = e; mPtrIndicator.onMove(e.getX(), e.getY()); float offsetX = mPtrIndicator.getOffsetX(); float offsetY = mPtrIndicator.getOffsetY(); if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) { if (mPtrIndicator.isInStartPosition()) { mPreventForHorizontal = true; } } if (mPreventForHorizontal) { return dispatchTouchEventSupper(e); } boolean moveDown = offsetY > 0; boolean moveUp = !moveDown; boolean canMoveUp = mPtrIndicator.hasLeftStartPosition(); if (DEBUG) { boolean canMoveDown = mPtrHandler != null && mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView); PtrCLog.v(LOG_TAG, "ACTION_MOVE: offsetY:%s, currentPos: %s, moveUp: %s, canMoveUp: %s, moveDown: %s: canMoveDown: %s", offsetY, mPtrIndicator.getCurrentPosY(), moveUp, canMoveUp, moveDown, canMoveDown); } // disable move when header not reach top if (moveDown && mPtrHandler != null && !mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView)) { return dispatchTouchEventSupper(e); } if ((moveUp && canMoveUp) || moveDown) { movePos(offsetY); return true; } } return dispatchTouchEventSupper(e); }
private void onRelease(boolean stayForLoading) { tryToPerformRefresh(); if (mStatus == PTR_STATUS_LOADING) { // keep header for fresh if (mKeepHeaderWhenRefresh) { // scroll header back if (mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && !stayForLoading) { mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToKeepHeaderWhileLoading(), mDurationToClose); } else { // do nothing } } else { tryScrollBackToTopWhileLoading(); } } else { if (mStatus == PTR_STATUS_COMPLETE) { notifyUIRefreshComplete(false); } else { tryScrollBackToTopAbortRefresh(); } } }
onRealse()中,如果功能上,通过执行 tryToPerformRefresh
方法,如果向下拉动的位移已经超过了触发下拉刷新的偏移量 mOffsetToRefresh
,并且当前状态是 PTR_STATUS_PREPARE,执行刷新功能回调。行为上,如果没有达到触发刷新的偏移量,或者当前状态为 PTR_STATUS_COMPLETE,或者刷新过程中不保持头部位置,则执行向上的位置回复动作。
PtrClassicDefaultHeader的具体实现:
@Override public void onUIReset(PtrFrameLayout frame) { //重置 View resetView(); mShouldShowLastUpdate = true; tryUpdateLastUpdateTime(); } private void resetView() { //隐藏忙碌进度条 hideRotateView(); mProgressBar.setVisibility(INVISIBLE); } private void hideRotateView() { //隐藏箭头 View mRotateView.clearAnimation(); mRotateView.setVisibility(INVISIBLE); }
PtrClassicFrameLayout.java:经典下拉刷新实现类
!--StartFragment>
以上是关于Android源代码学习笔记:android-Ultra-Pull-To-Refresh-master的主要内容,如果未能解决你的问题,请参考以下文章
Android源代码学习笔记:android-Ultra-Pull-To-Refresh-master