There are many refresh controls in the Android camp now, and they are of varying quality. The author tried to work on its personalization and scrolling from a different angle. The author hopes that this refresh control can support most list controls like Google's SwipeRefreshLayout, and have more functions. It is best to support personalization very conveniently. Whether the ability to cross the border during scrolling will also bring a better interactive experience than ordinary refresh controls. The open source library is here, TwinklingRefreshLayout. If you like it, please star it. The author's article also revolves around the implementation of this control. For convenience, the author inherits TwinklingRefreshLayout directly from FrameLayout instead of ViewGroup, which can save some troubles such as onMeasure and onLayout. The Header and Footer are done by setting the Gravity property of View through LayoutParams. 1. View's onAttachedToWindow() method First of all, View has no obvious life cycle, and we can't add a header and footer to the control in the addView() constructor, so the appropriate time for this operation is before onDraw() - in the onAttachedToWindow() method. At this point, the View is added to the form, and the View has a Surface for display, and will start drawing. Therefore, it is guaranteed to be called before onDraw(), but it may be called at any time before onDraw(Canvas), including before or after onMeasure(int, int). It is more suitable for performing some initialization operations. (In addition, this method will also be called back when the Home button is blocked)
As for TwinklingRefreshLayout, Header and Footer need to be displayed in time, and View has no obvious life cycle, so setting it in onAttachedToWindow() can ensure that the refresh control is added before onDraw().
However, when TwinklingRefreshLayout is applied in an Activity or Fragment, the onAttachedToWindow() method may be re-triggered by executing onResume, which may cause the Header and Footer to be repeatedly created and block the originally added View. Therefore, it is necessary to add a judgment:
2. View event distribution mechanism The event distribution process is completed by dispatchTouchEvent, onInterceptTouchEvent and onTouchEvent. Since the event is transmitted from top to bottom, for ViewGroup, I think the most important method is onInterceptTouchEvent, which determines whether the event can continue to be transmitted downward. See the following pseudo code:
As shown in the code, if ViewGroup intercepts the event (onInterceptTouchEvent returns true), the event will be consumed in the onTouchEvent method of ViewGroup and will not be passed to the child View; otherwise, the event will be handed over to the child View for distribution. What we need to do is to intercept the event in time when the child View scrolls to the top or bottom, and let ViewGroup's onTouchEvent handle the sliding event. 3. Determine if the child View scrolls to the boundary When should the event be intercepted? For Header, when the finger slides down, that is, dy>0 and the sub-View has scrolled to the top (cannot scroll up anymore), it is intercepted; for bottom, when dy<0 and the sub-View has scrolled to the bottom (cannot scroll down anymore), it is intercepted:
Determine whether the View can continue to scroll up. For SDK versions above 14, the v4 package provides the following method:
In other cases, it is directly handed over to the child View, and ViewGroup has no control over it. 4. ViewGroup's onTouchEvent method At this point, the scrolling of the child View has been handed over to the child View itself. The events that ViewGroup needs to handle are only two critical states, that is, the state where the user may want to refresh when pulling down and the state where the user may want to load more when pulling up. This is the state recorded above. The next thing is simple, just listen to ACTION_MOVE and ACTION_UP. First, in ACTION_DOWN, we need to record the original finger pressing position mTouchY, and then in a series of ACTION_MOVE processes, get the current displacement (ev.getY()-mTouchY), and then use some calculation method to continuously calculate the displacement distance offsetY of the current child View, call mChildView.setTranslationY(offsetY) to continuously set the displacement of the child View, and at the same time apply for the layout height to HeadLayout to complete the display of the top control. The calculation method used by the author is the interpolator. In ACTION_UP, it is necessary to determine whether the displacement of the child View has reached the requirement of entering the refresh or loading more state, that is, mChildView.getTranslationY() >= mHeadHeight - mTouchSlop, where mTouchSlop exists to prevent jitter. When it is determined that the refresh state has been entered, the displacement of the current child View is between HeadHeight and maxHeadHeight, so the displacement of the child View needs to be returned to HeadHeight, otherwise it will be directly returned to 0. 5. Interpolator Interpolator is used for time interpolation in animation. Its function is to map the change of floating point value from 0 to 1 to another floating point value change. The calculation method mentioned above is as follows:
Among them, (dy / mWaveHeight / 2) is a floating point value between 0 and 1. As the pull-down height increases, this value becomes larger and larger, and the interpolation obtained through decelerateInterpolator also becomes larger and larger, but the change in these values is getting smaller and smaller (decelerate effect). dy represents the distance the finger moves. This is just a calculation method used by the author for the softness of sliding. The maximum distance of head displacement is mWaveHeight = dy/2. From this perspective, you can find that dy / mWaveHeight / 2 will change from 0 to 1. Interpolator inherits from the TimeInterpolator interface. The source code is as follows:
getInterpolation receives a float parameter between 0.0 and 1.0, where 0.0 represents the start of the animation and 1.0 represents the end of the animation. The return value can be greater than 1.0 or less than 0.0, such as OvershotInterpolator. So getInterpolation() is a function that returns a function value of about 0 to 1 when the input is 0 to 1. 6. Property Animation As mentioned above, when the finger is lifted, the displacement of mChildView will either return to mHeadHeight or return to 0. Directly calling setTranslationY() is not user-friendly, so we use attribute animation here. Originally, we could use mChildView.animate() method to complete attribute animation directly, but because it needs to be compatible with lower versions and call back some parameters, we use ObjectAnimator here:
Traditional tween animation can only realize four animation operations: move, scale, rotate, and fade in and out. Moreover, it only changes the display effect of the View and the appearance of the canvas, but does not actually change the properties of the View. For example, if you use tween animation to move a button, the button will only respond if you click it in the original position, while attribute animation can actually move the button. The simplest way to use attribute animation is to use ValueAnimator:
It can pass multiple parameters, such as ValueAnimator.ofFloat(0f, 5f, 3f, 10f), and it will calculate in sequence according to the set interpolator. For example, if you want to make a heartbeat effect, it is a good choice to use ValueAnimator to control the current scale value of the heart. In addition, you can also call the setStartDelay() method to set the animation delay time, call the setRepeatCount() and setRepeatMode() methods to set the number of animation loops and the loop mode. If you want to achieve the displacement of View, ValueAnimator is obviously more troublesome. We can use the subclass of ValueAnimator, ObjectAnimator, as follows:
The first value passed in is an Object, not limited to View. The second parameter passed in is an attribute of the Object. For example, if "abc" is passed in, ObjectAnimator will look for getAbc() and setAbc(...) methods in the Object. If not, the animation will have no effect. It should handle the corresponding exceptions internally. In addition, you can use AnimatorSet to play multiple attribute animations at the same time, or write attribute animations in XML. 7. Personalize the Header and Footer interfaces To realize personalized Header and Footer, the most important thing is of course to call back the coefficients during the sliding process. When ACTION_MOVE, ACTION_UP, and when mChildView is performing attribute animation, the current state of mChildView is very clear, so just write an interface.
The getView() method ensures that the externally set View can be obtained in TwinklingRefreshLayout. onPullingDown() is the callback method for ACTION_MOVE during the pull-down process, onPullReleasing() is the callback method for ACTION_UP in the pull-down state, and startAnim() is the callback method for refreshing. Among them, fraction = mChildView.getTranslationY()/mHeadHeight. When fraction = 1, the displacement of mChildView is exactly the height of HeadLayout. When fraction>1, it exceeds the height of HeadLayout, and its maximum height can reach mWaveHeight/mHeadHeight. In this way, we only need to write a View to implement this interface to achieve personalization, and all the necessary parameters are available! 8. Implement out-of-bounds rebound It is a common problem of refresh controls that inherit and ViewGroup that cannot respond to cross-border when the finger quickly scrolls to the top. Without inheriting from a specific list control, it cannot obtain the list control's Scroller, the current scrolling speed of the list control, and it cannot predict when the list control will scroll to the top; at the same time, except for the critical state event, all other events of ViewGroup are handled by the child View. The only operation we can obtain about the child View is the simple finger touch event. So, let's do it!
We handed the touch events on mChildView to a tool class GestureDetector to handle, which can assist in detecting the user's single click, slide, long press, double click, fast slide, etc. Here we only need to rewrite the onFling() method and get the velocity velocityY of the finger in the Y direction. If we can find that mChildView has scrolled to the top in time, the problem can be solved.
In addition, you can use VelocityTracker to obtain the speed, which is more troublesome:
Continue to implement out-of-bounds rebound. For RecyclerView and AbsListView, they provide OnScrollListener to obtain the scroll status:
The author selected a critical value of the scrolling speed. The cross-border rebound is allowed only when the scrolling speed in the Y direction is greater than 5000. The OnScrollListener of RecyclerView allows us to obtain the change of the scrolling state. When scrolling to the top, the scrolling is completed, the state changes to SCROLL_STATE_IDLE, and the cross-border rebound animation is executed. This strategy also has some defects. It cannot obtain the scrolling speed when the mChildView scrolls to the top, and it cannot achieve a more friendly cross-border effect according to different scrolling speeds. The current cross-border height is fixed and needs to be optimized later, such as using acceleration to calculate. Whether it is feasible remains to be verified. 9. Rolling delay calculation strategy The above method works well for RecyclerView and AbsListView, but it is a headache for ScrollView and WebView. The only way to judge is to use a delay calculation strategy to see if it reaches the top after a period of time. The idea of the delay strategy is to achieve a progressive calculation effect by sending a series of delayed messages. Specifically, you can use the postDelayed method of Handler or View, or the sleep method of thread. Another point to mention is that if you need to calculate a value in a continuous loop, such as a custom View that needs to implement an animation based on a certain value change, it is best not to use Thread + while loop to calculate. Using ValueAnimator would be a better choice. Here the author chose the Handler method.
When the scrolling speed is greater than 5000, a recalculation message is sent. After the Handler receives the message, it delays for a period of time and continues to send messages to itself until the time runs out or the mChildView scrolls to the top or the user performs another Fling action.
ALL_DELAY_TIMES is the maximum number of times that can be calculated. When the Handler receives the MSG_START_COMPUTE_SCROLL message, if the mChildView has not scrolled to the boundary, it will send itself a MSG_CONTINUE_COMPUTE_SCROLL message after 10ms, and then continue to judge. Then, when the boundary is crossed, it will rebound. 10. Implement personalized header Here I will demonstrate how to easily make a personalized Header, such as a Sina Weibo style refresh Header (as shown in the first picture below).
Note the use of fractions, such as the code refreshArrow.setRotation(fraction headHeight / maxHeadHeight 180) above, fraction * headHeight represents the current head sliding distance, and then calculate its ratio to the maximum height, and then multiply it by 180, so that the Arrow can rotate exactly 180 degrees when sliding to the maximum distance. The startAnim() method is automatically called after onRefresh. To achieve the effect shown in Figure 2, you can refer to the author's open source library TwinklingRefreshLayout. Summarize So far, I have finished talking about all the core ideas of implementing this refresh control. No advanced technology is used in it. It just requires us to be more patient, debug more, don't avoid bugs, and challenge ourselves more. |
<<: 21 questions and answers about Android View event mechanism
>>: Android Security: Intent Scheme Url Attack
News reports that my country's oil and gas dr...
There is no doubt that the topic of mini programs...
With the continuous development of science and te...
There is a fine line between going against the cu...
According to the 2022 Workplace Health Insights R...
Audit expert: Zhan Mingjin PhD, Chinese Academy o...
Author: New Media Center of Institute of Physics,...
What is the billing method for 360 search promoti...
The Internet continues to develop, and online mar...
In the morning, I took the curriculum development...
As the name implies, global variables are variabl...
In order to give some inspiration to colleagues w...
"Asymptomatic infections" have become a...
How much does it cost to join the Jiaozuo educati...
Text | Intelligent Relative Theory Author | Chen ...