In our projects, we often have the need to pull up and pull down to refresh. Almost all listView and RecyclerView will be accompanied by the need to pull up and pull down to refresh. If we use some open source controls, we have to update them when we change the controls. Now we roll up our sleeves and write a universal refresh control. Ideas: - Write a RefreshLayout that inherits RelativeLayout
- Add header and footer controls as refresh controls
- Refresh operation through event distribution
- Controlling the movement of controls through animation
Purpose: Make all its child controls available, even a TextView - public class RefreshLayout extends RelativeLayout {
-
- /**
- * The speed ratio when sliding the control
- */
- private final int V_REFRESH = 2;
- /**
- * Is it a refresh process?
- * true
- * false is not
- * Refresh can only be performed when it is false
- */
- private boolean mIsRefreshDuring;
- /**
- * You can pull down to refresh
- */
- private boolean mCanDownPull;
- /**
- * Pull-up refresh is possible
- */
- private boolean mCanUpPull;
- /**
- * Determine whether it is the first move after touching
- */
- private boolean mIsFirstMove;
- /**
- * The distance of translation on the y-axis
- */
- private int mDistanceY;
- /**
- * Refresh interface object
- */
- privateOnRefresh mOnRefresh;
- /**
- * Variables used to control event interception
- */
- private boolean mCanIntercept;
- private int mTouchSlop;
- private int mDistance;
- private LayoutParams mHeaderParams;
- private View mHeaderView;
- private View mFootView;
- private int mHeaderMaxHeight;
- private int mStartY;
- private LayoutParams mFootParams;
- private int mFootMaxHeight;
- private PullCallBack mCallBack;
- private View mChildView;
- private ObjectAnimator mAnimator;
-
- public RefreshLayout(Context context) {
- super(context);
- initData();
- }
-
- public RefreshLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initData();
- }
-
- public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- initData();
- }
-
- /**
- * The interface that the head and tail controls must implement
- */
- public interface HeadAndFootCallBack {
- //Set properties
- void setAttribute();
-
- //Start refreshing
- void startPull();
-
- //Stop refreshing
- void stopPull();
- }
-
- /**
- * The dragged control subclass must implement
- */
- public interface PullCallBack {
- boolean canDownPull();
-
- boolean canUpPull();
- }
-
- private void initData() {
- //Drawing cannot be performed without calling this method
- setWillNotDraw( false );
- }
-
- /**
- * This method must be used after the pull-down refresh is completed
- */
- public void downPullFinish() {
- mAnimator.setFloatValues(mChildView.getTranslationY(), 0);
- mAnimator.start();
- ((HeadAndFootCallBack) mHeaderView).stopPull();
- }
-
- /**
- * This method must be called after the pull-up is completed
- */
- public void upPullFinish() {
- mAnimator.setFloatValues(mChildView.getTranslationY(), 0);
- mAnimator.start();
- ((HeadAndFootCallBack) mFootView).stopPull();
- }
-
- /**
- * Automatic pull-down refresh
- */
- public void autoDownPullForHead() {
- postDelayed(new Runnable() {
- @Override
- public void run() {
- mCanDownPull = true ;
- mCanUpPull = false ;
- mAnimator.setFloatValues(10, mHeaderMaxHeight);
- mAnimator.start();
- ((HeadAndFootCallBack) mHeaderView).startPull();
- mOnRefresh.onDownPullRefresh();
- }
- }, 500);
- }
-
- /**
- * Automatic pull-down refresh
- */
- public void autoUpPullForHead() {
- postDelayed(new Runnable() {
- @Override
- public void run() {
- mCanDownPull = false ;
- mCanUpPull = true ;
- mAnimator.setFloatValues(0, mFootMaxHeight);
- mAnimator.start();
- ((HeadAndFootCallBack) mFootView).startPull();
- mOnRefresh.onUpPullRefresh();
- }
- }, 500);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return mCanIntercept;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return true ;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- Log.e( "shen" , "mIsRefreshDuring=" + mIsRefreshDuring);
- if (mIsRefreshDuring)/*If refresh is in progress, MotionEvent will not be obtained*/ {
- return super.dispatchTouchEvent(event);
- }
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mStartY = ( int ) event.getY();
- initPull();
- break;
- case MotionEvent.ACTION_MOVE:
- if (event.getPointerCount() == 1) {
- int moveY = ( int ) event.getY();
- mDistanceY = (moveY - mStartY) / V_REFRESH;
- if (!mIsFirstMove && mDistanceY != 0 && mDistanceY < mTouchSlop) {
- mCanDownPull = mDistanceY > 0;
- mCanUpPull = !mCanDownPull;
- mIsFirstMove = true ;
- }
- if (mCanDownPull && mCallBack.canDownPull()) {
- upDataForDownPull(); //Pull down to refresh
- mChildView.setEnabled( false );
- mCanIntercept = true ;
- }
- if (mCanUpPull && mCallBack.canUpPull()) {
- upDataForUpPull(); //Pull up to load
- mChildView.setEnabled( false );
- mCanIntercept = true ;
- }
- mStartY = moveY;
- }
- break;
- case MotionEvent.ACTION_UP:
- mIsRefreshDuring = true ;
- mIsFirstMove = false ;
- if (mHeaderParams.height >= mHeaderMaxHeight)/*You can pull down to refresh*/ {
- ((HeadAndFootCallBack) mHeaderView).startPull();
- mOnRefresh.onDownPullRefresh();
- } else if (mFootParams.height >= mFootMaxHeight)/*You can pull up to refresh*/ {
- ((HeadAndFootCallBack) mFootView).startPull();
- mOnRefresh.onUpPullRefresh();
- } else if (mHeaderParams.height > 0 && mHeaderParams.height < mHeaderMaxHeight)/*Cannot pull down to refresh, retract*/ {
- releaseForDownFinished();
- } else if (mFootParams.height > 0 && mFootParams.height < mFootMaxHeight)/*Cannot pull down to refresh, retract*/ {
- releaseForUpFinished();
- } else {
- mIsRefreshDuring = false ;
- mCanIntercept = false ;
- }
- break;
- }
- super.dispatchTouchEvent(event);
- return true ;
- }
-
- /**
- * Each touch requires initialization
- */
- private void initPull() {
- mCanDownPull = false ;
- mCanUpPull = false ;
- }
-
- /**
- * No pull-up refresh required
- */
- private void releaseForUpFinished() {
- mAnimator.setFloatValues(mChildView.getTranslationY(), 0);
- mAnimator.start();
- }
-
- /**
- * No need to pull down to refresh
- */
- private void releaseForDownFinished() {
- mAnimator.setFloatValues(mChildView.getTranslationY(), 0);
- mAnimator.start();
- }
-
- /**
- * Handle gestures when pulling up
- */
- private void upDataForUpPull() {
- if (mDistanceY != 0) {
- mFootParams.height -= mDistanceY;
- if (mFootParams.height <= 0) {
- mFootParams.height = 0;
- }
- if (mFootParams.height >= mFootMaxHeight) {
- mFootParams.height = mFootMaxHeight;
- }
- mChildView.setTranslationY(-mFootParams.height);
- mFootView.requestLayout();
- }
- }
-
- /**
- * Handle gestures when pulling down
- */
- private void upDataForDownPull() {
- if (mDistanceY != 0) {
- mHeaderParams.height += mDistanceY;
- if (mHeaderParams.height >= mHeaderMaxHeight) { //***
- mHeaderParams.height = mHeaderMaxHeight;
- }
- if (mHeaderParams.height <= 0) { //Minimum
- mHeaderParams.height = 0;
- }
- mChildView.setTranslationY(mHeaderParams.height);
- mHeaderView.requestLayout();
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- // Load header
- mHeaderView = getChildAt(0);
- if (!(mHeaderView instanceof HeadAndFootCallBack)) {
- new IllegalStateException( "HeaderView must implement the HeadAndFootCallBack interface" );
- }
- ((HeadAndFootCallBack) mHeaderView).setAttribute();
- mHeaderParams = (LayoutParams) mHeaderView.getLayoutParams();
- mHeaderParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
-
- //Load end
- mFootView = getChildAt(2);
- if (!(mFootView instanceof HeadAndFootCallBack)) {
- new IllegalStateException( "FootView must implement the HeadAndFootCallBack interface" );
- }
- ((HeadAndFootCallBack) mFootView).setAttribute();
- mFootParams = (LayoutParams) mFootView.getLayoutParams();
- mFootParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
-
- mChildView = getChildAt(1);
- if (!(mChildView instanceof HeadAndFootCallBack)) {
- new IllegalStateException( "ChildView must implement the PullCallBack interface" );
- }
- mCallBack = (PullCallBack) getChildAt(1);
-
- //Set up the animation
- mAnimator = ObjectAnimator.ofFloat(mChildView, "translationY" , 0);
- mAnimator.setInterpolator(new DecelerateInterpolator());
- mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int translationY = ( int ) mChildView.getTranslationY();
- if (mCanUpPull) { //Slide down from the position moved to
- mFootParams.height = Math. abs (translationY);
- mFootView.requestLayout();
- } else if (mCanDownPull) {
- mHeaderParams.height = Math. abs (translationY);
- mHeaderView.requestLayout();
- }
- Log.e( "shen" , "translationY=" + translationY);
- Log.e( "shen" , "mHeaderParams.height=" + mHeaderParams.height);
- if (translationY == 0) {
- mChildView.setEnabled( true );
- mDistanceY = 0; // reset
- mIsRefreshDuring = false ; //Reset
- mCanIntercept = false ;
- } else {
- mIsRefreshDuring = true ;
- }
- }
- });
- }
-
- @Override
- protected void onSizeChanged( int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- mDistance = mTouchSlop * 5;
- //Set the initial properties of the pull-down header
- mHeaderMaxHeight = mHeaderParams.height;
- mHeaderParams.height = 0;
- mHeaderView.requestLayout();
- //Set the initial properties of the pull-up tail
- mFootMaxHeight = mFootParams.height;
- mFootParams.height = 0;
- mFootView.requestLayout();
- }
-
- /**
- * Pull-down/pull-up event monitoring
- */
- public interface OnRefresh {
- /**
- * Pull down to refresh
- */
- void onDownPullRefresh();
-
- /**
- * Pull-up loading
- */
- void onUpPullRefresh();
- }
-
- public void setOnRefresh(OnRefresh onRefresh) {
- mOnRefresh = onRefresh;
- }
-
- }
Add three controls to it. The first and last controls are for refreshing the head and footer, and the second one is for normal display. The head and footer must implement the HeadAndFootCallBack interface to set properties and notify the start and end of refreshing. Difficulty: Now let’s talk about the difficulties encountered during development - Since the judgment is in dispatchTouchEvent, if the control and its child controls do not consume the event, the event will not be sent to it, because if the DOWN event is not consumed, all subsequent events will not be received. The solution is to let the control's onTouchEvent method return true. When the child control does not consume the event, it will return to the control for consumption, which will not cause the failure to receive the event due to the non-consumption of the DOWN event, resulting in the dispatchTouchEvent not consuming the event.
- Animation, animation is my pain. I am learning about estimators recently.
I think this control is well written. It can help us learn event distribution, animation, and interface callback, which is also of certain learning significance. |