The background,

We usually do not have so much time is to see the source code alone, or simply look at the source code encountered problems or do not know how to solve the source code from the point of view.

But we usually development process will certainly encounter this or that small problem, through Baidu, Google search are fruitless, want to try to analyze the source code and do not know where to start the analysis, resulting in the final give up.

This article is through a small problem, from the idea to the implementation of a step by step to teach you how to face a problem from the source point of view to analyze and solve the problem.

1.1 Background

On Android6.0 and above, clicking the “add shopping cart” button will cause TextView to jump (animation reset, scroll to start from scratch) as shown below:

1.2 Preparations

Under good source code AndroidStuido, generate an Android emulator, problematic demo project.

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)); }}); }Copy the code
<? 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="!!! Advertising!! "Vivo S7 will bring a full-scene selfie experience regardless of distance and light limitations, refreshing the flagship selfie standard in the 5G era."
        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="Add shopping cart"
        android:textColor="@android:color/background_dark"
        android:textSize="24sp"
        android:textStyle="bold" />
 
</RelativeLayout>
Copy the code

Second, the train of thought

First, let’s talk about the way to solve the problem. Personally, I also think the way to solve the problem is an important point of the article.

  • First go to Google and Baidu to find the principle of TextView running horselight and best to find the relevant key code, if not find a guarantee also want to find a breakthrough point of analysis.

  • Draw a flow chart to sort out the overall running light frame (if you just want to solve the problem in fact, the frame is not too fine, but here in order to clarify things, the principle will be said a little deeper).

  • Find out the key factors that affect the change of horselight animation and make a proper guess on the reasons that affect the change of variables.

  • Verify your guesses with debug.

  • Step 4 and step 5 continue the cycle until you find your answer.

Third, source code analysis

3.1 Analysis of the overall process of running lantern

Like most people, I Google first, stand on the shoulders of giants, and see if anyone can give me some ideas. Here’s how:

1) Open Google and search for “Android TextView Running lantern Principle”;

2) Open a few at random, at this time I am not going to scrutinize other people’s analysis, it is better to find the framework diagram, can not find the key code implementation is also good;

3) I didn’t find a framework, but I found an article that mentioned the startMarquee() method. This name makes sense because it is consistent with the parameters defined in XML. Android: ellipsize = “marquee”;

4) Search for TextView in AndroidStdio, open the class interface diagram and find the startMarquee() method. Here, I post the method below for convenience.

Take a quick look at the code;

Do some condition judgment whether to run horselight. For example, in action 9 and 10, the initialization is performed only when line is currently set to 1 and the ellipsize attribute is marquee. We know that we need to set singleline =”true” and ellipsize= “marquee” in the XML to activate the marquee. The start operation is then performed on line 23, and the details of the start operation are described below.

5) After confirming that we have found the right place, we will leave the details behind and continue to understand the implementation of the overall framework.

Find out where this method is used, and find that there are not many, some areas can be directly excluded, so that we can draw the main flow diagram below.

  • The first method in onDraw() will call the startMarquee() method based on the attribute.

  • The statMarquee() method initializes a Textview’s inner class Marquee().

  • The.start() method is called after mMarquee is initialized.

  • This method initializes some of the data values needed by the parent class from the TextView object that is passed in, which is its own property values.

  • The invalidate() method of the TextView is called after the value is initialized.

  • This triggers the onDraw() method, which moves the canvas based on the mMarquee property value.

3.2 Marquee

The first section just looked at the general flow, but we saw that TextView is just a user. The real business implementation of TextView is in an internal class called Marquee. Remember we left a hole in the above: inside startMarquee the mMarquee. Now that we’ve called the method inside the inner class, let’s look at what’s going on inside 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 be copied to textWidth, which is simply the width of the entire TextView control minus the left and right padding areas.

4) Line 14 sets the sliding gap. It can be seen from this that the default sliding gap of Android running lantern is one third of the length of the text.

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 the initial variables; Trigger textView’s onDraw method. This is the most common way to trigger a view refresh, and this is on the main thread so you just have to invalidate it.

7) Line 22 sets Choreographer to listen for events to continue controlling animations later.

Simply draw a diagram of TextView and TextView.Marquee and Choreographer.

TextView: Draws the Marquee entity, mainly in OnDraw to initialize the inner class TextView.Marquee.

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

Choreographer: A frame callback method for the system. Each frame provides a callback to the Marquee to trigger a refresh of the view, ensuring smooth animation, as discussed in more detail below.

3.3 Choreographer

Choreographer is a systematic approach, let’s take a look at what it is officially defined at 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.

This class is a vertical frame signal that listens to the system and is called back on each frame. It’s a low-level API, so if you’re doing something like Animation, use a higher level API.

It’s not recommended, I guess, because it calls back too often and can affect performance. The number of callbacks is also related to the current refresh rate of the phone’s screen. For a system with a refresh rate of 60, the postframe allback will call back once for 1000/60 = 16.6 milliseconds, and for a system with a refresh rate of 120 it will call back once for 1000/120 = 8.3 milliseconds. So in summary, the callback of this class cannot do time-consuming work.

Choreographer will listen to a system Receiver called DisplayEventReceiver that connects to the Connection of the underlying SurfaceFlinger. The SurfaceFlinger will send sync signal in real time and come up via onVsync callback.

Let’s look at what Marquee does in postFrameCallback; Choreographer calls a method called Tick that calculates bias values, let’s take a closer look at this method.

1) The first three lines define mPixelsPerMs, which looks familiar. In fact, it defines the sliding speed. 30dp corresponds to the PX value /1000ms. The default slide speed for android is 30 DPS.

2) In line 16, the current time of the callback currentMs and the time of the last callback mLastAnimationMs are used to calculate the trip value deltaMs in ms.

3) In line 18, calculate the displacement to be moved by the current time difference by deltaMs and mPixelsPerMs and copy it to mScroll.

4) Line 20, if the displacement is greater than the maximum, it equals the maximum.

5) At line 26, invalidate is called to refresh the TextView.

Since mMarquee was initialized and Textview was refreshed, the ondraw of Textview must be drawn using the data in mMarquess. The ondraw method is relatively long. Here we find two places where mMarquee is used, respectively;

Break points are placed on both places, and only section 2 is gone, so let’s focus on what is going on in code 2 (debug is the best way to do this if the path is already lost in the code). As you can see in code 2, the canvas is being moved horizontally based on the getScroll() value, and it’s constantly being moved back and forth to form an animation.

To sum up, calculate the time difference (currentMs-mlastanimationMS), then multiply this time difference by 30DP and copy it to mScroll. So it moves 30dp per second, and then it actively triggers the refresh of the TextView. Postframe Allback not only solves the problem of constantly triggering the running lantern animation, but also ensures the animation is smooth.

Let’s conclude the second part: TextView is finally drawn inside OnDraw via: Marquee → Choreographer → mScroll.

Now that we know how it works, let’s go back to the problem and analyze the problem.

Iv. Problem analysis

After the principle analysis in section 2 and combining with the phenomenon in the video, we know that the animation has been reset, which must be the change of mScroll.

4.1 Who triggers the mScroller reset

Put this together, and you can guess that after clicking the “Add cart” button, some code resets getScroll(), which is the mScroll of the Marquee member variable.

So with that guess in mind, let’s try to figure out where we set the mScroll to zero. The debug can trace up to the end of the TextView and find that someone triggered the onMeasure method of the TextView.

4.2 Who Triggers onMeasure

1) The view goes through its entire life cycle when it is initialized, as shown in the following figure;

2) onMeasure is triggered when requestLayout() is called.

When requestLayout is triggered by a child view, the whole view tree will be redrawn. In this case, the ViewGroup will not only complete its own measure process, but also iterate through the measure methods that call all child elements. Take Framelayout;

Line 35 will iterate over and trigger all the child view’s measure methods. Based on the above two facts, we propose the following hypothesis.

Child View A calls the requestLayout method, and the ViewGroup redraws, triggering the onMeasure() method of child View B.

So the goal is clear, the other child view in the video that shows the increment and the only thing it does is a setText.

4.3 How can onMeasure Be Triggered

The requestLayout method may be triggered in setText, so it is easy to verify:

  • Create a breakpoint in the setText entry method.

  • Breakpoints are placed wherever requestLayout is called.

Sure enough, the requestLayout method is called along the setText method debug. Try drawing the flowchart.

Strip out all the other logic and we see that it decides that the current layout is wrAP_content and executes a different logic. The “shopping cart” button is the wrap_content property, so requestLayout will go, which will trigger the redraw of the running light.

V. Problem solving

After analyzing the problem, the solution is obvious. Change the property of the “shopping cart” button to non-WRAP_content and try again.

Six, summarized

After this analysis, we take the maze as an example to summarize the harvest:

The analysis of the source code phenomenon needs to rely on their own knowledge of Android proficiency, and accurate guess as a premise. Android knowledge is more like a compass to navigate a maze.

Debug can be used as a branch to eliminate some errors and directly find the correct main line, more like adding several anchors to a maze for trial and error.

Use flowcharts to deepen your understanding of the framework. Flowcharts are more like a map of a maze and help you avoid detours.

Author: HouYutao, development team of Vivo official website Mall