案例演示:
GIF.gif
关键点:
- 通过设置Padding为负数来隐藏头/脚布局;
- 通过公式int paddingTop=-自身测量高度+(moveY-downY);
- 通过偏移量=moveY-downY >0 从而判断是下拉
- 通过设置标记位,来判断是否能加载更多;
- 通过判断最后一个可见条目为空闲状态并且是刚好等于集合size,且标记位满足条件;
实现思路:
实现步骤:
View mHeaderView = View.inflate(getContext(), R.layout.layout_headview, null);
// 提前手动测量宽高
mHeaderView.measure(0, 0);// 按照设置的规则测量
mHeaderViewHeight = mHeaderView.getMeasuredHeight();
// 设置内边距, 可以隐藏当前控件 , -自身高度
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
// 在设置数据适配器之前执行添加 头布局/脚布局 的方法.
addHeaderView(mHeaderView);
- 3.下拉头部显示设置(不断改变修改paddingTop)
- 4.监听触摸动作
//公式:
int paddingTop=-自身测量高度+(moveY-downY)
//触摸监听:
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 判断滑动距离, 给Header设置paddingTop
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
moveY = ev.getY();
// 如果是正在刷新中, 就执行父类的处理
if(currentState == REFRESHING){
return super.onTouchEvent(ev);
}
float offset = moveY - downY; // 移动的偏移量
// 只有 偏移量>0, 并且当前第一个可见条目索引是0, 才放大头部
if(offset > 0 && getFirstVisiblePosition() == 0){
int paddingTop = (int) (- mHeaderViewHeight + offset);
mHeaderView.setPadding(0, paddingTop, 0, 0);
if(paddingTop >= 0 && currentState != RELEASE_REFRESH){// 头布局完全显示
// 切换成释放刷新模式
currentState = RELEASE_REFRESH;
updateHeader(); // 根据最新的状态值更新头布局内容
}else if(paddingTop < 0 && currentState != PULL_TO_REFRESH){ // 头布局不完全显示
// 切换成下拉刷新模式
currentState = PULL_TO_REFRESH;
updateHeader(); // 根据最新的状态值更新头布局内容
}
return true; // 当前事件被我们处理并消费
}
break;
case MotionEvent.ACTION_UP:
// 根据刚刚设置状态
if(currentState == PULL_TO_REFRESH){
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
}else if(currentState == RELEASE_REFRESH){
mHeaderView.setPadding(0, 0, 0, 0);
currentState = REFRESHING;
updateHeader();
}else{
//防止“下拉刷新”和“加载更多”一起出现:
if (currentState==REFRESHING && isLoadMore){
currentState=PULL_TO_REFRESH;
}
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
public interface RefreshListener{
void onRefresh();
void onLoadMore();
}
public void setOnRefreshListener(RefreshListener refreshListener){
this.listener=refreshListener;
}
public class RefreshListView extends ListView implements AbsListView.OnScrollListener {
private View mHeaderView,mFooterView; // 头布局,脚布局
private float downY; // 按下的y坐标
private float moveY; // 移动后的y坐标
private int mHeaderViewHeight,mFooterViewHeight; // 头布局高度,脚布局高度
public static final int PULL_TO_REFRESH = 0;// 下拉刷新
public static final int RELEASE_REFRESH = 1;// 释放刷新
public static final int REFRESHING = 2; // 刷新中
private boolean isLoadMore=false;
private int currentState = PULL_TO_REFRESH; // 当前刷新模式
private RotateAnimation rotateUpAnim; // 箭头向上动画
private RotateAnimation rotateDownAnim; // 箭头向下动画
private View mArrowView; // 箭头布局
private TextView mTitleText,mTimeText; // 头布局标题
private ProgressBar pb; // 进度指示器
private RefreshListener listener;
public RefreshListView(Context context) {
super(context);
init();
}
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* 初始化头布局, 脚布局
* 滚动监听
*/
private void init() {
initHeaderView();
initFooterView();
initAnimation();
setOnScrollListener(this);
}
/**
* 初始化头布局的动画
*/
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, -360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
rotateDownAnim.setDuration(300);
rotateDownAnim.setFillAfter(true); // 动画停留在结束位置
}
/**
* 初始化头布局
*/
private void initHeaderView() {
mHeaderView = View.inflate(getContext(), R.layout.layout_headview, null);
mArrowView = mHeaderView.findViewById(R.id.iv_arrow);
pb = mHeaderView.findViewById(R.id.pb);
mTitleText = mHeaderView.findViewById(R.id.tv_title);
mTimeText=mHeaderView.findViewById(R.id.tv_time);
// 提前手动测量宽高
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_footview, null);
mFooterView.measure(0,0);
mFooterViewHeight=mFooterView.getMeasuredHeight();
mFooterView.setPadding(0,-mFooterViewHeight,0,0);
addFooterView(mFooterView);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 判断滑动距离, 给Header设置paddingTop
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
moveY = ev.getY();
// 如果是正在刷新中, 就执行父类的处理
if(currentState == REFRESHING){
return super.onTouchEvent(ev);
}
float offset = moveY - downY; // 移动的偏移量
// 只有 偏移量>0, 并且当前第一个可见条目索引是0, 才放大头部
if(offset > 0 && getFirstVisiblePosition() == 0){
int paddingTop = (int) (- mHeaderViewHeight + offset);
mHeaderView.setPadding(0, paddingTop, 0, 0);
if(paddingTop >= 0 && currentState != RELEASE_REFRESH){// 头布局完全显示
// 切换成释放刷新模式
currentState = RELEASE_REFRESH;
updateHeader(); // 根据最新的状态值更新头布局内容
}else if(paddingTop < 0 && currentState != PULL_TO_REFRESH){ // 头布局不完全显示
// 切换成下拉刷新模式
currentState = PULL_TO_REFRESH;
updateHeader(); // 根据最新的状态值更新头布局内容
}
return true; // 当前事件被我们处理并消费
}
break;
case MotionEvent.ACTION_UP:
// 根据刚刚设置状态
if(currentState == PULL_TO_REFRESH){
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
}else if(currentState == RELEASE_REFRESH){
mHeaderView.setPadding(0, 0, 0, 0);
currentState = REFRESHING;
updateHeader();
}else{
//防止“下拉刷新”和“加载更多”一起出现:
if (currentState==REFRESHING && isLoadMore){
currentState=PULL_TO_REFRESH;
}
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
/**
* 根据状态更新头布局内容
*/
private void updateHeader() {
switch (currentState) {
case PULL_TO_REFRESH: // 切换回下拉刷新
// 做动画, 改标题
mArrowView.startAnimation(rotateDownAnim);
mTitleText.setText("下拉刷新");
break;
case RELEASE_REFRESH: // 切换成释放刷新
// 做动画, 改标题
mArrowView.startAnimation(rotateUpAnim);
pb.setVisibility(INVISIBLE);
mTitleText.setText("释放刷新");
break;
case REFRESHING: // 刷新中...
mArrowView.clearAnimation();
mArrowView.setVisibility(View.INVISIBLE);
pb.setVisibility(View.VISIBLE);
mTitleText.setText("正在刷新中...");
//
mTimeText.setText("最后更新时间:"+getTime());
if (listener!=null){
listener.onRefresh();
}
break;
default:
break;
}
}
private String getTime() {
long currentTimeMillis = System.currentTimeMillis();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(currentTimeMillis);
}
public void endRefreshing(){
if (isLoadMore){
//关闭加载更多:
isLoadMore=false;
mFooterView.setPadding(0,-mFooterViewHeight,0,0);
}else{
//关闭正在刷新:
mHeaderView.setPadding(0,-mHeaderViewHeight,0,0);
mHeaderView.clearAnimation();
currentState=PULL_TO_REFRESH;
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState==SCROLL_STATE_FLING || scrollState==SCROLL_STATE_IDLE ){
//在最后一个条目 且为空闲状态时候才加载更多
if (getLastVisiblePosition()==getCount()-1 && isLoadMore==false){
isLoadMore=true;
//脚布局显示:
mFooterView.setPadding(0,0,0,0);
setSelection(getCount());//跳转到最后一行,使其显示加载更多
//加载更多:
if (listener!=null){
listener.onLoadMore();
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
public interface RefreshListener{
void onRefresh();
void onLoadMore();
}
public void setOnRefreshListener(RefreshListener refreshListener){
this.listener=refreshListener;
}
}
网友评论