简单介绍
WarpLayout
用于捕捉手势操作并决定显示内容。具有以下职能:
- 计算HeaderView、ContentView、FooterView的显示位置和置放
- 手势捕捉判断
- 是否可显示头部、脚部视图的通用判断。具体判断交由Mover进行
AttachView
添加头部和脚步View的抽象View。具有以下职能:
- 根据展示高度显示不同的内容
- 根据展示高度判断 WarpLayout 是否可刷新操作
AttachViewMover
根据 ContentView 类型不同采取不同的判断逻辑。
代码
WarpLayout
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean headerMovable = false;
boolean footerMovable = false;
switch (event.getAction()) {
// 所以在此记录按下位置,判断是否消费点击时间
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
mLastY = mDownY;
mIsVerticalMove = false;
// mScroller.forceFinished(true);
break;
// 拦截满足条件的滑动事件,用来显示头部和脚部
case MotionEvent.ACTION_MOVE:
float currY = event.getY();
float absDx = Math.abs(event.getX() - mDownX);
float absDy = Math.abs(currY - mDownY);
// 最小滑动距离、手指滑动在 X、Y方向上的比例筛选
if (absDx >= mTouchSlop || absDy >= mTouchSlop) {
if (absDy / absDx > SCROLL_JUDGE_VERTICAL_HORIZONTAL) {
mIsVerticalMove = true;
}
}
if (mIsVerticalMove) {
headerMovable = isMovable4HeaderView(currY > mLastY);// ViewGroup下滑
footerMovable = isMovable4FooterView(currY < mLastY);// ViewGroup上滑
}
break;
}
Log.d("RefreshWarpLayout", "headerMovable:" + headerMovable + ";footerMovable:" + footerMovable);
return headerMovable || footerMovable;
}
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
// onInterceptTouchEvent 中 ACTION_DOWN 事件一定返回false
// MotionEvent.ACTION_DOWN 此事件可能在此处捕获
case MotionEvent.ACTION_DOWN:
// The incident is unlikely to happen
break;
// 消费 move 事件
case MotionEvent.ACTION_MOVE:
// 如果滑动没有到指定的距离,积累滑动事件
float fingerScrollY = event.getY() - mLastY;
if (Math.abs(fingerScrollY) < SCROLL_REFRESH_UI_VIEW_MINI_DISTANCE) {
return true;
}
// 申请想要的偏移量:offsetY4Ask,手指滑动距离 * 阻尼
int offsetY4Ask = (int) (fingerScrollY * (1 - damping));
mLastY = event.getY();
// 计算允许的偏移量
int offsetY4End = planMoveDistance(offsetY4Ask);
// 截取反向操作,下滑 --> 上滑:导致不合理出现 FooterView。将这种情况标记为非法操作
int scrolledDistanceAgo = getScrollY();
int scrolledNumAfter = scrolledDistanceAgo + offsetY4End;
mIsIllegalMove = scrolledDistanceAgo * scrolledNumAfter < 0;
// Log.d("RefreshWarpLayout", "mIsIllegalMove:" + mIsIllegalMove + ";ago【" + scrolledDistanceAgo + "】,after【" + scrolledNumAfter + "】");
// 恢复接近初始样子,但是留一点空间方便下次判断
if (mIsIllegalMove) {
// -1、1为 保留态,方便下次滑动,如:下拉 - 上滑回归 - 下拉 ... 等情况
// 因为此前忽略了 mTouchSlop 单位一下的移动量,所以避过了会产生BUG的临界值
if (Math.abs(scrolledDistanceAgo) == 1) {
offsetY4End = 0;
}else {
offsetY4End = (scrolledDistanceAgo < 0 ? -1 : 1) - scrolledDistanceAgo;
}
}
// 移动操作
scrollBy(0, offsetY4End);
callMoved();
break;
// 取消事件,以后的手势操作不会传递到本 View
case MotionEvent.ACTION_CANCEL:
// 松手操作
case MotionEvent.ACTION_UP:
mIsIllegalMove = false;
autoSelectActionAfterUp();
break;
}
return true;
}
private int planMoveDistance(int offsetY4Ask){
int offsetY4End = -offsetY4Ask;// 方向相反
// ViewGroup向下滚动,返回值 < 0,只有HeaderView全部显示了才不能向下滚动
// 最高点是:-HeaderView.height
if (offsetY4Ask > 0) {
int distanceLeft = Math.abs(-mHeaderView.getMeasuredHeight() - getScrollY());
if (offsetY4Ask > distanceLeft) {
offsetY4End = -distanceLeft;
}
}
// ViewGroup向上滚动,返回值 > 0,只有FooterView全部显示了才不能向上滚动
// 最低点是:FooterView.height
else if (offsetY4Ask < 0) {
int distanceLeft = Math.abs(mFooterView.getMeasuredHeight() - getScrollY());
if (Math.abs(offsetY4Ask) > distanceLeft) {
offsetY4End = distanceLeft;
}
}
return offsetY4End;
}
AttachView
继承AbsAttachView
实现添加的View
public class DefaultFooterView extends AbsAttachView {
private TextView tvShow;
private float canLoadPointPercent = 0.75f;
public DefaultFooterView(Context context) {
super(context);
}
@Override
public int getLayoutId() {
return R.layout.v_refresh_default_head;
}
@Override
public void bindView(View layoutView) {
tvShow = (TextView) layoutView.findViewById(R.id.tv_textView);
tvShow.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
}
@Override
public boolean canLoad(int offsetY) {
return Math.abs(offsetY) >= getMeasuredHeight() * canLoadPointPercent;
}
@Override
public void onMove(int offsetY) {
if (canLoad(offsetY)) {
onReady();
}else {
onNormal();
}
}
private void onNormal() {
tvShow.setText("上拉加载更多");
}
private void onReady() {
tvShow.setText("松手加载更多");
}
@Override
protected void onLoading() {
tvShow.setText("正在加载更多...");
}
@Override
protected void onComplete() {
tvShow.setText("加载更多完成");
}
}
AttachViewMover
用于适配不同的 ContentView 判断是否到顶或到底
private static class AbsListViewMover extends IAttachViewMover {
private AbsListView view = null;
AbsListViewMover(View contentView) {
view = (AbsListView) contentView;
}
@Override
public boolean canMovable4HeaderView() {
return view != null
// 第一个Item是否可见
&& view.getFirstVisiblePosition() == 0
// 没有数据也显示刷新头部
// ListView滑动到顶部没有,由于之前的判断,此时第一个ChildView展示的一定是第一个Item的内容
&& (view.getChildAt(0) == null || view.getChildAt(0).getTop() >= 0);
}
@Override
public boolean canMovable4FooterView() {
return view != null
// AbsListView适配器不能为空
&& view.getAdapter() != null
// 最后一个Item是否可见
&& view.getLastVisiblePosition() == view.getAdapter().getCount() - 1
// ListView滑动到底部没有,由于之前的判断,此时最后一个ChildView展示的一定是最后一个Item的内容。
// ChildView.bottom 参考的是 AbsListView,而不是本View
&& view.getHeight() == view.getChildAt(view.getChildCount() - 1).getBottom();
}
}
使用实例
XML
<org.hjf.view.refreshlayout.RefreshWarpLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="@dimen/y600">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</org.hjf.view.refreshlayout.RefreshWarpLayout>
JAVA
refreshWarpLayout.setLoadingListener(new RefreshWarpLayout.RefreshLoadingListener() {
@Override
public void onRefresh() {
TaskMgr.execTaskOnBGThread(Task.RUNNABLE_PREFERENCE_HIGH, new Task() {
@Override
protected void doTask() {
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
// 通知动作完成
refreshWarpLayout.loadComplete();
}
});
}
});
}
@Override
public void onLoadMore() {
TaskMgr.execTaskOnBGThread(Task.RUNNABLE_PREFERENCE_HIGH, new Task() {
@Override
protected void doTask() {
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
refreshWarpLayout.loadComplete();
}
});
}
});
}
});
具体例子在Demo中,还会更具需求和适配更多的View,还有更多东西哦