# 下拉刷新与自动加载

下拉刷新是Android App开发中非常常用的功能, 网上也有很多开源的下拉刷新控件. CoreLibs中原先使用的handmark pulltorefresh, 现在选用的则是 [Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh). 新的PTR框架有如下优点:

* 轻量
* 理论上支持所有的View
* 易扩展
* 易自定义
* 性能不错

具体的使用方法请参考上面的链接, 这里就不再赘述了. 但是呢, Ultra ptr也不是没有缺点, 比如库中提供了Lollipop风格的下拉头部, 如果想要在每一个下拉控件中使用还需要加入不少代码. 然后每次使用下拉组件的时候需要一些相似的配置代码. 最主要的是Ultra ptr只支持下拉, 而不支持加载更多. 因此我们需要扩展一下这个库, 目标有两个:

1. 默认头部变为Lollipop风格, 去掉重复代码, 使用更简洁
2. 加入自动加载更多 - auto load more.

ptr的扩展类均位于com.corelibs.views.ptr下. 以下是包结构:

```
| ptr
  | layout 扩展的布局
    -PtrAutoLoadMoreLayout //自动加载更多布局
    -PtrLollipopLayout //Lolipop头部风格布局
  | loadmore
    | adapter
      -GridViewAdapter //GridView系列适配器
      -ListViewAdapter //ListView系列适配器
      -LoadMoreAdapter //自动加载更多的适配类
      -RecyclerViewAdapter //RecyclerView系列适配器
    | widget
      -AutoLoadMoreGridView //自动加载更多的GridView
      -AutoLoadMoreListView //自动加载更多的ListView
      -AutoLoadMoreSwipeMenuListView //自动加载更多的带侧滑菜单的ListView
      -AutoLoadMoreRecyclerView //自动加载更多的RecyclerView
    -AutoLoadMoreHandler //自动加载更多的真正处理类
    -AutoLoadMoreHook //PtrAutoLoadMoreLayout的child需实现此类以供PtrAutoLoadMoreLayout获取AutoLoadMoreHandler
    -OnScrollListener //兼容AdapterView与RecyclerView的OnScrollListener
```

## layout.PtrLollipopLayout

我们首要目标是去掉重复的配置代码, 使用起来更简洁. 首先看看一个简单的例子:

```
<com.corelibs.views.ptr.layout.PtrLollipopLayout
    android:id="@+id/ptrLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="30sp"
        android:text="拉我拉我"/>

</com.corelibs.views.ptr.layout.PtrLollipopLayout>
```

Activity代码:

```
@Bind(R.id.ptrLayout) PtrLollipopLayout<TextView> ptrLayout;

ptrLayout.setRefreshCallback(new PtrLollipopLayout.RefreshCallback() {
    @Override public void onRefreshing(PtrFrameLayout frame) {
        ptrLayout.getPtrView().setText("我被刷了");
        ptrLayout.complete();
    }
});
```

效果如下:

![图1](/files/-LsLm8IrJ2ReAmtmL1kR)

用法非常简单, 只需使用PtrLollipopLayout包裹任意你想要刷新的控件即可. 在代码中, 如果在声明PtrLollipopLayout时加上泛型, 如`PtrLollipopLayout<TextView>`, 就可以使用`ptrLayout.getPtrView()`将PtrLollipopLayout内的TextView取出. 如果不加泛型, 则需要单独为TextView设置id, 并使用ButterKnife bind出来. 两种方式均可.

PtrLollipopLayout内部默认使用了Lollipop风格的下拉头部, 并且做了一些配置工作. 我们同样可以在代码中为PtrLollipopLayout做一些个性化的配置, 如通过setHeaderView(View header)设置自己的头部, 请注意, 自定义的头部必须实现PtrUIHandler接口. 如果出现PtrLollipopLayout解决不了的滑动冲突, 可以调用`setPtrHandler(PtrHandler ptrHandler)`自行处理滑动.

以下是几个需要注意的点:

1. 此控件只能包含一个子View.
2. 此控件仅支持下拉刷新, 如果需要自动加载, 请使用PtrAutoLoadMoreLayout
3. 如果出现横向滑动冲突, 请设置disableWhenHorizontalMove(boolean)为true.
4. 如果不想为child设置id并使用findViewById取出, 可以在声明PtrLollipopLayout的时候带上child类型的泛型, 然后就可以使用getPtrView()取出child. 如PtrLollipopLayout\<ScrollView>.
5. 刷新完成或加载完成后请调用complete().

## layout.PtrAutoLoadMoreLayout

接下来是第二个目标 - 自动加载更多. 先看栗子:

```
<com.corelibs.views.ptr.layout.PtrAutoLoadMoreLayout
    android:id="@+id/ptrLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.corelibs.views.ptr.loadmore.widget.AutoLoadMoreListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:listSelector="#00000000"
        android:divider="#aaa"
        android:dividerHeight="1dp"/>

</com.corelibs.views.ptr.layout.PtrAutoLoadMoreLayout>
```

ListView item布局:

```
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#333"
    android:gravity="center">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center"
        android:textColor="#fff"
        android:textSize="14sp"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"/>

</LinearLayout>
```

Activity代码:

```
@Bind(R.id.ptrLayout) PtrAutoLoadMoreLayout<AutoLoadMoreListView> ptrLayout;

private Handler handler = new Handler(); // 使用handler模拟网络请求
private int count = 0; // 模拟页数计数

protected void init(Bundle savedInstanceState) {
    // 简单的适配器
    final QuickAdapter<String> adapter = new QuickAdapter<String>(this, R.layout.item_test) {
        @Override protected void convert(BaseAdapterHelper helper, String item) {
            helper.setText(R.id.text, item);
        }
    };

    ptrLayout.setLoadingBackgroundColor(0xff333333); // 设置自动加载视图的背景颜色
    ptrLayout.getPtrView().setAdapter(adapter); // 为AutoLoadMoreListView设置Adapter

    adapter.addAll(getData()); // 为adapter添加数据

    // 设置刷新和加载回调
    ptrLayout.setRefreshLoadCallback(new PtrAutoLoadMoreLayout.RefreshLoadCallback() {
        @Override public void onRefreshing(PtrFrameLayout frame) {
            adapter.replaceAll(getData()); // 替换adapter中的数据

            ptrLayout.enableLoading(); // 重新启用自动加载
            ptrLayout.complete(); // 刷新完成
            count = 0; // 重置模拟计数
        }

        @Override public void onLoading(PtrFrameLayout frame) {
            count++; // 模拟页数++
            handler.postDelayed(new Runnable() { // 模拟网络加载延迟
                @Override public void run() {
                    adapter.addAll(getData()); // 将数据加入adapter中.
                    ptrLayout.complete(); // 加载完成

                    if (count > 2)
                        ptrLayout.disableLoading(); // 禁用自动加载
                }
           }, 1500);
        }
    });
}

// 模拟数据
private List<String> getData() {
    List<String> data = new ArrayList<>();
    for (int i = 0; i < 25; i++)
        data.add("呵呵呵呵" + (i + 1));

    return data;
}
```

效果:

![图2](/files/-LsLm8IujFSQpeRIbFca)

使用带自动加载的下拉刷新就要比单纯的下拉刷新复杂的多. 这种时候就不能使用PtrLollipopLayout而需要使用PtrAutoLoadMoreLayout. 一般情况下, 自动加载更多只会出现在有ListView/GridView的情况下. 因此PtrAutoLoadMoreLayout的子视图基本都是ListView/GridView, 或他们的派生类.

但是如果直接使用PtrAutoLoadMoreLayout加上ListView/GridView, 也是无法实现自动加载的, 如:

```
<com.corelibs.views.ptr.layout.PtrAutoLoadMoreLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</com.corelibs.views.ptr.layout.PtrAutoLoadMoreLayout>
```

不仅没有效果, 还会报如下错误:

```
java.lang.IllegalStateException: PtrAutoLoadMoreLayout child should implement AutoLoadMoreHook
```

这是因为PtrAutoLoadMoreLayout只是一个外壳, 本身只带有下拉刷新的功能, 不带有自动加载的功能. PtrAutoLoadMoreLayout所有有关自动加载的api全部是代理至另外一个类 - AutoLoadMoreHandler. AutoLoadMoreHandler才是真正处理自动加载功能的类. PtrAutoLoadMoreLayout需要借助AutoLoadMoreHook来获取AutoLoadMoreHandler, 因此PtrAutoLoadMoreLayout的子控件必须实现AutoLoadMoreHook. AutoLoadMoreHook的定义:

```
/**
 * {@link com.corelibs.views.ptr.layout.PtrAutoLoadMoreLayout}的child view需要实现此接口,
 * 供PtrAutoLoadMoreLayout获取{@link AutoLoadMoreHandler}.
 */
public interface AutoLoadMoreHook {
    /**
     * {@link com.corelibs.views.ptr.layout.PtrAutoLoadMoreLayout}需通过此方法获取
     * {@link AutoLoadMoreHandler}对象.
     */
    AutoLoadMoreHandler getLoadMoreHandler();
}
```

现在PtrAutoLoadMoreLayout就可以通过getLoadMoreHandler来获取AutoLoadMoreHandler实现自动加载更多的功能了. 那么, 例子中的AutoLoadMoreListView又是什么鬼? AutoLoadMoreListView是CoreLibs中预定义好的一个控件, 它实现了AutoLoadMoreHook. 如果我们需要一个带自动加载的ListView, 就可以使用AutoLoadMoreListView. AutoLoadMoreListView全部代码:

```
public class AutoLoadMoreListView extends ListView implements AutoLoadMoreHook {

    public AutoLoadMoreListView(Context context) {
        super(context);
    }

    public AutoLoadMoreListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AutoLoadMoreListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public AutoLoadMoreHandler getLoadMoreHandler() {
        return new AutoLoadMoreHandler<>(getContext(), new ListViewAdapter<ListView>(this));
    }
}
```

AutoLoadMoreListView的代码非常简单, 除了三个继承自ListView需要实现的构造函数外, 就只有一个实现了AutoLoadMoreHook的getLoadMoreHandler方法. 该方法中也只是new了一个AutoLoadMoreHandler对象并返回而已.

如果我们有一个自定义的ListView, 实现了侧滑菜单, 名字叫SwipeMenuListView, 我们想要为SwipeMenuListView加上下拉和自动加载怎么办? 很简单, 定义一个新的继承自SwipeMenuListView, 并且实现了AutoLoadMoreHook的控件, 然后我们在PtrAutoLoadMoreLayout中包含该控件即可. 使用方法除了SwipeMenuListView自己的API外, 其他与默认的ListView完全一样. 代码如下:

```
public class AutoLoadMoreSwipeMenuListView extends SwipeMenuListView implements AutoLoadMoreHook {

    public AutoLoadMoreSwipeMenuListView(Context context) {
        super(context);
    }

    public AutoLoadMoreSwipeMenuListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AutoLoadMoreSwipeMenuListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public AutoLoadMoreHandler getLoadMoreHandler() {
        return new AutoLoadMoreHandler<>(getContext(), new ListViewAdapter<SwipeMenuListView>(this));
    }
}
```

接着看看AutoLoadMoreHandler的定义与构造函数:

```
public class AutoLoadMoreHandler<T extends LoadMoreAdapter> {
    public AutoLoadMoreHandler(Context context, T wrapper);
}
```

AutoLoadMoreHandler的构造函数需要两个参数, 第一个context不用多说, 第二个LoadMoreAdapter又是何方神圣? LoadMoreAdapter是一个接口, 类似于适配器, 因此取名叫做Adapter:

```
/**
 * 针对ListView/GridView等View的适配接口, 用于带自动加载更多的视图
 * <BR/>
 * Created by Ryan on 2016/1/21.
 */
public interface LoadMoreAdapter<T extends ViewGroup> {
    /**
     * 添加FooterView的适配
     */
    void addFooterView(View v, Object data, boolean isSelectable);
    /**
     * 删除FooterView的适配
     */
    boolean removeFooterView(View v);
    /**
     * 设置OnScrollListener的适配
     */
    void setOnScrollListener(OnScrollListener<T> l);
    /**
     * 获取总行数的适配
     */
    int getRowCount();
    /**
     * 获取被包装的View
     */
    T getView();
}
```

可以看到LoadMoreAdapter的方法基本都是对ListView/GridView的一些方法的包装. LoadMoreAdapter的存在就是为了适配各种ListView/GridView. AutoLoadMoreHandler内部会在需要添加底部视图的时候去调用LoadMoreAdapter的addFooterView, 在需要计算何时要显示底部视图的时候调用getLastVisiblePosition, getRowCount等. 至于具体实现, 则交由各种实现类去处理. CoreLibs中目前定义好了两个实现类 - ListViewAdapter和GridViewAdapter. 两个类的代码差不多, 这里只贴ListViewAdapter的:

```
/**
 * 针对ListView或继承自ListView的控件的适配类
 * <BR/>
 * Created by Ryan on 2016/1/21.
 */
public class ListViewAdapter<T extends ListView> implements LoadMoreAdapter<T> {

    private T listView;

    public ListViewAdapter(T listView) {
        this.listView = listView;
    }

    @Override
    public void addFooterView(View v, Object data, boolean isSelectable) {
        listView.addFooterView(v, data, isSelectable);
    }

    @Override
    public boolean removeFooterView(View v) {
        return listView.removeFooterView(v);
    }

    @Override
    public void setOnScrollListener(final OnScrollListener<T> l) {
        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override public void onScrollStateChanged(AbsListView view, int scrollState) {}

            @Override public void onScroll(AbsListView view, int firstVisibleItem,
                                           int visibleItemCount, int totalItemCount) {
                if (l != null)
                    l.onScroll(listView, firstVisibleItem, visibleItemCount, totalItemCount);
            }
        });
    }

    @Override
    public int getRowCount() {
        return listView.getCount();
    }

    @Override
    public T getView() {
        return listView;
    }
}
```

代码逻辑比较简单, 基本都是直接调用listView的方法而没有很多逻辑判断. 可以看到, AutoLoadMoreSwipeMenuListView中的getLoadMoreHandler的返回语句`new AutoLoadMoreHandler<>(getContext(), new ListViewAdapter<SwipeMenuListView>(this))`也是使用的ListViewAdapter, 只不过泛型传递的是SwipeMenuListView. 这意味着, 只要是继承自ListView的控件都可以使用ListViewAdapter. GridView同理. 如果以后有了其他类型的控件可以创建一个新的LoadMoreAdapter的实现类.

接下来看看RecylerView的Adapter, 相较于ListView/GridView的会复杂一下:

```
/**
 * 针对RecyclerView或继承自RecyclerView的控件的适配类
 * <BR/>
 * Created by Ryan on 2016/9/21.
 */
public class RecyclerViewAdapter<T extends RecyclerView>
        implements LoadMoreAdapter<T> {

    private T recyclerView;

    public RecyclerViewAdapter(T recyclerView) {
        this.recyclerView = recyclerView;
    }

    @Override
    public void addFooterView(View v, Object data, boolean isSelectable) {
        AbstractHeaderAndFooterWrapper adapter =
                (AbstractHeaderAndFooterWrapper) recyclerView.getAdapter();
        adapter.addFootView(v);
    }

    @Override
    public boolean removeFooterView(View v) {
        AbstractHeaderAndFooterWrapper adapter =
                (AbstractHeaderAndFooterWrapper) recyclerView.getAdapter();
        adapter.removeFootView(v);
        return true;
    }

    @Override @SuppressWarnings("unchecked")
    public void setOnScrollListener(final OnScrollListener<T> l) {
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                int firstVisibleItem = 0, lastVisibleItem, visibleItemCount = 0;
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

                if (layoutManager instanceof LinearLayoutManager) {
                    LinearLayoutManager manager = (LinearLayoutManager) layoutManager;
                    firstVisibleItem = manager.findFirstVisibleItemPosition();
                    lastVisibleItem = manager.findLastVisibleItemPosition();
                    visibleItemCount = lastVisibleItem - firstVisibleItem + 1;
                }

                if (layoutManager instanceof StaggeredGridLayoutManager) {
                    StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) layoutManager;
                    firstVisibleItem = manager.findFirstVisibleItemPositions(null)[0];
                    lastVisibleItem = manager.findLastVisibleItemPositions(null)[1];
                    visibleItemCount = lastVisibleItem - firstVisibleItem + 1;
                }

                if (l != null)
                    l.onScroll((T) recyclerView, firstVisibleItem, visibleItemCount, recyclerView.getAdapter().getItemCount());

            }
        });
    }

    @Override
    public int getRowCount() {
        return recyclerView.getAdapter().getItemCount();
    }

    @Override
    public T getView() {
        return recyclerView;
    }
}
```

由于RecyclerView与传统的AdapterView的OnScrollListener不一样, 所以在`setOnScrollListener`代码中做了一些额外的适配工作. 如果需要使用自定义的LayoutManager, 建议继承自系统默认的三个 - LinearLayoutManager, GridLayoutManager以及StaggeredGridLayoutManager, 因为RecyclerViewAdapter中只适配了这几个的LayoutManager. 或者可以新建一个Adapter专门对自定义的LayoutManager做处理.

接着, 我们来看看具体实现自动加载更多功能的AutoLoadMoreHandler的关键代码:

```
public class AutoLoadMoreHandler<T extends LoadMoreAdapter> {
    public static final int DEFAULT_WHEN_TO_LOADING = 1;

    private T adapter;
    private int whenToLoading = DEFAULT_WHEN_TO_LOADING; // 当滚动到倒数第几个条目时触发加载

    private PtrAutoLoadMoreLayout.RefreshLoadCallback callback;  //刷新与加载的回调
    private PtrFrameLayout ptrFrameLayout;

    public AutoLoadMoreHandler(Context context, T adapter) {
        this.context = context;
        this.adapter = adapter;
    }

    public void setup(PtrFrameLayout ptrFrameLayout) {
        this.ptrFrameLayout = ptrFrameLayout;
        init();
    }

    public void setRefreshLoadCallback(PtrAutoLoadMoreLayout.RefreshLoadCallback callback) {
        this.callback = callback;
    }

    private void init() {
        // 通过OnScrollListener来监听滑动, 判断何时触发加载操作
        adapter.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {}

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                // 当前如果是DISABLED, REFRESHING以及FORCE_REFRESH状态, 或者处于下拉刷新的状态时,不再触发加载
                if (state != State.DISABLED && state != State.REFRESHING
                        && state != State.FORCE_REFRESH && !ptrFrameLayout.isRefreshing()) {
                    // 如果条目没有超过一个屏幕, 也不触发
                    if (visibleItemCount < totalItemCount) {
                        if (view.getCount() > 0)
                            if (aboutToLoad())
                                setLoadingStatus();
                    }
                }
            }
        });
    }

    private boolean aboutToLoad() {
        for (int i = 0; i < whenToLoading; i++) {
            // 如果当前最后可见的条目等于总条目-whenToLoading-i, 则触发加载
            if (adapter.getLastVisiblePosition() == (adapter.getRowCount() - whenToLoading - i))
                return true;
        }
        return false;
    }

    private void setLoadingStatus() {
        setLoadingState(State.REFRESHING);
    }

    private void setLoadingState(State state) {
        // 如果当前处于DISABLED, 则状态不能被设置成FINISHED或者REFRESHING, 也就是说无法触发加载
        if (this.state == State.DISABLED && (state == State.FINISHED || state == State.REFRESHING))
            return;
        this.state = state;
        load();
    }

    private void load() {
        switch (state) {
            case ENABLED:
                break;

            case REFRESHING:
            case FORCE_REFRESH:
                showLoadingView();
                break;

            case DISABLED:
            case FINISHED:
                hideLoadingView();
                break;
        }
    }

    private synchronized void showLoadingView() {
        if (!isLoading) {
            loadingContent.setVisibility(View.VISIBLE);
            progress.startAnimation();
            isLoading = true;
            if (callback != null)
                callback.onLoading(ptrFrameLayout);
        }
    }

    private synchronized void hideLoadingView() {
        if (isLoading) {
            loadingContent.setVisibility(View.GONE);
            progress.stopAnimation();
            isLoading = false;
        }
    }

    public enum State {
        /** 刷新结束 **/
        FINISHED,
        /** 刷新中 **/
        REFRESHING,
        /** 失效 **/
        DISABLED,
        /** 生效, 默认状态 **/
        ENABLED,
        /** 强制刷新 **/
        FORCE_REFRESH
    }
}
```

上述代码逻辑也不是很复杂, 相信配合注释不难理解. 需要注意的是, 在构造函数中, AutoLoadMoreHandler并没有做一些初始化操作, 仅仅是赋值. 初始化操作init是在setup方法中被调用的. 那么setup何时会被调用? 这需要看看PtrAutoLoadMoreLayout的部分代码:

```
public class PtrAutoLoadMoreLayout<T> extends PtrLollipopLayout<T> {
    private AutoLoadMoreHandler loadMoreHandler;

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        loadMoreHandler = setupHook().getLoadMoreHandler();

        if (loadMoreHandler == null)
            throw new IllegalStateException("AutoLoadMoreHandler should not be null");

        loadMoreHandler.setup(this);
    }

    private AutoLoadMoreHook setupHook() {
        if (mContent != null && mContent instanceof AutoLoadMoreHook) {
            return (AutoLoadMoreHook) mContent;
        } else {
            throw new IllegalStateException("PtrAutoLoadMoreLayout child should implement AutoLoadMoreHook");
        }
    }
}
```

可以看到, 在PtrAutoLoadMoreLayout被从xml解析完成之后, 就会去调用setupHook()获取AutoLoadMoreHook, 并通过AutoLoadMoreHook的getLoadMoreHandler来获取AutoLoadMoreHandler对象. 然后调用setup. setupHook方法中的mContent就是被PtrAutoLoadMoreLayout包裹的子控件. 到此, 整个自动加载更多就解释的差不多了.

以下是使用PtrAutoLoadMoreLayout需要注意的几个地方:

1. 如果只需下拉刷新功能, 请使用PtrLollipopLayout
2. 此控件中的child view必须实现AutoLoadMoreHook
3. 此控件是对AutoLoadMoreHandler功能的转发. AutoLoadMoreHook的getLoadMoreHandler()需要的就是AutoLoadMoreHandler.&#x20;
4. 刷新完成或加载完成后请调用complete(), 而不是refreshComplete()或loadingFinished().

## 总结

下面总结一下PtrLollipopLayout与PtrAutoLoadMoreLayout在使用上的相同点与区别:

1. 只有下拉刷新功能时应该使用PtrLollipopLayout, 带有自动加载更多时应该使用PtrAutoLoadMoreLayout.
2. 两者都应该使用complete()方法来结束刷新或者加载状态.
3. PtrLollipopLayout使用setRefreshCallback(RefreshCallback callback)来设置回调.
4. PtrAutoLoadMoreLayout使用setRefreshLoadCallback(RefreshLoadCallback callback)来设置回调.
5. RefreshLoadCallback继承自RefreshCallback, 比RefreshCallback多了onLoading方法.
6. RefreshCallback定义在PtrLollipopLayout内, RefreshLoadCallback定义在PtrAutoLoadMoreLayout内.
7. PtrLollipopLayout与PtrAutoLoadMoreLayout都只能包含一个子视图.
8. PtrLollipopLayout子视图可以是任意View, 但是PtrAutoLoadMoreLayout的子视图必须实现AutoLoadMoreHook接口.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryan-8.gitbook.io/android-architecture-journey/widgets/refresh_and_load.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
