这几天要找实习了,有时候感觉自己真的是好笨啊,明明编程了两年半,但是想想自己好像真的是什么都不会,简历不知道写什么,真的是好心塞啊。最近总是想起周星驰的一句话 ”努力,奋斗“ (出自喜剧之王)
本来这一是一个完整的几乎可以解决所有的关系到列表加载的框架,我今天早上勉强完成,测试完了之后,下午已经没有心情投入继续完善注释的事情中了,所以这会先上传上来,(说实话,其实也是为了找实习,因为我现在要重新写我的简历了,实在是投入不进去写程序了。。。。看了好多招聘的信息,最后还是决定自己重新写一份简历,)
注意:这确实是一个功能比较完善的上拉加载 下拉刷新(本质上没有那么玄乎,几乎都只有关于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 初始化之后,记得执行一下这句话,会自动开启第一次加载。