Hand in hand to explain the page turning effect of ViewPager: learn from the source code!

preface

The first post-2020 article, let's have a light topic. Working from home, UI Meimei has a whim to make a sliding special effect. ViewPager+TabLayout, a cliche. ViewPager , is the basic sliding switching control, and TabLayout , is the title bar part used with ViewPager (but TabLayout can also be used independently of ViewPager). According to the information found, Google engineers reserved an interface for coquettish animation special effects when ViewPager was founded. We can easily use this interface for animation programming, but TabLayout is more sad. Not only the animation did not reserve an interface, but even some conventional operation interfaces were not provided, Therefore, some people have also appeared on the Internet to create new # xxTabLayout controls according to the original # TabLayout code.

This article will provide the example effect of "ViewPager+TabLayout", development ideas, and * * Demo * * github project. Interested children's shoes hope to leave messages and communicate more.

Demo address: https://github.com/18598925736/StudyTabLayout/tree/hank_v1

Text outline

  • Reference effect

  • Pre skills

  • Realization idea

  • critical code

  • Thinking expansion

text

Reference effect

The mobile phone recording screen given by # UI # Meimei in the above figure is the sliding switching effect on a version of # app.

What we need to develop is the sliding switching control in the following half.

Pre skills

Through the study of ViewPager's possible special effects, it is found that it has the possibility of this kind of animation special effects, so we don't need to customize the controls.

However, the font size of the top TabLayout changes, and the length and position of the indicator changes. It seems that Google can't get the TabLayout, so we have to do DIY by ourselves

To complete this special effect, two skills must be in place:

  • android view animation is an original animation type in android system. The principle is to draw the view in the specified area and repeat it according to the specified rules, but the event interaction carried by the original view will not be affected. Because the event interaction cannot be truly inherited, it is replaced by attribute animation. But it still has its own value. Without involving interaction and only considering visual effects, its efficiency is higher than attribute animation.

  • Mathematical modeling idea} don't get me wrong. The mathematical modeling mentioned here is a way of thinking, which expresses the phenomena we see with the naked eye in the form of mathematical formulas. It's not a profound operation. Children's shoes who have learned custom controls and deeply practiced them should be able to realize that if you want to really complete a # DIY control from 0, there will be a lot of mathematical calculation, and have good mathematical thinking ability, which can be like a fish in water when customizing.

Realization idea

1, Source code research

To transform ViewPager into a special effect, first of all, we need to know that ViewPager is a container ViewGroup, and how its internal sub views are placed. Although we can visually feel that the sub views are placed horizontally, as technicians, we should dare to go to the bottom and speak with the source code.

Enter the source code and find the {onLayout method (the following is the key code I refined):

1.  `protected  void onLayout(boolean changed,  int l,  int t,  int r,  int b)  {`

2.  `final  int count = getChildCount();`

3.  `...`

4.  `for  (int i =  0; i < count; i++)  {`

5.  `if  (child.getVisibility()  != GONE)  {`

6.  `final  LayoutParams lp =  (LayoutParams) child.getLayoutParams();`

7.  `int childLeft =  0;`

8.  `int childTop =  0;`

9.  `if  (lp.isDecor)  {`

10.  `...`

11.  `}`

12.  `}`

13.  `}`

14.  `...`

15.  `for  (int i =  0; i < count; i++)  {`

16.  `final  View child = getChildAt(i);`

17.  `if  (child.getVisibility()  != GONE)  {`

18.  `final  LayoutParams lp =  (LayoutParams) child.getLayoutParams();`

19.  `ItemInfo ii;`

20.  `if  (!lp.isDecor &&  (ii = infoForChild(child))  !=  null)  {`

21.  `int loff =  (int)  (childWidth * ii.offset);`

22.  `int childLeft = paddingLeft + loff;`

23.  `int childTop = paddingTop;`

24.  `if  (lp.needsMeasure)  {`

25.  `// This was added during layout and needs measurement.`

26.  `// Do it now that we know what we're working with.`

27.  `lp.needsMeasure =  false;`

28.  `final  int widthSpec =  MeasureSpec.makeMeasureSpec(`

29.  `(int)  (childWidth * lp.widthFactor),`

30.  `MeasureSpec.EXACTLY);`

31.  `final  int heightSpec =  MeasureSpec.makeMeasureSpec(`

32.  `(int)  (height - paddingTop - paddingBottom),`

33.  `MeasureSpec.EXACTLY);`

34.  `child.measure(widthSpec, heightSpec);`

35.  `}`

36.  `if  (DEBUG)  {`

37.  `Log.v(TAG,  "Positioning #"  + i +  " "  + child +  " f="  + ii.object`

38.  `+  ":"  + childLeft +  ","  + childTop +  " "  + child.getMeasuredWidth()`

39.  `+  "x"  + child.getMeasuredHeight());`

40.  `}`

41.  `child.layout(childLeft, childTop,`

42.  `childLeft + child.getMeasuredWidth(),`

43.  `childTop + child.getMeasuredHeight());`

44.  `}`

45.  `}`

46.  `}`

47.  `}`

In the above code, count is cycled for two rounds, the first of which is for LP Isdecor is true, which means that if the current view is a decoration and not the view provided by the adapter, it returns true.

Obviously, we want to discuss how the views provided by the adapter are placed, so we ignore this one.

In the following cycle, you can see:

1.  `child.layout(childLeft, childTop,`

2.  `childLeft + child.getMeasuredWidth(),`

3.  `childTop + child.getMeasuredHeight());`

This is the core code of child's layout. Tracing back to these four parameters, we can know that: the first and third parameters represent left and right, and they are all associated with an int intloff=(int)(childWidth*ii.offset); While the second and fourth parameters represent top and bottom, they are not linked to any dynamic parameters.

Therefore, it can be concluded that the sub View layout of ViewPager will only have position deviation in the X-axis direction, and will remain flush up and down in the Y-direction.

In fact, it can be traced back to intloff=(int)(childWidth*ii.offset); Look at how the position deviation in the x-axis direction is caused, but the purpose has been achieved. Trace it when necessary.

If it is determined to be horizontally arranged, what about the left-right sliding logic?

Find the {onTouchEvent() method and find action in it_ Move logical branch:

1.  `case  MotionEvent.ACTION_MOVE:`

2.  `if  (!mIsBeingDragged)  {`

3.  `final  int pointerIndex = ev.findPointerIndex(mActivePointerId);`

4.  `if  (pointerIndex ==  -1)  {`

5.  `// A child has consumed some touch events and put us into an inconsistent`

6.  `// state.`

7.  `needsInvalidate = resetTouch();`

8.  `break;`

9.  `}`

10.  `final  float x = ev.getX(pointerIndex);`

11.  `final  float xDiff =  Math.abs(x - mLastMotionX);`

12.  `final  float y = ev.getY(pointerIndex);`

13.  `final  float yDiff =  Math.abs(y - mLastMotionY);`

14.  `if  (DEBUG)  {`

15.  `Log.v(TAG,  "Moved x to "  + x +  ","  + y +  " diff="  + xDiff +  ","  + yDiff);`

16.  `}`

17.  `if  (xDiff > mTouchSlop && xDiff > yDiff)  {`

18.  `if  (DEBUG)  Log.v(TAG,  "Starting drag!");`

19.  `mIsBeingDragged =  true;`

20.  `requestParentDisallowInterceptTouchEvent(true);`

21.  `mLastMotionX = x - mInitialMotionX >  0  ? mInitialMotionX + mTouchSlop :`

22.  `mInitialMotionX - mTouchSlop;`

23.  `mLastMotionY = y;`

24.  `setScrollState(SCROLL_STATE_DRAGGING);`

25.  `setScrollingCacheEnabled(true);`

27.  `// Disallow Parent Intercept, just in case`

28.  `ViewParent parent = getParent();`

29.  `if  (parent !=  null)  {`

30.  `parent.requestDisallowInterceptTouchEvent(true);`

31.  `}`

32.  `}`

33.  `}`

34.  `// Not else! Note that mIsBeingDragged can be set above.`

35.  `if  (mIsBeingDragged)  {`

36.  `// Scroll to follow the motion event`

37.  `final  int activePointerIndex = ev.findPointerIndex(mActivePointerId);`

38.  `final  float x = ev.getX(activePointerIndex);`

39.  `needsInvalidate |= performDrag(x);`

40.  `}`

41.  `break;`

What we need to pay attention to is the law of dragging in the X direction. So, follow , finalfloatx = ev getX(pointerIndex); Find the key method of this variable and finally lock it: performDrag(x); It is the key entrance to deal with the displacement in the X direction.

1.  `private  boolean performDrag(float x)  {`

2.  `boolean needsInvalidate =  false;`

4.  `final  float deltaX = mLastMotionX - x;`

5.  `mLastMotionX = x;`

7.  `...`

8.  `// Don't lose the rounded component`

9.  `mLastMotionX += scrollX -  (int) scrollX;`

10.  `scrollTo((int) scrollX, getScrollY());  // Key code 1, horizontal image scrolling of the control on the canvas`

11.  `pageScrolled((int) scrollX);// Key code 2, pass scrollX further down`

13.  `return needsInvalidate;`

14.  `}`

Two key codes are found. One is the scrollto that handles sliding, and the other is the "pageScrolled(scrollX) that passes" scrollX "down. I understand the first sentence, but I don't understand the second sentence. Keep going.

1.  `private  boolean pageScrolled(int xpos)  {`

2.  `...`

3.  `final  float pageOffset =  (((float) xpos / width)  - ii.offset)`

4.  `/  (ii.widthFactor + marginOffset);`

5.  `final  int offsetPixels =  (int)  (pageOffset * widthWithMargin);`

7.  `mCalledSuper =  false;`

8.  `onPageScrolled(currentPage, pageOffset, offsetPixels);`

9.  `if  (!mCalledSuper)  {`

10.  `throw  new  IllegalStateException(`

11.  `"onPageScrolled did not call superclass implementation");`

12.  `}`

13.  `return  true;`

14.  `}`

The tracking parameter xpos knows the offset information in the x direction, and finally enters onPageScrolled(...) method.

1.  `protected  void onPageScrolled(int position,  float offset,  int offsetPixels)  {`

2.  `// Offset any decor views if needed - keep them on-screen at all times.`

3.  `if  (mDecorChildCount >  0)  {`

4.  `...  // Decoration is still being processed here, so you don't have to look at it, and the parameters are not entered here`

5.  `}`

7.  `dispatchOnPageScrolled(position, offset, offsetPixels);`

9.  `if  (mPageTransformer !=  null)  {`

10.  `final  int scrollX = getScrollX();`

11.  `final  int childCount = getChildCount();`

12.  `for  (int i =  0; i < childCount; i++)  {`

13.  `final  View child = getChildAt(i);`

14.  `final  LayoutParams lp =  (LayoutParams) child.getLayoutParams();`

16.  `if  (lp.isDecor)  continue;`

17.  `final  float transformPos =  (float)  (child.getLeft()  - scrollX)  / getClientWidth();`

18.  `mPageTransformer.transformPage(child, transformPos);`

19.  `}`

20.  `}`

22.  `mCalledSuper =  true;`

23.  `}`

There are two more key codes:

dispatchOnPageScrolled(position,offset,offsetPixels);

After clicking in and looking, I found that only OnPageChangeListener listening callback was called.

If we set sliding monitoring, we can receive a callback when sliding. I believe everyone has used this.

mPageTransformer.transformPage(child,transformPos);

It's strange here. This code returns the sub view and the current location information of the sub view to the outside world.

So what can the outside world do after getting these two parameter values? In theory, you can do anything.

2, Conclusion of exploring source code

  1. The initial sub views of ViewPager are placed horizontally. It is flush up and down in the longitudinal direction.

  2. ViewPager sets the current position parameters of the child view and the child view through pagetransformer Transform page (view, position) can be fed back to the outside world and can do a lot. For example, let the sub views that are arranged horizontally become placed vertically, or {let the sub views that are about to slide out of the screen fly out at an inclined angle at a certain acceleration and do whatever they want. This is the basis on which we can complete this animation.

3, Exploration on the parameter law of PageTransformer

ViewPager provides the possibility of a DIY sliding effect. However, before doing animation, we still need to understand the change law of these two parameters.

Create a new Android project and write the code and layout of "ViewPager+TabLayout". This effect is probably running:

At the same time, we add the setPageTransformer(...) method to the viewpager and print the log.

1.  `viewPager.adapter =  MyFragmentPagerAdapter(supportFragmentManager);`

2.  `viewPager.offscreenPageLimit =  3  // Cache at least 3, so that the left and right sides are displayed` 

3.  `viewPager.setPageTransformer(true,  ViewPager.PageTransformer  { view, position ->`

4.  `Log.d("setPageTransformer",  "view:${view.hashCode()} | position:${position}")`

5.  `})`

Then start the app and look at the log:

1.  `03-1214:14:46.2221583-1583/? D/setPageTransformer: view:136851691| position:0.0`

2.  `03-1214:14:46.2221583-1583/? D/setPageTransformer: view:147234376| position:1.0`

3.  `03-1214:14:46.2221583-1583/? D/setPageTransformer: view:75203809| position:2.0`

4.  `03-1214:14:46.2221583-1583/? D/setPageTransformer: view:35279366| position:3.0`

It can be seen that at the beginning, four sub views are initialized, and the location information is 0.0 / 1.0 / 2.0 / 3.0 respectively. This is because I set the offscreen pagelimit to 3, so in addition to the current view, I will initialize views other than the three screens. This means that: the position of the current view is 0, and to the right, the position will increase. For each increased view, the position will increase by 1.0. Conversely, we can also deduce that to the left, the position will decrease for each view. To verify our derivation, let's slide and observe the change of position:

Slide one space to the left.

The log is summarized as follows:

The position of the child view whose hashCode is 136851691 has changed from 0.0 to - 1.0.

1.  `03-1214:22:11.8361583-1583/? D/setPageTransformer: view:136851691| position:-1.0`

However, for the child view with original hashCode of 147234376 and position of 1, the position becomes 0.0.

1.  `03-1214:22:11.8361583-1583/? D/setPageTransformer: view:147234376| position:0.0`

Try sliding another grid again. The sub view with hashCode ^ 136851691 ^ has changed from -0.99326146 ^ to 0.0. The decimal here is probably caused by the loss of calculation accuracy. It can be considered as changing from - 1.0 , to , 0.0.

Draw a picture to describe the conclusion just now (pink is the current field of vision):

OK, I have learned that the change law of position is basically mastered. Then we can split the animation and program it. The next part is to release the key code.

end

Redis is based on memory and is often used as a technology for caching, and redis is stored in the form of key value. Redis is the most widely used cache in today's Internet technology architecture, which is often used in work. Redis is also one of the most popular questions that interviewers like to ask in the technical interview of middle and senior back-end engineers. Therefore, as a Java developer, redis is something we must master.

Redis is a leader in the field of NoSQL database. If you need to know how redis realizes high concurrency and massive data storage, this Tencent expert's Manual "redis source code log notes" will be your best choice.

From - 1.0 , to , 0.0.

Draw a picture to describe the conclusion just now (pink is the current field of vision):

[external chain picture transferring... (img-vM05s8rj-1623621023381)]

OK, I have learned that the change law of position is basically mastered. Then we can split the animation and program it. The next part is to release the key code.

end

Redis is based on memory and is often used as a technology for caching, and redis is stored in the form of key value. Redis is the most widely used cache in today's Internet technology architecture, which is often used in work. Redis is also one of the most popular questions that interviewers like to ask in the technical interview of middle and senior back-end engineers. Therefore, as a Java developer, redis is something we must master.

Redis is a leader in the field of NoSQL database. If you need to know how redis realizes high concurrency and massive data storage, this Tencent expert's Manual "redis source code log notes" will be your best choice.

[external chain picture transferring... (img-g39uYpT2-1623621023382)]

Interested friends can Click like + stamp here for free Tencent experts handwritten Redis source code log notes pdf version!

Keywords: Java Interview Programmer

Added by dbakker on Thu, 03 Feb 2022 13:28:24 +0200