English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Implementation of Countdown Effects Similar to JD and Taobao in Android Using TextView

Today, I bring to you a countdown for various e-commerce APPs such as JD.com, Taobao, and Vip.com using just a TextView. Recently, the company has been working overtime and didn't have time to organize it. Today, it's a rare day off, so I want to share this with everyone. I just want to learn together and review it later. Why did I think of using a TextView to implement it? Because recently, the company has been doing some optimization work, one of which is the countdown style. The colleague who developed the original control used multiple TextViews to piece together, resulting in quite a lot of redundant code. So the project manager said: 'Little Hong, this is for you to optimize, and you also need to ensure that it has certain extensibility.' At that time, I was dumbfounded and didn't know where to start optimizing. Then I checked the countdown of various APPs such as JD.com, Ele.me, and Vip.com, and opened the hierarchy interface in the developer, and found that they all have a common feature: a View without using multiple TextViews to piece together. I believe everyone knows the advantages of using just one TextView over using multiple TextViews to piece together. Let's take a look at a few interfaces to see.


When you see this, you naturally think of implementing it with a custom View. Yes, custom View can indeed achieve such an effect. But today we will not use custom View. Instead, we will use a TextView to achieve this.

Since the project manager requires that the optimized code has extensibility, so this code design adds some object-oriented knowledge. There are some of my own design and architectural ideas.

The design idea of this demo:

          1. Write a base class for countdown to implement the most common and basic countdown functions without any style. Let this base class inherit from the CountDownTimer class and implement some functions in this base class.

Save a TextView object and display the countdown data each time in the TextView, and then publish a getmDateTv() method to return a TextView object. Then you can simply get this TextView object and display it in the layout of the interface. It is very convenient.

          2. Then, for different styles of countdown, you just need to write different subclasses to inherit the most ordinary countdown base class, and then overwrite the two methods of setting data and setting style, then you can add different styles to the most ordinary countdown. Next time if you need to expand a new countdown style, you don't need to change the code of other classes, just write a derived class of the ordinary countdown and overwrite the two methods, making the expandability more flexible.

          3. Then, through a TimerUtils management class, bear the pressure of subclasses and superclasses, and distribute the required functions of subclasses and superclasses to the TimerUtils class. And this TimerUtils management class is the only class that interacts with the client, such as obtaining the countdown object and obtaining the TextView object of the countdown through this management class, avoiding the client from directly dealing with the base class and subclasses of the countdown. Thus, the encapsulation and concealment of the class are reflected.

Below is the simple UML class diagram of this demo design:


Through the above analysis, let's see which knowledge points are needed for the implementation of this demo.

 1Usage of CountDownTimer class.

    2. Usage of SpannableString.

    3. Encapsulation of MikyouCountDownTimer.

    4. Implementation of the custom MikyouBackgroundSpan.

Firstly, through the above analysis, we need to review the knowledge about CountDownTimer. CountDownTimer is a very simple class, and we can understand its usage by looking at its source code. Its usage is naturally known.

CountDownTimer is an abstract class.

// 
// Source code recreated from a .class file by IntelliJ IDEA 
// (powered by Fernflower decompiler) 
// 
package android.os; 
public abstract class CountDownTimer { 
public CountDownTimer(long millisInFuture, long countDownInterval) { 
throw new RuntimeException("Stub!"); 
} 
public final synchronized void cancel() { 
throw new RuntimeException("Stub!"); 
} 
public final synchronized CountDownTimer start() { 
throw new RuntimeException("Stub!"); 
} 
public abstract void onTick(long var1); 
public abstract void onFinish(); 
}

You can see that the total duration of the countdown is millisFuture, and the countDownInterval step length is default to be1000ms, so all data are initialized through their constructors, and then you need to override a callback method onTick, where one of the parameters is the remaining time in milliseconds after each step. Then, all we need to do is in the onTick method, to set every interval corresponding to the step length,1000ms milliseconds for time formatting to get the corresponding countdown time format. This is the basic countdown style implemented. The formatting of the countdown format uses the formatDuration method from the DurationFormatUtils class in the apache common lang package. By passing a time format, it will automatically convert the countdown to the corresponding mTimePattern style (HH:mm:ss or dd days HH hours mm minutes ss seconds).

Secondly, review the usage of SpannableString.

In Android, EditText is used for editing text, and TextView is used for displaying text. However, sometimes we need to set the style of the text within them. Android provides the SpannableString class to process specified text.

1) ForegroundColorSpan text color

private void setForegroundColorSpan() {
SpannableString spanString = new SpannableString("Foreground Color");
ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}

2) BackgroundColorSpan text background color

private void setBackgroundColorSpan() {
SpannableString spanString = new SpannableString("Background Color");
BackgroundColorSpan span = new BackgroundColorSpan(Color.YELLOW);
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.append(spanString);
}

3) StyleSpan font style: bold, italic, etc.

private void setStyleSpan() { 
SpannableString spanString = new SpannableString("Bold italic"); 
StyleSpan span = new StyleSpan(Typeface.BOLD_ITALIC); 
spanString.setSpan(span, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 
tv.append(spanString); 
}

4RelativeSizeSpan relative size

private void setRelativeFontSpan() { 
SpannableString spanString = new SpannableString("Font relative size"); 
spanString.setSpan(new RelativeSizeSpan(2.5f), 0, 6,Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 
tv.append(spanString); 
}

5TypefaceSpan text font

private void setTypefaceSpan() { 
SpannableString spanString = new SpannableString("Text font"); 
spanString.setSpan(new TypefaceSpan("monospace"), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 
tv.append(spanText); 
}

6URLSpan text hyperlink

private void addUrlSpan() { 
SpannableString spanString = new SpannableString("Hyperlink"); 
//www.baidu.com 
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 
tv.append(spanString); 
}

7ImageSpan image

private void addImageSpan() { 
SpannableString spanString = new SpannableString(" "); 
Drawable d = getResources().getDrawable(R.drawable.ic_launcher); 
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE); 
spanString.setSpan(span, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 
tv.append(spanString); 
}

8ClickableSpan text with click event

private TextView textView; 
textView = (TextView)this.findViewById(R.id.textView); 
String text = "Display Activity"; 
SpannableString spannableString = new SpannableString(text); 
spannableString.setSpan(new ClickableSpan() { 
@Override 
public void onClick(View widget) { 
Intent intent = new Intent(Main.this, OtherActivity.class); 
startActivity(intent); 
} 
// Indicates that the entire length of the text triggers this event when clicked 
}, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 
textView.setText(spannableString); 
textView.setMovementMethod(LinkMovementMethod.getInstance());

9UnderlineSpan Underline

private void addUnderLineSpan() { 
SpannableString spanString = new SpannableString("Underline"); 
UnderlineSpan span = new UnderlineSpan(); 
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 
tv.append(spanString); 
}

10StrikethroughSpan

Strikethrough

private void addStrikeSpan() { 
SpannableString spanString = new SpannableString("Strikethrough"); 
StrikethroughSpan span = new StrikethroughSpan(); 
spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 
tv.append(spanString); 
}

11SuggestionSpan

Equivalent to a placeholder

12MaskFilterSpan

Decorative effects, such as blur (BlurMaskFilter), emboss (EmbossMaskFilter)

13RasterizerSpan

Raster effect

14) AbsoluteSizeSpan

Absolute size (text font)

private void setAbsoluteFontSpan() { 
SpannableString spannableString = new SpannableString("4 
AbsoluteSizeSpan absoluteSizeSpan = new AbsoluteSizeSpan(40); 
spannableString.setSpan(absoluteSizeSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 
editText.append(spannableString); 
}

15) DynamicDrawableSpan Set image, based on text baseline or bottom alignment.

16) TextAppearanceSpan

Text appearance (including font, size, style, and color)

private void setTextAppearanceSpan() { 
SpannableString spanString = new SpannableString("文本外貌"); 
TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(this, android.R.style.TextAppearance_Medium); 
spanString.setSpan(textAppearanceSpan, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 
tv.append(spanString); 
}

Alright, with the above review knowledge, now we can truly start the implementation of the demo, and then let's encapsulate our countdown step by step together.

One, write a MikyouCountDownTimer base class, let it inherit from the CountDownTimer class, and publish the initSpanData and setBackgroundSpan methods for other style countdown subclass usage, which can implement the most basic countdown function.

package com.mikyou.countdowntimer.bean; 
import android.content.Context; 
import android.os.CountDownTimer; 
import android.text.style.ForegroundColorSpan; 
import android.widget.TextView; 
import com.mikyou.countdowntimer.myview.MikyouBackgroundSpan; 
import com.mikyou.countdowntimer.utils.TimerUtils; 
import org.apache.commons.lang.time.DurationFormatUtils; 
import java.util.ArrayList; 
import java.util.List; 
/** 
* Created by mikyou on 16-10-22. 
*/ 
public class MikyouCountDownTimer extends CountDownTimer{ 
private Context mContext;//The context object passed in 
protected TextView mDateTv;//A TextView to implement countdown 
private long mGapTime;//The time interval passed in, that is, the total duration of the countdown 
private long mCount = 1000;//The step length of the countdown, usually1000 represents every1s jumps once 
private String mTimePattern = "HH:mm:ss";//The time style passed in, such as: HH:mm:ss HH o'clock mm minutes ss seconds dd days HH o'clock mm minutes ss seconds 
private String mTimeStr; 
protected List<MikyouBackgroundSpan> mBackSpanList; 
protected List<ForegroundColorSpan> mTextColorSpanList; 
private int mDrawableId; 
private boolean flag = false;//Set the flag to control the initialization of Span data once 
protected String[] numbers;//This array is used to save the numerical values of days, hours, minutes, and seconds after each countdown character is split 
protected char[] nonNumbers;//Saves the intervals between days, hours, minutes, and seconds ("day", "hour", "minute", "second", or ":") 
//Used for the inner spacing of countdown style, font size, font color, and the color of the countdown interval 
private int mSpanPaddingLeft,mSpanPaddingRight,mSpanPaddingTop,mSpanPaddingBottom; 
private int mSpanTextSize; 
private int mSpanTextColor; 
protected int mGapSpanColor; 
public MikyouCountDownTimer(Context mContext, long mGapTime, String mTimePattern,int mDrawableId) { 
this(mContext,mGapTime,1000,mTimePattern,mDrawableId); 
} 
public MikyouCountDownTimer(Context mContext, long mGapTime, int mCount, String mTimePattern,int mDrawableId) { 
super(mGapTime,mCount); 
this.mContext = mContext; 
this.mGapTime = mGapTime;//The total duration of the countdown 
this.mCount = mCount;//The step length of each countdown, the default is1000 
this.mDrawableId= mDrawableId;//Used to set the id of the drawable for the background 
this.mTimePattern = mTimePattern;//Time format: such as HH:mm:ss or dd days HH hours mm minutes ss seconds, etc. 
mBackSpanList = new ArrayList<>(); 
mTextColorSpanList = new ArrayList<>(); 
mDateTv = new TextView(mContext,null); 
} 
//Publish these methods for setting countdown styles for external calls, thus allowing flexible customization of countdown styles 
public MikyouCountDownTimer setTimerTextSize(int textSize){ 
this.mSpanTextSize = textSize; 
return this; 
} 
public MikyouCountDownTimer setTimerPadding(int left,int top,int right,int bottom){ 
this.mSpanPaddingLeft = left; 
this.mSpanPaddingBottom = bottom; 
this.mSpanPaddingRight = right; 
this.mSpanPaddingTop = top; 
return this; 
} 
public MikyouCountDownTimer setTimerTextColor(int color){ 
this.mSpanTextColor = color; 
return this; 
} 
public MikyouCountDownTimer setTimerGapColor(int color){ 
this.mGapSpanColor = color; 
return this; 
} 
//Set the style of the countdown Span, and publish it for subclasses to implement 
public void setBackgroundSpan(String timeStr) { 
if (!flag){ 
initSpanData(timeStr); 
flag = true; 
} 
mDateTv.setText(timeStr); 
} 
//Set the data of the countdown Span, publish it for implementation by various subclasses 
public void initSpanData(String timeStr) { 
numbers = TimerUtils.getNumInTimerStr(timeStr); 
nonNumbers = TimerUtils.getNonNumInTimerStr(timeStr); 
} 
protected void initBackSpanStyle(MikyouBackgroundSpan mBackSpan) { 
mBackSpan.setTimerPadding(mSpanPaddingLeft,mSpanPaddingTop,mSpanPaddingRight,mSpanPaddingBottom); 
mBackSpan.setTimerTextColor(mSpanTextColor); 
mBackSpan.setTimerTextSize(mSpanTextSize); 
} 
@Override 
public void onTick(long l) { 
if (l > 0) { 
mTimeStr = DurationFormatUtils.formatDuration(l, mTimePattern); 
//This is the formatDuration method in the DurationFormatUtils class of the common lang package in Apache, by passing in 
//A time format will automatically convert the countdown to the corresponding style of mTimePattern (HH:mm:ss or dd days HH:mm:ss) 
setBackgroundSpan(mTimeStr); 
} 
} 
@Override 
public void onFinish() { 
mDateTv.setText("Countdown finished"); 
} 
//This is the object used to return the TextView that displays the countdown. 
public TextView getmDateTv() { 
startTimer(); 
return mDateTv; 
} 
public void cancelTimer(){ 
this.cancel(); 
} 
public void startTimer(){ 
this.start(); 
} 
public String getmTimeStr() { 
return mTimeStr; 
} 
}

The TimerUtils class is used to save different countdown formats, such as HH:mm:ss, HH hours mm minutes ss seconds, dd days HH hours mm minutes ss seconds, etc. Now we can take a look at the simple basic styles.

Secondly, customize MikyouBackgroundSpan to inherit from ImageSpan. This class is very important and is used to add styles to the TextView for countdowns. Why can a TextView be used to achieve this?

Don't forget there is also a very powerful class called SpannableString, which can set the style of each character in a string, with many styles. Finally, through the TextView, there is a setSpan method that can be passed in.
A SpannableString object is completed. But why do we need to define a custom Span? This is because it is very strange that among so many Span styles in android, there is none that can directly set a drawable object file. So I searched online for a long time and couldn't find anything. Finally, on stackOverFlow, I found a solution provided by a foreigner. The solution is to rewrite the ImageSpan and then you can achieve the setting of drawable files.

package com.mikyou.countdowntimer.myview; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.graphics.Rect; 
import android.graphics.drawable.Drawable; 
import android.text.style.ImageSpan; 
/** 
* Created by mikyou on 16-10-22. 
*/ 
public class MikyouBackgroundSpan extends ImageSpan { 
private Rect mTextBound; 
private int maxHeight = 0; 
private int maxWidth = 0; 
private int mPaddingLeft = 20; 
private int mPaddingRight = 20; 
private int mPaddingTop = 20; 
private int mPaddingBottom = 20; 
private int mTextColor = Color.GREEN; 
private int mTextSize = 50; 
public MikyouBackgroundSpan(Drawable d, int verticalAlignment) { 
super(d, verticalAlignment); 
mTextBound = new Rect(); 
} 
public MikyouBackgroundSpan setTimerTextColor(int mTextColor) { 
this.mTextColor = mTextColor; 
return this; 
} 
public MikyouBackgroundSpan setTimerTextSize(int textSize) { 
this.mTextSize = textSize; 
return this; 
} 
public MikyouBackgroundSpan setTimerPadding(int left, int top, int right, int bottom) { 
this.mPaddingLeft = left; 
this.mPaddingRight = right; 
this.mPaddingBottom = bottom; 
this.mPaddingTop = top; 
return this; 
} 
@Override 
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { 
//Draw the background of the text content 
paint.setTextSize(mTextSize); 
//Measure the width and height of the text, obtained through mTextBound 
paint.getTextBounds(text.toString(), start, end, mTextBound); 
//Setting the width and height of the text background, passing in the left, top, right, and bottom parameters 
maxWidth = maxWidth < mTextBound.width() ?63; mTextBound.width() : maxWidth; 
maxHeight = maxHeight < mTextBound.height() ?63; mTextBound.height() : maxHeight; 
//Setting the maximum width and height is to prevent the countdown from being redrawn during the number switch, which may cause the border width and height of the countdown to shake. 
// Therefore, each time the maximum height and width are obtained instead of measuring the height and width each time 
getDrawable().setBounds(0, 0, maxWidth+mPaddingLeft+mPaddingRight, mPaddingTop+mPaddingBottom+maxHeight); 
//Draw text background 
super.draw(canvas, text, start, end, x, top, y, bottom, paint); 
//Set the text color 
paint.setColor(mTextColor); 
//Set the font size 
paint.setTextSize(mTextSize); 
int mGapX = (getDrawable().getBounds().width() - maxWidth)/2; 
int mGapY = (getDrawable().getBounds().height() - maxHeight)/2; 
//Draw text content 
canvas.drawText(text.subSequence(start, end).toString(), x + mGapX, y - mGapY + maxHeight/3, paint); } 
}

Three, the implementation of countdown in style one, style one refers to, for example:12Hour36Minute27seconds, or12:36:27It is to separate the number and hour, minute, second or ":", and then customize each number block (12 36 27) and the style of intervals (hour minute second or :), including adding background and borders to the number blocks. In the number array of MikyouCountDownTimer, [12 36 27While the nonumer array stores [hour minute second] or [ : : ]d spacing characters.

package com.mikyou.countdowntimer.bean; 
import android.content.Context; 
import android.text.SpannableString; 
import android.text.method.LinkMovementMethod; 
import android.text.style.ForegroundColorSpan; 
import android.text.style.ImageSpan; 
import com.mikyou.countdowntimer.myview.MikyouBackgroundSpan; 
import com.mikyou.countdowntimer.utils.TimerUtils; 
/** 
* Created by mikyou on 16-10-22. 
*/ 
public class JDCountDownTimer extends MikyouCountDownTimer { 
private SpannableString mSpan; 
private Context mContext; 
private int mDrawableId; 
public JDCountDownTimer(Context mContext, long mGapTime, String mTimePattern, int mDrawableId) { 
super(mContext, mGapTime, mTimePattern, mDrawableId); 
this.mContext = mContext; 
this.mDrawableId = mDrawableId; 
} 
/** 
* Rewrite the parent class's initSpanData method 
* Get the custom MikyouBackgroundSpan object corresponding to each block of numbers through the number array 
* Then, through MikyouBackgroundSpan object, define the style of each block of numbers including background, border, and border rounded style, and then add these objects to the collection 
* Get each interval's ForegroundColorSpan object through the nonNumber array 
* Then, through these objects, we can define the style of each interval block, because only ForegroundColorSpan is defined, so we can only define 
* The font color of each interval block, the setmGapSpanColor method is also provided for external free customization of the style of each interval 
* In fact, other Spans can also be defined, and the implementation is also very simple. 
* */ 
@Override 
public void initSpanData(String timeStr) { 
super.initSpanData(timeStr); 
for (int i = 0; i<numbers.length;i++} 
MikyouBackgroundSpan mBackSpan = new MikyouBackgroundSpan(mContext.getDrawable(mDrawableId), ImageSpan.ALIGN_BOTTOM); 
initBackSpanStyle(mBackSpan);}} 
mBackSpanList.add(mBackSpan); 
} 
for (int i= 0; i<nonNumbers.length;i++} 
ForegroundColorSpan mGapSpan = new ForegroundColorSpan(mGapSpanColor); 
mTextColorSpanList.add(mGapSpan); 
} 
} 
/** Override the parent class's setBackgroundSpan method 
* We know that setting the Span style mainly controls two variables: start, end index 
* to determine the style of the substring from the start to end position of the string 
* mGapLen = 1, indicating the length of an interval block, 
* For example:12Hour36Minute27The interval length of the "hour", "minute", and "second" of the second 
* Therefore, by traversing the Span collection, set Span to the string, 
* It is not difficult to deduce that the start index of the Span of each number block: start = i*numbers[i].length() + i*mGapLen; 
* end = start + numbers[i].length(); 
* */ 
@Override 
public void setBackgroundSpan(String timeStr) { 
super.setBackgroundSpan(timeStr); 
int mGapLen = 1; 
mSpan = new SpannableString(timeStr); 
for (int i = 0; i < mBackSpanList.size(); i++} 
int start = i*numbers[i].length() + i*mGapLen; 
int end = start + numbers[i].length(); 
TimerUtils.setContentSpan(mSpan, mBackSpanList.get(i), start, end); 
if (i < mTextColorSpanList.size()){//Here in order to prevent12:36:27This style, this style has only 2 intervals, so we need to make a judgment to prevent array out of bounds 
TimerUtils.setContentSpan(mSpan, mTextColorSpanList.get(i), end, end + mGapLen); 
} 
} 
mDateTv.setMovementMethod(LinkMovementMethod.getInstance());//This method is very important and needs to be called, otherwise the countdown drawn will be overlapping. 
mDateTv.setText(mSpan); 
} 
}

4. The countdown implementation of style two, which is different from style one, for example:12Hour36Minute27seconds, or12:36:27This is to separate each number and hours, minutes, seconds, or : and then customize the style of each number block (1 2 3 6 2 7) and the style of the interval (hours minutes seconds or :), including adding background and border to the number blocks. The vipNumber array in MikyouCountDownTimer stores [1 2 3 6 2 7The vipnonNumer array stores the interval characters [hours minutes seconds] or [ : : ]d.

package com.mikyou.countdowntimer.bean; 
import android.content.Context; 
import android.text.SpannableString; 
import android.text.method.LinkMovementMethod; 
import android.text.style.ForegroundColorSpan; 
import android.text.style.ImageSpan; 
import com.mikyou.countdowntimer.myview.MikyouBackgroundSpan; 
import com.mikyou.countdowntimer.utils.TimerUtils; 
import java.util.ArrayList; 
import java.util.List; 
/** 
* Created by mikyou on 16-10-22. 
*/ 
public class VIPCountDownTimer extends MikyouCountDownTimer { 
private SpannableString mSpan; 
private Context mContext; 
private int mDrawableId; 
private List<MikyouBackgroundSpan> mSpanList; 
private String[] vipNumbers; 
private char[] vipNonNumbers; 
public VIPCountDownTimer(Context mContext, long mGapTime, String mTimePattern, int mDrawableId) { 
super(mContext, mGapTime, mTimePattern, mDrawableId); 
this.mContext = mContext; 
this.mDrawableId = mDrawableId; 
mSpanList = new ArrayList<>(); 
} 
/** Override the parent class's setBackgroundSpan method 
* We know that setting the Span style mainly controls two variables: start, end index 
* to determine the style of the substring from start to end position of the string, indicating the position range of each number substring in the entire string 
* mGapLen = 1, indicating the length of an interval block, 
* For example:12Hour36Minute27The interval length of the "hour", "minute", and "second" of the second 
* Therefore, by traversing the Span collection, set Span to the string, 
* It is not difficult to deduce that the start index of the Span of each number block: start = i*numbers[i].length() + i*mGapLen; 
* end = start + numbers[i].length(); 
* */ 
@Override 
public void setBackgroundSpan(String timeStr) { 
int mGapLen = 1; 
mSpan = new SpannableString(timeStr); 
initSpanData(timeStr); 
int start = 0 ; 
int count = 0; 
for (int i = 0; i < vipNumbers.length; i++} 
for (int j = start; j < start + vipNumbers[i].toCharArray().length; j++, count++} 
TimerUtils.setContentSpan(mSpan, mSpanList.get(count), j, j+mGapLen); 
} 
//At this point, it means that we have traversed the numbers of a certain block, so we need to update the start variable with the current block of numbers 
start = start + vipNumbers[i].toCharArray().length; 
if (i < nonNumbers.length){ 
TimerUtils.setContentSpan(mSpan, mTextColorSpanList.get(i), start, start+mGapLen); 
start = start +mGapLen;//If it is an interval, we still need to add each interval length and update the start variable 
} 
} 
mDateTv.setMovementMethod(LinkMovementMethod.getInstance()); 
mDateTv.setText(mSpan); 
} 
/** 
* Rewrite the parent class's initSpanData method 
* Get the custom MikyouBackgroundSpan object corresponding to each block of numbers through the number array 
* Then, through MikyouBackgroundSpan object, define the style of each block of numbers including background, border, and border rounded style, and then add these objects to the collection 
* Get each interval's ForegroundColorSpan object through the nonNumber array 
* Then, through these objects, we can define the style of each interval block, because only ForegroundColorSpan is defined, so we can only define 
* The font color of each interval block, the setmGapSpanColor method is also provided for external free customization of the style of each interval 
* In fact, other Spans can also be defined, and the implementation is also very simple. 
* */ 
@Override 
public void initSpanData(String timeStr) { 
super.initSpanData(timeStr); 
vipNumbers = TimerUtils.getNumInTimerStr(timeStr);//Get each number, not each block of numbers, and add it to the array 
vipNonNumbers = TimerUtils.getNonNumInTimerStr(timeStr);//Get each interval character and add it to the array 
for (int i = 0; i < vipNumbers.length; i++} 
for (int j = 0; j < vipNumbers[i].toCharArray().length; j++}//Because we need to get each number, we still need to traverse each number in each block of numbers, so we need a two-layer loop 
MikyouBackgroundSpan mSpan = new MikyouBackgroundSpan(mContext.getDrawable(mDrawableId), ImageSpan.ALIGN_BOTTOM); 
initBackSpanStyle(mSpan); 
mSpanList.add(mSpan); 
} 
} 
for (int i= 0; i<vipNonNumbers.length;i++} 
ForegroundColorSpan mGapSpan = new ForegroundColorSpan(mGapSpanColor); 
mTextColorSpanList.add(mGapSpan); 
} 
} 
}

The TimerUtils management class mainly provides objects of countdown timers with different styles to the client, so this class directly establishes a relationship with the client, thus realizing the encapsulation of hiding the countdown subclasses and base classes from the outside.

package com.mikyou.countdowntimer.utils; 
import android.content.Context; 
import android.graphics.Color; 
import android.text.SpannableString; 
import android.text.Spanned; 
import android.text.style.ForegroundColorSpan; 
import com.mikyou.countdowntimer.bean.JDCountDownTimer; 
import com.mikyou.countdowntimer.bean.MikyouCountDownTimer; 
import com.mikyou.countdowntimer.bean.VIPCountDownTimer; 
/** 
* Created by mikyou on 16-10-22. 
*/ 
public class TimerUtils { 
public static final int JD_STYLE = 0; 
public static final int VIP_STYLE = 1; 
public static final int DEFAULT_STYLE = 3; 
public static final String TIME_STYLE_ONE = "HH:mm:ss"; 
public static final String TIME_STYLE_TWO = "HH时mm分ss秒"; 
public static final String TIME_STYLE_THREE = "dd天HH时mm分ss秒"; 
public static final String TIME_STYLE_FOUR = "dd天HH时mm分"; 
public static MikyouCountDownTimer getTimer(int style,Context mContext, long mGapTime, String mTimePattern, int mDrawableId){ 
MikyouCountDownTimer mCountDownTimer = null; 
switch (style){ 
case JD_STYLE: 
mCountDownTimer = new JDCountDownTimer(mContext,mGapTime,mTimePattern,mDrawableId); 
break; 
case VIP_STYLE: 
mCountDownTimer = new VIPCountDownTimer(mContext,mGapTime,mTimePattern,mDrawableId); 
break; 
case DEFAULT_STYLE: 
mCountDownTimer = new MikyouCountDownTimer(mContext,mGapTime,mTimePattern,mDrawableId); 
break; 
} 
return mCountDownTimer; 
} 
//Obtains the numeric blocks in the countdown string 
public static String[] getNumInTimerStr(String mTimerStr){ 
return mTimerStr.split("[^\\d]"); 
} 
//Obtains the non-numeric characters in the countdown string and filters out the numbers to recombine into a string, and splits the string into a character array, which is to save the intervals in the countdown. 
public static char[] getNonNumInTimerStr(String mTimerStr){ 
return mTimerStr.replaceAll("\\d", "").toCharArray();} 
} 
//Set the font color 
public static ForegroundColorSpan getTextColorSpan(String color){ 
ForegroundColorSpan mSpan = null; 
if (mSpan == null){ 
mSpan = new ForegroundColorSpan(Color.parseColor(color)); 
} 
return mSpan; 
} 
//Set content Span 
public static void setContentSpan(SpannableString mSpan, Object span, int start, 
int end) { 
mSpan.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 
} 
}

Now let's test the countdown we implemented using a TextView.

Using this countdown timer is very simple and convenient, only one line of code can implement a countdown style similar to JD.com and various e-commerce APPs.

package com.mikyou.countdowntimer; 
import android.graphics.Color; 
import android.os.Bundle; 
import android.support.v7.app.AppCompatActivity; 
import android.view.Gravity; 
import android.widget.LinearLayout; 
import android.widget.TextView; 
import com.mikyou.countdowntimer.utils.TimerUtils; 
public class MainActivity extends AppCompatActivity { 
private LinearLayout parent; 
private int padding =10; 
private int textSize = 40; 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
setContentView(R.layout.activity_main); 
parent = (LinearLayout) findViewById(R.id.parent); 
//Default style countdown, each style corresponds to four time formats 
/** 
* Default+Time format1:DEFAULT_STYLE <--TIME_STYLE_ONE = "HH:mm:ss" 
* */ 
TextView tv = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE,this,120000000,TimerUtils.TIME_STYLE_ONE,0) 
.getmDateTv(); 
parent.addView(tv); 
setmLayoutParams(tv); 
/** 
* Default+Time format2:DEFAULT_STYLE <--TIME_STYLE_TWO = "HH:mm:ss" 
* */ 
TextView tv1 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE,this,120000000,TimerUtils.TIME_STYLE_TWO,0) 
.getmDateTv(); 
parent.addView(tv1); 
setmLayoutParams(tv1); 
/** 
* Default+Time format3:DEFAULT_STYLE <--TIME_STYLE_THREE = "dd days HH:mm:ss" 
* */ 
TextView tv2 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE,this,120000000,TimerUtils.TIME_STYLE_THREE,0) 
.getmDateTv(); 
parent.addView(tv2); 
setmLayoutParams(tv2); 
/** 
* Default+Time format4:DEFAULT_STYLE <--TIME_STYLE_FOUR = "dd days HH:mm" 
* */ 
TextView tv3 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE,this,120000000,TimerUtils.TIME_STYLE_FOUR,0) 
.getmDateTv(); 
parent.addView(tv3); 
setmLayoutParams(tv3); 
//Style one countdown, which is a style where each block of numbers and each interval are separated, and each style corresponds to four time formats 
/** 
* Style one+Time format1:JD_STYLE <--TIME_STYLE_ONE = "HH:mm:ss" 
* */ 
TextView tv4= TimerUtils.getTimer(TimerUtils.JD_STYLE,this,120000000,TimerUtils.TIME_STYLE_ONE,R.drawable.timer_shape) 
.setTimerPadding(10,10,10,10)//Set the internal spacing 
.setTimerTextColor(Color.BLACK)//Set the font color 
.setTimerTextSize(40)//Set the font size 
.setTimerGapColor(Color.BLACK)//Set the interval color 
.getmDateTv();//Obtain the TextView object 
parent.addView(tv4); 
setmLayoutParams(tv4); 
/** 
* Style one+Time format2:JD_STYLE <--TIME_STYLE_TWO = "HH:mm:ss" 
* */ 
TextView tv5= TimerUtils.getTimer(TimerUtils.JD_STYLE,this,120000000,TimerUtils.TIME_STYLE_TWO,R.drawable.timer_shape2) 
.setTimerPadding(10,10,10,10) 
.setTimerTextColor(Color.WHITE) 
.setTimerTextSize(40) 
.setTimerGapColor(Color.BLACK) 
.getmDateTv(); 
parent.addView(tv5); 
setmLayoutParams(tv5); 
/** 
* Style one+Time format3:JD_STYLE <-->TIME_STYLE_THREE = "dd days HH:mm:ss" 
* */ 
TextView tv6= TimerUtils.getTimer(TimerUtils.JD_STYLE,this,120000000,TimerUtils.TIME_STYLE_THREE,R.drawable.timer_shape2) 
.setTimerPadding(10,10,10,10) 
.setTimerTextColor(Color.YELLOW) 
.setTimerTextSize(40) 
.setTimerGapColor(Color.BLACK) 
.getmDateTv(); 
parent.addView(tv6); 
setmLayoutParams(tv6); 
/** 
* Style one+Time format4:JD_STYLE <-->TIME_STYLE_FOUR = "dd days HH:mm" 
* */ 
TextView tv7= TimerUtils.getTimer(TimerUtils.JD_STYLE,this,120000000,TimerUtils.TIME_STYLE_FOUR,R.drawable.timer_shape2) 
.setTimerPadding(15,15,15,15) 
.setTimerTextColor(Color.BLUE) 
.setTimerTextSize(40) 
.setTimerGapColor(Color.BLACK) 
.getmDateTv(); 
parent.addView(tv7); 
setmLayoutParams(tv7); 
/** 
* Style two+Time format1:VIP_STYLE <--TIME_STYLE_ONE = "HH:mm:ss" 
* */ 
TextView tv8= TimerUtils.getTimer(TimerUtils.VIP_STYLE,this,120000000,TimerUtils.TIME_STYLE_ONE,R.drawable.timer_shape) 
.setTimerPadding(15,15,15,15) 
.setTimerTextColor(Color.BLACK) 
.setTimerTextSize(40) 
.setTimerGapColor(Color.BLACK) 
.getmDateTv(); 
parent.addView(tv8); 
setmLayoutParams(tv8); 
/** 
* Style two+Time format2:VIP_STYLE <--TIME_STYLE_TWO = "HH:mm:ss" 
* */ 
TextView tv9= TimerUtils.getTimer(TimerUtils.VIP_STYLE,this,120000000,TimerUtils.TIME_STYLE_TWO,R.drawable.timer_shape2) 
.setTimerPadding(15,15,15,15) 
.setTimerTextColor(Color.WHITE) 
.setTimerTextSize(40) 
.setTimerGapColor(Color.BLACK) 
.getmDateTv(); 
parent.addView(tv9); 
setmLayoutParams(tv9); 
/** 
* Style two+Time format3:VIP_STYLE <-->TIME_STYLE_THREE = "dd days HH:mm:ss" 
* */ 
TextView tv10= TimerUtils.getTimer(TimerUtils.VIP_STYLE,this,120000000,TimerUtils.TIME_STYLE_THREE,R.drawable.timer_shape2) 
.setTimerPadding(15,15,15,15) 
.setTimerTextColor(Color.YELLOW) 
.setTimerTextSize(40) 
.setTimerGapColor(Color.BLACK) 
.getmDateTv(); 
parent.addView(tv10); 
setmLayoutParams(tv10); 
/** 
* Style two+Time format4:VIP_STYLE <-->TIME_STYLE_FOUR = "dd days HH:mm" 
* */ 
TextView tv11= TimerUtils.getTimer(TimerUtils.VIP_STYLE,this,120000000,TimerUtils.TIME_STYLE_FOUR,R.drawable.timer_shape2) 
.setTimerPadding(15,15,15,15) 
.setTimerTextColor(Color.BLUE) 
.setTimerTextSize(40) 
.setTimerGapColor(Color.BLACK) 
.getmDateTv(); 
parent.addView(tv11); 
setmLayoutParams(tv11); 
} 
private void setmLayoutParams(TextView tv) { 
tv.setGravity(Gravity.CENTER_HORIZONTAL); 
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tv.getLayoutParams(); 
params.setMargins(20,20,20,20); 
tv.setLayoutParams(params); 
} 
}

two drawable files:

with border style

<?xml version="1.0" encoding="utf-8"-8"?> 
<shape xmlns:android="http://schemas.android.com/apk/res/android"://schemas.android.com/apk/res/android" 
android:shape="rectangle" 
> 
<corners android:radius="5px"/> 
<stroke android:color="#88000000" android:width="1dp"/> 
</shape>

with background and border style

<?xml version="1.0" encoding="utf-8"-8"?> 
<shape xmlns:android="http://schemas.android.com/apk/res/android"://schemas.android.com/apk/res/android" 
android:shape="rectangle" 
> 
<corners android:radius="10px"/> 
<solid android:color="#000000"/> 
</shape>

Now let's take a look at the results we have achieved.

Take a look at the running results, it's not bad, actually, its style can be defined in many ways, mainly depending on your own creativity and ideas. If there are any shortcomings in this countdown encapsulation, please give more suggestions. But it is still very convenient and simple to use, one line of code can solve it. This countdown is used in many places, and everyone can directly introduce it to their own projects if they need it.

Demo Download

The above is what the editor introduces to everyone about implementing various countdown effects in Android using TextView, hoping it will be helpful to everyone. If you have any questions, please leave a message, and the editor will reply to everyone in time. At the same time, I also want to express my sincere gratitude to everyone for their support of the Yana Tutorial website!

Statement: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not assume any relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#oldtoolbag.com (Please replace # with @ when sending an email for reporting. Provide relevant evidence, and once verified, this site will immediately delete the infringing content.)

You May Also Like