自定义listView 实现 下拉刷新+下拉加载的功能

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tiger_gy/article/details/88530938

                自定义listView 实现 下拉刷新+下拉加载的功能

                                                                                                                                                                       图上是最终的实现效果。

1、首先自定头部的下拉刷新布局 和底部加载更多的布局。

2、通过使用setPadding的设置默认让头部布局和底部布局隐藏。

3、listView下拉的时候通过修改padding让头布局显示出来。listView 上拉的时候也是通过修改padding 显示出加载更多。

4、触摸动态修改头布局根据paddingTop 来判断头部的显示

       -paddingTop=0 完全显示。

       -paddingTop<0 不完全显示。 松开后自动隐藏

      -paddingTop>0 完全显示+顶部空白

5、松手后根据当前的paddingTop决定是否执行刷新。

     -paddingTop<0 不完全显示 恢复

     -paddingTop>0 完全显示,执行正在刷新。

下面直接上代码

mainActivity.java

package com.hz.refreshlist;


import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.hz.refreshlist.view.RefreshListView;

import java.util.ArrayList;

public class MainActivity extends Activity {
    private RefreshListView lvListView;
    private ArrayList<String> listDatas;
    private MyAdapter myAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /*去掉顶部标题 设置数据*/
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);


        lvListView = (RefreshListView) findViewById(R.id.lv_listView);
        lvListView.setRefreshListener(new RefreshListView.OnRefreshListener() {
            @Override
            public void onRefresh() {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        listDatas.add(0,"我是下拉刷新的数据"+Math.random()*10+"");
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                myAdapter.notifyDataSetChanged();
                                lvListView.onRefreshComplete();
                            }
                        });
                    }
                }).start();
            }

            @Override
            public void onLoadMore() {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        listDatas.add("我是加载更多的数据"+Math.random()*10+"");
                        listDatas.add("我是加载更多的数据"+Math.random()*10+"");
                        listDatas.add("我是加载更多的数据"+Math.random()*10+"");
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                myAdapter.notifyDataSetChanged();
                                lvListView.onRefreshComplete();
                            }
                        });
                    }
                }).start();

            }
        });
        /*产生测试的数据*/
        listDatas = new ArrayList<String>();
        for (int i = 0; i < 30; i++) {
            listDatas.add("这是一条ListView数据: " + i);
        }
        /*数据适配器*/
        myAdapter = new MyAdapter();
        lvListView.setAdapter(myAdapter);

    }

    private class MyAdapter extends BaseAdapter {
        /**
         * How many items are in the data set represented by this Adapter.
         *
         * @return Count of items.
         */
        @Override
        public int getCount() {
            return listDatas.size();
        }

        /**
         * Get the data item associated with the specified position in the data set.
         *
         * @param position Position of the item whose data we want within the adapter's
         *                 data set.
         * @return The data at the specified position.
         */
        @Override
        public Object getItem(int position) {
            return listDatas.get(position);
        }

        /**
         * Get the row id associated with the specified position in the list.
         *
         * @param position The position of the item within the adapter's data set whose row id we want.
         * @return The id of the item at the specified position.
         */
        @Override
        public long getItemId(int position) {
            return position;
        }

        /**
         * Get a View that displays the data at the specified position in the data set. You can either
         * create a View manually or inflate it from an XML layout file. When the View is inflated, the
         * parent View (GridView, ListView...) will apply default layout parameters unless you use
         * {@link LayoutInflater#inflate(int, ViewGroup, boolean)}
         * to specify a root view and to prevent attachment to the root.
         *
         * @param position    The position of the item within the adapter's data set of the item whose view
         *                    we want.
         * @param convertView The old view to reuse, if possible. Note: You should check that this view
         *                    is non-null and of an appropriate type before using. If it is not possible to convert
         *                    this view to display the correct data, this method can create a new view.
         *                    Heterogeneous lists can specify their number of view types, so that this View is
         *                    always of the right type (see {@link #getViewTypeCount()} and
         *                    {@link #getItemViewType(int)}).
         * @param parent      The parent that this view will eventually be attached to
         * @return A View corresponding to the data at the specified position.
         */
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            TextView textView = new TextView(parent.getContext());
            textView.setTextSize(18f);
            textView.setTextColor(Color.BLUE);
            textView.setText(listDatas.get(position));

            return textView;
        }
    }
}

activity_mian.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hz.refreshlist.MainActivity">

    <com.hz.refreshlist.view.RefreshListView
        android:id="@+id/lv_listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.hz.refreshlist.view.RefreshListView>

</LinearLayout>

RefreshListView.java

package com.hz.refreshlist.view;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
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.hz.refreshlist.R;

import java.text.SimpleDateFormat;

/**
 * 包含下拉刷新功能的listView
 * Created by Administrator on 2019/3/12.
 */

public class RefreshListView extends ListView implements AbsListView.OnScrollListener {

    private View mHeaderView;
    private int mHeaderViewHeight;
    private int mFooterViewHeight;
    private float dowY;//按下的Y轴的距离
    private float moveY;//移动的Y轴的距离
    public static final int PULL_TO_REFRESH = 0;// 下拉刷新
    public static final int RELEASE_REFRESH = 1;// 释放刷新
    public static final int REFRESHING = 2; // 刷新中
    private int currentState = PULL_TO_REFRESH; // 当前刷新模式
    private RotateAnimation rotateUpAnim; // 箭头向上动画
    private RotateAnimation rotateDownAnim; // 箭头向下动画
    private ImageView ivArrow;//箭头图片
    private ProgressBar pbProgress;//滚动条
    private TextView tvTitle;//头部局标题
    private TextView tvDescLastRefresh;//时间刷新内容
    private OnRefreshListener mListener; // 刷新监听
    private View mFooterView;
    public static int SCROLL_STATE_IDLE = 0; // 空闲
    public static int SCROLL_STATE_TOUCH_SCROLL = 1; // 触摸滑动
    public static int SCROLL_STATE_FLING = 2; // 滑翔
    private boolean isLoadingMore; // 是否正在加载更多
    /**
     *代码里面
     *  @param context
     */
    public RefreshListView(Context context) {
        super(context);
        init();

    }



    /**
     * 包含属性
     * @param context
     * @param attrs
     */
    public RefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    /**
     *包含样式
     *  @param context
     * @param attrs
     * @param defStyleAttr
     */
    public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    /**
     * 初始化头布局, 脚布局
     * 滚动监听
     */
    private void init() {
        initHeaderView();
        initAnimation();

        initFooterView();
        setOnScrollListener(this);
    }

    /**
     * 初始化头布局
     */
    private void initHeaderView() {
        mHeaderView = View.inflate(getContext(), R.layout.layout_header_list,null);

        ivArrow = (ImageView) mHeaderView.findViewById(R.id.iv_arrow);
        pbProgress = (ProgressBar) mHeaderView.findViewById(R.id.pb_progress);
        tvTitle = (TextView)mHeaderView. findViewById(R.id.tv_title);
        tvDescLastRefresh = (TextView) mHeaderView.findViewById(R.id.tv_desc_last_refresh);

        /*提前手动测量宽高*/
        mHeaderView.measure(0, 0);
        /*获取自身高度*/
        mHeaderViewHeight = mHeaderView.getMeasuredHeight();
        /*设置内边距,可以隐藏布局  -自身高度*/
        mHeaderView.setPadding(0,-mHeaderViewHeight,0,0);
        addHeaderView(mHeaderView);
    }
    /**
     * 初始化脚布局
     */
    private void initFooterView() {
        mFooterView = View.inflate(getContext(), R.layout.layout_footer_list,null);
          /*提前手动测量宽高*/
        mFooterView.measure(0, 0);
        /*获取自身高度*/
        mFooterViewHeight = mFooterView.getMeasuredHeight();
        /*设置内边距,可以隐藏布局  -自身高度*/
        mFooterView.setPadding(0,-mFooterViewHeight,0,0);
        addFooterView(mFooterView);
    }
    /**
     * 初始化头布局的动画
     */
    private void initAnimation() {
        // 向上转, 围绕着自己的中心, 逆时针旋转0 -> -180.
        rotateUpAnim=new RotateAnimation(0f,-180f,
                Animation.RELATIVE_TO_SELF,0.5f,
                Animation.RELATIVE_TO_SELF,0.5f);
        rotateUpAnim.setDuration(300);
        // 动画停留在结束位置
        rotateUpAnim.setFillAfter(true);
        // 向下转, 围绕着自己的中心, 逆时针旋转 -180 -> -360
        rotateDownAnim=new RotateAnimation(-180f,-360f,
                Animation.RELATIVE_TO_SELF,0.5f,
                Animation.RELATIVE_TO_SELF,0.5f);
        rotateDownAnim.setDuration(300);
        rotateDownAnim.setFillAfter(true);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        /*判断滑动距离,给Header设置paddingTop*/
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                dowY =ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                moveY =ev.getY();
                // 如果是正在刷新中, 就执行父类的处理
                if(currentState == REFRESHING){
                    return super.onTouchEvent(ev);
                }

                // 移动的偏移量
                float offset=moveY-dowY;
                // 只有 偏移量>0, 并且当前第一个可见条目索引是0, 才放大头部
                if (offset>0&&getFirstVisiblePosition()==0){
                    //int paddingTop = -自身高度 + 偏移量
                    int paddingTop= (int) (-mHeaderViewHeight+offset);
                    mHeaderView.setPadding(0,paddingTop,0,0);
                    if (paddingTop>=0&&currentState!=RELEASE_REFRESH){// 头布局完全显示
                        // 切换成释放刷新模式
                        currentState=RELEASE_REFRESH;
                        Log.d("RefreshListView", "释放刷新");
                        updateHeader();
                    }else if(paddingTop < 0 &&currentState!=PULL_TO_REFRESH){// 头布局不完全显示
                        // 切换下拉刷新模式
                        Log.d("RefreshListView", "下拉刷新");
                        currentState=PULL_TO_REFRESH;
                        updateHeader();
                    }
                    return true; // 当前事件被我们处理并消费
                }

                break;
            case MotionEvent.ACTION_UP:
                // 根据刚刚设置状态
                if (currentState==PULL_TO_REFRESH){
                    //- paddingTop < 0 不完全显示, 恢复
                    mHeaderView.setPadding(0,-mHeaderViewHeight,0,0);
                }else {
                    //- paddingTop >= 0 完全显示, 执行正在刷新...
                    mHeaderView.setPadding(0,0,0,0);
                    currentState = REFRESHING;
                    updateHeader();
                }
                break;

        }
        return super.onTouchEvent(ev);
    }

    /**
     *  根据状态更新头布局内容
     */
    private void updateHeader() {
        switch (currentState){
            case PULL_TO_REFRESH://切换成下拉刷新
                //切换动画,改标题
                ivArrow.startAnimation(rotateDownAnim);
                tvTitle.setText("下拉刷新");
                break;
            case RELEASE_REFRESH://切换成释放刷新
                //切换动画,改标题
                ivArrow.startAnimation(rotateUpAnim);
                tvTitle.setText("释放刷新");
                break;
            case REFRESHING://切换成正在刷新
                //切换动画改标题
                ivArrow.clearAnimation();
                ivArrow.setVisibility(INVISIBLE);
                pbProgress.setVisibility(VISIBLE);
                tvTitle.setText("正在刷新中");
                if (mListener!=null){
                    mListener.onRefresh();
                }
                break;
        }
    }

    /**
     * 刷新结束恢复界面效果
     */
    public void onRefreshComplete(){
        /*是否加载更多*/
        if (isLoadingMore){
            // 加载更多
            mFooterView.setPadding(0,-mFooterViewHeight,0,0);
            isLoadingMore = false;
        }else {
            // 下拉刷新
            currentState=PULL_TO_REFRESH;
            tvTitle.setText("下拉刷新");
            mHeaderView.setPadding(0,-mHeaderViewHeight,0,0);
            ivArrow.setVisibility(VISIBLE);
            pbProgress.setVisibility(INVISIBLE);
            String time=getTime();
            tvDescLastRefresh.setText("最后刷新时间:"+time);
        }

    }

    private String getTime() {
        long currentTimeMillis = System.currentTimeMillis();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(currentTimeMillis);
    }

    public void setRefreshListener(OnRefreshListener mListener){
        this.mListener=mListener;
    }

    /**
     * *  public static int SCROLL_STATE_IDLE = 0; // 空闲
     public static int SCROLL_STATE_TOUCH_SCROLL = 1; // 触摸滑动
     public static int SCROLL_STATE_FLING = 2; // 滑翔
     * @param view        The view whose scroll state is being reported
     * @param scrollState The current scroll state. One of
     *                    {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // 状态更新的时候
        if(isLoadingMore){
            return; // 已经在加载更多.返回
        }
        // 最新状态是空闲状态, 并且当前界面显示了所有数据的最后一条. 加载更多
        if (scrollState==SCROLL_STATE_IDLE &&getLastVisiblePosition()>=(getCount() - 1)){
            isLoadingMore=true;
            Log.d("RefreshListView", "显示加载更多");
            mFooterView.setPadding(0,0,0,0);
            setSelection(getCount());
            if (mListener!=null){
                mListener.onLoadMore();
            }
        }
    }

    /**
     * Callback method to be invoked when the list or grid has been scrolled. This will be
     * called after the scroll has completed
     * @param view             The view whose scroll state is being reported
     * @param firstVisibleItem the index of the first visible cell (ignore if
     *                         visibleItemCount == 0)
     * @param visibleItemCount the number of visible cells
     * @param totalItemCount   the number of items in the list adaptor
     */
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {


    }

    public interface OnRefreshListener{
        void onRefresh(); // 下拉刷新

        void onLoadMore();// 加载更多
    }
}

layout_footer_list.xml

<?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:layout_height="wrap_content"
    android:gravity="center">
    <ProgressBar
        android:layout_margin="5dp"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:indeterminateDrawable="@drawable/shape_progress" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="加载更多..."
        android:textColor="#F00"
        android:layout_marginLeft="15dp"
        android:textSize="18sp" />
</LinearLayout>

layout_header_list.xml

<?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="match_parent"
    android:orientation="horizontal">

    <FrameLayout
        android:layout_margin="5dp"
        android:layout_width="50dp"
        android:layout_height="50dp" >

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

        <ProgressBar
            android:id="@+id/pb_progress"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:indeterminateDrawable="@drawable/shape_progress"
            android:visibility="invisible"/>
    </FrameLayout>

    <LinearLayout
        android:layout_margin="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:text="下拉刷新"
            android:textColor="#F00"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_desc_last_refresh"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:singleLine="true"
            android:text="最后刷新时间: 2015-10-11 09:20:35"
            android:textColor="#666"
            android:textSize="14sp" />
    </LinearLayout>

</LinearLayout>

shape_progress.xml

<?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" >

    <!-- android:innerRadius="20dp"
     android:thickness="5dp"-->
    <shape
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:innerRadiusRatio="2.5"
        android:shape="ring"
        android:thicknessRatio="10"
        android:useLevel="false" >
        <gradient
            android:centerColor="#44FF0000"
            android:endColor="#00000000"
            android:startColor="#ff0000"
            android:type="sweep" />
    </shape>

</rotate>

最后上传这个下拉的图片,大功告成了。

猜你喜欢

转载自blog.csdn.net/tiger_gy/article/details/88530938