记录一下,以防忘记
完整代码
public class PullLayout extends ViewGroup {
private View mHeaderView;//头部布局
private View mFooterView;//尾部布局
private int mHeaderHeight;//头布局高度
private int mFooterHeight;//尾部局高度
private Status mStatus = Status.NORMAL;
private int mlastMoveY;//最后点击位置
private int mLastYIntercept;
private TextView mHeaderText;
private ProgressBar mHeaderProgressBar;
private ImageView mHeaderArrow;
private TextView mFooterText;
private ProgressBar mFooterProgressBar;
private int mLayoutContentHeight;
public PullLayout(Context context) {
this(context, null);
}
public PullLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
}
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PullLayout);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
addHeader();
View childAt = getChildAt(1);
// if (childAt instanceof AdapterView) {
// } else if (childAt instanceof ScrollView) {
// } else {
// throw new ClassCastException("PullLayout 布局内只允许放置一个布局 AdapterView ScrollView");
// }
addFooter();
}
private void addHeader() {
mHeaderView = LayoutInflater.from(getContext()).inflate(R.layout.pull_header, null, false);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
addView(mHeaderView,0, params);
mHeaderText = (TextView) findViewById(R.id.header_text);
mHeaderProgressBar = (ProgressBar) findViewById(R.id.header_progressbar);
mHeaderArrow = (ImageView) findViewById(R.id.header_arrow);
}
private void addFooter() {
mFooterView = LayoutInflater.from(getContext()).inflate(R.layout.pull_footer, null, false);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
addView(mFooterView, params);
mFooterText = (TextView) findViewById(R.id.footer_text);
mFooterProgressBar = (ProgressBar) findViewById(R.id.footer_progressbar);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mLayoutContentHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child == mHeaderView) {
child.layout(0, 0 - child.getMeasuredHeight(), child.getMeasuredWidth(), 0);
mHeaderHeight = child.getHeight();
} else if (child == mFooterView) {
child.layout(0, mLayoutContentHeight, child.getMeasuredWidth(), mLayoutContentHeight + child.getMeasuredHeight());
mFooterHeight = child.getHeight();
} else {
child.layout(0, mLayoutContentHeight, child.getMeasuredWidth(), mLayoutContentHeight + child.getMeasuredHeight());
if (i < getChildCount()) {
if (child instanceof ScrollView) {
mLayoutContentHeight += getMeasuredHeight();
continue;
}
mLayoutContentHeight += child.getMeasuredHeight();
}
}
}
}
/**
* ACTION_DOWN 不需要拦截
* ACTION_UP 不需要拦截
* <p>
* 当事件为 ACTION_MOVE 时,
* 如果是向下滑动,判断第一个child是否滑倒最上面,如果是,则更新状态为 TRY_REFRESH;
* 如果是向上滑动,则判断最后一个child是否滑动最底部,如果是,则更新状态为TRY_LOADMORE。然后返回 intercept = true
* <p>
* 其他情况不进行拦截
*
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
int y = (int) ev.getY();
//当状态为刷新或者加载中的时候不进行拦截
if (mStatus == Status.REFRESHING || mStatus == Status.LOADING) {
return false;
}
switch (ev.getAction()) {
//ACTION_DOWN 不进行拦截
case MotionEvent.ACTION_DOWN: {
// 拦截时需要记录点击位置,不然下一次滑动会出错
mlastMoveY = y;
intercept = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (y > mLastYIntercept) {//向下滑动
View child = getChildAt(1);
intercept = getRefreshIntercept(child);
if (intercept) {
updateStatus(mStatus.TRY_REFRESH);
}
} else if (y < mLastYIntercept) { //向上滑动
View child = getChildAt(1);
intercept = getLoadMoreIntercept(child);
if (intercept) {
updateStatus(mStatus.TRY_LOADMORE);
}
} else {
intercept = false;
}
break;
}
//ACTION_UP 不进行拦截
case MotionEvent.ACTION_UP: {
intercept = false;
break;
}
}
mLastYIntercept = y;
return intercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY();
if (mStatus == Status.REFRESHING || mStatus == Status.LOADING) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mlastMoveY = y;
break;
case MotionEvent.ACTION_MOVE:
int dy = mlastMoveY - y;
// 一直在下拉
if (getScrollY() <= 0 && dy <= 0) {
if (mStatus == Status.TRY_LOADMORE) {
scrollBy(0, dy / 30);
} else {
scrollBy(0, dy / 3);
}
} else if (getScrollY() >= 0 && dy >= 0) {// 一直在上拉
if (mStatus == Status.TRY_REFRESH) {
scrollBy(0, dy / 30);
} else {
scrollBy(0, dy / 3);
}
} else {
scrollBy(0, dy / 3);
}
beforeRefreshing(dy);
beforeLoadMore();
break;
case MotionEvent.ACTION_UP:
if (getScrollY() <= -mHeaderHeight) { // 下拉刷新,并且到达有效长度
releaseWithStatusRefresh();
if (onFinishedListener != null) {
onFinishedListener.refresh();
}
} else if (getScrollY() >= mFooterHeight) { // 上拉加载更多,达到有效长度
releaseWithStatusLoadMore();
if (onFinishedListener != null) {
onFinishedListener.loadMore();
}
} else {
releaseWithStatusTryRefresh();
releaseWithStatusTryLoadMore();
}
break;
}
mlastMoveY = y;
return super.onTouchEvent(event);
}
private OnFinishedListener onFinishedListener;
public void setOnFinishedListener(OnFinishedListener onFinishedListener) {
this.onFinishedListener = onFinishedListener;
}
interface OnFinishedListener {
void refresh();
void loadMore();
}
/**
* 更新 滑动状态
*
* @param status
*/
private void updateStatus(Status status) {
mStatus = status;
}
/**
* 滑动状态
*/
enum Status {
TRY_REFRESH,//刷新状态
TRY_LOADMORE,//加载更多状态
REFRESHING,//刷新中
LOADING,//加载中
NORMAL //正常状态
}
/判断是否进行拦截
/**
* 判断
* 刷新 是否进行拦截
*
* @param child
* @return
*/
private boolean getRefreshIntercept(View child) {
boolean intercept = false;
if (child instanceof AdapterView) {
intercept = adapterViewRefreshIntercept(child);
} else if (child instanceof ScrollView) {
intercept = scrollViewRefreshIntercept(child);
} else if (child instanceof RecyclerView) {
intercept = recyclerViewRefreshIntercept(child);
}
return intercept;
}
/**
* 判断
* 加载更多 是否进行拦截
*
* @param child
* @return
*/
private boolean getLoadMoreIntercept(View child) {
boolean intercept = false;
if (child instanceof AdapterView) {
intercept = adapterViewLoadMoreIntercept(child);
} else if (child instanceof ScrollView) {
intercept = scrollViewLoadMoreIntercept(child);
} else if (child instanceof RecyclerView) {
intercept = recyclerViewLoadMoreIntercept(child);
}
return intercept;
}
/**
* 判断
* AdapterView 下拉刷新是否进行拦截
* <p>
* 当屏幕中AdapterView 的第一个可见Item 是首Item,且 首Item的上端为 0 进行拦截
*
* @param child
* @return
*/
private boolean adapterViewRefreshIntercept(View child) {
boolean intercept = true;
AdapterView adapterChild = (AdapterView) child;
if (adapterChild.getFirstVisiblePosition() != 0
|| adapterChild.getChildAt(0).getTop() != 0) {
intercept = false;
}
return intercept;
}
/**
* 判断
* AdapterView 加载更多 是否进行拦截
* <p>
* 当屏幕中AdapterView 最后一个可见item 是末尾Item
* 且末尾Item距离屏幕底部 不小于控件测量高度 进行拦截
*
* @param child
* @return
*/
private boolean adapterViewLoadMoreIntercept(View child) {
boolean intercept = false;
AdapterView adapterChild = (AdapterView) child;
if (adapterChild.getLastVisiblePosition() == adapterChild.getCount() - 1 &&
(adapterChild.getChildAt(adapterChild.getChildCount() - 1).getBottom() >= getMeasuredHeight())) {
intercept = true;
}
return intercept;
}
/**
* 判断
* ScrollView 刷新是否拦截
* <p>
* 当ScrollView 的左上角相对于 本控件左上角在Y轴的偏移量 <= 0 时 进行拦截
*
* @param child
* @return
*/
private boolean scrollViewRefreshIntercept(View child) {
boolean intercept = false;
if (child.getScrollY() <= 0) {
intercept = true;
}
return intercept;
}
/**
* 判断
* ScrollView加载更多是否拦截
* <p>
* 当ScrollView 的左上角相对于 本控件左上角在Y轴的偏移量
* >=
* ScrollView 的第一个子控件的高度与ScrollView的高度差
* 时,进行拦截
*
* @param child
* @return
*/
private boolean scrollViewLoadMoreIntercept(View child) {
boolean intercept = false;
ScrollView scrollView = (ScrollView) child;
View scrollChild = scrollView.getChildAt(0);
if (scrollView.getScrollY() >= (scrollChild.getHeight() - scrollView.getHeight())) {
intercept = true;
}
return intercept;
}
/**
* 判断
* RecyclerView刷新是否拦截
* <p>
* 当RecyclerView 当前滑过的距离 <= 0时 进行拦截
* <p>
* computeVerticalScrollExtent()是当前屏幕显示的区域高度
* computeVerticalScrollOffset() 是当前屏幕之前滑过的距离
* computeVerticalScrollRange()是整个View控件的高度
*
* @param child
* @return
*/
private boolean recyclerViewRefreshIntercept(View child) {
boolean intercept = false;
RecyclerView recyclerView = (RecyclerView) child;
if (recyclerView.computeVerticalScrollOffset() <= 0) {
intercept = true;
}
return intercept;
}
/**
* 判断
* RecyclerView加载更多是否拦截
* <p>
* 当RecyclerView 当前显示的高度 与 当前滑过的距离之和
* >=
* 整个RecyclerView的高度 时 进行拦截
*
* @param child
* @return
*/
private boolean recyclerViewLoadMoreIntercept(View child) {
boolean intercept = false;
RecyclerView recyclerView = (RecyclerView) child;
if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset()
>= recyclerView.computeVerticalScrollRange()) {
intercept = true;
}
return intercept;
}
/判断是否进行拦截
/修改头 尾布局的状态
/**
* 滑动时刷新
*
* @param dy
*/
public void beforeRefreshing(float dy) {
//计算旋转角度
int scrollY = Math.abs(getScrollY());
scrollY = scrollY > mHeaderHeight ? mHeaderHeight : scrollY;
float angle = (float) (scrollY * 1.0 / mHeaderHeight * 180);
mHeaderArrow.setRotation(angle);
if (getScrollY() <= -mHeaderHeight) {
mHeaderText.setText("松开刷新");
} else {
mHeaderText.setText("下拉刷新");
}
}
/**
* 滑动加载更多
*/
public void beforeLoadMore() {
if (getScrollY() >= mHeaderHeight) {
mFooterText.setText("松开加载更多");
} else {
mFooterText.setText("上拉加载更多");
}
}
/**
* 刷新结束
*/
public void refreshFinished() {
scrollTo(0, 0);
mHeaderText.setText("下拉刷新");
mHeaderProgressBar.setVisibility(GONE);
mHeaderArrow.setVisibility(VISIBLE);
updateStatus(Status.NORMAL);
}
/**
* 加载更多结束
*/
public void loadMoreFinished() {
mFooterText.setText("上拉加载");
mFooterProgressBar.setVisibility(GONE);
scrollTo(0, 0);
updateStatus(Status.NORMAL);
}
/**
* 下拉刷新 未达到有效长度
*/
private void releaseWithStatusTryRefresh() {
scrollBy(0, -getScrollY());
mHeaderText.setText("下拉刷新");
updateStatus(Status.NORMAL);
}
/**
* 上拉加载更多
*/
private void releaseWithStatusTryLoadMore() {
scrollBy(0, -getScrollY());
mFooterText.setText("上拉加载更多");
updateStatus(Status.NORMAL);
}
/**
* 下拉刷新 达到有效长度
*/
private void releaseWithStatusRefresh() {
scrollTo(0, -mHeaderHeight);
mHeaderProgressBar.setVisibility(VISIBLE);
mHeaderArrow.setVisibility(GONE);
mHeaderText.setText("正在刷新");
updateStatus(Status.REFRESHING);
}
/**
* 上拉加载更多 达到有效长度
*/
private void releaseWithStatusLoadMore() {
scrollTo(0, mFooterHeight);
mFooterText.setText("正在加载");
mFooterProgressBar.setVisibility(VISIBLE);
updateStatus(Status.LOADING);
}
/修改头 尾布局的状态
}
pull_header.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<TextView
android:id="@+id/header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="下拉刷新"
android:textSize="16sp" />
<ProgressBar
android:id="@+id/header_progressbar"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_toLeftOf="@+id/header_text"
android:visibility="gone" />
<ImageView
android:id="@+id/header_arrow"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/header_text"
android:layout_toStartOf="@+id/header_text"
android:src="@drawable/ic_arrows" />
</RelativeLayout>
pull_footer.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<TextView
android:id="@+id/footer_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="上拉加载"/>
<ProgressBar
android:id="@+id/footer_progressbar"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_toLeftOf="@+id/footer_text"
android:visibility="gone"/>
</RelativeLayout>
使用
布局xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.biginner.com.refretest.PullLayout
android:id="@+id/refre"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_item"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</android.biginner.com.refretest.PullLayout>
</LinearLayout>
Activity
public class MainActivity extends AppCompatActivity implements PullLayout.OnFinishedListener{
private ListView listView;
private PullLayout pullLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
pullLayout = (PullLayout) findViewById(R.id.refre);
pullLayout.setOnFinishedListener(this);
ArrayList<String> strings = new ArrayList<>();
for (int i = 0; i < 50; i++) {
strings.add("测试数据"+(i+1));
}
listView = (ListView) findViewById(R.id.list_item);
Adapter adapter = new Adapter(this);
adapter.setData(strings);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, "点击了"+position, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void refresh() {
pullLayout.refreshFinished();
Toast.makeText(this, "刷新完毕", Toast.LENGTH_SHORT).show();
}
@Override
public void loadMore() {
pullLayout.loadMoreFinished();
Toast.makeText(this, "加载完毕", Toast.LENGTH_SHORT).show();
}
class Adapter extends BaseAdapter {
private List<String> data;
private Context context;
public void setData(List<String> data) {
this.data = data;
notifyDataSetChanged();
}
public Adapter(Context context) {
this.context = context;
}
@Override
public int getCount() {
return data == null ? 0 : data.size();
}
@Override
public String getItem(int position) {
return data == null ? "" : data.get(position);
}
@Override
public long getItemId(int position) {
return data == null ? 0 : position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.layout_item, null);
holder = new ViewHolder();
holder.textView = convertView.findViewById(R.id.tv);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.textView.setText(data.get(position));
return convertView;
}
class ViewHolder {
TextView textView;
}
}
}