Custom ListView for pull-down refresh and pull-up loading

To realize the pull-down refresh and pull-up loading of ListView, you need to add headerView and footerView first, and control the paddingTop of the head and tail layout during the dragging process. First, set the paddingTop to a negative value to hide the header. During the drop-down process, change the paddingTop of the headerView continuously to achieve the effect of the headerView slowly displaying in the drop-down process.

The pull-down refresh is to hide the head of listview, and then drag the screen with gestures to refresh.

So, how to hide the head of listview? Here we use header.setPadding(0,-headerViewHeight,0,0) to hide the header.

When motionevent.action "down" is triggered, a down y offset value is required to record the y when the gesture is pressed,
Then, when motionevent.action'move 'is triggered, a move is needed to record the offset value of the move,
Then dy = move Y - down y gets the distance the fingers are sliding. Then use paddingTop = (int) (dy - mHeadViewHeight) to get the distance from the top of the head to the top of the screen. If you want paddingtop > = 0, the header is fully displayed. If paddingtop < 0, the header is not fully displayed.

Drop down refresh status:

 /**
     * Drop-down refresh
     */
    public static final int STATE_PULL_TO_REFRESH = 1;

    /**
     * Release refresh
     */
    public static final int STATE_RELASE_TO_REFRESH = 2;

    /**
     * Refresh
     */
    public static final int STATE_REFRESHING = 3;

To customize a ListView with pull-down refresh and pull-up loading:

package com.example.a01_pulltorefreshview.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.a01_pulltorefreshview.R;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Customize the ListView with pull-down refresh and pull-up loading
 * Created by xiaoyehai on 2016/11/24.
 */
public class PullToRefreshListView extends ListView implements AbsListView.OnScrollListener {

    /**
     * Drop-down refresh
     */
    public static final int STATE_PULL_TO_REFRESH = 1;

    /**
     * Release refresh
     */
    public static final int STATE_RELASE_TO_REFRESH = 2;

    /**
     * Refresh
     */
    public static final int STATE_REFRESHING = 3;

    /**
     * current state
     */
    private int mCurrentState = STATE_PULL_TO_REFRESH;

    /**
     * Drop down refresh header layout
     */
    private View mHeadView;

    /**
     * Pull down refresh status
     */
    private TextView tvState;

    /**
     * Pull down refresh time
     */
    private TextView tvTime;

    /**
     * Drop down refresh rotation arrow picture
     */
    private ImageView ivArrow;

    /**
     * Drop down refresh ProgressBar
     */
    private ProgressBar pbProgress;

    /**
     * Pull down refresh Y coordinate when pressing
     */
    private float startY;
    //private int startY = -1;

    /**
     * Pull down refresh Y coordinate when moving
     */
    private float endY;

    /**
     * Drop down refresh head layout height
     */
    private int mHeadViewHeight;

    /**
     * Pull down refresh arrow rotation animation
     */
    private RotateAnimation animUp;

    /**
     * Pull down refresh arrow rotation animation
     */
    private RotateAnimation animDown;

    /**
     * Drop down the padding at the top of the refresh process
     */
    private int paddingTop;

    /**
     * Pull up load bottom layout
     */
    private View mFooterView;

    /**
     * Pull up load bottom layout height
     */
    private int mFooterViewHeight;

    /**
     * Are you loading more
     */
    private boolean isLoadMore;

    /**
     * Pull down refresh and pull up load event listening
     */
    private onRefreshListener onRefreshListener;

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

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

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

    /**
     * Initialize head layout, foot layout
     */
    private void init() {

        initHeadView();

        initFoodView();

        initAnimation();

    }

    /**
     * Initialize header layout
     */
    private void initHeadView() {
        mHeadView = View.inflate(getContext(), R.layout.pull_to_refresh_head, null);
        this.addHeaderView(mHeadView);
        ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
        tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
        tvTime = (TextView) mHeadView.findViewById(R.id.tv_time);
        pbProgress = (ProgressBar) mHeadView.findViewById(R.id.pd_loading);

        //Measure the width and height according to the set rules
        mHeadView.measure(0, 0);
        //Control height
        mHeadViewHeight = mHeadView.getMeasuredHeight();

        //Hide head layout
        mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
    }

    /**
     * Initialize bottom, load more
     */
    private void initFoodView() {
        mFooterView = View.inflate(getContext(), R.layout.pull_to_refresh_footer, null);
        this.addFooterView(mFooterView);

        //Measurement control
        mFooterView.measure(0, 0);
        //Height of control
        mFooterViewHeight = mFooterView.getMeasuredHeight();
        //Hidden controls
        mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);

        //Set listening, load more
        this.setOnScrollListener(this);

    }

    /**
     * Initialize head layout arrow animation
     */
    private void initAnimation() {
        animUp = new RotateAnimation(0, -180,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animUp.setDuration(200);
        animUp.setFillAfter(true);

        animDown = new RotateAnimation(-180, 0,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animDown.setDuration(200);
        animDown.setFillAfter(true);
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //Record the Y coordinate pressed by the starting point
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:

                //Added ad rotation header layout:
                //When the user presses and holds the advertisement image to pull down, action "down" will be consumed by viewpager,
                //As a result, startY has not been assigned. You need to retrieve it here
                //                if (startY == -1) {
                //                    startY = (int) ev.getY();
                //                }

                //If you are refreshing at this time, skip the cycle and do not let it refresh again
                if (mCurrentState == STATE_REFRESHING) {
                    break;
                    //return super.onTouchEvent(ev); / / or write like this to execute the processing of the parent class
                }

                endY = ev.getY();

                //Finger slide offset
                float dy = endY - startY;

                //The location of the first item currently displayed
                int firstVisiblePosition = getFirstVisiblePosition();
                //Pull down and display the first visible item to draw out the control
                if (dy > 0 && firstVisiblePosition == 0) {
                    //Calculating the padding of the current control
                    paddingTop = (int) (dy - mHeadViewHeight);
                    mHeadView.setPadding(0, paddingTop, 0, 0);

                    //When the control is pulled to the full display, it is released to refresh
                    if (paddingTop >= 0 && mCurrentState != STATE_RELASE_TO_REFRESH) {
                        mCurrentState = STATE_RELASE_TO_REFRESH;  //Release refresh status

                        //Refresh header layout
                        refreshState();
                    } else if (paddingTop < 0 && mCurrentState != STATE_PULL_TO_REFRESH) {
                        //Incomplete display, drop-down refresh
                        mCurrentState = STATE_PULL_TO_REFRESH;//Pull down refresh status

                        //Refresh header layout
                        refreshState();
                    }
                    return true; //The current event is consumed, the TouchMove is blocked, and listview is not allowed to process the move event. As a result, listview cannot slide
                }
                break;
            case MotionEvent.ACTION_UP:
                //                startY = -1;
                if (mCurrentState == STATE_PULL_TO_REFRESH) { //When the pull-down is not fully displayed, release it or when you go back, restore it
                    mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
                } else if (mCurrentState == STATE_RELASE_TO_REFRESH) { //When you release the refresh status, raise your hand to refresh
                    mCurrentState = STATE_REFRESHING;
                    refreshState();

                    //Displaying the header layout completely while refreshing
                    mHeadView.setPadding(0, 0, 0, 0);

                    //Interface callback, notify loading data
                    if (onRefreshListener != null) {
                        onRefreshListener.onRefresh();
                    }
                }
                break;

        }
        return super.onTouchEvent(ev);
    }

    /**
     * Refresh control based on current state
     */
    private void refreshState() {
        switch (mCurrentState) {
            case STATE_PULL_TO_REFRESH:
                // TODO: 2016/11/24 pull down refresh status
                tvState.setText("Drop-down refresh");
                pbProgress.setVisibility(INVISIBLE);
                ivArrow.setVisibility(VISIBLE);
                ivArrow.startAnimation(animDown);

                break;
            case STATE_RELASE_TO_REFRESH:
                // TODO: 2016/11/24 release refresh status
                tvState.setText("Release refresh");
                pbProgress.setVisibility(INVISIBLE);
                ivArrow.setVisibility(VISIBLE);
                ivArrow.startAnimation(animUp);
                break;
            case STATE_REFRESHING:
                // Todo: refreshing status on November 24, 2016
                tvState.setText("Refresh...");
                pbProgress.setVisibility(VISIBLE);
                ivArrow.setVisibility(INVISIBLE);

                ivArrow.clearAnimation();   //Clear arrow animation, otherwise it cannot be hidden
                break;
        }
    }

    /**
     * Refresh end, collapse control
     *
     * @param success:Whether the pull-down refresh is successful
     */
    public void onRefreshComplete(boolean success) {
        if (isLoadMore) { //Load more
            //When more is loaded, collapse the control
            mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
            isLoadMore = false;
        } else { //Drop-down refresh
            mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
            mCurrentState = STATE_PULL_TO_REFRESH;
            tvState.setText("Drop-down refresh");
            pbProgress.setVisibility(INVISIBLE);
            ivArrow.setVisibility(VISIBLE);

            //Set the refresh time. The time will be updated only after the refresh is successful
            if (success) {
                tvTime.setText("Last refresh time:" + getCurrentTime());
            }
        }
    }

    /**
     * Set the time in the pull-down refresh
     */
    private String getCurrentTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = sdf.format(new Date());
        return time;
    }

    /**
     * SCROLL_STATE_IDLE:When the screen stops scrolling
     * SCROLL_STATE_TOUCH_SCROLL: When the screen scrolls in touch mode and the fingers are still on the screen
     * SCROLL_STATE_FLING: When the user swipes the screen and raises his finger, the screen scrolls in inertia
     *
     * @param view
     * @param scrollState
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO: 2016/11/19 callback of sliding state change
        if (scrollState == SCROLL_STATE_IDLE) { //Idle state
            int lastVisiblePosition = getLastVisiblePosition();
            //Show last item and no more loaded
            if (lastVisiblePosition == getCount() - 1 && !isLoadMore) {
                isLoadMore = true;
                mFooterView.setPadding(0, 0, 0, 0); //display

                //setSelection(getCount() - 1); / / displayed on the last item
                setSelection(getCount()); //Show on last item

                //Inform the main interface to load the data on the next page
                if (onRefreshListener != null) {
                    onRefreshListener.onLoadMore();
                }
            }
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // Todo: callback of sliding process on November 19, 2016
    }

    /**
     * Callback interface for pull-down refresh
     */
    public interface onRefreshListener {
        //Notification refresh (refreshing)
        void onRefresh();

        //Notification load more
        void onLoadMore();
    }

    /**
     * Expose interface, set monitor
     *
     * @param onRefreshListener
     */
    public void setOnRefreshListener(PullToRefreshListView.onRefreshListener onRefreshListener) {
        this.onRefreshListener = onRefreshListener;
    }
}

Head layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="10dp">

        <ImageView
            android:id="@+id/iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:src="@mipmap/common_listview_headview_red_arrow" />

        <ProgressBar
            android:id="@+id/pd_loading"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:indeterminateDrawable="@drawable/custom_progress"
            android:visibility="invisible" />
    </FrameLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Drop-down refresh"
            android:textColor="#f00"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="3dp"
            android:text="Last refresh time: 2015-10-21 09:00:30"
            android:textColor="#a000"
            android:textSize="16sp" />
    </LinearLayout>

</LinearLayout>

custom_progress:

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360">

    <shape
        android:innerRadius="18dp"
        android:shape="ring"
        android:thickness="5dp"
        android:useLevel="false">

        <gradient
            android:centerColor="#9f00"
            android:endColor="#f00"
            android:startColor="#fff"
            android:type="sweep" />
    </shape>
</rotate>

Bottom layout:

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

    <ProgressBar
        android:id="@+id/pd_loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminateDrawable="@drawable/custom_progress" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Loading..."
        android:textColor="#f00"
        android:layout_marginLeft="10dp"
        android:textSize="18sp" />

</LinearLayout>

Usage:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.a01_pulltorefreshview.MainActivity">

    <com.example.a01_pulltorefreshview.widget.PullToRefreshListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
package com.example.a01_pulltorefreshview;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Window;

import com.example.a01_pulltorefreshview.widget.PullToRefreshListView;

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

/**
 * Custom pull-down refresh and pull-up loaded ListView
 */
public class MainActivity extends AppCompatActivity {

    private PullToRefreshListView mListView;

    private List<String> list;

    private ListViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        mListView = (PullToRefreshListView) findViewById(R.id.listview);
        list = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            list.add("This is a piece. ListView Data:" + i);
        }

        //Add header, to be added before setAdapter
        adapter = new ListViewAdapter(this, list);
        mListView.setAdapter(adapter);

        mListView.setOnRefreshListener(new PullToRefreshListView.onRefreshListener() {
            @Override
            public void onRefresh() {
                //Todo: call back the method and load the data when the pull-down refresh occurs on November 24, 2016
                loadData();
            }

            @Override
            public void onLoadMore() {
                // TODO: 2016/11/24 pull-up callback for loading data on the next page
                loadMoreData();
            }
        });
    }

    private void loadMoreData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //Drop down refreshed data
                list.add("Pull up loaded data");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapter.notifyDataSetChanged();
                        //End of load, collapse control
                        mListView.onRefreshComplete(true);
                    }
                });
            }
        }).start();
    }

    private void loadData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //Drop down refreshed data
                list.add(0, "Drop down refreshed data");

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapter.notifyDataSetChanged();
                        //End of load, collapse control
                        mListView.onRefreshComplete(true);
                    }
                });
            }
        }).start();
    }
}

Tags: Android Java xml encoding

Posted on Wed, 01 Apr 2020 08:16:34 -0700 by nathanr