下拉刷新及相关框架

来源:互联网 发布:没有好奇心 知乎 编辑:程序博客网 时间:2024/05/18 03:40

android-Ultra-Pull-to-Refresh 深入理解及使用


下拉刷新,几乎是每个 Android 应用都会需要的功能。 android-Ultra-Pull-To-Refresh (以下简称 UltraPTR )便是一个强大的 Andriod 下拉刷新框架。
主要特点:
(1).继承于 ViewGroup, Content 可以包含任何 View。
(2).简洁完善的 Header 抽象,方便进行拓展,构建符合需求的头部。

项目地址:
https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh

竞品:
https://github.com/chrisbanes/Android-PullToRefresh
https://github.com/johannilsson/android-pulltorefresh
https://github.com/Demievil/SwipeRefreshLayout

参考文章:
android-Ultra-Pull-To-Refresh源码解析: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
公共技术点之 View 绘制流程:http://codekk.com/open-source-project-analysis/detail/Android/lightSky/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8B%20View%20%E7%BB%98%E5%88%B6%E6%B5%81%E7%A8%8B
公共技术点之 View 事件传递:http://codekk.com/open-source-project-analysis/detail/Android/Trinea/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8B%20View%20%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92
Android事件分发机制完全解析,带你从源码的角度彻底理解(上、下):http://blog.csdn.net/guolin_blog/article/details/9097463

1.添加依赖

compile 'in.srain.cube:ultra-ptr:1.0.10'

2.XML界面配置

详细官方中文文档:https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh/blob/master/README-cn.md

<?xml version="1.0" encoding="utf-8"?><in.srain.cube.views.ptr.PtrClassicFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:cube_ptr="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#f1f1f1"    cube_ptr:ptr_duration_to_close="200"    cube_ptr:ptr_duration_to_close_header="1000"    cube_ptr:ptr_keep_header_when_refresh="true"    cube_ptr:ptr_pull_to_fresh="false"    cube_ptr:ptr_ratio_of_header_height_to_refresh="1.2"    cube_ptr:ptr_resistance="1.7">    <WebView        android:layout_width="match_parent"        android:layout_height="match_parent" /></in.srain.cube.views.ptr.PtrClassicFrameLayout>

自定义属性实现

//1.在attr里定义属性<declare-styleable name="PtrFrameLayout"?//2.在构造方法里获取TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.PtrFrameLayout);arr.recycle();

如何获取header及content

1.重写view的onFinishInflate方法
2.判断childCount
childCount > 2 throw excetpion
childCount = 2 header+content
childCount = 1 content
childCount = 0 errorView
3.mHeaderView.bringToFront(),不懂什么意思

3.Java代码配置

// the following are default settings//1.设置阻尼系数mPtrFrame.setResistance(1.7f);//2.下拉刷新头部的比率mPtrFrame.setRatioOfHeaderHeightToRefresh(1.2f);//3.设置从松手的位置到头部所要的时间mPtrFrame.setDurationToClose(200);//4.设置从头部到顶部所要的时间mPtrFrame.setDurationToCloseHeader(1000);//5.default is false,false下拉就进行刷新,true松手到超过指定高度才进行刷新mPtrFrame.setPullToRefresh(false);//6.default is true,true刷新时有消息头,flase刷新时没有消息头mPtrFrame.setKeepHeaderWhenRefresh(true);

1.mPtrFrame.setResistance(1.7f);

//setp 1.在PtrIndicator.class里面设置resistancemPtrIndicator.setResistance(arr.getFloat(R.styleable.PtrFrameLayout_ptr_resistance, mPtrIndicator.getResistance()));private float mResistance = 1.7f;//setp 2.在MotionEvent.ACTION_MOVE中计算,新的offsetY。(即offsetY / mResistance)mPtrIndicator.onMove(e.getX(), e.getY());public final void onMove(float x, float y) {    float offsetX = x - mPtLastMove.x;    float offsetY = (y - mPtLastMove.y);    processOnMove(x, y, offsetX, offsetY);    mPtLastMove.set(x, y);}protected void processOnMove(float currentX, float currentY, float offsetX, float offsetY) {    setOffset(offsetX, offsetY / mResistance);}protected void setOffset(float x, float y) {    mOffsetX = x;    mOffsetY = y;}private PointF mPtLastMove = new PointF();//setp 3.在MotionEvent.ACTION_MOVE的时候使用新的offsetYmovePos(offsetY);mContent.offsetTopAndBottom(change);

2.mPtrFrame.setRatioOfHeaderHeightToRefresh(1.2f);

//setp 1.在attr里设置header高度的比率float ratio = mPtrIndicator.getRatioOfHeaderToHeightRefresh();ratio = arr.getFloat(R.styleable.PtrFrameLayout_ptr_ratio_of_header_height_to_refresh, ratio);mPtrIndicator.setRatioOfHeaderHeightToRefresh(ratio);//setp 1'.在java代码里设置header高度的比率 public void setRatioOfHeaderHeightToRefresh(float ratio) {    mPtrIndicator.setRatioOfHeaderHeightToRefresh(ratio);}//以上两种方式都会调用PtrIndicator的方法public void setRatioOfHeaderHeightToRefresh(float ratio) {    mRatioOfHeaderHeightToRefresh = ratio;    mOffsetToRefresh = (int) (mHeaderHeight * ratio);}//setp 2.在onMeasure的时候设置header的高度及栈值mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;mPtrIndicator.setHeaderHeight(mHeaderHeight);public void setHeaderHeight(int height) {    mHeaderHeight = height;    updateHeight();}protected void updateHeight() {    mOffsetToRefresh = (int) (mRatioOfHeaderHeightToRefresh * mHeaderHeight);}public int getOffsetToRefresh() {    return mOffsetToRefresh;}//setp 3.在MotionEvent.ACTION_UP,使用这个栈值//3.1onRelease(false);//3.2tryToPerformRefresh();//3.3mPtrIndicator.isOverOffsetToRefresh()public boolean isOverOffsetToRefresh() {    return mCurrentPos >= getOffsetToRefresh();}public int getOffsetToRefresh() {    return mOffsetToRefresh;}//疑问:setRatioOfHeaderHeightToRefresh与onMeasure的调用顺序

3.mPtrFrame.setDurationToClose(200);

//step 1.设置方式省略//step 2.在MotionEvent.ACTION_UP中使用这个值private void onRelease(boolean stayForLoading)//step 3.要满足正在加载中&当刷新的时候保持头部&当前的pos要大于offsetToKeepHeader.//这三个条件都要满足,才会执行回滚头部。(也就是从当前位置回滚到offsetToKeepHeader)if (mStatus == PTR_STATUS_LOADING) {        // keep header for fresh        if (mKeepHeaderWhenRefresh) {            // scroll header back            if (mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && !stayForLoading) {                mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToKeepHeaderWhileLoading(), mDurationToClose);            }        }}//step 4.那什么是offsetToKeepHeader?可以看到它的默认值为mHeaderHeight,也有公开的方法可以设置,///但是,不知道应该在什么时候设置private int mOffsetToKeepHeaderWhileLoading = -1;public void setOffsetToKeepHeaderWhileLoading(int offset) {    mOffsetToKeepHeaderWhileLoading = offset;}public int getOffsetToKeepHeaderWhileLoading() {    return mOffsetToKeepHeaderWhileLoading >= 0 ? mOffsetToKeepHeaderWhileLoading : mHeaderHeight;}

4.mPtrFrame.setDurationToCloseHeader(1000);

//step 1.当主动调用刷新完成的private void performRefreshComplete() {    mStatus = PTR_STATUS_COMPLETE;    notifyUIRefreshComplete(false);}//step 2.通知UI刷新完成private void notifyUIRefreshComplete(boolean ignoreHook) {    tryScrollBackToTopAfterComplete();    tryToNotifyReset();}private void tryScrollBackToTopAfterComplete() {    tryScrollBackToTop();}//step 3.回滚到起点(即顶部)private void tryScrollBackToTop() {    if (!mPtrIndicator.isUnderTouch()) {        mScrollChecker.tryToScrollTo(PtrIndicator.POS_START, mDurationToCloseHeader);    }}//问题:mDurationToClose和mDurationToCloseHeader可能会有冲突?//当网络请求的响应的时候.即小于mDurationToClose的时间。在UI就会有卡顿的问题//根本原因:当执行mDurationToClose一半的时候,在调用refreshComplete()的,就会有一个回退的效果,然//后在执行mDurationToCloseHeader

5.mPtrFrame.setPullToRefresh(false);

//step 1.设置方法略//step 2.在下拉的时候,调用movePos(即MotionEvent.ACTION_MOVE)private void movePos(float deltaY) {    updatePos(change);}private void updatePos(int change) {// Pull to Refresh    if (mStatus == PTR_STATUS_PREPARE) {        // reach fresh height while moving from top to bottom        if (isUnderTouch && !isAutoRefresh() && mPullToRefresh                && mPtrIndicator.crossRefreshLineFromTopToBottom()) {            tryToPerformRefresh();        }        // reach header height while auto refresh        if (performAutoRefreshButLater() && mPtrIndicator.hasJustReachedHeaderHeightFromTopToBottom()) {            tryToPerformRefresh();        }    }}//step 3.执行下拉刷新(就是没有滑动超过指定高度的时候,就执行下拉刷新。相当于一个提前执行网络请求的一个效果) private boolean tryToPerformRefresh() {    performRefresh();} private void performRefresh() {    if (mPtrUIHandlerHolder.hasHandler()) {        mPtrUIHandlerHolder.onUIRefreshBegin(this);    }    if (mPtrHandler != null) {        mPtrHandler.onRefreshBegin(this);    }}

6.mPtrFrame.setKeepHeaderWhenRefresh(true);

//step 1.设置省略(xml,java两种方式)//step 2.在MotionEvent.ACTION_UP时调用onRelease(false)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();        }    }}//step 3.与mDurationToClose相比,不再是回滚到mHeaderHeight(默认值),而是直接回滚到顶点(0)。//这样做达到了。回滚的时候隐藏header的目的

4.设置下拉刷新监听

注意:如果有下拉问题,可以选择性重写checkCanDoRefresh

mPtrFrame.setPtrHandler(new PtrHandler() {    //检查能否下刷新    @Override    public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {        return PtrDefaultHandler.checkContentCanBePulledDown(frame, mWebView, header);    }    //开始下拉刷新    @Override    public void onRefreshBegin(PtrFrameLayout frame) {        updateData();    }});

需要自己实现checkContentCanBePulledDown

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);    }}

5.手动回调完成下拉刷新

mPtrFrame.refreshComplete();

6.自定义下拉刷新的头部

5.1 自定义view,并实现PtrUIHandler接口

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);}

5.2 用过xml布局或者setHeaderView,设置头部

5.3 设置PtrUIHandler

ptrHome.addPtrUIHandler(ptrUIHandler);

7.自动下拉刷新

ptrFrame.postDelayed(new Runnable() {    @Override    public void run() {        ptrFrame.autoRefresh(true);    }}, 150);

8.其他设置

1. setPinContent(false)

解释:设置ContentView是否顶住.(设置为true即content不动,只有header随着移动)

//step 1.在MotionEvent.ACTION_MOVE中调用movePos(offsetY)private void movePos(float deltaY) {    updatePos(change);}private void updatePos(int change) {    mHeaderView.offsetTopAndBottom(change);    if (!isPinContent()) {        mContent.offsetTopAndBottom(change);    }}

2.setLoadingMinTime(500)

解释:设置网络加载时间最少为500毫秒

//step 1.在MotionEvent.ACTION_UP的时候调用onRelease(false)//step 2.执行下拉刷新,并保存mLoadingStartTime private void onRelease(boolean stayForLoading) {    tryToPerformRefresh();}private boolean tryToPerformRefresh() {    if (mStatus != PTR_STATUS_PREPARE) {        return false;    }    //    if ((mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && isAutoRefresh()) || mPtrIndicator.isOverOffsetToRefresh()) {        mStatus = PTR_STATUS_LOADING;        performRefresh();    }    return false;}private void performRefresh() {    mLoadingStartTime = System.currentTimeMillis();    if (mPtrUIHandlerHolder.hasHandler()) {        mPtrUIHandlerHolder.onUIRefreshBegin(this);        if (DEBUG) {            PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshBegin");        }    }    if (mPtrHandler != null) {        mPtrHandler.onRefreshBegin(this);    }}//step 3.在调用refreshComplete()时计算delay。并且postDelayedfinal public void refreshComplete() {    int delay = (int) (mLoadingMinTime - (System.currentTimeMillis() - mLoadingStartTime));    if (delay <= 0) {        performRefreshComplete();    } else {        postDelayed(new Runnable() {            @Override            public void run() {                performRefreshComplete();            }        }, delay);    }}//可处理mDurationToClose和mDurationToCloseHeader的冲突的问题

3.disableWhenHorizontalMove(false)

解释:为了解决与viewpager的手势冲突问题(即在x轴滑动的距离大于在y轴滑动的距离,则直接返回,UltraPtr不做处理)

//step 1.在MotionEvent.ACTION_MOVEif (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {    if (mPtrIndicator.isInStartPosition()) {            mPreventForHorizontal = true;    }}if (mPreventForHorizontal) {    return dispatchTouchEventSupper(e);}

9.其他问题

1.UltraPtr如何实现滑动动画

解答:通过Scroller与ScrollChecker。
1.Scroller为滚动的封装类,通过偏移来描述从一个点到移动另一个点,坐标的变化情况。(mScroller.startScroll(startX, startY, dx, dy, duration)
2.ScrollChecker实现Runnable接口的线程类,但是其run放在主线程执行。根据其变更的坐标,去设置header
和content的offsetTopAndBottom(int offset),最后在刷新界面
3.使用view.post(action),不断执行ScrollChecker的run方法。这样到header和content位置不断变化,然后产生动画
4.最后mScroller.computeScrollOffset()或者mScroller.isFinished()来判断是否到了指定的间隔

2.如何测量header和content?

3.为什么UltraPtr没有实现加载更多

对比 Android-PullToRefresh 项目,UltraPTR 没有实现 加载更多 的功能,但我认为 下拉刷新 和 加载更多 不是同一层次的功能, 下拉刷新 有更广泛的需求,可以适用于任何页面。而 加载更多 的功能应该交由具体的 Content 自己去实现。这应该是和 Google 官方推出 SwipeRefreshLayout 是相同的设计思路,但对比 SwipeRefreshLayout, UltraPTR 更灵活,更容易拓展。

4.事件处理流程是怎么样

5.类的关系图是怎样的

9.PtrIndicator

字段

  1. mIsUnderTouch-在ACTION_DOWN为true,ACTION_UP为false
  2. mPtLastMove-在ACTION_DOWN和ACTION_MOVE都要更新这个坐标
  3. mOffsetX,mOffsetY-偏移量,当前坐标减去mPtLastMove
  4. mOffsetToRefresh-(int) (mHeaderHeight * ratio)
  5. mCurrentPos-ACTION_MOVE的时候更新这个坐标
  6. mLastPos-ACTION_MOVE保存上一个mCurrentPos坐标
  7. mPressedPos-ACTION_DOWN保存mCurrentPos坐标
  8. mRefreshCompleteY-刷新完成的时候保存mCurrentPos坐标

方法

//滑动的位置与Header的高度的百分比public float getCurrentPercent()//在顶部上面public boolean willOverTop(int to)//在顶部public boolean isAlreadyHere(int to)//顶部有空余public boolean hasLeftStartPosition()//在开始点public boolean isInStartPosition()//‘刚刚’离开开始点public boolean hasJustLeftStartPosition() //‘刚刚’回到开始点public boolean hasJustBackToStartPosition()//超过设定的刷新高度public boolean hasMovedAfterPressedDown()public boolean goDownCrossFinishPosition()public boolean crossRefreshLineFromTopToBottom()public boolean hasJustReachedHeaderHeightFromTopToBottom()

可选

public void setOffsetToKeepHeaderWhileLoading(int offset) {    mOffsetToKeepHeaderWhileLoading = offset;}public int getOffsetToKeepHeaderWhileLoading() {    return mOffsetToKeepHeaderWhileLoading >= 0 ? mOffsetToKeepHeaderWhileLoading : mHeaderHeight;}public boolean isOverOffsetToKeepHeaderWhileLoading() {    return mCurrentPos > getOffsetToKeepHeaderWhileLoading();}
1 0
原创粉丝点击