下拉刷新、上拉加载更多

记录一下,以防忘记

完整代码

public class PullLayout extends ViewGroup {

    private View mHeaderView;//头部布局
    private View mFooterView;//尾部布局
    private int mHeaderHeight;//头布局高度
    private int mFooterHeight;//尾部局高度

    private Status mStatus = Status.NORMAL;
    private int mlastMoveY;//最后点击位置
    private int mLastYIntercept;
    private TextView mHeaderText;
    private ProgressBar mHeaderProgressBar;
    private ImageView mHeaderArrow;
    private TextView mFooterText;
    private ProgressBar mFooterProgressBar;
    private int mLayoutContentHeight;

    public PullLayout(Context context) {
        this(context, null);
    }

    public PullLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PullLayout);
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        addHeader();
        View childAt = getChildAt(1);
//        if (childAt instanceof AdapterView) {
//        } else if (childAt instanceof ScrollView) {
//        } else {
//            throw new ClassCastException("PullLayout 布局内只允许放置一个布局  AdapterView   ScrollView");
//        }
        addFooter();
    }

    private void addHeader() {
        mHeaderView = LayoutInflater.from(getContext()).inflate(R.layout.pull_header, null, false);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        addView(mHeaderView,0, params);

        mHeaderText = (TextView) findViewById(R.id.header_text);
        mHeaderProgressBar = (ProgressBar) findViewById(R.id.header_progressbar);
        mHeaderArrow = (ImageView) findViewById(R.id.header_arrow);
    }

    private void addFooter() {
       mFooterView = LayoutInflater.from(getContext()).inflate(R.layout.pull_footer, null, false);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        addView(mFooterView, params);
        mFooterText = (TextView) findViewById(R.id.footer_text);
        mFooterProgressBar = (ProgressBar) findViewById(R.id.footer_progressbar);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mLayoutContentHeight = 0;

        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child == mHeaderView) {
                child.layout(0, 0 - child.getMeasuredHeight(), child.getMeasuredWidth(), 0);
                mHeaderHeight = child.getHeight();
            } else if (child == mFooterView) {
                child.layout(0, mLayoutContentHeight, child.getMeasuredWidth(), mLayoutContentHeight + child.getMeasuredHeight());
                mFooterHeight = child.getHeight();
            } else {
                child.layout(0, mLayoutContentHeight, child.getMeasuredWidth(), mLayoutContentHeight + child.getMeasuredHeight());
                if (i < getChildCount()) {
                    if (child instanceof ScrollView) {
                        mLayoutContentHeight += getMeasuredHeight();
                        continue;
                    }
                    mLayoutContentHeight += child.getMeasuredHeight();
                }
            }
        }
    }


    /**
     * ACTION_DOWN 不需要拦截
     * ACTION_UP 不需要拦截
     * <p>
     * 当事件为 ACTION_MOVE 时,
     * 如果是向下滑动,判断第一个child是否滑倒最上面,如果是,则更新状态为 TRY_REFRESH;
     * 如果是向上滑动,则判断最后一个child是否滑动最底部,如果是,则更新状态为TRY_LOADMORE。然后返回 intercept = true
     * <p>
     * 其他情况不进行拦截
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false;
        int y = (int) ev.getY();

        //当状态为刷新或者加载中的时候不进行拦截
        if (mStatus == Status.REFRESHING || mStatus == Status.LOADING) {
            return false;
        }

        switch (ev.getAction()) {
            //ACTION_DOWN 不进行拦截
            case MotionEvent.ACTION_DOWN: {
                // 拦截时需要记录点击位置,不然下一次滑动会出错
                mlastMoveY = y;
                intercept = false;
                break;
            }
            case MotionEvent.ACTION_MOVE: {


                if (y > mLastYIntercept) {//向下滑动
                    View child = getChildAt(1);
                    intercept = getRefreshIntercept(child);

                    if (intercept) {
                        updateStatus(mStatus.TRY_REFRESH);
                    }
                } else if (y < mLastYIntercept) { //向上滑动
                    View child = getChildAt(1);
                    intercept = getLoadMoreIntercept(child);

                    if (intercept) {
                        updateStatus(mStatus.TRY_LOADMORE);
                    }
                } else {
                    intercept = false;
                }
                break;
            }
            //ACTION_UP 不进行拦截
            case MotionEvent.ACTION_UP: {
                intercept = false;
                break;
            }
        }

        mLastYIntercept = y;
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y = (int) event.getY();
        if (mStatus == Status.REFRESHING || mStatus == Status.LOADING) {
            return true;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mlastMoveY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int dy = mlastMoveY - y;
                // 一直在下拉
                if (getScrollY() <= 0 && dy <= 0) {
                    if (mStatus == Status.TRY_LOADMORE) {
                        scrollBy(0, dy / 30);
                    } else {
                        scrollBy(0, dy / 3);
                    }
                } else if (getScrollY() >= 0 && dy >= 0) {// 一直在上拉
                    if (mStatus == Status.TRY_REFRESH) {
                        scrollBy(0, dy / 30);
                    } else {
                        scrollBy(0, dy / 3);
                    }
                } else {
                    scrollBy(0, dy / 3);
                }

                beforeRefreshing(dy);
                beforeLoadMore();

                break;
            case MotionEvent.ACTION_UP:
                if (getScrollY() <= -mHeaderHeight) { // 下拉刷新,并且到达有效长度
                    releaseWithStatusRefresh();
                    if (onFinishedListener != null) {
                        onFinishedListener.refresh();
                    }
                } else if (getScrollY() >= mFooterHeight) { // 上拉加载更多,达到有效长度
                    releaseWithStatusLoadMore();
                    if (onFinishedListener != null) {
                        onFinishedListener.loadMore();
                    }
                } else {
                    releaseWithStatusTryRefresh();
                    releaseWithStatusTryLoadMore();
                }
                break;
        }

        mlastMoveY = y;
        return super.onTouchEvent(event);
    }

    private OnFinishedListener onFinishedListener;

    public void setOnFinishedListener(OnFinishedListener onFinishedListener) {
        this.onFinishedListener = onFinishedListener;
    }

    interface OnFinishedListener {
        void refresh();

        void loadMore();
    }

    /**
     * 更新 滑动状态
     *
     * @param status
     */
    private void updateStatus(Status status) {
        mStatus = status;
    }

    /**
     * 滑动状态
     */
    enum Status {
        TRY_REFRESH,//刷新状态
        TRY_LOADMORE,//加载更多状态
        REFRESHING,//刷新中
        LOADING,//加载中

        NORMAL  //正常状态
    }

    /判断是否进行拦截

    /**
     * 判断
     * 刷新 是否进行拦截
     *
     * @param child
     * @return
     */
    private boolean getRefreshIntercept(View child) {
        boolean intercept = false;

        if (child instanceof AdapterView) {
            intercept = adapterViewRefreshIntercept(child);
        } else if (child instanceof ScrollView) {
            intercept = scrollViewRefreshIntercept(child);
        } else if (child instanceof RecyclerView) {
            intercept = recyclerViewRefreshIntercept(child);
        }
        return intercept;
    }

    /**
     * 判断
     * 加载更多 是否进行拦截
     *
     * @param child
     * @return
     */
    private boolean getLoadMoreIntercept(View child) {
        boolean intercept = false;

        if (child instanceof AdapterView) {
            intercept = adapterViewLoadMoreIntercept(child);
        } else if (child instanceof ScrollView) {
            intercept = scrollViewLoadMoreIntercept(child);
        } else if (child instanceof RecyclerView) {
            intercept = recyclerViewLoadMoreIntercept(child);
        }
        return intercept;
    }


    /**
     * 判断
     * AdapterView 下拉刷新是否进行拦截
     * <p>
     * 当屏幕中AdapterView 的第一个可见Item 是首Item,且 首Item的上端为 0   进行拦截
     *
     * @param child
     * @return
     */
    private boolean adapterViewRefreshIntercept(View child) {
        boolean intercept = true;
        AdapterView adapterChild = (AdapterView) child;
        if (adapterChild.getFirstVisiblePosition() != 0
                || adapterChild.getChildAt(0).getTop() != 0) {

            intercept = false;
        }
        return intercept;
    }

    /**
     * 判断
     * AdapterView 加载更多  是否进行拦截
     * <p>
     * 当屏幕中AdapterView 最后一个可见item 是末尾Item
     * 且末尾Item距离屏幕底部 不小于控件测量高度   进行拦截
     *
     * @param child
     * @return
     */
    private boolean adapterViewLoadMoreIntercept(View child) {
        boolean intercept = false;
        AdapterView adapterChild = (AdapterView) child;
        if (adapterChild.getLastVisiblePosition() == adapterChild.getCount() - 1 &&
                (adapterChild.getChildAt(adapterChild.getChildCount() - 1).getBottom() >= getMeasuredHeight())) {
            intercept = true;
        }
        return intercept;
    }

    /**
     * 判断
     * ScrollView 刷新是否拦截
     * <p>
     * 当ScrollView 的左上角相对于 本控件左上角在Y轴的偏移量 <= 0 时 进行拦截
     *
     * @param child
     * @return
     */
    private boolean scrollViewRefreshIntercept(View child) {
        boolean intercept = false;
        if (child.getScrollY() <= 0) {
            intercept = true;
        }
        return intercept;
    }

    /**
     * 判断
     * ScrollView加载更多是否拦截
     * <p>
     * 当ScrollView 的左上角相对于 本控件左上角在Y轴的偏移量
     * >=
     * ScrollView 的第一个子控件的高度与ScrollView的高度差
     * 时,进行拦截
     *
     * @param child
     * @return
     */
    private boolean scrollViewLoadMoreIntercept(View child) {
        boolean intercept = false;
        ScrollView scrollView = (ScrollView) child;
        View scrollChild = scrollView.getChildAt(0);

        if (scrollView.getScrollY() >= (scrollChild.getHeight() - scrollView.getHeight())) {
            intercept = true;
        }
        return intercept;
    }

    /**
     * 判断
     * RecyclerView刷新是否拦截
     * <p>
     * 当RecyclerView 当前滑过的距离 <= 0时  进行拦截
     * <p>
     * computeVerticalScrollExtent()是当前屏幕显示的区域高度
     * computeVerticalScrollOffset() 是当前屏幕之前滑过的距离
     * computeVerticalScrollRange()是整个View控件的高度
     *
     * @param child
     * @return
     */
    private boolean recyclerViewRefreshIntercept(View child) {
        boolean intercept = false;

        RecyclerView recyclerView = (RecyclerView) child;
        if (recyclerView.computeVerticalScrollOffset() <= 0) {
            intercept = true;
        }
        return intercept;
    }

    /**
     * 判断
     * RecyclerView加载更多是否拦截
     * <p>
     * 当RecyclerView 当前显示的高度 与 当前滑过的距离之和
     * >=
     * 整个RecyclerView的高度 时 进行拦截
     *
     * @param child
     * @return
     */
    private boolean recyclerViewLoadMoreIntercept(View child) {
        boolean intercept = false;

        RecyclerView recyclerView = (RecyclerView) child;
        if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset()
                >= recyclerView.computeVerticalScrollRange()) {
            intercept = true;
        }

        return intercept;
    }
    /判断是否进行拦截


    /修改头 尾布局的状态

    /**
     * 滑动时刷新
     *
     * @param dy
     */
    public void beforeRefreshing(float dy) {
        //计算旋转角度
        int scrollY = Math.abs(getScrollY());
        scrollY = scrollY > mHeaderHeight ? mHeaderHeight : scrollY;
        float angle = (float) (scrollY * 1.0 / mHeaderHeight * 180);
        mHeaderArrow.setRotation(angle);


        if (getScrollY() <= -mHeaderHeight) {
            mHeaderText.setText("松开刷新");
        } else {
            mHeaderText.setText("下拉刷新");
        }
    }

    /**
     * 滑动加载更多
     */
    public void beforeLoadMore() {
        if (getScrollY() >= mHeaderHeight) {
            mFooterText.setText("松开加载更多");
        } else {
            mFooterText.setText("上拉加载更多");
        }
    }

    /**
     * 刷新结束
     */
    public void refreshFinished() {
        scrollTo(0, 0);
        mHeaderText.setText("下拉刷新");
        mHeaderProgressBar.setVisibility(GONE);
        mHeaderArrow.setVisibility(VISIBLE);
        updateStatus(Status.NORMAL);
    }

    /**
     * 加载更多结束
     */
    public void loadMoreFinished() {
        mFooterText.setText("上拉加载");
        mFooterProgressBar.setVisibility(GONE);
        scrollTo(0, 0);
        updateStatus(Status.NORMAL);
    }

    /**
     * 下拉刷新  未达到有效长度
     */
    private void releaseWithStatusTryRefresh() {
        scrollBy(0, -getScrollY());
        mHeaderText.setText("下拉刷新");
        updateStatus(Status.NORMAL);
    }

    /**
     * 上拉加载更多
     */
    private void releaseWithStatusTryLoadMore() {
        scrollBy(0, -getScrollY());
        mFooterText.setText("上拉加载更多");
        updateStatus(Status.NORMAL);
    }

    /**
     * 下拉刷新 达到有效长度
     */
    private void releaseWithStatusRefresh() {
        scrollTo(0, -mHeaderHeight);
        mHeaderProgressBar.setVisibility(VISIBLE);
        mHeaderArrow.setVisibility(GONE);
        mHeaderText.setText("正在刷新");
        updateStatus(Status.REFRESHING);
    }

    /**
     * 上拉加载更多  达到有效长度
     */
    private void releaseWithStatusLoadMore() {
        scrollTo(0, mFooterHeight);
        mFooterText.setText("正在加载");
        mFooterProgressBar.setVisibility(VISIBLE);
        updateStatus(Status.LOADING);
    }
    /修改头 尾布局的状态

}

pull_header.xml
这里写图片描述

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp">

    <TextView
        android:id="@+id/header_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="下拉刷新"
        android:textSize="16sp" />

    <ProgressBar
        android:id="@+id/header_progressbar"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_toLeftOf="@+id/header_text"
        android:visibility="gone" />

    <ImageView
        android:id="@+id/header_arrow"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_toLeftOf="@+id/header_text"
        android:layout_toStartOf="@+id/header_text"
        android:src="@drawable/ic_arrows" />

</RelativeLayout>

pull_footer.xml
这里写图片描述

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:padding="10dp">

  <TextView
    android:id="@+id/footer_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:text="上拉加载"/>

  <ProgressBar
    android:id="@+id/footer_progressbar"
    android:layout_width="30dp"
    android:layout_height="30dp"
    android:layout_toLeftOf="@+id/footer_text"
    android:visibility="gone"/>

</RelativeLayout>

使用

布局xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <android.biginner.com.refretest.PullLayout
        android:id="@+id/refre"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@+id/list_item"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></ListView>
    </android.biginner.com.refretest.PullLayout>

</LinearLayout>

Activity

public class MainActivity extends AppCompatActivity implements PullLayout.OnFinishedListener{

    private ListView listView;
    private PullLayout pullLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_main);
        pullLayout = (PullLayout) findViewById(R.id.refre);
        pullLayout.setOnFinishedListener(this);


        ArrayList<String> strings = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            strings.add("测试数据"+(i+1));
        }

        listView = (ListView) findViewById(R.id.list_item);

        Adapter adapter = new Adapter(this);
        adapter.setData(strings);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(MainActivity.this, "点击了"+position, Toast.LENGTH_SHORT).show();
            }
        });


    }

    @Override
    public void refresh() {
        pullLayout.refreshFinished();
        Toast.makeText(this, "刷新完毕", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loadMore() {
        pullLayout.loadMoreFinished();
        Toast.makeText(this, "加载完毕", Toast.LENGTH_SHORT).show();
    }

    class Adapter extends BaseAdapter {
        private List<String> data;
        private Context context;

        public void setData(List<String> data) {
            this.data = data;
            notifyDataSetChanged();
        }

        public Adapter(Context context) {
            this.context = context;
        }

        @Override
        public int getCount() {
            return data == null ? 0 : data.size();
        }

        @Override
        public String getItem(int position) {
            return data == null ? "" : data.get(position);
        }

        @Override
        public long getItemId(int position) {
            return data == null ? 0 : position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                convertView = LayoutInflater.from(context).inflate(R.layout.layout_item, null);
                holder = new ViewHolder();
                holder.textView = convertView.findViewById(R.id.tv);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.textView.setText(data.get(position));
            return convertView;
        }

        class ViewHolder {
            TextView textView;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值