Implementation of Android drop-down refresh framework

Posted by tony_l on Sun, 16 Jun 2019 01:15:57 +0200

1. About drop-down refresh

Drop-down refresh was first invented by Loren Brichter, the founder of twitter. There is a theory that pull-down refresh is an application that can be used to arrange feeds in a chronological order from new to old. In this application scenario, users will naturally pull down to find the updated content, so pull-down refresh is very reasonable. You can refer to this article: Interesting drop-down refresh Now I post an interesting drop-down refresh case.

Figure 1. Interesting drop-down refresh cases (1)


Figure 1. Interesting drop-down refresh cases (2)


2. Realization Principle

In the above examples, the appearance is good, and his essence is the same, that is, a drop-down refresh control usually consists of the following parts:
[1]Header
Header s usually have drop-down arrows, text, progress bars and other elements that change their state according to the drop-down distance, thus displaying different styles.
[2]Content
This part is the content area. There are many examples on the Internet that add headers directly to ListView, but this has limitations, because in many cases, ListView is not necessarily used to display data. We put the View to display content in one of our containers. If you want to implement a drop-down refresh to display data with ListView, you need to create a ListView to rotate to my container. We handle the events of this container (down, move, up), and if we pull down, we slide down the entire layout to display the header.
[3]Footer
Footer can be used to display up-pull arrows, automatically load more progress bars, etc.

To sum up the above three parts, it is the layout structure shown in the following figure:
Figure 3. Layout structure of drop-down refresh

With regard to the above figure, several points need to be explained:
1. This layout is extended to Linear Layout and arranged vertically.
2. The order from top to bottom is: Header, Content, Footer
3. Content fills in the parent control and makes Header and Footer invisible by setting top and padding of the bottom, that is, to make it out of the screen.
4. When pulling down, scrollTo method is called to slide down the whole layout, thus displaying the Header, and pulling up is just the opposite of pulling down.
5. Derivative classes need to implement: Fill Content View into parent container, for example, if you want to use it, then you need to add ListView, ScrollView, WebView and so on to container.
6. The red area in the picture above is the size of the screen. (Strictly speaking, the size of the screen is not accurate, it should be said that the content area is more accurate.)

3. Realization

Understanding the principle and process of implementation, we try to achieve specific implementation. First, in order to better expand and design more reasonable in the future, we abstract the function of drop-down refresh into an interface:

1,IPullToRefresh<T extends View>

Its specific definition is as follows:
  1. public interface IPullToRefresh<T extends View> {  
  2.     public void setPullRefreshEnabled(boolean pullRefreshEnabled);  
  3.     public void setPullLoadEnabled(boolean pullLoadEnabled);  
  4.     public void setScrollLoadEnabled(boolean scrollLoadEnabled);  
  5.     public boolean isPullRefreshEnabled();  
  6.     public boolean isPullLoadEnabled();  
  7.     public boolean isScrollLoadEnabled();  
  8.     public void setOnRefreshListener(OnRefreshListener<T> refreshListener);  
  9.     public void onPullDownRefreshComplete();  
  10.     public void onPullUpRefreshComplete();  
  11.     public T getRefreshableView();  
  12.     public LoadingLayout getHeaderLoadingLayout();  
  13.     public LoadingLayout getFooterLoadingLayout();  
  14.     public void setLastUpdatedLabel(CharSequence label);  
  15. }  
This interface is generic. It accepts a derivative class of View, because it's not just a View to be placed in our container.

2,PullToRefreshBase<T extends View>
This class implements the IPullToRefresh interface, which is inherited from LinearLayout as an abstract base class for drop-down refresh. If you want to implement the drop-down refresh of ListView, you just need to extend this class and implement some necessary methods. The main responsibilities of this category are as follows:
  • Handling events in onInterceptTouchEvent() and onTouchEvent(): When the View of the content (such as ListView) is at the top, then pull down, we must truncate the event, and then move the event to the onTouchEvent() method, in which we scroll the entire View according to the distance of the move.
  • Create Header, Footer and Content View: Call methods to create Views in the constructor, and derivative classes can override these methods to provide different styles of Header and Footer. It will call createHeader Loading Layout and createFooter Loading Layout methods to create Header and Footer Content View. The method of creating Content View by Header and Footer is an abstract method. Derivative classes must be allowed to override these methods to provide different styles of Header and Footer. Class implementation, returns a non-null View, and then the container adds the View to itself.
  • Set up various states: There are many states, such as pull-up, pull-up, refresh, load, release, etc. It will change the state according to the distance pulled by the user. It will also change the state of Header and Footer, and then Header and Footer will display the corresponding interface style according to the state.
3. PullToRefreshBase < T extends View > Inheritance Relations
Here I implement three drop-down refresh derived classes: ListView, ScrollView and WebView. Their inheritance relationships are as follows:

Figure 4. Inheritance relationship of PullToRefreshBase class

With regard to the PullToRefreshBase class and its factions and classes, several points need to be explained:
  • For ListView, ScrollView and WebView, the implementation of whether they slide to the top or bottom is different. Therefore, in the PullToRefreshBase class, two abstract methods need to be invoked to determine whether the current location is at the top or bottom, and the derived class must implement these two methods. For ListView, for example, the condition that it slides to the top is that the first child is fully visible and the first post is 0. These two abstract methods are:
  1. /** 
  2.  * Determine whether the refreshed View slides to the top 
  3.  *  
  4.  * @return true Represents that you have slid to the top, otherwise false 
  5.  */  
  6. protected abstract boolean isReadyForPullDown();  
  7.   
  8. /** 
  9.  * Determine whether the refreshed View slides to the end 
  10.  *  
  11.  * @return true Represents that it has slid to the bottom, otherwise false 
  12.  */  
  13. protected abstract boolean isReadyForPullUp();  
  • The abstract way to create a drop-down refreshable View (that is, content view) is
  1. /** 
  2.  * Create View s that can be refreshed 
  3.  *  
  4.  * @param context context 
  5.  * @param attrs attribute 
  6.  * @return View 
  7.  */  
  8. protected abstract T createRefreshableView(Context context, AttributeSet attrs);  
4,LoadingLayout
Loading Layout is an abstraction that refreshes Layout. It is an abstract base class. Header and Footer both extend to this class. This kind of abstract class provides two abstract methods:
  • getContentSize
This method returns the current size of the refresh Layout, which usually returns the height of the layout. In order to expand to horizontal pull in the future, the method name is not taken as getLayoutHeight() or something like that. This return value will be used as a critical value for refreshing after loosening hands. If the drop-down offset value is greater than this value, it will be considered refreshing, otherwise it will not refresh. This method must be implemented by derived classes.
  • setState
This method is used to set the current status of the refresh Layout. The PullToRefreshBase class calls this method. When it enters the pull-down, release and other actions, it calls this method. The derived class only needs to display different interfaces according to these states. When the pull-down state is shown below, the arrow is displayed and the loading icon is displayed when the refresh state is refreshed.
Possible state values are: RESET, PULL_TO_REFRESH, RELEASE_TO_REFRESH, REFRESHING, NO_MORE_DATA.

The inheritance relationship between LoadingLayout and its derived classes is shown in the following figure:

Fig. 5. Class diagrams of Loading Layout and its derived classes

We can make our own headers and Footers at will. We can also implement Headers and Footers in various drop-down refresh cases as shown in Figures 1 and 2. Just rewrite the two methods getContentSize() and setState(). Header Loading Layout, which by default displays arrow-like layout, while Rotate Loading Layout displays a rotating icon.

5. Event handling
We must override the onInterceptTouchEvent() and onTouchEvent() methods associated with two events of the PullToRefreshBase class. Because ListView, ScrollView and WebView are placed inside PullToRefreshBase, the event is first passed to the PullToRefreshBase#onInterceptTouchEvent() method, so we should deal with ACTION_MOVE event in this method to determine if the current ListView, ScrollView, WebView is at the top or bottom, and if so, start truncating the event. Once the event is truncated, subsequent events are passed to the PullToRefreshBase#onInterceptTouchEvent() method, and we move the entire layout in the ACTION_MOVE event to achieve pull-down or pull-up actions.

6. Scroll to
As you can see from the layout structure in Figure 3, by default, Header and Footer are placed at the top and bottom of Content View. By setting padding to let him run out of the screen, if we scroll down the whole layout for a certain distance, then Header will be displayed. Based on this situation, in my implementation, I finally call scroll To to reality. Now pull down.

Generally speaking, the important points of implementation are these. Some details will be encountered in the implementation. You can refer to the code.

4. How to use it

The code using drop-down refresh is as follows
  1. @Override  
  2.     public void onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.           
  5.         mPullListView = new PullToRefreshListView(this);  
  6.         setContentView(mPullListView);  
  7.           
  8.         //Upload is unavailable  
  9.         mPullListView.setPullLoadEnabled(false);  
  10.         //Scroll to the end to automatically load available  
  11.         mPullListView.setScrollLoadEnabled(true);  
  12.           
  13.         mCurIndex = mLoadDataCount;  
  14.         mListItems = new LinkedList<String>();  
  15.         mListItems.addAll(Arrays.asList(mStrings).subList(0, mCurIndex));  
  16.         mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListItems);  
  17.           
  18.         //Get the actual ListView  
  19.         mListView = mPullListView.getRefreshableView();  
  20.         //Binding data  
  21.         mListView.setAdapter(mAdapter);         
  22.         //Set up listener for drop-down refresh  
  23.         mPullListView.setOnRefreshListener(new OnRefreshListener<ListView>() {  
  24.             @Override  
  25.             public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {  
  26.                 mIsStart = true;  
  27.                 new GetDataTask().execute();  
  28.             }  
  29.   
  30.             @Override  
  31.             public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {  
  32.                 mIsStart = false;  
  33.                 new GetDataTask().execute();  
  34.             }  
  35.         });  
  36.         setLastUpdateTime();  
  37.           
  38.         //Automatic refresh  
  39.         mPullListView.doPullRefreshing(true500);  
  40.     }  
This is to initialize a drop-down refresh layout and call setContentView to set it into Activity.
After the drop-down refresh is complete, we can call onPullDownRefreshComplete() and onPullUpRefreshComplete() methods to stop refreshing and loading.

5. Operation effect

Here is a list of demo's running effects.

Figure 6. ListView drop-down refresh, pay attention to the style of Header and Footer


Figure 7. Draw-down refresh effect of WebView and ScrollView


6. Source Download

It's not my original idea to implement this drop-down refresh framework. I also refer to a lot of open source, and draw on what I think is better, so as to form my things. I mainly refer to the following demo:
https://github.com/chrisbanes/Android-PullToRefresh This demo is well written, but it's too complicated. As we all know, once it's complicated, if we need to add some needs, we need to work harder. In fact, I just simplify it to meet our own needs.

Download the source code, please click on me

Reprinted please indicate the source
http://blog.csdn.net/leehong2005/article/details/12567757 
Thank you!!!


7. Bug repair


Known bug fixes are as follows, the viewer who found the code bug can also give me feedback, thank you.~~~

1. For the drop-down refresh of ListView, when scroll to autoload is enabled, if footer changes from hidden to displayed, an exception will appear.
This problem has been fixed. The code for amendment is as follows:
  • PullToRefreshListView#setScrollLoadEnabled method, the revised code is as follows:
  1. @Override  
  2. public void setScrollLoadEnabled(boolean scrollLoadEnabled) {  
  3.     if (isScrollLoadEnabled() == scrollLoadEnabled) {  
  4.         return;  
  5.     }  
  6.       
  7.     super.setScrollLoadEnabled(scrollLoadEnabled);  
  8.       
  9.     if (scrollLoadEnabled) {  
  10.         //Setting Footer  
  11.         if (null == mLoadMoreFooterLayout) {  
  12.             mLoadMoreFooterLayout = new FooterLoadingLayout(getContext());  
  13.             mListView.addFooterView(mLoadMoreFooterLayout, nullfalse);  
  14.         }  
  15.           
  16.         mLoadMoreFooterLayout.show(true);  
  17.     } else {  
  18.         if (null != mLoadMoreFooterLayout) {  
  19.             mLoadMoreFooterLayout.show(false);  
  20.         }  
  21.     }  
  22. }  
  • LoadingLayout show method, the revised code is as follows:
  1. /** 
  2.  * Display or hide this layout 
  3.  *  
  4.  * @param show flag 
  5.  */  
  6. public void show(boolean show) {  
  7.     // If is showing, do nothing.  
  8.     if (show == (View.VISIBLE == getVisibility())) {  
  9.         return;  
  10.     }  
  11.       
  12.     ViewGroup.LayoutParams params = mContainer.getLayoutParams();  
  13.     if (null != params) {  
  14.         if (show) {  
  15.             params.height = ViewGroup.LayoutParams.WRAP_CONTENT;  
  16.         } else {  
  17.             params.height = 0;  
  18.         }  
  19.           
  20.         requestLayout();  
  21.         setVisibility(show ? View.VISIBLE : View.INVISIBLE);  
  22.     }  
  23. }  
After changing the LayoutParameter, call the requestLayout() method.
  • Picture Rotation Compatible 2.x System
I thought about this system which only needs to be compatible with more than 3.x, but found that many netizens encountered compatibility problems in the use process, this time I took time to achieve this compatibility.
The modifications to onPull are as follows:
  1. @Override  
  2. public void onPull(float scale) {  
  3.     if (null == mRotationHelper) {  
  4.         mRotationHelper = new ImageViewRotationHelper(mArrowImageView);  
  5.     }  
  6.       
  7.     float angle = scale * 180f; // SUPPRESS CHECKSTYLE  
  8.     mRotationHelper.setRotation(angle);  
  9. }  

The main function of ImageView Rotation Helper is to realize the rotation function of ImageView. The internal version is distinguished. The implementation code is as follows:

  1. /** 
  2.      * The image view rotation helper 
  3.      *  
  4.      * @author lihong06 
  5.      * @since 2014-5-2 
  6.      */  
  7.     static class ImageViewRotationHelper {  
  8.         /** The imageview */  
  9.         private final ImageView mImageView;  
  10.         /** The matrix */  
  11.         private Matrix mMatrix;  
  12.         /** Pivot X */  
  13.         private float mRotationPivotX;  
  14.         /** Pivot Y */  
  15.         private float mRotationPivotY;  
  16.           
  17.         /** 
  18.          * The constructor method. 
  19.          *  
  20.          * @param imageView the image view 
  21.          */  
  22.         public ImageViewRotationHelper(ImageView imageView) {  
  23.             mImageView = imageView;  
  24.         }  
  25.           
  26.         /** 
  27.          * Sets the degrees that the view is rotated around the pivot point. Increasing values 
  28.          * result in clockwise rotation. 
  29.          * 
  30.          * @param rotation The degrees of rotation. 
  31.          * 
  32.          * @see #getRotation() 
  33.          * @see #getPivotX() 
  34.          * @see #getPivotY() 
  35.          * @see #setRotationX(float) 
  36.          * @see #setRotationY(float) 
  37.          * 
  38.          * @attr ref android.R.styleable#View_rotation 
  39.          */  
  40.         public void setRotation(float rotation) {  
  41.             if (APIUtils.hasHoneycomb()) {  
  42.                 mImageView.setRotation(rotation);  
  43.             } else {  
  44.                 if (null == mMatrix) {  
  45.                     mMatrix = new Matrix();  
  46.                       
  47.                     //Calculate the center of rotation  
  48.                     Drawable imageDrawable = mImageView.getDrawable();  
  49.                     if (null != imageDrawable) {  
  50.                         mRotationPivotX = Math.round(imageDrawable.getIntrinsicWidth() / 2f);  
  51.                         mRotationPivotY = Math.round(imageDrawable.getIntrinsicHeight() / 2f);  
  52.                     }  
  53.                 }  
  54.                   
  55.                 mMatrix.setRotate(rotation, mRotationPivotX, mRotationPivotY);  
  56.                 mImageView.setImageMatrix(mMatrix);  
  57.             }  
  58.         }  
  59.     }  

At the core, if you rotate ImageView on version 2.x, you use Matrix.

  • PullToRefreshBase construction method is compatible with 2.x
The constructor of the three parameters declares the following annotations:
    @SuppressLint("NewApi")
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)

Topics: Android Attribute github