android-Ultra-Pull-To-Refresh Source Parsing

Project address: https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh

1. Functional introduction

Drop-down refresh is a feature that almost every Android application needs. android-Ultra-Pull-To-Refresh (hereinafter referred to as UltraPTR) is a powerful Andriod drop-down refresh framework.
Main features:
(1) Inherited from ViewGroup, Content can contain any View.
(2) Simple and perfect Header abstraction, easy to expand, build a head that meets the needs.

Contrast Android-PullToRefresh Project, UltraPTR There are no more functions to load, but I don't think that pull-down refresh and load more are functions of the same level. Drop-down refresh has a wider demand and can be applied to any page. And loading more functions should be handed over to specific ones Content implements itself. This should be the same design idea as the official launch of Swipe Refresh Layout by Google, but compared with Swipe Refresh Layout, UltraPTR is more flexible and easier to expand.

2. Overall design

The overall design of UltraPTR is simple and clear.
Firstly, two interfaces, functional interface and UI interface are abstracted.
PtrHandler represents the drop-down refresh function interface, including the refresh function callback method and the method to determine whether the drop-down can be made. Users implement this interface to refresh data.
PtrUIHandler represents a drop-down refresh UI interface, including callback methods for preparing drop-down, drop-down, drop-down completion, reset and position change during drop-down. Usually, headers need to implement this interface to handle changes in the Header UI during drop-down refresh.
The whole project revolves around the core class PtrFrameLayout. PtrFrameLayout represents a drop-down refresh custom control.
PtrFrameLayout inherits from ViewGroup and has and can only have two sub-Views, header and content. Usually the Header implements the PtrUIHandler interface, and Content can be any View.
Like all custom controls, PtrFrameLayout determines the size and location of controls by rewriting onFinishInflate, onMeasure, and onLayout. The drop-down behavior of the control is determined by rewriting dispatchTouchEvent.

3. Flow chart

Refer to the 4.1.5 PtrFrameLayout event interception flow chart

4. Detailed design

4.1 Introduction to Core Class Functions

4.1.1 PtrHandler.java

The pull-down refresh function interface contains the following two methods to abstract the pull-down refresh function.

public void onRefreshBegin(final PtrFrameLayout frame)

Refresh callback function, where users write their own refresh function implementation, processing business data refresh.

public boolean checkCanDoRefresh(final PtrFrameLayout frame, final View content, final View header)

Determine whether a drop-down refresh is possible. The Content of UltraPTR can contain anything, and the user decides whether or not it can be pulled down.
For example, if Content is TextView, you can return true directly, indicating that you can drop-down refresh.
If Content is ListView, return true when the first item is at the top, indicating that it can be refreshed drop-down.
If Content is ScrollView, it returns true when it slides to the top, indicating that it can be refreshed.

4.1.2 PtrDefaultHandler.java

The abstract class implements the PtrHandler.java interface, gives the default implementation of checkCanDoRefresh, and gives the judgment method of whether common View can be pulled down.

@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
    return checkContentCanBePulledDown(frame, content, header);
}
public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
    /**
     * If Content is not ViewGroup, return true to indicate that you can drop it down</br>
     * For example: TextView, ImageView
     */
    if (!(content instanceof ViewGroup)) {
        return true;
    }

    ViewGroup viewGroup = (ViewGroup) content;

    /**
     * If Content has no child View (empty content), return true to indicate that it can be dropped down.
     */
    if (viewGroup.getChildCount() == 0) {
        return true;
    }

    /**
     * If Content is Abs ListView (GridView), when the first item is not visible, return false and do not pull down.
     */
    if (viewGroup instanceof AbsListView) {
        AbsListView listView = (AbsListView) viewGroup;
        if (listView.getFirstVisiblePosition() > 0) {
            return false;
        }
    }

    /**
     * If the SDK version is more than 14, you can use canScrollVertically to determine whether you can slide up in the vertical direction.</br>
     * Can't go up, indicating that it's sliding to the top or Content can't go up, return true, and drop down.</br>
     * You can go up, return false, and not drop down.
     */
    if (Build.VERSION.SDK_INT >= 14) {
        return !content.canScrollVertically(-1);
    } else {
        /**
         * SDK Version less than 14, if Content is ScrollView or AbsListView, determine the sliding position by getScrollY</br>
         * If the position is 0, it means at the top, returning true, you can drop it down.
         */
        if (viewGroup instanceof ScrollView || viewGroup instanceof AbsListView) {
            return viewGroup.getScrollY() == 0;
        }
    }

    /**
     * Finally, determine the top value of the first subview</br>
     * If the first child view has margin, when top== margin Top of child view + padding Top of content, it means at the top, return true, and drop down.</br>
     * If there is no margin, when top==content's paddinTop, it means at the top, returns true, and can be pulled down.
     */
    View child = viewGroup.getChildAt(0);
    ViewGroup.LayoutParams glp = child.getLayoutParams();
    int top = child.getTop();
    if (glp instanceof ViewGroup.MarginLayoutParams) {
        ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) glp;
        return top == mlp.topMargin + viewGroup.getPaddingTop();
    } else {
        return top == viewGroup.getPaddingTop();
    }
}

In particular, there are some minor bug s in the above code. Issue

if (viewGroup instanceof ScrollView || viewGroup instanceof AbsListView) {
    return viewGroup.getScrollY() == 0;
}

If Content is Abs ListView (GridView), the value obtained through getScrollY() is always 0, so the judgment of this code is invalid.

Note: The above bug s are in the new version( 3a34b2e) Repair has been made. The following is the latest version of the code.

public static boolean canChildScrollUp(View view) {
    if (android.os.Build.VERSION.SDK_INT < 14) {
        if (view instanceof AbsListView) {
            final AbsListView absListView = (AbsListView) view;
            return absListView.getChildCount() > 0
                    && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                    .getTop() < absListView.getPaddingTop());
        } else {
            return view.getScrollY() > 0;
        }
    } else {
        return view.canScrollVertically(-1);
    }
}
public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
    return !canChildScrollUp(content);
}
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
    return checkContentCanBePulledDown(frame, content, header);
}

The new way of judging is also relatively simple and clear.
Of course, the above is a general view of the judgment. If you encounter a View with special requirements, or customize the View. Users still have to make their own judgments to meet their needs.

4.1.3 PtrUIHandler.java

Pull-down refresh UI interface, the abstraction of pull-down refresh UI changes. In general, the Header implements this interface to handle changes in the Header UI during the drop-down process.
It contains the following five methods.

public void onUIReset(PtrFrameLayout frame);

Content goes back to the top, the Header disappears, and after the entire drop-down refresh process is complete, the View is reset.

public void onUIRefreshPrepare(PtrFrameLayout frame);

Ready to refresh, call when Header will appear.

public void onUIRefreshBegin(PtrFrameLayout frame);

To start refreshing, the Header is called before it enters the refresh state.

public void onUIRefreshComplete(PtrFrameLayout frame);

When the refresh is over, the Header is called before it starts moving up.

public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, int oldPosition, int currentPosition, float oldPercent, float currentPercent);

Position change callback during pull-down.

4.1.4 PtrUIHandlerHolder.java

Implement the UI interface PtrUIHandler, encapsulate the PtrUIHandler and organize it into a linked list. The purpose of encapsulating linked list is that the author hopes that the caller can implement PtrUIHandler like Header, and capture the onUI Reset, onUI Refresh Prepare, onUI Refresh Begin, onUI Refresh Complete to achieve their own logic or UI effect, which are managed by PtrUIHandler Holder. You just need to add it to the list through the addHandler method, which is quite convenient for developers who want to do some processing.

4.1.5 PtrFrameLayout.java

UltraPTR core class, custom control class.
As a custom control, UltraPTR has eight custom properties.
ptr_header, set the header id.
ptr_content, setting the content id.
ptr_resistance, damping coefficient, default: 1.7f, the larger, the more difficult it is to feel pull down.
ptr_ratio_of_header_height_to_refresh, the position ratio when triggering refresh, default, 1.2f, move to head height The refresh operation can be triggered at 1.2 times.
ptr_duration_to_close, rebound delay, default 200 ms, rebound to a new height time.
ptr_duration_to_close_header, head rebound time, default 1000ms.
ptr_pull_to_fresh, whether the refresh keeps the header, the default value is true.
ptr_keep_header_when_refresh, drop-down refresh/release refresh, default to release refresh.

The following is an analysis of this type from two aspects: display and behavior.
(1) Display (View drawing)
Reference Technical Points, Public Technology Points View Drawing Process

@Override
protected void onFinishInflate() {...}

UltraPTR has and only has two subviews, rewriting the onFinishInflate method to determine the Header and Content.
Header can be set in code by setHeaderView, or by two custom attributes, ptr_header and ptr_content. Or directly in the layout file, for PtrFrameLayout adds two sub-View s, and then judges the assignment in onFinishInflate.
Usually, the Header implements the PtrUIHandler interface.
Finally, the Header instance is assigned to the mHeaderView variable and the Content instance to the mContent variable.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    ...
    // Measuring Header
    if (mHeaderView != null) {
        measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0);
        MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
        mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
        mOffsetToRefresh = (int) (mHeaderHeight * mRatioOfHeaderHeightToRefresh);
        ...
    }
    // Measuring Content 
    if (mContent != null) {
        measureContentView(mContent, widthMeasureSpec, heightMeasureSpec);
        ...
    }
}

Rewrite onMeasure, measure Header and Content, assign Header's height to mHeaderHeight variable, and assign the calculated drop-down refresh offset to mOffsetToRefresh variable.

@Override
protected void onLayout(boolean flag, int i, int j, int k, int l) {...}

PtrFrameLayout inherits ViewGroup, which must rewrite the onLayout method to locate the child View.
PtrFrameLayout has only two sub-View s.
For Header

final int top = paddingTop + lp.topMargin + offsetX - mHeaderHeight;

For Content

final int top = paddingTop + lp.topMargin + offsetX;

As can be seen from the above code, when calculating the Header top value, the height of a Header offset upward (mHeader Height) will initially hide the Header.
There's a offsetX variable in the code (I think it's better to change it to offsetY), initially 0. With the drop-down process, offsetX will gradually increase, so that both Header and Content will move downward, and Header will show the effect of the drop-down position movement.

(2) Behavior (View Event)
Reference Technical Points, Public Technology Points View Event Passing
ViewGroup's event handling usually overrides the onInterceptTouchEvent method or dispatchTouchEvent method, while PtrFrameLayout overrides the dispatchTouchEvent method.
The event processing flow chart is as follows:

The above two points need to be analyzed.

  1. The onRelease method executed when ACTION_UP or ACTION_CANCEL.
    Functionally, by executing the tryToPerformRefresh method, if the downward pull displacement has exceeded the offset triggering the drop-down refresh, and the current state is PTR_STATUS_PREPARE, performs the refresh function callback.
    Behaviorally, if the offset to trigger the refresh is not reached, or the current state is PTR_STATUS_COMPLETE, or the head position is not maintained during the refresh process, the upward position recovery action is performed.
  2. In ACTION_MOVE, determine whether you can move vertically.
    The direction of ACTION_MOVE is downward, if mPtrHandler is not empty, and mPtrHandler.checkCanDoRefresh returns a value of true, you can move, Header and Content move down, otherwise, the event is handled by the parent class.
    ACTION_MOVE moves upward. If the current position is larger than the starting position, it can move. Header and Content move upward. Otherwise, the event is handled by the parent class.

4.1.6 PtrClassicDefaultHeader.java

Head Implementation of Classic Drop-Down Refresh

PtrClassic DefaultHeader implements the PtrUIHandler interface.
The classical style of Header implementation can be used as a reference for our implementation of custom header. The following is the specific implementation.

@Override
public void onUIReset(PtrFrameLayout frame) {
    resetView();
    mShouldShowLastUpdate = true;
    tryUpdateLastUpdateTime();
}
private void resetView() {
    hideRotateView();
    mProgressBar.setVisibility(INVISIBLE);
}
private void hideRotateView() {
    mRotateView.clearAnimation();
    mRotateView.setVisibility(INVISIBLE);
}

Reset View, hide busy progress bar, hide arrow View, update last refresh time.

@Override
public void onUIRefreshPrepare(PtrFrameLayout frame) {
    mShouldShowLastUpdate = true;
    tryUpdateLastUpdateTime();
    mLastUpdateTimeUpdater.start();

    mProgressBar.setVisibility(INVISIBLE);

    mRotateView.setVisibility(VISIBLE);
    mTitleTextView.setVisibility(VISIBLE);
    if (frame.isPullToRefresh()) {
        mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down_to_refresh));
    } else {
        mTitleTextView.setText(getResources().getString(R.string.cube_ptr_pull_down));
    }
}

Prepare to refresh, hide busy progress bar, display arrow View, display text, if it is a drop-down refresh, show "drop-down refresh", if it is a release refresh, show "drop-down".

@Override
public void onUIRefreshBegin(PtrFrameLayout frame) {
    mShouldShowLastUpdate = false;
    hideRotateView();
    mProgressBar.setVisibility(VISIBLE);
    mTitleTextView.setVisibility(VISIBLE);
    mTitleTextView.setText(R.string.cube_ptr_refreshing);

    tryUpdateLastUpdateTime();
    mLastUpdateTimeUpdater.stop();
}

Start refreshing, hide arrow View, display busy progress bar, display text, show "loaded...", update the last refresh time.

@Override
public void onUIRefreshComplete(PtrFrameLayout frame) {

    hideRotateView();
    mProgressBar.setVisibility(INVISIBLE);

    mTitleTextView.setVisibility(VISIBLE);
    mTitleTextView.setText(getResources().getString(R.string.cube_ptr_refresh_complete));

    // update last update time
    SharedPreferences sharedPreferences = getContext().getSharedPreferences(KEY_SharedPreferences, 0);
    if (!TextUtils.isEmpty(mLastUpdateTimeKey)) {
        mLastUpdateTime = new Date().getTime();
        sharedPreferences.edit().putLong(mLastUpdateTimeKey, mLastUpdateTime).commit();
    }
}

At the end of the refresh, hide the arrow View, hide the busy progress bar, display the text, show "Update Completed" and write the last refresh time.

@Override
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, int lastPos, int currentPos, float oldPercent, float currentPercent) {
    final int mOffsetToRefresh = frame.getOffsetToRefresh();
    if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {
        if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
            crossRotateLineFromBottomUnderTouch(frame);
            if (mRotateView != null) {
                mRotateView.clearAnimation();
                mRotateView.startAnimation(mReverseFlipAnimation);
            }
        }
    } else if (currentPos > mOffsetToRefresh && lastPos <= mOffsetToRefresh) {
        if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
            crossRotateLineFromTopUnderTouch(frame);
            if (mRotateView != null) {
                mRotateView.clearAnimation();
                mRotateView.startAnimation(mFlipAnimation);
            }
        }
    }
}

Position change callback during pull-down.
In the case of dragging, when the drop-down distance is from less than the refresh height to more than the refresh height, the arrow View changes from downward to upward, while changing the text display.
When the drop-down distance is from greater than the refresh height to less than the refresh height, the arrow View changes from upward to downward, while changing the text display.

4.1.7 PtrClassicFrameLayout.java

Inherit PtrFrameLayout.java, classic drop-down refresh implementation class.
PtrClassic DefaultHeader is added as the header. Users only need to set Content when using it.

4.1.8 PtrUIHandlerHook.java

The hook task class implements the Runnable interface, which can be understood as inserting a task to execute between the original operations.
A hook task can only be performed once, by calling takeOver. At the end of execution, the user needs to call the resume method to restore the original operation.
If the hook task has been executed, calling takeOver will directly restore the original operation.
Set RefreshCompleteHook (PtrUIHandler Hook hook) of the PtrFrameLayout class. When the user calls the refreshComplete() method to indicate that the refresh has ended, if any The hook exists. First, the takeOver method of the hook is executed. At the end of the execution, the user needs to actively call the resume method of the hook before proceeding. Header bounces back to the top.

4.1.9 MaterialHeader.java

Material Design Style Header Implementation

4.1.10 StoreHouseHeader.java

StoreHouse style header implementation

4.1.11 PtrLocalDisplay.java

Display related tooling classes for obtaining screen width (pixels, dp), height (pixels, dp) and screen density of user equipment. At the same time, the conversion methods of dp and px are provided.

Class 4.2 Diagrams

5. Chat

5.1 Advantages

5.1.1 Abstract Decoupling of Head Behavior

Brush micro-blog, brush space, brush circles of friends, "brush" has become a habit of many people.
Nowadays mobile applications, almost all users actively refresh operations, are completed by pull-down.
So a well-designed drop-down refresh head can make your application eye-catching.
UltraPTR abstracts the behavior of the head, which makes it easy for users to customize their own drop-down refresh head to achieve various effects.

5.1.2 Content can contain any View

The Content of UltraPTR can contain any View. The advantage of this is that the refresh operation in the whole project, whether it is ListView, GridView or a Linear Layout, can be done with UltraPTR, simple and unified.

5.2 Expectations


At present, in the process of pull-down of UltraPTR, both Header and Content will change their position.

It is hoped that more flexible behaviour will be added to meet such needs.
for example
Knowingly, neither Header nor Content changes their position when they are pulled down, but the effect changes in Header.

Evernote, Content does not move when dropping down, Header has position change.

To sum up, we want to be more abstract about the behavior changes of Header and Content in the drop-down process. Real Ultra

Note: In the new version( 3a34b2e You can use

public void setPinContent(boolean pinContent)

Fix the Content so that the drop-down only moves the Header position.

5.3 More on loading

UltraPTR does not integrate more functions to load. The project's Issue also mentions the desire to add this feature.
Want to add a drop-down load.................. 35
If more pull-up loads are integrated into it, it will be invincible 8
The author responded that pull-down refresh and load more, not the same level of functionality. Loading more should not be done by UltraPTR, but by Content itself.
I also think that's appropriate. The power of UltraPTR is that its Content can be any View. Because the refresh action can be done on any View, such as a TextView, an ImageView, a WebView or a Linear Layout layout. Loading more functionality, however, is often used only for listView, GridView, etc., and most Views don't need this functionality. So it's better to leave it to ListView or GridView itself.

This article is reproduced from: http://a.codekk.com/detail/Android/Grumoon/android-Ultra-Pull-To-Refresh%20%E6%BA%90%E7%A0%81%E8%A3%E6%90%9E%

Keywords: Java Android less SDK

Added by curt3006 on Wed, 10 Jul 2019 22:19:05 +0300