原本一直是用前辈写的一个PullToRefresh控件,但是有些地方和需求不符,所以对其进行了重写,下面展示重写后的代码:
最主要的改动就是一个页面,里面出现了PullToRefresh控件,控件里面又包含了Viewpager,这时,由于PullToRefresh的onInterceptTouchEvent的拦截事件,导致Viewpager左右滑动效果并不理想,所以需要对其进行修改,另外就是上下拉动的速率.
package com.cxd.pulltorefresh;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import java.util.IllegalFormatException;
/**
* @author cxd
* @ClassName: com.cxd.pulltorefresh
* @Description:
* @date 2015/12/16
*/
public class PullToRefresh extends LinearLayout {
private String TAG = "PullToRefresh";
//顶部或底部视图控件刷新状态
//拉动去刷新
private static final int PULL_TO_REFRESH = 2;
//释放去刷新
private static final int RELEASE_TO_REFRESH = 3;
//正在刷新中
private static final int REFRESHING = 4;
// 当前刷新控件状态
// 向上拉
private static final int PULL_UP_STATE = 0;
// 向下拉
private static final int PULL_DOWN_STATE = 1;
// 布局填充器
private LayoutInflater mInflater;
// 头部视图
private View mHeaderView;
// 头部视图高度
private int mHeaderViewHeight;
// 底部视图
private View mFooterView;
// 底部视图高度
private int mFooterViewHeight;
// 需适配view
private AdapterView<?> mAdapterView;
// 滚动view
private ScrollView mScrollView;
// y的最后距离
private int mLastMotiony;
// 头部刷新是否可用
private boolean isHeadable;
// 底部刷新是否可用
private boolean isFootable;
// 当前的拉动状态
private int mPullState;
//头部当前所处状态
private int mHeadState;
//底部当前所处状态
private int mFootState;
//头部刷新有效度(触发刷新向下的拉动距离)
private float HeaderPower = 0.3f;
//底部刷新有效度(触发刷新向上的拉动距离)
private float FooterPower = 0.3f;
//头部刷新事件
private onHeaderRefreshListener onHeaderRefreshListener;
//底部刷新事件
private onFooterRefreshListener onFooterRefreshListener;
public PullToRefresh(Context context) {
super(context);
init();
}
public PullToRefresh(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PullToRefresh(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化
*/
private void init() {
mInflater = LayoutInflater.from(getContext());
addHeaderView();
}
/**
* 加载xml文件完成的时候触发
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
addFooterView();
initContentAdapterView();
}
/**
* 添加头部视图
*/
private void addHeaderView() {
// 获取头部视图
mHeaderView = mInflater.inflate(R.layout.refresh_header, this, false);
// 计算头部视图宽高
measureView(mHeaderView);
// 设置头部视图所在位置
mHeaderViewHeight = mHeaderView.getMeasuredHeight();
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeaderViewHeight);
params.topMargin = -(mHeaderViewHeight);
Log.e(TAG, params.topMargin + "");
addView(mHeaderView, params);
}
/**
* 增加底部视图
*/
private void addFooterView() {
mFooterView = mInflater.inflate(R.layout.refresh_footer, this, false);
measureView(mFooterView);
mFooterViewHeight = mFooterView.getMeasuredHeight();
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mFooterViewHeight);
addView(mFooterView, params);
}
/**
* 初始化内容,分为两种情况,一种是AdaptervIEW,一种是ScrollView
*/
private void initContentAdapterView() {
int count = getChildCount();
// 如果包含的子视图小于3个,则说明没有里面没有内容,报异常
if (count < 3) {
throw new IllegalArgumentException("The Layout must contains 3 Views");
}
View view = null;
for (int i = 0; i < count; i++) {
view = getChildAt(i);
if (view instanceof AdapterView<?>) {
mAdapterView = ((AdapterView<?>) view);
}
if (view instanceof ScrollView) {
mScrollView = ((ScrollView) view);
}
}
// 内容必须是adapterView和ScrollView中的一种
if (mAdapterView == null && mScrollView == null) {
throw new IllegalArgumentException("The Content of Layout must instanceof AdapterView/ScrollView");
}
}
/**
* 计算控件的高宽
*
* @param view
*/
private void measureView(View view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
if (params == null) {
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT
, ViewGroup.LayoutParams.WRAP_CONTENT);
}
// 获取宽度(外间距,内间距,view的宽度)
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, params.width);
int pHeight = params.height;
int childHeightSpec;
// MeasureSpec 它有三种模式:
// UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小
// EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小
// AT_MOST(至多),子元素至多达到指定大小的值
if (pHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(pHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
view.measure(childWidthSpec, childHeightSpec);
}
/**
* 拦截触摸事件
*
* @param e
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
int y = (int) e.getRawY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:// 按下时
Log.e(TAG, "onInterceptTouchEvent-->ACTION_DOWN");
mLastMotiony = y;
break;
case MotionEvent.ACTION_MOVE:// 移动时
Log.e(TAG, "onInterceptTouchEvent-->ACTION_MOVE");
int deltaY = y - mLastMotiony;
return isRefreshViewScroll(deltaY);
}
return false;
}
/**
* 触发事件
*
* @param e
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent e) {
int y = (int) e.getRawY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent-->ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent-->ACTION_MOVE");
int deltaY = y - mLastMotiony;
if (mPullState == PULL_DOWN_STATE) {
headerPrepareToRefresh(deltaY);
} else if (mPullState == PULL_UP_STATE) {
footerPrepareToRefresh(deltaY);
}
mLastMotiony = y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
int topMargin = getHeadViewTopMargin();
if (mPullState == PULL_DOWN_STATE) {
if (topMargin >= 0) {
headRefreshing();
} else {
setHeaderViewTopMargin(-mHeaderViewHeight);
}
} else if (mPullState == PULL_UP_STATE) {
if (Math.abs(topMargin) > (mHeaderViewHeight + mFooterViewHeight)) {
footRefreshing();
} else {
setHeaderViewTopMargin(-mHeaderViewHeight);
}
}
break;
}
return super.onTouchEvent(e);
}
/**
* 判断当前是否需要拦截触摸事件,做出上拉和下拉的反应
*
* @param delta
* @return
*/
private boolean isRefreshViewScroll(int delta) {
//头部或者底部正在刷新中.....
if (mHeadState == REFRESHING || mFootState == REFRESHING) {
return false;
}
if (mAdapterView != null) {
// 下拉
if (delta > 1 && isHeadable) {
View child = mAdapterView.getChildAt(0);
// 判断adapterView里面是否含有东西
// 如果为空的话,则返回false
if (child == null) {
return false;
}
// child距离父容器顶部的距离?
int top = child.getTop();
// 如果AdapterView当前第一个可见的位置是数据的第一个,且处于AdapterView顶部
if (mAdapterView.getFirstVisiblePosition() == 0
&& top == 0) {
mPullState = PULL_DOWN_STATE;
return true;
}
int pTop = mAdapterView.getPaddingTop();
if (mAdapterView.getFirstVisiblePosition() == 0
&& Math.abs(pTop - top) <= 8) {// ????为什么小于8
mPullState = PULL_DOWN_STATE;
return true;
}
} else if (delta < 0 && isFootable) {// 上拉
// 获取AdapterView里面子视图的个数
int childCount = mAdapterView.getChildCount();
// 判断其中视图个数是否小于1,防止getChildAt时出错
if (childCount < 1) {
return false;
}
// 获取AdapterView中最后一个子视图
View lastChild = mAdapterView.getChildAt(childCount - 1);
if (lastChild == null) {
return false;
}
// 如果最后一个子视图距底部距离小于刷新控件的高度
// AdapterView的最后一个显示的位置是最后一个子视图
if (lastChild.getBottom() <= getHeight()
&& mAdapterView.getLastVisiblePosition() == childCount - 1) {
mPullState = PULL_UP_STATE;
return true;
}
}
} else if (mScrollView != null) {//滚动视图
View child = mScrollView.getChildAt(0);
if (child == null) {
return false;
}
if (delta > 0 && mScrollView.getScrollY() == 0) {//处于顶部且做出下拉的动作
mPullState = PULL_DOWN_STATE;
return true;
} else if (child.getMeasuredHeight() <= (getHeight() + mScrollView.getScrollY())
&& delta < 0) {//处于底部且做出上拉的动作
mPullState = PULL_UP_STATE;
return true;
}
}
return false;
}
/**
* 头部准备刷新
*
* @param delta
*/
private void headerPrepareToRefresh(int delta) {
int newTopMargin = changeingHeaderViewTopMargin(delta);
//当头部全部展示出来时,设置当前状态为释放去刷新
if (newTopMargin >= 0 && mHeadState != RELEASE_TO_REFRESH) {
mHeadState = RELEASE_TO_REFRESH;
} else if (newTopMargin <= 0 && Math.abs(newTopMargin) > mHeaderViewHeight) {
mHeadState = PULL_TO_REFRESH;
}
}
/**
* 底部准备刷新
*
* @param delta
*/
private void footerPrepareToRefresh(int delta) {
int newTopMargin = changeingHeaderViewTopMargin(delta);
//如果底部拉开距离足够展示底部视图,则设置当前状态为释放刷新,反之则为拖动去刷新
if (Math.abs(newTopMargin) > (mHeaderViewHeight + mFooterViewHeight)
&& mFootState != RELEASE_TO_REFRESH) {
mFootState = RELEASE_TO_REFRESH;
} else if (Math.abs(newTopMargin) <= (mHeaderViewHeight + mFooterViewHeight)) {
mFootState = PULL_TO_REFRESH;
}
}
/**
* 改变头部视图距上距离
*/
private int changeingHeaderViewTopMargin(int delta) {
LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
if (delta > 0 && mPullState == PULL_UP_STATE
&& Math.abs(params.topMargin) <= mHeaderViewHeight) {
return params.topMargin;
}
if (delta < 0 && mPullState == PULL_DOWN_STATE
&& Math.abs(params.topMargin) >= mHeaderViewHeight) {
return params.topMargin;
}
float newTopMargin;
if (mPullState == PULL_UP_STATE) {
newTopMargin = params.topMargin + delta * FooterPower;
} else if (mPullState == PULL_DOWN_STATE) {
newTopMargin = params.topMargin + delta * HeaderPower;
} else {
newTopMargin = params.topMargin;
}
params.topMargin = (int) newTopMargin;
mHeaderView.setLayoutParams(params);
invalidate();
return params.topMargin;
}
/**
* 头部刷新
*/
private void headRefreshing() {
//设置当前状态为刷新中
mHeadState = REFRESHING;
//设置头部视图据顶部距离为0,使其恰好包括头部视图
setHeaderViewTopMargin(0);
if (onHeaderRefreshListener != null) {
onHeaderRefreshListener.onHeaderRefresh(this);
}
}
/**
* 底部刷新
*/
private void footRefreshing() {
//设置当前状态为刷新中
mFootState = REFRESHING;
//获取头部视图和底部视图高度,设置头部距离,使其恰好包括底部视图
int top = mHeaderViewHeight + mFooterViewHeight;
setHeaderViewTopMargin(-top);
if (onFooterRefreshListener != null) {
onFooterRefreshListener.onFoorerRefresh(this);
}
}
/**
* 刷新完成
*/
public void onRefreshComplete() {
mHeadState = PULL_TO_REFRESH;
mFootState = PULL_TO_REFRESH;
setHeaderViewTopMargin(-mHeaderViewHeight);
}
/**
* 头部刷新完成
*/
public void onHeaderComplete() {
mHeadState = PULL_TO_REFRESH;
setHeaderViewTopMargin(-mHeaderViewHeight);
}
/**
* 底部刷新完成
*/
public void onFooterComplete() {
mFootState = PULL_TO_REFRESH;
setHeaderViewTopMargin(-mHeaderViewHeight);
}
/**
* 获得头部视图距上距离
*
* @return
*/
private int getHeadViewTopMargin() {
LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
return params.topMargin;
}
/**
* 设置头部视图距上距离
*/
private void setHeaderViewTopMargin(int topMargin) {
LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
params.topMargin = topMargin;
mHeaderView.setLayoutParams(params);
}
/**
* 下拉刷新事件
*/
public interface onHeaderRefreshListener {
void onHeaderRefresh(PullToRefresh view);
}
/**
* 上拉刷新事件
*/
public interface onFooterRefreshListener {
void onFoorerRefresh(PullToRefresh view);
}
/**
* 设置刷新是否可用
*
* @param isRefresh
*/
public void setRefreshable(boolean isRefresh) {
this.isHeadable = isRefresh;
this.isFootable = isRefresh;
}
/**
* 设置头部刷新是否可用
*
* @param isHeadable
*/
public void setIsHeadable(boolean isHeadable) {
this.isHeadable = isHeadable;
}
/**
* 设置底部刷新是否可用
*
* @param isFootable
*/
public void setIsFootable(boolean isFootable) {
this.isFootable = isFootable;
}
/**
* 设置头部拉动距离的有效度
* @param headerPower
*/
public void setHeaderPower(float headerPower) {
HeaderPower = headerPower;
}
/**
* 设置底部拉动距离的有效度
* @param footerPower
*/
public void setFooterPower(float footerPower) {
FooterPower = footerPower;
}
}
可能有的时候头部和底部视图需要自己设计,为了偷懒,我主要都是以图片为主,用progressbar作为轮播图片的控件
refresh_header内代码:
<?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:gravity="center"
android:orientation="vertical"
android:paddingBottom="15dp"
android:paddingTop="10dp">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@anim/hz_progress_bottom" />
</LinearLayout>
主要起作用的是android:indeterminateDrawable="@anim/hz_progress_bottom"这句代码,这是一个不断轮换图片(其实是动画)的设置,
然后再看一眼这里面是怎么写的
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:duration="40">
<clip
android:clipOrientation="horizontal"
android:drawable="@mipmap/load_small_01"
android:gravity="center" />
</item>
<item android:duration="40">
<clip
android:clipOrientation="horizontal"
android:drawable="@mipmap/load_small_02"
android:gravity="center" />
</item>
......重复这里,这里和百度查到的不太一样,(百度里查到的经常是这种
<item android:drawable="@drawable/p1" android:duration="150" /> <item android:drawable="@drawable/p2" android:duration="150" />)但是会导致图片会根据大小进行重复和其他意外情况,特地又找了这种方法,至于这个的具体方式,暂时还不是很明白,求大神赐教...
</animation-list>
补充下:有的小伙伴可能直接拿过去就用了,不知道布局文件里怎么写,导致没有效果,现在补充一下,上拉和下拉是纵向的,这个就不多解释了
<com.cxd.pulltorefresh.PullToRefresh android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</ScrollView>
</com.cxd.pulltorefresh.PullToRefresh>
今天就主要总结这个了,之所以是02,是因为01现存在草稿箱中,书写当天由于一些工作上的事情打断了,会尽快补上....
,