One, foreword

With the continuous development of online shopping, the countdown countdown has become very common in various e-commerce applications, which can improve users’ click rate and order rate.

However, most domestic e-commerce applications only support Chinese and do not adapt to other languages. Therefore, when the countdown is displayed in the same line with other texts, there is no need to consider the display mode of countdown. In overseas applications, due to the need to adapt to a variety of languages, some small languages have long texts, so when the countdown and other texts are displayed in the same line, it is necessary to give full consideration to multi-language adaptation, how to gracefully complete the countdown adaptive display is a problem worth thinking about.

To further optimize the countdown effect, we added a digital scrolling animation for the countdown, as shown in the picture below. The function of countdown will inevitably bring performance consumption, how to avoid the performance problems caused by countdown, this paper will also give corresponding solutions.

Two, to achieve the basic functions of the countdown

2.1 Analysis of requirements and principles

The control is expected to present two states, X days until the event starts and X days until the event ends, so an activity state property is required and pre-time text is set with the event start or not property. The specific time, hour, minute and second are independent of each other, so they are divided into separate TextViews for processing.

The core of the countdown control is a timer. There is already a CountDownTimer class available in Android to implement the countdown function. In addition, some listening interfaces need to be implemented.

2.2 Specific Implementation

2.2.1 Callback listening interface design

First, define the callback interface

public interface OnCountDownTimerListener {
    /** * the method to call while the countdown is in progress@paramMillisUntilFinished time (milliseconds) */
    void onRemain(long millisUntilFinished);
 
    /** * countdown ends */
    void onFinish(a);
 
    /** * The method called every minute */
    void onArrivalOneMinute(a);
 
}
Copy the code

Three methods are defined in this interface:

OnRemain (long zipuntilfinished) : a method of countdown callbacks

OnFinish () : Countdown to the end of the callback, used to switch the active state, pause the timer, etc

OnArrivalOneMinute () : every minute callback, used to report the buried point

2.2.2 View construction and binding

Secondly, initialize the custom view. Based on the actual development requirements, subdivide the whole control into several independent TextViews such as modified text, days, hours, minutes and seconds, and initialize it in the custom BaseCountDownTimerView:

private void init(a) {
     mDayTextView = findViewById(R.id.days_tv);
     mHourTextView = findViewById(R.id.hours_tv);
     mMinTextView = findViewById(R.id.min_tv);
     mSecondTextView = findViewById(R.id.sec_tv);
     mHeaderText = findViewById(R.id.header_tv);
     mDayText = findViewById(R.id.new_arrival_day);
 }
Copy the code

2.2.3 Build private methods for internal use

We first construct a method to set the remaining time, taking the remaining milliseconds as input, converting the time into a specific day and minute second inside the method, and assigning the result to the TextView

 private void setSecond(long millis) {
 
     long day = millis / ONE_DAY;
     long hour = millis / ONE_HOUR - day * 24;
     long min = millis / ONE_MIN - day * 24 * 60 - hour * 60;
     long sec = millis / ONE_SEC - day * 24 * 60 * 60 - hour * 60 * 60 - min * 60;
 
     String second = (int) sec + ""; / / SEC.
     String minute = (int) min + ""; / / points
     String hours = (int) hour + ""; / /
     String days = (int) day + ""; / / day
 
     if (hours.length() == 1) {
         hours = "0" + hours;
     }
     if (minute.length() == 1) {
         minute = "0" + minute;
     }
     if (second.length() == 1) {
         second = "0" + second;
     }
 
     if (day == 0) {
         mDayTextView.setVisibility(GONE);
         mDayText.setVisibility(GONE);
     } else {
         setDayText(day);
         mDayTextView.setVisibility(VISIBLE);
         mDayText.setVisibility(VISIBLE);
     }
 
     mDayTextView.setText(days);
 
     if (mFirstSetTimer) {
         mHourTextView.setInitialNumber(hours);
         mMinTextView.setInitialNumber(minute);
         mSecondTextView.setInitialNumber(second);
         mFirstSetTimer = false;
     } else{ mHourTextView.flipNumber(hours); mMinTextView.flipNumber(minute); mSecondTextView.flipNumber(second); }}Copy the code

It should be noted that when the unit time is single digit, “0” should be added before the number for the sake of visual unity.

Second, build a method that creates the countdown as follows:

private void createCountDownTimer(final int eventStatus) {
       if(mCountDownTimer ! =null) {
           mCountDownTimer.cancel();
       }
       mCountDownTimer = new CountDownTimer(mMillis, 1000) {
           @Override
           public void onTick(long millisUntilFinished) {
               // Planning requirements: When the countdown is 00:00:01, the activity status will be refreshed, and the countdown will not show the status of 00:00:00
               if (millisUntilFinished >= ONE_SEC) {
                   setSecond(millisUntilFinished);
                   // When the activity state is in progress, the callback is invoked every minute
                   if (eventStatus == HomeItemViewNewArrival.EVENT_START) {
                       mArrivalOneMinuteFlag--;
                       if(mArrivalOneMinuteFlag == Constant.ZERO) { mArrivalOneMinuteFlag = Constant.SIXTY; mOnCountDownTimerListener.onArrivalOneMinute(); }}}}@Override
           public void onFinish(a) { mOnCountDownTimerListener.onFinish(); }}; }Copy the code

In this method, an instance of CountDownTimer is created. CountDownTimer() takes two parameters, the total time remaining and the refresh interval.

In the onTick() method of the instance, the setSecond() method is called to refresh the view periodically after each interval (i.e., 1s) to complete the countdown control update. In addition, there is a requirement for one-minute periodic reporting of buried points in the product, which can also be done in the onTick() method. In actual project events, if there is a scheduled task requirement, it can also be set freely in this method. Finally, override the CountDownTimer’s onFinish() method to trigger onFinish() in the Listener interface.

2.2.4 Build public methods for external use

The first is to set the countdown listening event:

public void setDownTimerListener(OnCountDownTimerListener listener) {
    this.mOnCountDownTimerListener = listener;
}

Copy the code

The second is to expose a way to set the initial time and start or end of an activity:

public void setDownTime(long millis) {
    this.mMillis = millis;
}
 
 
public void setHeaderText(int eventStatus) {
    if (eventStatus == HomeItemViewNewArrival.EVENT_NOT_START) {
        mHeaderText.setText("Start in");
    } else {
        mHeaderText.setText("Ends in"); }}Copy the code

Last but not least, we need to design methods for the countdown class to start and cancel the countdown:

public void startDownTimer(int eventStatus) {
        mArrivalOneMinuteFlag = Constant.SIXTY;
        mFirstSetTimer = true;
        // Set the initial value for the countdown
        setSecond(mMillis);
        createCountDownTimer(eventStatus);// Create a countdown
        mCountDownTimer.start();
    }
 
    public void cancelDownTimer(a) {
        mCountDownTimer.cancel();
    }
Copy the code

In the start countdown method, the initial value of the countdown is initialized and the countdown is created. Finally, the start() method of the CountDownTimer instance is called to start the countdown. In the cancel method, cancel the countdown by calling the cancel() method of the CountDownTimer instance directly.

2.3 Actual call of countdown class

To actually call the countdown control, you simply add the countdown class layout to the concrete layout and instantiate BaseCountDownTimerView in the calling class. Then, use setDownTime() and setHeaderText() to initialize the data, and setDownTimerListener() to set up a listener for the View instance.

Finally, call startDownTimer() to start the countdown.

if(view ! =null) {
            view.setDownTime(mDuration);
            view.setHeaderText(mEventStatus);
            view.startDownTimer(mEventStatus);
            view.setDownTimerListener(new BaseCountDownTimerView.OnCountDownTimerListener() {
                @Override
                public void onRemain(long millisUntilFinished) {}@Override
                public void onFinish(a) {
                    view.cancelDownTimer();
                    if (bean.mNewArrivalType == TYPE_EVENT && mEventStatus == EVENT_START) {
                        mEventStatus = EVENT_END;
                        // Before the activity state is in progress, the countdown changes to 0, if there is another activity/new product, refresh the data for the next activity/new product
                        refreshNewArrivalBeanDate(bean);
                        onBindView(bean, 1.true.null);
                    } else{ setEventStatus(bean); }}@Override
                public void onArrivalOneMinute(a) {}});Copy the code

Three, realize the overall layout of the countdown

3.1 Requirement Description

In a multi-language environment or different screen conditions, the length of some language controls is too long, and the need for adaptive controls to fold display to adapt to THE UI specification

3.2 Implementation Plan

We originally considered only instantiating an object with a custom countdown control, but during the process of designing the object layout, we found that it was not convenient for one object to display at the end of the line or at the beginning of the second line after folding. Therefore, this paper uses the method of presetting two countdown objects at the same time in the layout, one object at the end of the line, the other at the beginning of the second line.

During the measure process, if the measured control width is greater than a certain width threshold, the view at the beginning of the second row is initialized and the view visible state at the end of the line is set to Gone; if the view is smaller than a certain width threshold, the view at the end of the line is initialized and the view visible state at the beginning of the second row is set to Gone

Take a look at the XML layout file first. Here is an overall layout file main_view_header_new_ARRIVAL with the title and countdown at the end of the line

<? xml version="1.0" encoding="utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/qb_px_48">
 
    <com.example.website.general.ui.widget.TextView
        android:id="@+id/new_arrival_txt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_centerInParent="true"
        android:layout_marginStart="@dimen/qb_px_20"
        android:text="@string/new_arrival"
        android:textColor="@color/common_color_de000000"
        android:textSize="@dimen/qb_px_16"
        android:textStyle="bold" />
 
    <com.example.website.widget.BaseCountDownTimerView
        android:id="@+id/count_down_timer_short"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentEnd="true"
        android:layout_marginEnd="@dimen/qb_px_20"
        android:gravity="center_vertical" />
</RelativeLayout>
Copy the code

It can be seen in action below

However, this layout can only show everything in a single line, so you need to extend the two-line presentation on this layout to take a look at the layout for main_list_ITEM_home_new_ARRIVAL

<? xml version="1.0" encoding="utf-8"? > <merge 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="wrap_content"
    android:orientation="vertical"
    tools:parentTag="android.widget.LinearLayout">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
 
        <include layout="@layout/main_view_header_new_arrival"/>
 
        <com.example.website.widget.BaseCountDownTimerView
            android:id="@+id/count_down_timer_long"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_marginStart="@dimen/qb_px_20"
            android:layout_marginTop="@dimen/qb_px_n_4"
            android:layout_marginEnd="@dimen/qb_px_20"
            android:layout_marginBottom="@dimen/qb_px_8"
            android:gravity="center_vertical" />
    </LinearLayout>
 
</merge>
Copy the code

It can be seen in action below

Instantiate the two views separately in the class.

View.inflate(getContext(), R.layout.main_list_item_home_new_arrival, this);
mBaseCountDownTimerViewShort = findViewById(R.id.count_down_timer_short); // End of line countdown view
mBaseCountDownTimerViewLong = findViewById(R.id.count_down_timer_long); // The first row countdown view
Copy the code

Through the above steps to fix the layout of the countdown control in two cases, then it is time to consider the judgment conditions of folding display.

In a multilingual environment, the width of the title TextView and the countdown View is uncertain, so you need to consider the width of the two controls. At the same time, because of planning requirements, also need to consider the display requirements of some language special situations. The judging code is as follows:

private boolean isShortCountDownTimerViewShow(a) {
        String languageCode = LocaleManager.getInstance().getCurrentLanguage();
        if (Constant.EN_US.equals(languageCode) || Constant.EN_GB.equals(languageCode) || Constant.EN_AU.equals(languageCode)) {
            // Due to the requirements of the program, American English, British English and Australian English are mandatory to display on the right side of the title bar of New Arrivals
            return true;
        } else {
            View newArrivalHeader = inflate(mContext, R.layout.main_view_header_new_arrival, null);
            TextView newArrivalTextView = newArrivalHeader.findViewById(R.id.new_arrival_txt);
            LinearLayout countDownTimer = newArrivalHeader.findViewById(R.id.count_down_timer_short);
            int measureSpecW = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            int measureSpecH = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            newArrivalTextView.measure(measureSpecW, measureSpecH);
            countDownTimer.measure(measureSpecW, measureSpecH);
            VLog.i(TAG, countDownTimer.getMeasuredWidth() + "--" + newArrivalTextView.getMeasuredWidth());
 
            if (countDownTimer.getMeasuredWidth() + newArrivalTextView.getMeasuredWidth() <= mContext.getResources().getDimensionPixelSize(R.dimen.qb_px_302)) {
                return true;
            } else {
                return false; }}}Copy the code

In the code, can be customized according to the actual needs of a specific several languages whether the display line.

For most of the rest of the language, you can use the MeasureSpec. MakeMeasureSpec (0, MeasureSpec. UNSPECIFIED) for measureSpecW and measureSpecH, The first parameter is the specification value obtained after the system measures the View, where 0 stands for omission (measure method was directly called before the system drew the View, so the width and height is 0, and this value has nothing to do with the width and height finally obtained). The second parameter, MeasureSpec.UNSPECIFIED, means that the parent container has no restrictions on the View. After the acquisition is completed, the measurement of the width of the specific view is successfully completed.

Through the return value of this method, we can control the display and hide of two countdown views, so as to achieve the effect of adaptive folding display.

if (isShortCountDownTimerViewShow()) {
               initCountDownTimerView(mBaseCountDownTimerViewShort, bean);
               mBaseCountDownTimerViewShort.setVisibility(VISIBLE);
               mBaseCountDownTimerViewLong.setVisibility(GONE);
           } else {
               initCountDownTimerView(mBaseCountDownTimerViewLong, bean);
               mBaseCountDownTimerViewShort.setVisibility(GONE);
               mBaseCountDownTimerViewLong.setVisibility(VISIBLE);
           }
Copy the code

In addition, this method is not limited to countdown control view, for a variety of custom views in multiple languages, can still use this measurement method to achieve the beautiful display of adaptive line feed.

Four, realize the countdown animation effect

4.1 Principle analysis of countdown digital rolling animation

As can be seen from the renderings, hours, minutes and seconds are all two digits, and the number changes in the same rule: first, from the single digit change, the old number from the normal display area to move up a certain distance, the new number from the bottom up to move up a certain distance to the normal display area. If the units digit goes down to 0, then the tens digit needs to go down, so the change is that the tens and the ones place move together.

The specific implementation ideas are as follows:

1. Treat the two digits of hour/minute/second as a numeric scroll component;

2. Split the two digits of the digit rolling component into a digit array, and the change operation is for a single element in the array.

3. Save the old number, compare the array elements of the old number and the new number one by one, draw the new number with the same digit, and move the different digits together;

4. When moving a digit, you need to move the old digit upward from 0 to the maximum negative scrolling distance; At the same time, move the new number up to the maximum scrolling distance of 0; The maximum scrolling distance is the height of the digital scrolling control, which needs to be determined according to the actual UI draft.

4.2 Implementation

4.2.1 Countdown rolling Component initialization

The countdown scroll component inherits from TextView and sets the Maximum scroll distance and Paintbrush properties in the constructor, both of which need to be determined based on the actual UI.

Wherein, the maximum scrolling distance mMaxMoveHeight is the overall height of the UI draft/min/second digital control; The font color and size set by the brush are the font color and size of the time/min/second digits in the UI draft. The specific code is as follows:

// constructor
public NumberFlipView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
 
    mResources = context.getResources();
    // Maximum scrolling height 18dp
    mMaxMoveHeight = mResources.getDimensionPixelSize(R.dimen.qb_px_18);
 
    // Set the brush properties
    setPaint();
}
 
// Set the brush properties
private void setPaint(a) {
    // Set the drawing number to white
    mPaint.setColor(Color.WHITE);
    // Set the drawing number style to solid
    mPaint.setStyle(Paint.Style.FILL);
    // Set draw digital font bold
    mPaint.setFakeBoldText(true);
    // Set the drawing text size to 14dp
    mPaint.setTextSize(mResources.getDimensionPixelSize(R.dimen.qb_px_14));
}
Copy the code

4.2.2 Draw the countdown scroll component

Drawing the countdown numbers is done by overriding onDraw(). First, split the old and new numbers into the corresponding array of numbers;

The specific code is as follows:

// Split the new number into a new number array
for (int i = 0; i < mNewNumber.length(); i++) {
    mNewNumberArray.add(String.valueOf(mNewNumber.charAt(i)));
}
 
// Split the old number into an array of old numbers
for (int i = 0; i < mOldNumber.length(); i++) {
    mOldNumberArray.add(String.valueOf(mOldNumber.charAt(i)));
}
Copy the code

Then draw the number: when drawing the new number, judge whether the old number and the new number are the same bit by bit, if the number is the same, draw the new number directly; If the numbers are different, both the old and new numbers need to be moved.

The specific code is as follows:

// The text width of the two-digit newNumber
int textWidth = mResources.getDimensionPixelSize(R.dimen.qb_px_16);
 
float curTextWidth = 0;
 
for (int i = 0; i < mNewNumberArray.size(); i++) {
    // The boundary of each number in newNumber
    mPaint.getTextBounds(mNewNumberArray.get(i), 0, mNewNumberArray.get(i).length(), mTextRect);
    // The width of each digit in newNumber
    int numWidth = mResources.getDimensionPixelSize(R.dimen.qb_px_5);
 
    // Determine whether the old and new digits are the same bit by bit
    if (mNewNumberArray.get(i).equals(mOldNumberArray.get(i))) {
        // If the number is the same, draw a new number directly
        canvas.drawText(mNewNumberArray.get(i), getWidth() * ONE_HALF - textWidth * ONE_HALF + curTextWidth,
        getHeight() * ONE_HALF + mTextRect.height() * ONE_HALF, mPaint);
 
    } else {
        // The numbers are different. Both the old and new numbers need to be moved
        canvas.drawText(mOldNumberArray.get(i), getWidth() * ONE_HALF - textWidth * ONE_HALF + curTextWidth,
        mOldNumberMoveHeight + getHeight() * ONE_HALF + mTextRect.height() * ONE_HALF, mPaint);
 
        canvas.drawText(mNewNumberArray.get(i), getWidth() * ONE_HALF - textWidth * ONE_HALF + curTextWidth,
        mNewNumberMoveHeight + getHeight() * ONE_HALF + mTextRect.height() * ONE_HALF, mPaint);
 
    }
 
    curTextWidth += (numWidth + mResources.getDimensionPixelSize(R.dimen.qb_px_3));
Copy the code

GetWidth () gets the entire width of the countdown control; TextWidth is the width of two digits; NumWidth is the width of a single number; CurTextWidth =numWidth+ the space between two numbers.

Horizontal drawing of tens digits starts with getWidth()/2 + textWidth/2; Horizontal drawing of the units digit starts with getWidth()/2textWidth/2 + curTextWidth. GetHight () gets the entire height of the countdown control; Textrect.height () gets the height of the number.

The vertical position of the old number is mOldNumberMoveHeight + getHeight()/2 + textRect.height()/2; New digital drawing vertical to the starting position for mNewNumberMoveHeightgetHeight () / 2 + textRect. Height () / 2.

4.2.3 Realization of countdown digital rolling effect

The scrolling effect of old numbers and new numbers is implemented by using the ValueAnimator to constantly change the scrolling distance of old numbers mOldNumberMoveHeight and the scrolling distance of new numbers mNewNumberMoveHeight.

Within the specified animation time FLIP_NUMBER_DURATION, mNewNumberMoveHeight needs to change from the maximum scrolling distance mMaxMoveHeight to 0, MOldNumberMoveHeight Specifies the maximum scrolling distance to change from 0 to negative mMaxMoveHeight. Each time a new scroll distance is calculated, the invalidate() method is called, triggering the onDraw() method, which continuously draws old and new numbers to achieve the effect of the number scroll.

The specific code is as follows:

/* Using ValueAnimator, change the value from MAX_MOVE_HEIGHT to 0 within the specified time FLIP_NUMBER_DURATION, assign each change to mNewNumberMoveHeight, At the same time, assign the value mNewNumberMoveHeight - MAX_MOVE_HEIGHT to mOldNumberMoveHeight, and redraw it to achieve the new number and the old number slide up; * /
mNumberAnimator = ValueAnimator.ofFloat(mMaxMoveHeight, 0);
mNumberAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mNewNumberMoveHeight = (float) animation.getAnimatedValue(); mOldNumberMoveHeight = mNewNumberMoveHeight - mMaxMoveHeight; invalidate(); }}); mNumberAnimator.setDuration(FLIP_NUMBER_DURATION); mNumberAnimator.start();Copy the code

4.3 Specific Use

First introduced in the layout, used the same way as TextView. The layout of hours, minutes, and seconds as shown below:

<! -- -- -- > < com. Example. Materialdesginpractice. NumberFlipView android: id ="@+id/hours_tv"
    android:layout_width="@dimen/qb_px_22"
    android:layout_height="@dimen/qb_px_18"
    android:gravity="center"
    android:background="@drawable/number_bg"
    android:textSize="@dimen/qb_px_14"
    android:textColor="@color/common_color_ffffff"/ > <! - - - > < com. Example. Materialdesginpractice. NumberFlipView android: id ="@+id/min_tv"
    android:layout_width="@dimen/qb_px_22"
    android:layout_height="@dimen/qb_px_18"
    android:gravity="center"
    android:background="@drawable/number_bg"
    android:textSize="@dimen/qb_px_14"
    android:textColor="@color/common_color_ffffff"/ > <! - seconds - > < com. Example. Materialdesginpractice. NumberFlipView android: id ="@+id/sec_tv"
    android:layout_width="@dimen/qb_px_22"
    android:layout_height="@dimen/qb_px_18"
    android:gravity="center"
    android:background="@drawable/number_bg"
    android:textSize="@dimen/qb_px_14"
    android:textColor="@color/common_color_ffffff"/>
Copy the code

Then find the corresponding countdown digital control by id:

mHourTextView = findViewById(R.id.hours_tv);
mMinTextView = findViewById(R.id.min_tv);
mSecondTextView = findViewById(R.id.sec_tv);
Copy the code

Finally call the time/minute/second countdown digital control method, set the initial value of the countdown or countdown new number. For the first time, the setInitialNumber() method is called to set the initial value. Otherwise, call the flipNumber() method to set a new countdown value.

The specific usage is as follows:

if (mFirstSetTimer) {
    mHourTextView.setInitialNumber(hours);
    mMinTextView.setInitialNumber(minute);
    mSecondTextView.setInitialNumber(second);
    mFirstSetTimer = false;
} else {
    mHourTextView.flipNumber(hours);
    mMinTextView.flipNumber(minute);
    mSecondTextView.flipNumber(second);
}
Copy the code

Optimize the countdown performance

5.1 Principle analysis of countdown digital rolling animation

In the implementation, the countdown control is a child of the ListView, and the ListView is in a Fragment.

In order to reduce power consumption, it is necessary to pause the countdown when the countdown control is not visible; Restart the countdown when the countdown control reappears within visibility. Below is the scene where the countdown stops and starts.

5.2 Implementation

5.2.1 Pause countdown

The page slides and the countdown control slides out of the viewable area. When the countdown control slides out of the viewable range of the ListView, pause the countdown. The point of this case is to determine whether the child view has moved out of the ListView.

If your app only needs to be compatible with Android 7 or higher, you can override the onDetachedFromWindow() method to cancel the countdown inside the method body. Because this method is called every time a child view moves out of the ListView.

@Override
protected void onDetachedFromWindow(a) {
    super.onDetachedFromWindow();
    // Move off screen call, pause countdown
    stopCountDownTimerAndAnimation();
}
Copy the code

If the app needs to be compatible with android below 7, the above method will not work because the onDetachedFromWindow() method is not compatible with earlier versions. But the same effect can be achieved by overriding the onStartTemporaryDetach() method.

@Override
public void onStartTemporaryDetach(a) {
    super.onStartTemporaryDetach();
    // Move off screen call, pause countdown
    stopCountDownTimerAndAnimation();
}
Copy the code

Switch to another Fragment using TAB

When the countdown control is visible, you need to pause the countdown while tabbing to another Fragment. In this case, the Fragment of the countdown control is hidden. You can get the View of the countdown control while the Fragment is hidden, and then call its method to pause the countdown.

@Override
public void onFragmentHide(a) {
    super.onFragmentHide();
 
    // Pause the countdown
    stopNewArrivalCountDownTimerAndAnimation();
}
Copy the code

To obtain the View object where the countdown control is located, determine whether it is the View object where the countdown control is located by traversing the sub-view within the visible range of ListView. And then call the countdown control’s View objects stopCountDownTimerAndAnimation () method, suspend the countdown.

/** * Get the view object of the countdown control, pause the countdown */
private void stopNewArrivalCountDownTimerAndAnimation(a) {
    if(mListView ! =null) {
        for (int index = 0; index < mListView.getChildCount(); index++) {
            View view = mListView.getChildAt(index);
            if (view instanceofHomeItemViewNewArrival) { ((HomeItemViewNewArrival) view).stopCountDownTimerAndAnimation(); }}}}Copy the code

The application is switched to the background or another page

When the countdown control is in the visual range, the application switches to the background or clicks other contents of the interface where the countdown control is located to jump to another interface, the countdown needs to be suspended. These situations trigger the onStop() method on the Fragment where the countdown is. So you can override onStop(), get the View of the countdown control inside the method body, and then pause the countdown.

The above stopNewArrivalCountDownTimerAndAnimation () method.

@Override
public void onStop(a) {
    super.onStop();
 
    // Pause the countdown
    stopNewArrivalCountDownTimerAndAnimation();
}
Copy the code

5.2.2 Start countdown

The page slides and the countdown control slides into the viewable area

When the countdown control slides out of the viewable area and slides back into the viewable area, the Adapter’s getView() method is automatically called, followed by the countdown control’s onBindView() method. Since the onBindView() method initializes the countdown control, there is no need to start the countdown manually in this case.

TAB back to the Fragment where the countdown is

Use TAB to switch back to the Fragment where the countdown control is located. If the countdown control is visible at this time, you need to restart the countdown. Since the Fragment redisplays in this case, you can get the View of the countdown control while the Fragment is displayed and then call its methods to restart the countdown.

@Override
public void onFragmentShow(int source, int floor) {
    super.onFragmentShow(source, floor);
    // Restart the countdown
    refreshNewArrival();
}
Copy the code

Similarly, in order to obtain the View object of the countdown control, we need to determine whether it is the View object of the countdown control by traversing the sub-view within the visible range of the ListView. The refreshEventStatus () method of the View object on which the countdown control is located is then called to start the countdown.

/** * get the view object where the countdown control is located to start the countdown */
private void refreshNewArrival(a) {
    if(mListView ! =null) {
        for (int index = 0; index < mListView.getChildCount(); index++) {
            View view = mListView.getChildAt(index);
            if (view instanceofHomeItemViewNewArrival) { ((HomeItemViewNewArrival) view).refreshEventStatus(); }}}}Copy the code

The application is switched back to the foreground or rolled back from another screen

When the application switches back to the foreground or from another screen to the screen where the countdown control is located, if the countdown control is visible at this time, it needs to restart the countdown. These situations trigger the onResume() method of the Fragment where the countdown is. So you can override onResume(), get the View of the countdown control inside the method body, and then call its method to restart the countdown.

Where refreshNewArrival() is the same as above.

@Override
public void onResume(a) {
    super.onResume();
    // Restart the countdown
    refreshNewArrival();
}
Copy the code

Author: Liu Zhiyi, Zhen Yiqing, Vivo Internet Client team