关于自定义父控件实现右滑动最后,继续滑动,加载刷新更多
本次采用继承ReleativeLayout作为RecyclerView父控件实现,与前文处理不一样的地方这是弹性实现上文采用设置margin,本次是view的width。
实现过程,既然作为父控件,那么手势操作的处理,我们不用关心处理的过程,即onTouchEvent,我们关注事件的分发,即onInterceptTouchEvent
和dispatchTouchEvent。
那么onInterceptTouchEvent我们不拦截事件,让分发的流程正常向下,就是——
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/**
* 不拦截,直接传递给子的view。
*/
return false;
}
我们在dispatchTouchEvent中做处理。首先需要知道此函数返回值用处——
/**
* 事件分发
* false-->转给父类onTouchEvent
* dispatchTouchEvent(ev)-->事件向下分发onInterceptTouchEvent
* true-->事件被自身消耗
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mChildView == null) {
return super.dispatchTouchEvent(ev);
}
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startDownX = ev.getX();
isReleaseFinger = false;
case MotionEvent.ACTION_MOVE:
float nowX = ev.getX();
int scrollX = (int) (nowX - startDownX);
isReleaseFinger = false;
if ((isCanPullToLeftDirector() && scrollX < 0)) {// 继续向左边做移动
int delta = (int) (scrollX * DRAG_RATE);
mIRefreshMovedViewLayout.doToMove(delta);
if (mIRefreshMovedViewLayout.getVisibleWidth() > 0
&& mIRefreshMovedViewLayout.getRefreshState() < IPullToLeftRefreshState.STATE_REFRESHING) {
return super.dispatchTouchEvent(ev);
} else if (mIRefreshMovedViewLayout.getVisibleWidth() < 0){
mIRefreshMovedViewLayout.setVisibleWidth(UiUtils.dip2px(50));
}
return true;
} else {
startDownX = ev.getX();
if (mIRefreshMovedViewLayout.getVisibleWidth() < 0){
mIRefreshMovedViewLayout.setVisibleWidth(UiUtils.dip2px(50));
return true;
}
return super.dispatchTouchEvent(ev);
}
case MotionEvent.ACTION_UP:
isReleaseFinger = true;
default:
isReleaseFinger = false;
if (mIRefreshMovedViewLayout.releaseAction()) {
if (mOnPullToLeftListener != null) {
mOnPullToLeftListener.onPullToLeftRefresh();
} else {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
doWhatCompleteToRefresh();
}
}, 1200);
}
} else {
return super.dispatchTouchEvent(ev);
}
}
return super.dispatchTouchEvent(ev);
}
isCanPullToLeftDirector()方法作用为:是否滑动至最右端
具体如下:
@Override
public boolean isCanPullToLeftDirector() {
final RecyclerView.Adapter adapter = ((RecyclerView) mChildView).getAdapter();
if (null == adapter) {
return true;
}
final int lastItemPosition = adapter.getItemCount() - 1;
if (((RecyclerView) mChildView)
.getLayoutManager() instanceof LinearLayoutManager) {
final int lastVisiblePosition = ((LinearLayoutManager) ((RecyclerView) mChildView).getLayoutManager()).findLastVisibleItemPosition();
if (lastVisiblePosition >= lastItemPosition) {
final int childIndex = lastVisiblePosition - ((LinearLayoutManager) ((RecyclerView) mChildView)
.getLayoutManager()).findFirstVisibleItemPosition();
final int childCount = ((RecyclerView) mChildView).getChildCount();
final int index = Math.max(childIndex, childCount - 1);
final View lastVisibleChild = ((RecyclerView) mChildView).getChildAt(index);
if (lastVisibleChild != null) {
boolean isArriveToMostRight = lastVisibleChild.getRight() <= mChildView.getRight() - mChildView.getLeft();
return isArriveToMostRight;
}
}
}
return false;
}
本类实现的全部代码:
public class PullToLeftRefreshLinearLayout extends RelativeLayout
implements ViewTreeObserver.OnGlobalLayoutListener, IPullToLeftRefresh{
private static final float DRAG_RATE = 0.50f;
/**
* 必须是RecyclerView
*/
private View mChildView;
/**
* 第二个控件,必须实现IRefreshMovedViewLayout接口
*/
private IRefreshMovedViewLayout mIRefreshMovedViewLayout;
/**
* 用于记录childView(RecyclerView)正常的布局位置
*/
private Rect originalRect = new Rect();
/**
* 是否松开了手指
*/
private boolean isReleaseFinger;
/**
* 按下时候的X坐标
*/
private float startDownX;
/**
* 刷新监听
*/
OnPullToLeftListener mOnPullToLeftListener;
/**
* 设置监听
* @param leftListener
*/
public void setOnPullToLeftListener(OnPullToLeftListener leftListener){
this.mOnPullToLeftListener = leftListener;
}
/**
* 回调接口
*/
public interface OnPullToLeftListener{
void onPullToLeftRefresh();
}
public PullToLeftRefreshLinearLayout(Context context) {
super(context);
}
public PullToLeftRefreshLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public PullToLeftRefreshLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
/**
* 初始值设置
*/
originalRect.set(mChildView.getLeft(), mChildView.getTop(), mChildView.getRight(), mChildView.getBottom());
}
/**
* 得到子孩子RecyclerView
* @return
*/
public RecyclerView getChildView(){
if (mChildView == null){
return null;
}
return (RecyclerView) mChildView;
}
/**
* 加载布局后初始化,这个方法会在加载完布局后调用
*/
@Override
protected void onFinishInflate() {
if (getChildCount() > 0) {
if (getChildCount() != 2){
throw new RuntimeException("必须有两个子控件且仅能有两个子控件");
}
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i) instanceof RecyclerView) {
if (mChildView == null) {
mChildView = getChildAt(i);
} else {
throw new RuntimeException("只能存在一个RecyclerView");
}
} else if (getChildAt(i) instanceof IRefreshMovedViewLayout) {
if (this.mIRefreshMovedViewLayout == null) {
this.mIRefreshMovedViewLayout = (IRefreshMovedViewLayout) getChildAt(i);
}else {
throw new RuntimeException("只能存在一个实现IRefreshMovedViewLayout接口的控件");
}
}
}
}
if (mChildView == null) {
throw new RuntimeException("子容器中必须有一个RecyclerView");
}
if (this.mIRefreshMovedViewLayout == null) {
throw new RuntimeException("子容器中必须有一个实现IRefreshMovedViewLayout接口的控件");
}
getViewTreeObserver().addOnGlobalLayoutListener(this);
((RecyclerView) mChildView).addOnScrollListener(new RecyclerView.OnScrollListener() {
boolean isToLeftSliding = false;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
//if (newState == RecyclerView.SCROLL_STATE_IDLE && isToLeftSliding){
// recyclerView.smoothScrollBy(UiUtils.dip2px(50),0);
//}
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dx>0){
isToLeftSliding = true;
} else {
isToLeftSliding = false;
}
super.onScrolled(recyclerView, dx, dy);
}
});
super.onFinishInflate();
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onGlobalLayout() {
requestLayout();
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
@Override
public boolean isCanPullToLeftDirector() {
final RecyclerView.Adapter adapter = ((RecyclerView) mChildView).getAdapter();
if (null == adapter) {
return true;
}
final int lastItemPosition = adapter.getItemCount() - 1;
if (((RecyclerView) mChildView)
.getLayoutManager() instanceof LinearLayoutManager) {
final int lastVisiblePosition = ((LinearLayoutManager) ((RecyclerView) mChildView).getLayoutManager()).findLastVisibleItemPosition();
if (lastVisiblePosition >= lastItemPosition) {
final int childIndex = lastVisiblePosition - ((LinearLayoutManager) ((RecyclerView) mChildView)
.getLayoutManager()).findFirstVisibleItemPosition();
final int childCount = ((RecyclerView) mChildView).getChildCount();
final int index = Math.max(childIndex, childCount - 1);
final View lastVisibleChild = ((RecyclerView) mChildView).getChildAt(index);
if (lastVisibleChild != null) {
boolean isArriveToMostRight = lastVisibleChild.getRight() <= mChildView.getRight() - mChildView.getLeft();
return isArriveToMostRight;
}
}
}
return false;
}
@Override
public void doWhatCompleteToRefresh() {
mIRefreshMovedViewLayout.refreshComplete();
}
@Override
public void doWhatRecoverLayout() {
mChildView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom);
requestLayout();
}
@Override
public void setMoveViews(View movedView) {
if (movedView instanceof IRefreshMovedViewLayout) {
this.mIRefreshMovedViewLayout = (IRefreshMovedViewLayout) movedView;
requestLayout();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/**
* 不拦截,直接传递给子的view。
*/
return false;
}
/**
* 事件分发
* false-->转给父类onTouchEvent
* dispatchTouchEvent(ev)-->事件向下分发onInterceptTouchEvent
* true-->事件被自身消耗
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mChildView == null) {
return super.dispatchTouchEvent(ev);
}
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startDownX = ev.getX();
isReleaseFinger = false;
case MotionEvent.ACTION_MOVE:
float nowX = ev.getX();
int scrollX = (int) (nowX - startDownX);
isReleaseFinger = false;
if ((isCanPullToLeftDirector() && scrollX < 0)) {// 继续向左边做移动
int delta = (int) (scrollX * DRAG_RATE);
mIRefreshMovedViewLayout.doToMove(delta);
if (mIRefreshMovedViewLayout.getVisibleWidth() > 0
&& mIRefreshMovedViewLayout.getRefreshState() < IPullToLeftRefreshState.STATE_REFRESHING) {
return super.dispatchTouchEvent(ev);
} else if (mIRefreshMovedViewLayout.getVisibleWidth() < 0){
mIRefreshMovedViewLayout.setVisibleWidth(UiUtils.dip2px(50));
}
return true;
} else {
startDownX = ev.getX();
if (mIRefreshMovedViewLayout.getVisibleWidth() < 0){
mIRefreshMovedViewLayout.setVisibleWidth(UiUtils.dip2px(50));
return true;
}
return super.dispatchTouchEvent(ev);
}
case MotionEvent.ACTION_UP:
isReleaseFinger = true;
default:
isReleaseFinger = false;
if (mIRefreshMovedViewLayout.releaseAction()) {
if (mOnPullToLeftListener != null) {
mOnPullToLeftListener.onPullToLeftRefresh();
} else {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
doWhatCompleteToRefresh();
}
}, 1200);
}
} else {
return super.dispatchTouchEvent(ev);
}
}
return super.dispatchTouchEvent(ev);
}
}
下面是跟随滑动有移动的View的实现,放在RecyclerView右侧,即父控件PullToLeftRefreshLinearLayout最右端对齐,
当中TextTip提供给使用者对四个状态的提示文案:
public class MovedViewLayout extends LinearLayout implements IRefreshMovedViewLayout{
public class TextTip{
public String mRefreshing = "正在刷新加载";
public String mReleaseFingerToRefresh = "松开手指开始刷新加载";
public String mRefreshComplete = "刷新加载完成";
public String mRefreshNormal = "查看更多";
}
/**
* 状态提示类,供给外部使用
*/
TextTip mTextTip;
public TextTip getTextTip(){
return mTextTip;
}
/**
* 默认状态是正常
*/
private @IPullToLeftRefreshState int mRefreshState = IPullToLeftRefreshState.STATE_NORMAL;
public TextView tv_moved_view;
public ImageView iv_moved_view;
public LinearLayout mContainer;
public int mMeasuredWidth;
IPullRefreshStateListener mIPullRefreshStateListener;
public void setIPullRefreshStateListener(IPullRefreshStateListener zIPullRefreshStateListener){
this.mIPullRefreshStateListener = zIPullRefreshStateListener;
}
public MovedViewLayout(Context context) {
this(context, null);
}
public MovedViewLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MovedViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initSetLayout(context);
}
private void initSetLayout(Context context){
LayoutInflater.from(context).inflate(R.layout.look_more, this);
mContainer = (LinearLayout) findViewById(R.id.ll_moved_view);
iv_moved_view = (ImageView) findViewById(R.id.iv_moved_view);
tv_moved_view = (TextView) findViewById(R.id.tv_moved_view);
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
setGravity(Gravity.CENTER);
mContainer.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
this.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
mMeasuredWidth = getMeasuredWidth();
setVisibility(GONE);
mTextTip = new TextTip();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
/**
* 重置状态。
*/
public void resetState() {
/**
* 移动至0会移到屏幕外,不可见
*/
smoothScrollTo(0);
setRefreshState(IPullToLeftRefreshState.STATE_NORMAL);
}
@Override
public void doToMove(float delta) {
if (getVisibleWidth() > 0 || delta < 0) {
if(getVisibility() == View.GONE)
setVisibility(VISIBLE);
if (getVisibleWidth()>mMeasuredWidth*4){
return;
}
setVisibleWidth((int) Math.abs(delta) + getVisibleWidth());
if (mRefreshState <= IPullToLeftRefreshState.STATE_RELEASE_TO_REFRESH) {
if (getVisibleWidth()>mMeasuredWidth * 2) {
setRefreshState(IPullToLeftRefreshState.STATE_RELEASE_TO_REFRESH);
//} else if (mRefreshState == IPullToLeftRefreshState.STATE_RELEASE_TO_REFRESH){
// setRefreshState(IPullToLeftRefreshState.STATE_REFRESHING);
} else {
setRefreshState(IPullToLeftRefreshState.STATE_NORMAL);
}
}
}
}
@Override
public boolean releaseAction() {
boolean isOnRefresh = false;
int visibleWidth = getVisibleWidth();
if (visibleWidth == 0)
isOnRefresh = false;
if (getVisibleWidth() > mMeasuredWidth && mRefreshState < IPullToLeftRefreshState.STATE_REFRESHING) {
setRefreshState(IPullToLeftRefreshState.STATE_REFRESHING);
isOnRefresh = true;
}
if (mRefreshState == IPullToLeftRefreshState.STATE_REFRESHING && visibleWidth <= mMeasuredWidth) {
//return;
}
int destWidth = 0;
if (mRefreshState == IPullToLeftRefreshState.STATE_REFRESHING) {
destWidth = mMeasuredWidth;
}
/**
* destWidth = 0:移动至不可见
*/
smoothScrollTo(destWidth);
return isOnRefresh;
}
@Override
public void refreshComplete() {
setRefreshState(IPullToLeftRefreshState.STATE_DONE_FINISHED);
new Handler().postDelayed(new Runnable() {
public void run() {
resetState();
}
}, 1000);
}
@Override
public int getVisibleWidth() {
int zWidth = mContainer.getWidth();
return zWidth;
}
@Override
public void setVisibleWidth(int value) {
if (value < 0)
value = 0;
LayoutParams lp = (LayoutParams) mContainer.getLayoutParams();//new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
lp.width = value;
mContainer.setLayoutParams(lp);
invalidate();
}
private void smoothScrollTo(int destWidth) {
ValueAnimator animator = ValueAnimator.ofInt(getVisibleWidth(), destWidth);
animator.setDuration(300).start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setVisibleWidth((int) animation.getAnimatedValue());
}
});
animator.start();
}
@Override
public void setRefreshState(@IPullToLeftRefreshState int state) {
if (state == mRefreshState)
return;
switch (state){
case IPullToLeftRefreshState.STATE_NORMAL:
if(getVisibility() == View.GONE)
setVisibility(VISIBLE);
tv_moved_view.setText(mTextTip.mRefreshNormal);
break;
case IPullToLeftRefreshState.STATE_RELEASE_TO_REFRESH:
if(getVisibility() == View.GONE) setVisibility(VISIBLE);
tv_moved_view.setText(mTextTip.mReleaseFingerToRefresh);
break;
case IPullToLeftRefreshState.STATE_REFRESHING:
if(getVisibility() == View.GONE) setVisibility(VISIBLE);
tv_moved_view.setText(mTextTip.mRefreshing);
break;
case IPullToLeftRefreshState.STATE_DONE_FINISHED:
if(getVisibility() == View.GONE) setVisibility(VISIBLE);
tv_moved_view.setText(mTextTip.mRefreshComplete);
break;
}
if (mIPullRefreshStateListener != null){
mIPullRefreshStateListener.onPullRefreshState(mRefreshState, state);
}
mRefreshState = state;
}
@Override
public int getRefreshState() {
return mRefreshState;
}
@Override
public void doInitRefreshState(@IPullToLeftRefreshState int state, boolean isArriveToMostRight) {
if (isArriveToMostRight)
smoothScrollTo(mMeasuredWidth);
else
smoothScrollTo(0);
this.mRefreshState = state;
}
}
当中涉及的四种状态的接口定义:
@Retention(RetentionPolicy.SOURCE)
@IntDef({
IPullToLeftRefreshState.STATE_DONE_FINISHED,
IPullToLeftRefreshState.STATE_NORMAL,
IPullToLeftRefreshState.STATE_REFRESHING,
IPullToLeftRefreshState.STATE_RELEASE_TO_REFRESH
})
//@Documented()
public @interface IPullToLeftRefreshState {
/**
* 正常状态下
*/
int STATE_NORMAL = 10;
/**
* 松开手指去刷新
*/
int STATE_RELEASE_TO_REFRESH = 11;
/**
* 正在刷新
*/
int STATE_REFRESHING = 12;
/**
* 刷新完成
*/
int STATE_DONE_FINISHED = 13;
}
跟随滑动而移动View需要实现的接口定义:
public interface IRefreshMovedViewLayout {
void doToMove(float delta);
boolean releaseAction();
void refreshComplete();
int getVisibleWidth();
void setVisibleWidth(int value);
void setRefreshState(@IPullToLeftRefreshState int state);// 设置状态
@IPullToLeftRefreshState int getRefreshState();// 获取刷新状态
void doInitRefreshState(@IPullToLeftRefreshState int state, boolean isArriveToMostRight);
}
父控件需要实现的接口定义:
public interface IPullToLeftRefresh {
/**
* 是否在最后能够进行向左拉
* @return
*/
boolean isCanPullToLeftDirector();
/**
* 完成刷新
*/
void doWhatCompleteToRefresh();
/**
* 还原布局
*/
void doWhatRecoverLayout();
/**
* 跟随移动的view
*/
void setMoveViews(View movedView);
}
状态发生变化时调用的回调接口:
public interface IPullRefreshStateListener {
void onPullRefreshState(@IPullToLeftRefreshState int fromState, @IPullToLeftRefreshState int toState);
}