SwipeRefreshLayout based pull-up loading more controls

It has been more than half a year since the last blog. CSDN has been revised recently, and various user experience is also being inaccessible to countless people. However, the blog should be written and make complaints about the topic.
Now there are many projects using lists to display data, and ListView and RecyclerView are familiar to all. In the actual project, all the interfaces in the background must be paged, so page loading is also a natural thing. Based on the Google native drop-down refresh control SwipeRefreshLayout, the following implements more functions of pull-up loading. Direct code:
CustomSwipeRefreshLayout.java

package com.jackie.sample.custom_view;

import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;

import com.jackie.sample.R;
import com.jackie.sample.utils.LogUtils;

/**
 * Created by Jackie on 2018/1/11
 * Custom pull-down refresh pull-up load control currently only supports ListView
 */
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout implements OnScrollListener {
    private Context mContext;

    // Pull up operation when sliding to the bottom
    private int mTouchSlop;

    // ListView
    private ListView mListView;

    // Pull up the listener to the bottom pull up loading operation
    private OnLoadListener mOnLoadListener;
    private OnScrollListener mOnScrollListener;
    private OnScrollStateChangeListener mOnScrollStateChangeListener;

    // FooterView in loading ListView
    private View mFooterView;

    // y coordinate when pressed
    private int mDownY;
    // y coordinate when moving, used together with mdown to determine whether to pull up or pull down when sliding to the bottom
    private int mMoveY;
    // Whether it is loading (pull-up to load more)
    private boolean mIsLoading = false;

    private int mCanLoadCount = 6;
    private int mStartLoadCount = 3;

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

    public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        initView(context);
    }

    private void initView(Context context) {
        mContext = context;

        // Indicates that when sliding, the movement of the hand must be greater than this distance before moving the control. If it is less than this distance, the mobile control will not be triggered. For example, ViewPager uses this distance to judge whether the user is turning pages
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        // Set the start and end positions of the drop-down progress
//        setProgressViewOffset(false, DensityUtils.dp2px(context, 20), DensityUtils.dp2px(context, 80));
    }

    @Override  
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
        super.onLayout(changed, left, top, right, bottom);

        // Initialize ListView object  
        if (mListView == null) {
            getListView();
        }
    }  

    /** 
     * Get ListView object 
     */  
    private void getListView() {  
        int childCount = getChildCount();

        if (childCount > 0) {
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);

                if (childView instanceof ListView) {
                    mListView = (ListView) childView;

                    // Set the scrolling listener to ListView so that it can also be loaded automatically when scrolling
                    mListView.setOnScrollListener(this);
                }
            }
        }  
    }

    /**
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {  
        final int action = event.getAction();  

        switch (action) {  
            case MotionEvent.ACTION_DOWN:
                // Press down
                mDownY = (int) event.getRawY();

                break;
            case MotionEvent.ACTION_MOVE:
                // move
                mMoveY = (int) event.getRawY();

                break;
            case MotionEvent.ACTION_UP:  
                // lift
                if (canLoad()) {  
                    loadData();  
                }

                break;  
            default:  
                break;  
        }  

        return super.dispatchTouchEvent(event);  
    }  

    // Whether to load more or not depends on the fact that the ListView is not being loaded at the bottom and is a pull-up operation
    private boolean canLoad() {
        if (mListView == null) {
            LogUtils.showLog("Unable to load");
            return false;
        }

        return isBottom() && !mIsLoading && isPullUp() && mListView.getAdapter().getCount() > mCanLoadCount && !isRefreshing();
    }

    /**
     * Set how many lists to pull up to load more
     * @param canLoadCount
     */
    public void setCanLoadCount(int canLoadCount) {
        this.mCanLoadCount = canLoadCount;
    }

    // Determine if it's at the bottom
    private boolean isBottom() {
        if (mListView != null && mListView.getAdapter() != null) {  
            return mListView.getLastVisiblePosition() >= (mListView.getAdapter().getCount() - mStartLoadCount);
        }

        return false;  
    }

    /**
     * Set to load more when pulling up to the last few items in the list
     * @param startLoadCount
     */
    public void setStartLoadCount(int startLoadCount) {
        this.mStartLoadCount = startLoadCount;
    }

    // Whether it is pull-up operation
    private boolean isPullUp() {  
        return (mDownY - mMoveY) >= mTouchSlop;
    }  

    // If it reaches the bottom and is pull-up, then the onLoad method is executed
    private void loadData() {  
        if (mOnLoadListener != null) {  
            // Set status  
            setLoading(true);

            mOnLoadListener.onLoad();
        }  
    }  

    private void setLoading(boolean loading) {
        mIsLoading = loading;

        if (mIsLoading) {
            mFooterView = LayoutInflater.from(mContext).inflate(R.layout.listview_footer, null, false);

//            if (mListView.getFooterViewsCount() <= 0) {
//                mListView.addFooterView(mFooterView);
//            }
//
//            mFooterView.setVisibility(View.VISIBLE);

            mListView.addFooterView(mFooterView);
        } else {
            if (mListView == null || mListView.getFooterViewsCount() <= 0 || mListView.getAdapter() == null || mFooterView == null) {
                return;
            }

//            mFooterView.setVisibility(View.GONE);

            mListView.removeFooterView(mFooterView);

//          ObjectAnimator animation = ObjectAnimator.ofFloat(mFooterView, "scaleY", 1, 0);
//          animation.setDuration(100);
//          animation.start();
//          animation.addListener(new Animator.AnimatorListener() {
//              public void onAnimationStart(Animator arg0) {
//
//              }
//
//              public void onAnimationRepeat(Animator arg0) {
//
//              }
//
//              public void onAnimationEnd(Animator arg0) {
//                  mListView.removeFooterView(mFooterView);
//
//              }
//
//              public void onAnimationCancel(Animator arg0) {
//
//              }
//          });

            mDownY = 0;
            mMoveY = 0;
        }  
    }  

    // Set up listening for pull-up loading
    public void setOnLoadListener(OnLoadListener loadListener) {  
        mOnLoadListener = loadListener;  
    }

    // Agents with sliding state changes
    @Override  
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (mOnScrollStateChangeListener != null) {
            mOnScrollStateChangeListener.onScrollStateChanged(view, scrollState);
        }
    }  

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

        // When scrolling to the bottom, you can also load more  
        if (canLoad()) {  
            loadData();  
        }
    }  

    // Load more listeners
    public interface OnLoadListener {
        void onLoad();
    }

    public interface OnScrollListener {
        void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount);
    }

    public interface OnScrollStateChangeListener {
        void onScrollStateChanged(AbsListView listView, int state);
    }

    public void onRefreshComplete() {
        setLoading(false);
        setRefreshing(false);

//        if (isPullUp()) {
//            setLoading(false);
//        } else {
//            setRefreshing(false);
//        }
    }

    public void setOnScrollListener(OnScrollListener onScrollListener) {
        this.mOnScrollListener = onScrollListener;
    }

    public void setOnScrollStateChangeListener(OnScrollStateChangeListener onScrollStateChangeListener) {
        this.mOnScrollStateChangeListener = onScrollStateChangeListener;
    }
}

listview_footer.xml

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"
    android:layout_height="wrap_content"  
    android:gravity="center"  
    android:paddingBottom="8dp"
    android:paddingTop="5dp" >

    <ProgressBar  
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleSmall"
        android:layout_centerVertical="true"  
        android:layout_centerHorizontal="true"  
        android:paddingRight="100dp"  
        android:indeterminate="true" />  

    <TextView  
        android:layout_width="match_parent"
        android:layout_height="wrap_content"  
        android:layout_gravity="center"  
        android:gravity="center"  
        android:paddingTop="5dip"  
        android:text="Load more..."  
        android:textAppearance="?android:attr/textAppearanceMedium"  
        android:textColor="@android:color/darker_gray"  
        android:textSize="14sp"  
        android:textStyle="bold" />  

</RelativeLayout>  

It can be seen that the code is very simple and the comments are very clear. I will not elaborate here. Since SwipeRefreshLayout itself is a pull-down loading control, after encapsulation, the control has more functions of pull-down refresh and pull-up loading, and the usage has no task change:

<com.jackie.sample.custom_view.CustomSwipeRefreshLayout
        android:id="@+id/swipe_refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none" />
</com.jackie.sample.custom_view.CustomSwipeRefreshLayout>

The only disadvantage is that this control only supports ListView, not RecyclerView. Interested students can optimize it by themselves.
SwipeRefreshLayoutActivity .java

package com.jackie.sample.material_design;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.jackie.sample.R;
import com.jackie.sample.custom_view.CustomSwipeRefreshLayout;
import com.jackie.sample.utils.ThreadUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Jackie on 2018/1/11.
 * Custom pull down refresh pull up load control
 */

public class SwipeRefreshLayoutActivity extends AppCompatActivity {
    private CustomSwipeRefreshLayout mSwipeRefreshLayout;
    private ListView mListView;

    private ArrayAdapter mAdapter;

    private List<String> mList;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_swipe_refresh_layout);

        initView();
        initData();
        initEvent();
    }

    private void initView() {
        mSwipeRefreshLayout = (CustomSwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
        mListView = (ListView) findViewById(R.id.list_view);

        mList = new ArrayList<>();
    }

    private void initData() {
        for (int i = 0; i < 20; i++) {
            mList.add("The first" + (i + 1) + "Data");
        }

        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mList);
        mListView.setAdapter(mAdapter);
    }

    private void refreshData() {
        ThreadUtils.newThread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 5; i++) {
                            mList.add("The first" + (i + 1) + "Data");
                        }

                        mAdapter.notifyDataSetChanged();

                        mSwipeRefreshLayout.onRefreshComplete();
                    }
                });
            }
        });
    }

    private void initEvent() {
        mSwipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);

        /**
         * Drop down refresh
         */
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
               refreshData();
            }
        });

        /**
         * Pull up to load more
         */
        mSwipeRefreshLayout.setOnLoadListener(new CustomSwipeRefreshLayout.OnLoadListener() {
            @Override
            public void onLoad() {
                refreshData();
            }
        });
    }
}

The renderings are as follows:

Tags: Android Java xml Google

Posted on Mon, 04 May 2020 10:36:29 -0700 by scott.stephan