Android 我关于上拉加载,下拉刷新的处理方式(可以解决viewpager)

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

这几天要找实习了,有时候感觉自己真的是好笨啊,明明编程了两年半,但是想想自己好像真的是什么都不会,简历不知道写什么,真的是好心塞啊。最近总是想起周星驰的一句话 ”努力,奋斗“ (出自喜剧之王)

本来这一是一个完整的几乎可以解决所有的关系到列表加载的框架,我今天早上勉强完成,测试完了之后,下午已经没有心情投入继续完善注释的事情中了,所以这会先上传上来,(说实话,其实也是为了找实习,因为我现在要重新写我的简历了,实在是投入不进去写程序了。。。。看了好多招聘的信息,最后还是决定自己重新写一份简历,)

注意:这确实是一个功能比较完善的上拉加载 下拉刷新(本质上没有那么玄乎,几乎都只有关于touch事件的处理就可以完成)

功能:一般而言,一个app刚开始加载的时候会对于整个页面进行一些初始化工作,比如显示出 加载页面,消失加载失败,加载成功等页面。这个我提供了一个接口,叫做 firstInit() ...忽然写到这里我决定继续去完善我的代码,不能让我的成果被找实习的糟糕心态摧毁。。。


首先展示我的整个工具类,略微复杂,如果要使用的话 请复制使用吧,读别人的代码总是一件很痛苦的事情。

思路:重写onTouchEvent            重写 onInterruptTouchEvent控制事件,    两个AsyncTask的去执行,(一个负责开始加载,一个负责后续加载) 开始吧, 

这个能够解决的问题

1.页面初始化时需要做的工作,

2.可以加头部,但是一定要在设置完了init之后再加入

3.头部可以加入viewpager,不会被影响,我的项目中真实用到的。 interrupt主要是为了解决这个问题,我其实对于事件分发不是很用的熟。

4.几乎可以完成一整个页面处理所有加载的流程,包括下拉刷新 上拉加载 初始加载错误等。

好晚啊,但是我准备看看消息分发,。。。


这里上传上去我的代码,如果找到实习之后,我会对代码进行一次更新,写的更健壮,更好用,更便捷。

最后:个人感觉上拉加载,下拉刷新,说情况一样,其实每个人的情况又不同,希望我的思路能给与一点帮助。


package com.chunni.android.chunni.tool;


import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.chunni.android.chunni.R;

import java.util.concurrent.ExecutorService;


/**
 * Created by 蒲公英 on 2017/5/31.
 */

public class ListViewUpload5 extends ListView {

    private float TouchX;
    private float TouchY;
    private int mTouchSlop;
    // 上一次触摸时的X坐标
    private float mPrevX;

    /**
     * 定义一个结构体存储数据信息
     */
    public static class Info {
        public int size;
        public Object object;

        public Info(int size, Object object) {
            this.size = size;
            this.object = object;
        }
    }

    private boolean FIRST = true;

    //开始加载的标识符号
    private final int LOAD = 100002;
    //开始加载的标识符号
    private final int INIT = 100027;
    //如果加载失败,比如你去网上获取内容了,失败了
    private final int FAIL = 100007;

    //true为加载, false为刷新
    private boolean LOADACT = false;
    private boolean FLUSHACT = false;


    //一步加载几个
    private int STEP = 0;

    //附带自己线程池的初始化,就是你自己定义了线程池,让Ascnytask在你的里面运行

    /**
     * 附带自己线程池的初始化,就是你自己定义了线程池,让Ascnytask在你的里面运行
     *
     * @param doload    接口(因为要根据这里的进度去操作另外的页面)
     * @param STEP      一步加载的数量
     * @param executors 你自己定义的线程池
     */
    public void initInPool(Doload doload, int STEP, ExecutorService executors) {
        this.doload = doload;
        this.STEP = STEP;
        this.executors = executors;
        addFooter();
        addHeader();
        getScreenHeight();
    }

    //没有自己线程池的初始化

    /**
     * 初始化
     *
     * @param doload 接口
     * @param STEP   一步的数量
     */
    public void init(Doload doload, int STEP) {
        this.doload = doload;
        this.STEP = STEP;
        addFooter();
        addHeader();
        getScreenHeight();
    }

    private int SCREEN_HEIGHT;       // 屏幕高度(像素)

    //线程池
    private ExecutorService executors = null;

    //如果(数据加载失败)时候你的load应该返回这个值
    public static final int BADDATA = -1;

    //下面的转圈圈
    private ProgressBar pro = null;
    //上面的转圈圈
    private ProgressBar proHeader = null;

    //标记是不是还能进行加载
    private boolean CAN_LOAD = false;
    //标记是不是还能进行加载
    private boolean CAN_FLUSH = false;

    //起点
    private float startLoad = 0;
    private float startFlush = 0;

    //最大高度 测量得到
    private int highestFooter = 0;
    private int highestHeader = 200;

    //大小设置工具
    private LayoutParams layoutParamsFooter = null;
    private LayoutParams layoutParamsHeader = null;

    //底部布局
    private LinearLayout footer = null;

    //顶部
    private View header = null;
    private TextView txtHeader = null;
    private ImageView imgHeader = null;

    //底部文字
    private TextView txtFooter = null;

    public ListViewUpload5(Context context) {
        super(context);
    }

    public ListViewUpload5(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    /**
     * load :加载的时候执行的内容,返回值是这一次加载的数量,如果网络请求失败,没有加载到,返回BADDATA(-1)
     * finish:加载完成时候的操作,就是这里加载完了,那边要执行的操作
     */
    public interface Doload {
        void firstInit();

        Info firstLoad();

        void firstFail();

        void firstVictory(Info info);

        int load();

        void victory();

        void failure();
    }

    //实例化接口
    private Doload doload;

    /**
     * 主要的代码:说一下想法,如果CAN_LOAD为true,那么就去并且已经滑动到最后了,那么按下才有加载
     * 这个时候就已经确定了startLoad 也就是你开始的地方
     * 然后开始滑动了,你移动的时候,根据你上拉的程度,不断地刷新底部view的高度,
     * 之后手指松开的时候,如果松开的位置-开始的位置 大于 底部的高度,可以加载,然后去执行Asynctask
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("true毛线");
                if (LOADACT) {
                    if (startLoad > 0) {
                        float move = ev.getY() - startLoad;
                        if (move < 0 && move > -highestFooter) {
                            set_foot((int) -move);
                            txtFooter.setText("上拉加载");
                        }
                        if (move < -highestFooter) {
                            set_foot(highestFooter);
                            txtFooter.setText("松开加载");
                        }
                    }
                }
                if (FLUSHACT) {
                    System.out.println("true毛线");
                    if (startFlush > 0) {
                        float move = ev.getY() - startFlush;
                        if (move > 0) {
                            int height = (int) (SCREEN_HEIGHT * 7 / 24 * Math.sin(2 * Math.PI / (4 * SCREEN_HEIGHT) * move));
                            set_head(height);
                            if (height < highestHeader) {
                                txtHeader.setText("下拉加载...");
                                imgHeader.setImageResource(R.mipmap.down);
                            } else {
                                txtHeader.setText("松开加载...");
                                imgHeader.setImageResource(R.mipmap.up);
                            }
                            return false;
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (LOADACT) {
                    if (startLoad > 0) {
                        if ((ev.getY() - startLoad) < -highestFooter) {
                            if (this.executors == null) {
                                new MyAsyncTask().execute();
                            } else {
                                new MyAsyncTask().executeOnExecutor(this.executors);
                            }
                        }
                    }
                }
                if (FLUSHACT) {
                    float move = ev.getY() - startFlush;
                    int height = (int) (SCREEN_HEIGHT * 7 / 24 * Math.sin(2 * Math.PI / (4 * SCREEN_HEIGHT) * move));
                    if (height > highestHeader) {
                        replyView((float) (SCREEN_HEIGHT * 7 / 24 * Math.sin(2 * Math.PI / (4 * SCREEN_HEIGHT) * (ev.getY() - startFlush))), highestHeader);
                    } else {
                        replyView((float) (SCREEN_HEIGHT * 7 / 24 * Math.sin(2 * Math.PI / (4 * SCREEN_HEIGHT) * (ev.getY() - startFlush))), 1);
                    }
                }
                startFlush = 0;
                startLoad = 0;
                FLUSHACT = false;
                LOADACT = false;
                break;
        }
        boolean temp = super.onTouchEvent(ev);
        System.out.println(temp);
        return temp;
    }

    public void run() {
        if (FIRST)
            doload.firstInit();
        imgHeader.setVisibility(GONE);
        new MyFirstAsyncTask().execute();
    }

    /**
     * 回弹
     */
    private void replyView(final float distance, final int origin) {
        int length = (int) (distance - origin);
        long time = length > 0 ? length * 2 : -length * 2;
        System.out.println(distance - origin);
        // 设置动画
        ValueAnimator anim = ObjectAnimator.ofFloat(distance - origin, 0.0F).setDuration(time);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                System.out.println((int) (origin + (Float) animation.getAnimatedValue()));
                set_head((int) (origin + (Float) animation.getAnimatedValue()));
            }
        });
        anim.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (origin == highestHeader)
                    run();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        anim.start();
    }


    /**
     * 设置底部高度为1,解决了好多设置不可见引起的问题。。
     *
     * @param height
     */
    private void set_foot(int height) {
        layoutParamsFooter.height = height;
        footer.setLayoutParams(layoutParamsFooter);
        Log.i("xjxu", height + " " + footer.getHeight());
    }

    /**
     * 设置底部高度为1,解决了好多设置不可见引起的问题。。
     *
     * @param height
     */
    private void set_head(int height) {
        layoutParamsHeader.height = height;
        header.setLayoutParams(layoutParamsHeader);
    }

    /**
     * 自定义的异步任务
     */
    private class MyAsyncTask extends AsyncTask<Void, Integer, Integer> {

        @Override
        protected Integer doInBackground(Void... objects) {
            publishProgress(LOAD);
            int sign = doload.load();
            if (sign == -1) {
                publishProgress(FAIL);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return sign;
        }

        @Override
        protected void onProgressUpdate(Integer... pros) {
            super.onProgressUpdate(pros);
            switch (pros[0]) {
                case LOAD:
                    CAN_LOAD = false;
                    startLoad = 0;
                    set_foot(highestFooter);
                    txtFooter.setText("加载中");
                    break;
                case FAIL:
                    set_foot(highestFooter);
                    pro.setVisibility(GONE);
                    txtFooter.setText("请检查你的网络连接");
                    break;
            }
        }

        @Override
        protected void onPostExecute(Integer count) {
            super.onPostExecute(count);
            if (count == STEP || count == BADDATA) {
                pro.setVisibility(VISIBLE);
                CAN_LOAD = true;
                set_foot(1);
                return;
            } else {
                CAN_LOAD = false;
                set_foot(highestFooter);
                txtFooter.setText("已经没有更多内容了");
                pro.setVisibility(GONE);
            }
            doload.victory();
        }
    }

    /**
     * 自定义的异步任务
     */
    private class MyFirstAsyncTask extends AsyncTask<Void, Integer, Info> {

        @Override
        protected Info doInBackground(Void... objects) {
            publishProgress(INIT);
            CAN_FLUSH = false;
            Info info = doload.firstLoad();
            return info;
        }

        @Override
        protected void onProgressUpdate(Integer... pros) {
            super.onProgressUpdate(pros);
            switch (pros[0]) {
                case INIT:
                    txtHeader.setText("正在加载...");
                    imgHeader.setVisibility(GONE);
                    System.out.println("不算数?");
                    proHeader.setVisibility(VISIBLE);
                    break;
            }
        }

        @Override
        protected void onPostExecute(Info info) {
            set_head(1);
            System.out.println("结束了");
            imgHeader.setVisibility(VISIBLE);
            proHeader.setVisibility(GONE);
            super.onPostExecute(info);
            CAN_FLUSH = true;
            if (info.size == BADDATA) {
                if (FIRST)
                    doload.firstFail();
                else {
                    doload.failure();
                }
                return;
            } else if (info.size == STEP) {
                CAN_LOAD = true;
                set_foot(1);
                pro.setVisibility(VISIBLE);
            } else {
                CAN_LOAD = false;
                set_foot(highestFooter);
                txtFooter.setText("已经没有更多内容了");
                pro.setVisibility(GONE);
            }
            doload.firstVictory(info);
            FIRST = false;
        }
    }

    /**
     * 添加底部
     */
    private void addFooter() {
        footer = (LinearLayout) LayoutInflater.from(this.getContext()).inflate(R.layout.listview_footer, null);
        //最后一次参数的意思是 是不是可以选择到,如果是true,点了会触发onitemclick
        this.addFooterView(footer, null, false);
        this.setFooterDividersEnabled(false);
        txtFooter = footer.findViewById(R.id.txt);
        pro = footer.findViewById(R.id.pro);
        //测量
        footer.measure(0, 0);
        highestFooter = footer.getMeasuredHeight();
        //初始化param
        layoutParamsFooter = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
        set_foot(1);
    }

    void addHeader() {
        header = LayoutInflater.from(this.getContext()).inflate(R.layout.listview_header, null);
        txtHeader = header.findViewById(R.id.txt);
        imgHeader = header.findViewById(R.id.img);
        //最后一次参数的意思是 是不是可以选择到,如果是true,点了会触发onitemclick
        addHeaderView(header);
        //测量
        header.measure(0, 0);
        //highestHeader = header.getMeasuredHeight();
        highestHeader = dp2px(100);
        layoutParamsHeader = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
        proHeader = header.findViewById(R.id.pro);
        set_head(1);
    }

    /**
     * convert dp to its equivalent px
     */
    protected int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    /**
    /**
     * convert sp to its equivalent px
     */
    protected int sp2px(int sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
    }

    private void getScreenHeight() {
        WindowManager wm = (WindowManager) this.getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        SCREEN_HEIGHT = dm.heightPixels;
    }

//    @Override
//    public boolean onInterceptTouchEvent(MotionEvent ev) {
//            switch (ev.getAction()) {
//                case MotionEvent.ACTION_DOWN:
//                    TouchX = ev.getX();
//                    TouchY = ev.getY();
//                    break;
//                case MotionEvent.ACTION_MOVE:
//                    if (Math.abs(ev.getY() - TouchY) > Math.abs(ev.getX() - TouchX)) {
//                        return true;
//                    } else {
//                        return false;
//                    }
//                case MotionEvent.ACTION_UP:
//                    TouchX = 0;
//                    TouchY = 0;
//                    break;
//            }
//            return true;
//    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mPrevX = event.getX();
                if (this.getFirstVisiblePosition() == 0 && CAN_FLUSH) {
                    startFlush = event.getY();
                    FLUSHACT = true;
                    break;
                }
                if (this.getLastVisiblePosition() == this.getCount() - 1 && CAN_LOAD) {
                    startLoad = event.getY();
                    LOADACT = true;
                }
                break;

            case MotionEvent.ACTION_MOVE:
                final float eventX = event.getX();
                float xDiff = Math.abs(eventX - mPrevX);
                // Log.d("refresh" ,"move----" + eventX + "   " + mPrevX + "   " + mTouchSlop);
                // 增加60的容差,让下拉刷新在竖直滑动时就可以触发
                if (xDiff > mTouchSlop + 60) {
                    return false;
                }
        }
        return super.onInterceptTouchEvent(event);
    }
}

上面是工具类。下面是我用到的两个布局,

listview_footer.xml

代码如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:layout_height="40dp"
        android:background="#dadada"
        android:gravity="center">
        <ProgressBar
            android:layout_width="27dp"
            android:layout_height="27dp"
            android:layout_marginRight="5dp"
            android:id="@+id/pro"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:textSize="13sp"
            android:textStyle="bold"
            android:textColor="#9c9c9c"
            android:gravity="center"
            android:id="@+id/txt"
            android:text="正在刷新..."/>
    </LinearLayout>
</LinearLayout>


listview_header.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">
    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:background="#ffffff"
        android:gravity="center"
        android:layout_height="match_parent">
        <RelativeLayout
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginRight="10dp">
            <ImageView
                android:layout_width="match_parent"
                android:id="@+id/img"
                android:src="@mipmap/up"
                android:scaleType="fitCenter"
                android:layout_height="match_parent" />
            <ProgressBar
                android:layout_width="match_parent"
                android:visibility="gone"
                android:id="@+id/pro"
                android:layout_height="match_parent" />
        </RelativeLayout>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:id="@+id/txt"
            android:text="下拉刷新..."/>
    </LinearLayout>
</LinearLayout>

下面是我在项目中使用的实例(更改了url)

接口说明:

firstInit:第一次加载时候要做的操作,比如隐藏成功,失败页面,显示加载页面。

firstLoad:第一次加载,事实上是发送网络请求,之所以第一次加载,是因为一般第一次加载包含头部数据。返回值是我自己定义的Info,构造函数是 一个数字(加载数据的长度,如果出错了,写为-1),一个object(这是你的加载的数据,不用管是什么,直接传入,无所谓是什么格式,jsonArray 或者 arrayList,但是一般而言都是一组数据)

firstFail:第一次失败之后,比如显示失败页面

firstVictory:第一次成功之后,第一次成功执行的是与别的时候不同的代码,大家都懂。一定记得初始化一次你的adapter 匹配数据。

load:正常加载时候的代码。返回加载到数据的条数,老规矩,如果失败了 -1;

victory:加载成功时候,要刷新数据,比如你还想Toast一下。

failure:加载失败时,比如你想Toast一下

最后一个init参数,你一次想加载多少个。。。

listViewUpload.setAdapter(uploadAdapter = new UploadAdapter());
        listViewUpload.init(new ListViewUpload5.Doload() {

            @Override
            public void firstInit() {
                MyInit();
            }

            @Override
            public ListViewUpload5.Info firstLoad() {
                String result = new NetWorkOut().outPost(App.SERVER + "article/artList?" +
                        "userId=" + App.USERPHONE +
                        "&start=" + 0 +
                        "&end=" + 5);
                System.out.println(result + " result");
                if (result == null)
                    return new ListViewUpload5.Info(-1, result);
                headers = jsonParser.parse(result).getAsJsonObject().get("data_header").getAsJsonArray();
                JsonArray jsonArray = jsonParser.parse(result)
                        .getAsJsonObject()
                        .get("data_upload")
                        .getAsJsonArray();
                return new ListViewUpload5.Info(jsonArray.size(), jsonArray);
            }

            @Override
            public void firstFail() {
                MyFailure();
            }

            @Override
            public void firstVictory(ListViewUpload5.Info info) {
                uploads = new JsonArray();
                JsonArray jsonArray = (JsonArray) info.object;
                System.out.println(jsonArray.size() + "出错了???");
                uploads.addAll(jsonArray);
                MyVictory();
            }

            @Override
            public int load() {
                String result = new NetWorkOut().outPost(App.SERVER + "article/artListSecond?" +
                        "userId=" + App.USERPHONE +
                        "&start=" + uploads.size() +
                        "&end=" + (uploads.size() + 5));
                if (result == null)
                    return -1;
                JsonArray  jsonArray = jsonParser.parse(result)
                        .getAsJsonObject()
                        .get("data_upload")
                        .getAsJsonArray();
                uploads.addAll(jsonArray);
                return jsonArray.size();
            }

            @Override
            public void victory() {
                uploadAdapter.notifyDataSetChanged();
            }

            @Override
            public void failure() {

            }
        }, 5);
一定要会写adapter,自己定义,数据不归这个工具管,工具只管你的加载过程,始末。

另外 我用到了两张图, 分别是 up.jpg down.jpg 请下载拷贝到你的mipmap,我用的是Android studio.




图片有点打
        listViewUpload.run();
做完了activity 初始化之后,记得执行一下这句话,会自动开启第一次加载。
阅读更多
换一批

没有更多推荐了,返回首页