1. Background

Presumably everyone doesn't usually have so much time to look at the source code alone, or just look at the source code and encounter problems or don't know how to solve it from the perspective of the source code.

However, everyone will encounter such or such small problems during the development process. Searching through Baidu and Google is fruitless. If you want to try to analyze the source code, you don't know where to start the analysis, which leads to the final abandonment.

This article is to start with a small problem, from the idea to the implementation step by step to teach you how to analyze and solve the problem from the perspective of source code when facing a problem.

1.1 Problem background

In Android 6.0 and above system versions, click the "Add to Cart" button, and the animation of the TextView marquee will jump (animation resets, and scrolling starts from scratch) as shown below:

1.2 Preliminary preparation

Download the AndroidStuido with good source code, generate an Android emulator, and the demo project in question.

protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       findViewById(R.id.show_tv).setSelected(true);
       final TextView changeTv = findViewById(R.id.change_tv);
       changeTv.setText(getString(R.string.shopping_count, mNum));
       findViewById(R.id.click_tv).setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               mNum++;
               changeTv.setText(getString(R.string.shopping_count, mNum));
           }
       });
   }
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <com.workshop.textview.MyTextView
        android:id="@+id/show_tv"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="30dp"
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:marqueeRepeatLimit="marquee_forever"
        android:padding="5dp"
        android:scrollHorizontally="true"
        android:textColor="@android:color/holo_blue_bright"
        android:singleLine="true"
        android:text="!!!广告!!!vivo S7手机将不惧距离与光线的限制,带来全场景化自拍体验,刷新了5G时代的自拍旗舰标准"
        android:textSize="24sp" />
 
 
    <TextView
        android:id="@+id/change_tv"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/shopping_count"
        android:textColor="@android:color/holo_orange_dark"
        android:textSize="28sp" />
 
    <TextView
        android:id="@+id/click_tv"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="30dp"
        android:background="@android:color/darker_gray"
        android:padding="5dp"
        android:singleLine="true"
        android:text="添加购物车"
        android:textColor="@android:color/background_dark"
        android:textSize="24sp"
        android:textStyle="bold" />
 
</RelativeLayout>

2. Ideas

Let’s talk about the idea of solving the problem first. Personally, I also think that the idea is a more important point of this article.

  • First go to Google and Baidu to find the principle of the textview marquee and find the relevant key code.
  • Draw a flowchart to sort out the overall marquee framework (if you just want to solve the problem, the framework does not need to be too detailed, but here in order to make things clear, the principle will be explained a little deeper).
  • Find the key factors that affect the changes of the marquee animation, and make an appropriate guess for the reasons that affect the changes of the variables.
  • Use debug means to verify your conjecture.
  • Steps 4 and 5 continue to loop, and finally find your own answer.

3. Source code analysis

3.1 Analysis of the overall process of the marquee

Like most people, I first Google it, stand on the shoulders of giants, and see if my predecessors can give me some ideas. The steps are as follows;

1) Open Google search "Android TextView marquee principle";

2) Just open a few. At this time, I am not going to take a close look at other people's analysis. It is better to find the frame diagram, and it is also good to find the key code implementation if you can't find it;

3) Sure enough, I didn't find a ready-made frame diagram, but I found an article that mentioned the startMarquee() method. When you see this name, you know it is reliable, because it is consistent with the marquee parameters defined in our xml. android:ellipsize="marquee";

4) Search for TextView in AndroidStdio, open the class interface diagram and find the startMarquee() method. For the convenience of analysis, I paste the method below.

Briefly analyze this code;

Made some conditional judgments on whether the marquee is not. Taking lines 9 and 10 as an example, the initialization operation is performed only when the currently set line is 1 and the ellipsize attribute is marquee. We know that the marquee can only be activated by setting singleline="true" and ellipsize="marquee" in xml, which just matches lines 9 and 10. After that, the specific content of the start operation start in line 23 will be analyzed later.

5) After confirming that the place found is correct, let's not go into the details and continue to understand the implementation of the entire framework.

Find the places where this method is used, and find that there are not many places, and some places can be directly excluded, so that the following main flowchart can be drawn.

  • The first method in onDraw() will determine whether to call the startMarquee() method according to the attribute.
  • In the statMarquee() method, a Textview inner class Marquee() is initialized.
  • The .start() method is called after initializing mMarquee.
  • This method will initialize some data values required by the marquee according to the incoming TextView object, that is, some of its own attribute values, for the parent class to use.
  • Call the invalidate() method of TextView after initializing the value.
  • After that, the onDraw() method will be triggered, and the onDraw() method will move the canvas according to the attribute value of mMarquee.

3.2 Marquee

The first section only analyzes the general process, but we see that TextView is just a user. The real business implementation of the marquee is in an inner class called Marquee. Do you remember that we left a hole above, it will be in startMarquee Call the mMarquee.start method. At this time, it has been transferred to the method in the inner class. Let's see what is done in the start method.

2) Line 10 sets the offset variable to 0.0f (1) Line 9 sets mStatus to MARQUEE_STARTING, indicating that this is the first slide.

3) Line 11 sets the actual width of the text to copy to textWidth, which is actually very simple, that is, the width of the entire TextView control minus the padding area on the left and right.

4) Line 14 sets the sliding distance gap. From here, it can be seen that the sliding distance of Android's default marquee is one-third of the text length.

5) Line 16 sets the maximum sliding distance mMaxScroll, which is actually the width of the word plus gap.

6) Line 21 calls textView.invalidate() after setting all initial variables; triggers the ondraw method of textview. This is also the most commonly used refresh method for triggering view refresh. This is to refresh everything in the main thread, as long as you use invalidate.

7) Line 22 sets the Choreographer to listen to events for subsequent control of the animation.

Simply draw a diagram of the relationship between TextView and TextView.Marquee and Choreographer.

TextView : Draw the entity of the marquee, mainly initialize the inner class TextView.Marquee in ondraw.

TextView.Marquee : used to manage the bias value onScroll of the marquee, and at the same time, call the invalidate method continuously to trigger the ondraw method of TextVIew to draw and display the text.

Choreographer : A frame callback method of the system. Each frame will provide a callback to Marquee to trigger the refresh of the view to ensure the smoothness of the animation. The Choreographer will be described in detail later.

3.3 Choreographer

Choreographer is a systematic method, let's first look at its official definition in Google;

Coordinates the timing of animations, input and drawing.......Applications typically interact with the choreographer indirectly using higher level abstractions in the animation framework or the view hierarchy. Here are some examples of things you can do using the higher-level APIs.

The translation is: this class is a vertical frame signal of the monitoring system, and it will be called back every frame. It's a low-level api, if you're doing something like Animation, use a higher-level api.

Understand : It is generally not recommended for you to use it. I guess it may be because it calls back too frequently, which may affect performance. The number of callbacks is also related to the refresh rate of the current mobile phone screen. For a system with a refresh rate of 60, the postFrameCallback will be called back once in 1000/60 = 16.6 milliseconds. If the refresh rate is 120, it will be 1000/120 = 8.3 milliseconds. Callback once. So in summary, the callback of this class cannot do time-consuming work.

Let's take a brief look at the implementation principle of choreographer. It will monitor a system Receiver called DisplayEventReceiver. This Receiver will be connected to the Connection of the underlying SurfaceFlinger. SurfaceFlinger will send a sync signal in real time and call it back through onVsync.

Let's take a look at what Marquee does in postFrameCallback; a method called Tick is called in Choreographer, which is used to calculate the bias value. Let's analyze this method in depth.

1) The first 3 lines define mPixelsPerMs. Does it look familiar? In fact, it defines the sliding speed, px value corresponding to 30dp/1000ms. That is, the default sliding speed of android marquee is 30dp per second.

2) In line 16, calculate the difference deltaMs through the current time currentMs of the callback and the time mLastAnimationMs of the last callback. The unit here is ms.

3) In line 18, the displacement to be moved by the current time difference is calculated through deltaMs and mPixelsPerMs, and copied to mScroll.

4) Line 20, if the displacement is greater than the maximum value, it is equal to the maximum value.

5) Line 26, call invalidate to refresh the TextView.

Since mMarquee has been initialized and the Textview has been refreshed, the ondraw of TextView must use the data in mMarquess for drawing. The method of ondraw is relatively long. Here we have found two places where mMarquee is used, namely;

Put a breakpoint on both places respectively, and find that only the second code segment is taken, then let's focus on what is done in the second code (in the case that the path is not clear through the code, debug is the best way. ). It can be seen that in the second code, the canvas is moved horizontally according to the value of getScroll(), and the movement is constantly called back to form an animation.

To sum up, calculate the time difference (currentMs - mLastAnimationMs), and then multiply this time difference by 30dp and copy it to mScroll. That is, move 30dp per second, and finally trigger the refresh of TextView. Using postFrameCallback not only solves the problem of continuously triggering the marquee animation, but also ensures the smoothness of the animation.

Let's make a conclusion for the second part: TextView passes: marquee → Choreographer → mScroll and finally draws the position of TextView in ondraw.

After knowing the principle, we next go back to the problem itself and analyze the problem.

Fourth, problem analysis

After analyzing the principle in the second section, we know that the animation has been reset, and the mScroll must have changed.

4.1 Who triggers mScroller reset

Combined with the whole phenomenon, it can be guessed that after clicking the "Add Shopping Cart" button, a certain piece of code resets the value of getScroll(), which is Marquee's member variable mScroll.

With guesswork, along this line of thought, let's find where to set mScroll to zero. Chasing up through debug, I found that someone triggered the onMeasure method of TextView.

4.2 Who triggers onMeasure

1) When the view is initialized, it will go through the complete life cycle, as shown in the following figure;

2) When the method of requestLayout() is called, onMeasure will be triggered.

And when the child view triggers the requestLayout, it will trigger the redraw of the entire view tree. At this time, the ViewGroup will not only complete its own measurement process, but also traverse and call the measure methods of all child elements. Take framelayout as an example;

Line 35 iterates over and triggers the measure method of all child views. Based on the above two facts, we propose the following hypothesis.

The child view A calls the requestLayout method, the viewgroup is redrawn, and the onMeasure() method of the child view B is triggered.

Then the goal is very clear, another child view in the video that shows the number increase and the only thing it does is setText.

4.3 How to trigger onMeasure

The previous guess is that we may have triggered the requestLayout method in setText, so it is simple to verify:

  • Set a breakpoint in the entry method of setText;
  • Put a breakpoint at all places where requestLayout is called.

As expected, the requestLayout method was called along the debug of the setText method. At this time, I tried to draw a flowchart.

Removing all other logic, we found that it would determine that the current layout is wrap\_content to execute a different logic. I saw that the "shopping cart" button is the wrap\_content attribute, so it will go to the requestLayout, which will then trigger the redraw of the marquee.

Five, problem solving

Through the conclusion of the problem analysis, the solution is obvious. Change the attribute of the "shopping cart" button to non-wrap_content and try again. Sure enough, the marquee will not be redrawn again. The modified code is as follows:

6. Summary

After this analysis, let's take the maze as an example to summarize the gains:

The analysis of the source code phenomenon needs to rely on one's own proficiency in Android knowledge and accurate guesses as the premise. Android knowledge is more like a compass for walking the maze.

Debugging can be used as a branch line to eliminate some errors and directly find the correct main line. It is more like adding a few anchor points in the maze to conduct trial and error.

Drawing more flowcharts can deepen your understanding of your framework. The flowchart is more like a map of the maze, helping you avoid detours.

Author: vivo official website mall development team - HouYutao

vivo互联网技术
3.3k 声望10.2k 粉丝