最近不忙,想到自己用到的PullToRefreshListView,就自己写着看看,之前都是用现成的,主要还是知道原理吧,
借鉴的一篇文章ListView下拉刷新,上拉自动加载更多
这篇文章写的很清楚,我就不重复了,直接贴代码吧
activity_mainxml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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.shop.hsz88.autolistviewdemo.MainActivity">
<com.shop.hsz88.autolistviewdemo.AutoListView
android:id="@+id/atlv"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
MainActiviyt.java
package com.shop.hsz88.autolistviewdemo;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private Context mContext;
private String tag;
private ArrayList<String> datas;
private AutoListView aulv;
private MyAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext=this;
tag="MainActivity";
initView();
initData();
}
private void initView() {
aulv = (AutoListView) findViewById(R.id.atlv);
}
private void initData() {
datas=new ArrayList<String>();
//添加数据
for (int i=0;i<20;i++){
datas.add("第"+i+"条数据");
}
myAdapter = new MyAdapter(mContext, datas);
aulv.setAdapter(myAdapter);
}
}
重点:自定义ListView
package com.shop.hsz88.autolistviewdemo;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.TextView;
/**
* Created by Administrator on 2017/11/16.
*/
public class AutoListView extends ListView implements AbsListView.OnScrollListener {
private String tag = "AutoListView";
// 定义header的四种状态和当前状态
private static final int NONE = 0;
private static final int PULL = 1;
private static final int RELEASE = 2;
private static final int REFRESHING = 3;
private int state;
// 区分PULL和RELEASE的距离的大小
private static final int SPACE = 50;
//头部顶部内边距
private int headerContentInitialHeight;
//头布局实际高度,而不是显示的高度
private int headerContentHeight;
//记录手指滑动ListView的状态
private int scrollState;
private int firstVisibleItem;
private View header_view;
private View foot_view;
//加一个标记,记录显示的第一个item是否是数据源的第一个
private boolean isFist = false;
//记录手指刚按下的位置
private float startY;
public AutoListView(Context context) {
super(context);
initView(context);
}
public AutoListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public AutoListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
//初始化头布局和尾布局
private void initView(Context context) {
//1.获取头布局
header_view = View.inflate(context, R.layout.layout_header, null);
/**
* 重点
*/
//获取头部的顶部内边距
headerContentInitialHeight = header_view.getPaddingTop();
//计算header_view的大小
measureView(header_view);
//获取header的测量高度(实际高度,另一个高度是显示高度(getHeight))
headerContentHeight = header_view.getMeasuredHeight();
//打印 38
Log.i(tag, "头布局测量高度==:" + headerContentHeight);
//将头布局顶部内边距设置为负让其不显示出来
topPadding(header_view, -headerContentHeight);
//2.获取脚布局
foot_view = View.inflate(context, R.layout.layout_foot, null);
//3.将头布局和脚布局添加到ListView中去,添加到第1条数据的前面和最后一条数据的后面
this.addHeaderView(header_view);
this.addFooterView(foot_view);
//一开始为初始状态
state = NONE;
//让头布局和脚布局都不显示
// header_view.setVisibility(GONE);
// foot_view.setVisibility(GONE);
//设置滑动监听
this.setOnScrollListener(this);
}
/**
* *监听着ListView的滑动状态改变。官方的有三种状态SCROLL_STATE_TOUCH_SCROLL、SCROLL_STATE_FLING、SCROLL_STATE_IDLE:
* SCROLL_STATE_TOUCH_SCROLL:手指正拖着ListView滑动
* SCROLL_STATE_FLING:ListView正自由滑动
* SCROLL_STATE_IDLE:ListView滑动后静止
*
* @param view
* @param scrollState
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//记录手指滑动的状态
this.scrollState = scrollState;
}
/**
* @param view
* @param firstVisibleItem 表示在屏幕中第一条显示的数据在adapter中的位置
* @param visibleItemCount 则表示屏幕中最后一条数据在adapter中的数据,
* @param totalItemCount 则是在adapter中的总条数
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//记录屏幕上显示的第一条数据在数据源的位置
this.firstVisibleItem = firstVisibleItem;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN://手指按下
//先判断显示的第一个item是不是数据源的第一个
if (firstVisibleItem == 0) {
isFist = true;
//记录手指按下y方向的位置
startY = ev.getY();
}
break;
case MotionEvent.ACTION_MOVE://手指滑动
//根据移动距离去切换头布局的状态
//每移动一像素就调用一次
whenMove(ev);
break;
//当用户保持按下操作,并从你的控件转移到外层控件时,会触发ACTION_CANCEL
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
//手指抬起或者移到控件外时,让头布局隐藏,或者执行刷新操作
if (state == PULL) {
//移动距离太小,执行隐藏
state = NONE;
Log.i(tag, "onTouchEvent None");
refreshHeaderViewByState();
} else if (state == RELEASE) {
//执行刷新操作,刷新完后要隐藏掉
state = REFRESHING;
refreshHeaderViewByState();
Log.i(tag, "onTouchEvent REFeeshing");
//在这里写获取数据请求
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 手指滑动走一遍的顺序为 12 4 32
* 获取手势状态,根据状态不同显示不同效果
*
* @param ev
*/
private void whenMove(MotionEvent ev) {
//显示的数据是否是数据源的第一个,不是直接返回
if (!isFist) {
return;
}
//手指移动的y方向位置
int tmpY = (int) ev.getY();
//计算位置差
int space = (int) (tmpY - startY);
Log.i(tag,"手指移动位置差==:"+space);
//根据手指移动距离减去头布局初始高度,来设置头布局的高度
int topPadding = space - headerContentHeight;
switch (state) {
case NONE:
if (space > 0) {
state = PULL;
//下拉刷新
refreshHeaderViewByState();
Log.i(tag, "状态:1");
}
break;
case PULL:
//将头部显示出来,动态显示,参数2根据手指滑动距离而改变
topPadding(header_view, topPadding);
//当处于手指正拖着ListView滑动,且滑动的距离大于头布局距离与设置距离大小时,就可以进行刷新操作
if (scrollState == SCROLL_STATE_TOUCH_SCROLL && space > headerContentHeight + SPACE) {
state = RELEASE;
//头布局显得是内容,松手刷新
refreshHeaderViewByState();
Log.i(tag, "状态2");
}
break;
//刷新操作
case RELEASE:
//设置头布局内边距
topPadding(header_view, topPadding);
Log.i(tag, "状态4");
//拉动距离小,就不刷新
if (space > 0 && space < headerContentHeight + SPACE) {
state = PULL;
//下拉刷新
refreshHeaderViewByState();
Log.i(tag, "状态3");
} else if (space <= 0) {
//回到初始位置
state = NONE;
refreshHeaderViewByState();
}
break;
}
}
/**
* 头部页面显示效果
*/
// 根据当前状态,调整header
private void refreshHeaderViewByState() {
switch (state) {
//初始状态,啥都没做,
case NONE:
//调整头部的大小
topPadding(header_view, -headerContentHeight);
((TextView) header_view.findViewById(R.id.tv_content)).setText("下拉刷新");
break;
case PULL:
//下拉状态,此时松开会还原到状态NONE,并不进行刷新
((TextView) header_view.findViewById(R.id.tv_content)).setText("下拉刷新");
//让其显示出来
header_view.findViewById(R.id.tv_content).setVisibility(VISIBLE);
break;
case RELEASE:
//同样是下拉状态,但此刻松开会执行刷新,进入状态REFRESHING
((TextView) header_view.findViewById(R.id.tv_content)).setText("松手刷新");
break;
case REFRESHING:
//正在执行刷新操作,
// 刷新结束后进入状态NONE。
//headerContentInitialHeight,为0
topPadding(header_view, headerContentInitialHeight);
Log.i(tag, "刷新结束==:" + headerContentInitialHeight);
//隐藏掉,不让其显示
((TextView) header_view.findViewById(R.id.tv_content)).setText("正在刷新");
/**
* 回到初始位置,如果不写咋只能刷新一次,延时回到刷新完成
*/
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
state = NONE;
refreshHeaderViewByState();
}
}, 3 * 1000);
break;
}
}
/**
* 用来计算header大小的。比较隐晦。因为header的初始高度就是0,貌似可以不用。
* 计算一个控件的大小
*
* @param child 需要计算的控件
*/
private void measureView(View child) {
//获取工具的配置参数对象
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
//如果p为空则创建一个,宽度匹配父布局,高度包裹内容
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
/**
* 计算子布局尺寸的最主要部分:
* 为特定子控件计算出它的MeasureSpec,(测量说明)
* 这个方法为一个子布局的任一一个维度(高度/宽度)计算出正确的MeasureSpec。
*/
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
/**
* 调整一个控件的内边距,距离顶部的距离
*
* @param view 需要调整的控件
* @param topPadding 顶部距离
*/
private void topPadding(View view, int topPadding) {
//左。上。右,下
view.setPadding(view.getPaddingLeft(), topPadding,
view.getPaddingRight(),
view.getPaddingBottom());
view.invalidate();
}
}
头布局
<?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">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="头布局" />
</LinearLayout>
item布局
<?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">
<TextView
android:id="@+id/tv_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="测试数据"
android:textSize="40sp"/>
</LinearLayout>