ListView上拉加载下拉刷新

作者: wxyass 分类: Android 发布时间: 2017-07-16 14:55

目前下拉刷新已经满大街都是,在自己的应用如果不使用这个模式的话,出门都不好意思和人家打招呼.

参考:

官方Github地址
官方中文版文档
原作者的分析

轮子的制造原理就不说了,网上很多,或许以后会懂,现在的关键是如何使用轮子

一 下载aar文件,并导入工程

1 下载aar文件

  1. 原作者官方下载 ultra-ptr-1.0.11.aar
  2. 个人备份下载 ultra-ptr-1.0.11.aar

2 导入工程libs文件夹,并修改build.gradle代码.

repositories {
    flatDir {
        dirs 'libs'
    }
}
-------------------------------------------
compile(name:'ultra-ptr-1.0.11',ext:'aar')   

如图:

3 Sync Gradle 一下

二 之前写过一个简单listview的使用,在此基础做下拉刷新

使用uil为ListView加载图片
将代码移植到本工程

2.1 将ListView控件用PtrClassicFrameLayout包起来
<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"
    >

    <in.srain.cube.views.ptr.PtrClassicFrameLayout
        android:id="@+id/ptr"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <ListView
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="10dp"
            android:descendantFocusability="beforeDescendants"
            android:dividerHeight="1dp"
            android:footerDividersEnabled="false"/>
    </in.srain.cube.views.ptr.PtrClassicFrameLayout>

</RelativeLayout>
2.2 修改MainActivity中的代码
ptr = (PtrClassicFrameLayout) findViewById(R.id.ptr);
//2设置刷新回调
ptr.setPtrHandler(new PtrDefaultHandler() {

    // 在这个方法回调中加载新数据,并关闭动画
    @Override
    public void onRefreshBegin(PtrFrameLayout ptrFrameLayout) {
        // 获取新数据
        count  = end;
        end = count+num;
        getData();
        adapter.notifyDataSetChanged();
        // 关闭下拉刷新的动画
        ptr.refreshComplete();
    }

    //1 将listview传入
    @Override
    public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
        return super.checkCanDoRefresh(frame, listview, header);
    }
});  

通过findViewById先找到PtrClassicFrameLayout控件,然后设置刷新回调,
在checkCanDoRefresh方法中将listview传进去,
在onRefreshBegin方法中获取新的数据并关闭下拉刷新的动画.

在onRefreshBegin方法中要根据产品的需求去获取数据, 需要跟产品和后台商量好到底刷新什么数据.

注意:下拉刷新的动画是ptr框架自带的,若觉得不好看,可自己去他的原码修改,或者通过其提供的api自定义头部修改.

2.3 以上就是最简单的下拉刷新使用

三 自定义下拉刷新时的头部动画

3.1 自定义头部动画,工程目录如图:

3.2 附上CircleView的代码
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

/**
 * CircleView 圆盘控件,可以旋转
 * Created by wxyass.
 */
public class CircleView extends View {

    /**
     * 控件的半径
     */
    private int mRadius;

    /**
     * 绘制弧形的画笔
     */
    private Paint mArcPaint;

    /**
     * 绘制弧形的区域
     */
    private RectF mRange;


    private int[] colors = {Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN};

    public CircleView(Context context) {
        this(context, null, 0);
    }

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

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init();
    }

    private void init() {
        mArcPaint = new Paint();
        mArcPaint.setAntiAlias(true);
        mArcPaint.setDither(true);
        mArcPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = 0;
        int height = 0;

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);


        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 45, getResources().getDisplayMetrics());
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 45, getResources().getDisplayMetrics());
        }

        //获取半径
        mRadius = Math.min(width, height) / 2;
        /**
         * 设置宽高为固定值
         */
        setMeasuredDimension(mRadius * 2, mRadius * 2);

         mRange = new RectF(0, 0, mRadius * 2, mRadius * 2);
    }


    @Override
    protected void onDraw(Canvas canvas) {

        float degree = 360/colors.length/2f;

        for (int i = 0; i < 8; i++) {
            mArcPaint.setColor(colors[i%4]);
            canvas.drawArc(mRange,-90f+degree*i,degree,true,mArcPaint);
        }

    }
}
3.3 附上CustomUltraRefreshHeader的代码
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import in.srain.cube.views.ptr.PtrFrameLayout;
import in.srain.cube.views.ptr.PtrUIHandler;
import in.srain.cube.views.ptr.indicator.PtrIndicator;

/**
 *
 * 自定义刷新的头部
 * Created by wxyass.
 */
public class CustomUltraRefreshHeader extends RelativeLayout implements PtrUIHandler {

    CircleView mCircleView;

    TextView mDescText;

    private ObjectAnimator anim;

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

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

    public CustomUltraRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initView();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec,100);
    }

    /**
     * 初始化布局
     */
    private void initView() {

        int circlewidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());

        mCircleView = new CircleView(getContext());

        LinearLayout.LayoutParams circleParams = new LinearLayout.LayoutParams(circlewidth,circlewidth);

        mCircleView.setLayoutParams(circleParams);

        mDescText = new TextView(getContext());

        LinearLayout.LayoutParams descParams = new LinearLayout.LayoutParams(circlewidth*3, ViewGroup.LayoutParams.WRAP_CONTENT);

        descParams.gravity = Gravity.CENTER;
        descParams.setMargins(circlewidth/2,0,0,0);
        mDescText.setLayoutParams(descParams);
        mDescText.setTextSize(12);
        mDescText.setTextColor(Color.GRAY);
        mDescText.setText("下拉刷新");

        //添加线性的父布局
        LinearLayout ll = new LinearLayout(getContext());
        LayoutParams llParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        llParams.addRule(CENTER_IN_PARENT);
        ll.setLayoutParams(llParams);
        ll.setPadding(10,10,10,10);

        ll.addView(mCircleView);
        ll.addView(mDescText);

        addView(ll);
    }

    @Override
    public void onUIReset(PtrFrameLayout frame) {
        //重置时,将动画置为初始状态
        mCircleView.setRotation(0f);
        Log.i("info","onUIReset");
    }

    @Override
    public void onUIRefreshPrepare(PtrFrameLayout frame) {
        mDescText.setText("下拉加载数据");
        Log.i("info","onUIRefreshPrepare");
    }

    @Override
    public void onUIRefreshBegin(PtrFrameLayout frame) {

        //开始刷新,启动动画
        anim = ObjectAnimator.ofFloat(mCircleView, "rotation", mCircleView.getRotation(), mCircleView.getRotation()+360f)
                .setDuration(500);
        anim.setRepeatCount(ValueAnimator.INFINITE);
        anim.setRepeatMode(ValueAnimator.RESTART);
        anim.start();

        mDescText.setText("正在加载数据");
        Log.i("info","onUIRefreshBegin");
    }

    @Override
    public void onUIRefreshComplete(PtrFrameLayout frame) {
        anim.cancel();
        mDescText.setText("加载完成");
        Log.i("info","onUIRefreshComplete");
    }

    @Override
    public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
        int headerHeight = ptrIndicator.getHeaderHeight();//头部的高度
        int lastPosY = ptrIndicator.getLastPosY();//上一次滑动的Y偏移值
        int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//舒心需要滑动的偏移量
        float offsetY = ptrIndicator.getOffsetY();//当前与上一次滑动处理的偏移值
        int currentPosY = ptrIndicator.getCurrentPosY();//当前系统偏移值

        if (isUnderTouch&&status== PtrFrameLayout.PTR_STATUS_PREPARE) {

            mCircleView.setRotation(currentPosY);
            if(currentPosY<offsetToRefresh&&lastPosY >= offsetToRefresh){
                //表示不刷新了
                mDescText.setText("下拉加载数据");
            }else if(currentPosY>offsetToRefresh&&lastPosY<=offsetToRefresh){
                mDescText.setText("松开加载更多");
            }
        }

       /* if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {
            if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {*//*
        Log.i("info","isUnderTouch"+isUnderTouch+"headHeight: "+headerHeight+" lastPosY "+lastPosY+" offsetToRefresh "+offsetToRefresh+" offsetY "+offsetY+" currentPosY "+currentPosY);*/
    }
}    
3.4 修改代码,调用自定义头部
//  自定义头部 下拉刷新时动画UI , 若不想用把下面3行代码注释掉
//创建我们的自定义头部视图
CustomUltraRefreshHeader header = new CustomUltraRefreshHeader(this);
//设置头部视图
ptr.setHeaderView(header);
//设置视图修改的回调,因为我们的CustomUltraRefreshHeader实现了PtrUIHandler
ptr.addPtrUIHandler(header);  

代码修改如图:

3.5 修改清单文件的name属性,然后重跑项目,查看效果
<application
    android:name=".MyApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".UseHeadActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
    <activity
        android:name=".ImageActivity"
        android:screenOrientation="portrait"/>
</application>
3.6 新下拉刷新头部效果如下

四 在框架基础上,自定义listview实现上拉加载更多

4.1 工程目录如图

4.2 附上UltraRefreshListener代码
/**
 * 数据接口的回调
 * Created by wxyass.
 */
public interface UltraRefreshListener {

    //下拉刷新
    void onRefresh();

    //上拉加载
    void addMore();
}
4.3 附上UltraRefreshListView代码
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.ListView;

import com.wxyass.tpultraptr.R;

import in.srain.cube.views.ptr.PtrClassicFrameLayout;
import in.srain.cube.views.ptr.PtrFrameLayout;
import in.srain.cube.views.ptr.PtrHandler;

/**
 * Created by wxyass.
 */
public class UltraRefreshListView extends ListView implements PtrHandler, AbsListView.OnScrollListener {

    private UltraRefreshListener mUltraRefreshListener;

    /**
     * 底部的布局:正在为您加载更多
     */
    private View footView;


    /**
     * 是否正在加载数据
     */
    private boolean isLoadData = false;

    /**
     * 是否是下拉刷新,主要在处理结果时使用
     */
    private boolean isRefresh = false;

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

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

    public UltraRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化布局,及添加一个跟布局
        initView();
    }

    private void initView() {
        footView = LayoutInflater.from(getContext()).inflate(R.layout.foot_ultra_refresh_listview, null);

        setOnScrollListener(this);
    }

    // 开始刷新动画
    @Override
    public void onRefreshBegin(PtrFrameLayout frame) {

        isLoadData = true;
        isRefresh = true;
        //下拉刷新的回调
        if (mUltraRefreshListener != null) {

            mUltraRefreshListener.onRefresh();
        }
    }

    @Override
    public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
        //  PtrHandler 的接口回调,是否能够加载数据的判断
        return !isLoadData && checkContentCanBePulledDown(frame, content, header);
    }

    // 从PtrHandler的默认实现类 PtrDefaultHandler中找到的,用以判断是否可以下拉刷新
    public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
        return !canChildScrollUp(content);

    }

    // 从PtrHandler的默认实现类 PtrDefaultHandler中找到的,用以判断是否可以下拉刷新
    public static boolean canChildScrollUp(View view) {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (view instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) view;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return view.getScrollY() > 0;
            }
        } else {
            return view.canScrollVertically(-1);
        }
    }

    /**
     * 设置ListView的监听回调
     */
    public void setUltraRefreshListener(UltraRefreshListener mUltraRefreshListener) {
        this.mUltraRefreshListener = mUltraRefreshListener;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        /*Log.i("info","isLoadData:"+isLoadData+" totalItemCount "+totalItemCount+" firstVisibleItem "+
                firstVisibleItem+" visibleItemCount "+ visibleItemCount);
*/
        //加载更多的判断
        if (totalItemCount > 1 && !isLoadData && totalItemCount == firstVisibleItem + visibleItemCount) {
            isRefresh = false;
            isLoadData = true;
            addFooterView(footView);
            mUltraRefreshListener.addMore();
        }
    }

    //刷新完成的后调用此方法还原布局
    public void refreshComplete() {
        isLoadData = false;
        if (isRefresh) {
            //获取其父控件,刷新
            ViewParent parent = getParent();
            if (parent instanceof PtrClassicFrameLayout) {
                ((PtrClassicFrameLayout) parent).refreshComplete();
            }
        } else {
            removeFooterView(footView);
        }
    }
}
4.4 附上底部加载更多布局 foot_ultra_refresh_listview.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="wrap_content"
              android:orientation="vertical">


    <TextView
        android:id="@+id/tv_load"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="10dp"
        android:text="正在为您加载更多"/>

    <Button
        android:id="@+id/btn_load_fail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="10dp"
        android:text="重新加载"
        android:visibility="gone"/>

</LinearLayout>
4.5 修改界面布局,用PtrClassicFrameLayout包裹自定义ListView
<?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="vertical">

    <in.srain.cube.views.ptr.PtrClassicFrameLayout
        android:id="@+id/ultra_ptr"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.wxyass.tpultraptr.RefreshList.view.UltraRefreshListView
            android:id="@+id/ultra_lv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </in.srain.cube.views.ptr.PtrClassicFrameLayout>

</LinearLayout>
4.6 修改代码,完成下列刷新,上拉加载的回调

修改上拉加载如图:

附上UseRefreshListActivity代码

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;

import com.wxyass.tpultraptr.RefreshList.refreshhead.CustomUltraRefreshHeader;
import com.wxyass.tpultraptr.RefreshList.view.UltraRefreshListView;
import com.wxyass.tpultraptr.RefreshList.view.UltraRefreshListener;

import java.util.ArrayList;

import in.srain.cube.views.ptr.PtrClassicFrameLayout;

/**
 * Created by wxyass.
 */
public class UseRefreshListActivity extends AppCompatActivity  {

    private PtrClassicFrameLayout mPtrFrame;

    private UltraRefreshListView mLv;

    ImgListAdapter adapter;
    ArrayList<String> imageUrl;

    int count = 1; // 起始位置
    int num = 5; // 每次下拉刷新加载几张图片,可自己修改
    int end = count+ num;// 刷新后的结束位置

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

        setContentView(R.layout.activity_listview_ultra);

        imageUrl = new ArrayList<String>();
        // 初始化数据
        getData(0);

        //查找控件
        mPtrFrame = ((PtrClassicFrameLayout) findViewById(R.id.ultra_ptr));

        mLv = ((UltraRefreshListView) findViewById(R.id.ultra_lv));

        //  自定义头部 下拉刷新时动画UI , 若不想用把下面3行代码注释掉

        //创建我们的自定义头部视图
        CustomUltraRefreshHeader header = new CustomUltraRefreshHeader(this);
        //设置头部视图
        mPtrFrame.setHeaderView(header);
        //设置视图修改的回调,因为我们的CustomUltraRefreshHeader实现了PtrUIHandler
        mPtrFrame.addPtrUIHandler(header);


        //设置数据刷新的会回调,这里需要传入一个PtrHandler, 因为UltraRefreshListView实现了PtrHandler
        mPtrFrame.setPtrHandler(mLv);

        adapter = new ImgListAdapter(imageUrl, getApplicationContext());
        mLv.setAdapter(adapter);



        /* PauseOnScrollListener这个类来控制ListView,GridView滑动过程中停止去加载图片.
        第一个参数就是我们的图片加载对象ImageLoader,
        第二个是控制是否在滑动过程中暂停加载图片,如果需要暂停传true就行了,
        第三个参数控制猛的滑动界面的时候图片是否加载。*/
        // 在自定义控件OnScrollListener已经对setOnScrollListener做了监听,用于加载更多,这里重写的话,加载更多就不能用了
        //mLv.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), false, true));

        // listview条目的点击事件
        mLv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent intent = new Intent(getApplicationContext(), ImageActivity.class);
                intent.putExtra("url", imageUrl.get(position));
                startActivity(intent);
            }
        });

        //设置上拉下拉的数据刷新回调接口
        mLv.setUltraRefreshListener(new UltraRefreshListener() {
            // 上拉加载
            @Override
            public void addMore() {
                mPtrFrame.postDelayed(new Runnable() {
                    @Override
                    public void run() {

                        // 获取新数据
                        count  = end;
                        end = count+num;
                        getData(1);
                        adapter.notifyDataSetChanged();
                        // 关闭下拉刷新的动画
                        stopResh();
                    }
                },1000);
            }
            // 下拉刷新
            @Override
            public void onRefresh() {
                mPtrFrame.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        // 获取新数据
                        count  = end;
                        end = count+num;
                        getData(0);
                        adapter.notifyDataSetChanged();
                        // 关闭下拉刷新的动画
                        stopResh();
                    }
                },1000);
            }


        });
    }

    String picName = "Pic_000000";
    // 获取新数据
    private void getData(int isRefresh) { // 0下拉刷新, 1上拉加载
        String imageString;
        for (int i = count; i < end; i++)
        {
            int length = String.valueOf(i).length();
            imageString = picName.substring(0,picName.length()-length)+i;
            imageString = "http://oss.wxyass.com/private/images/001/"+imageString+".jpg";
            if(0==isRefresh){
                imageUrl.add(0,imageString);// http://oss.wxyass.com/private/images/001/Pic_000076.jpg
            }else{
                imageUrl.add(imageString);
            }
        }
    }
    // 关闭动画(下拉刷新的动画)
    public void stopResh(){
        mLv.refreshComplete();
    }
} 
4.6 修改清单文件的Activity入口,重跑项目,查看效果.

4.7 效果如下,当加载完成,底部自动隐藏

五 以上,android-Ultra-Pull-To-Refresh的使用介绍基本这样了

本文项目源码

参考: 网上一些博客讲解
http://blog.csdn.net/yanzhenjie1003/article/details/53450488
http://blog.csdn.net/lisdye2/article/details/51449716
http://blog.csdn.net/sbvfhp/article/details/44959917
http://blog.csdn.net/mcy478643968/article/details/47401013
http://www.cnblogs.com/itgungnir/p/6211021.html
http://www.jianshu.com/p/543b4c110b88

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注