浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时

时间:2023-03-08 17:38:03

  今天给大家带来的是只使用一个TextView实现一个高仿京东、淘宝、唯品会等各种电商APP的活动倒计时。近期公司一直加班也没来得及时间去整理,今天难得歇息想把这个分享给大家。只求共同学习,以及自己兴许的复习。

为什么会想到使用一个TextView来实现呢?由于近期公司在做一些优化的工作,当中就有一个倒计时样式,原来开发的这个控件的同事使用了多个TextView拼接在一起的。实现的代码冗余比較大。故此项目经理就说:小宏这个就交给你来优化了。而且还要保证有一定的扩展性,当时就懵逼了。不知道从何处開始优化。然后我就查看京东。饿了么,唯品会等各个APP的倒计时,并在开发人员中打开层级界面显示,发现他们都有一个共同的特点就是一个View,没有使用多个TextView来拼接。

相信大家都知道只使用一个TextView比使用多个TextView拼接去实现的优势吧,以下最好还是来看看几个界面就知道了。

浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时

浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时

看到这个。大家心里自然就想到了自己定义View来实现吧。对,自己定义View确实能够实现这种效果。

可是今天我们不採用自己定义View来做。而是使用一个TextView来实现。

因为项目经理要求此次优化的代码具有可扩展性。所以此次代码的设计加了一些面向对象的知识。

有一些自己的设计和架构的思路。

此次demo的设计思路:

          1、编写一个倒计时的基类作为实现最普通和最主要的倒计时的功能,没有不论什么样式。让这个基类去继承CountDownTimer类,而且在该基类中

保存一个TextView的对象。而且把每次倒计时的数据,显示在TextView中。然后发布一个getmDateTv()方法返回一个TextView对象就可以。然后仅仅要拿到这个TextView对象显示界面的布局中就可以。

很方便。

          2、然后不相同式的倒计时,仅仅须要编写不同的子类去继承最普通的倒计时基类就可以,然后重写当中的设置数据和设置样式的两个方法就可以,然后就能给最普通的倒计时加入不同的样式。

下次假设须要扩展新的倒计时样式,不须要改变其它类的代码,仅仅需编写一个普通倒计时的派生类重写两个方法就可以。使得可扩展性更灵活。

          3、然后通过一个TimerUtils管理类,去集中承担子类和父类压力,让子类和父类所需实现功能分担到TimerUtils类中,而且该TimerUtils管理类是与client唯一打交道的类,比方获得倒计时对象以及获得倒计时的TextView对象都通过这个管理类分配,避免client直接与倒计时的基类和子类打交道。

从而使得类的封装性和隐藏性得到体现。

以下能够看下这个Demo设计的简单的UML类图:

浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时

通过以上思路分析以下我们就看看此次Demo的实现须要用到哪些知识点.

 1、CountDownTimer类的使用方法。

    2、SpannableString的使用方法。

    3、MikyouCountDownTimer的封装。

    4、自己定义MikyouBackgroundSpan的实现。

一、通过以上的分析我们首先得复习一下有关CountDownTimer的知识,CountDownTimer是一个非常easy的类我们能够看下的它的源代码。它的使用方法自然就知道了。

CountDownTimer是一个抽象类。

//
// 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();
}

能够看到倒计时的总时长就是millisFuture,和countDownInterVal间隔步长默认是1000ms,所以数据都是通过其构造器进行初始化,然后须要去重写一个回调方法onTick,

里面的一个參数就是每隔对应的步长后剩余的时间毫秒数。然后我们仅仅须要在onTick方法中将每隔1000ms时间毫秒数进行时间格式化就可以得到对应时间格式的倒计时这就是实现了最基本倒计时样式。

格式化倒计时格式採用的是apache中的common的lang包中DurationFormatUtils类中的formatDuration。通过传入一个时间格式就会自己主动将倒计时转换成对应的mTimePattern的样式(HH:mm:ss或dd天HH时mm分ss秒).

二、复习一下有关SpannableString的使用方法。

在Android中EditText用于编辑文本,TextView用于显示文本,可是有时候我们须要对当中的文本进行样式等方面的设置。Android为我们提供了SpannableString类来对指定文本进行处理。

1) ForegroundColorSpan        文本颜色



private void setForegroundColorSpan() {    

    SpannableString spanString = new SpannableString("前景色");    

    ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);    

    spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);    

    tv.append(spanString);    

}    





2) BackgroundColorSpan 文本背景色 



private void setBackgroundColorSpan() {    

    SpannableString spanString = new SpannableString("背景色");    

    BackgroundColorSpan span = new BackgroundColorSpan(Color.YELLOW);    

    spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);    

    tv.append(spanString);    

}   





3) StyleSpan         字体样式:粗体、斜体等

private void setStyleSpan() {    

    SpannableString spanString = new SpannableString("粗体斜体");    

    StyleSpan span = new StyleSpan(Typeface.BOLD_ITALIC);    

    spanString.setSpan(span, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);    

    tv.append(spanString);    

}    





4) RelativeSizeSpan 相对大小

private void setRelativeFontSpan() {  

    SpannableString spanString = new SpannableString("字体相对大小");  

    spanString.setSpan(new RelativeSizeSpan(2.5f), 0, 6,Spannable.SPAN_INCLUSIVE_EXCLUSIVE);  

    tv.append(spanString);      

}  





5) TypefaceSpan         文本字体

private void setTypefaceSpan() {  

    SpannableString spanString = new SpannableString("文本字体");  

    spanString.setSpan(new TypefaceSpan("monospace"), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  

    tv.append(spanText);  

}  





6) URLSpan 文本超链接



private void addUrlSpan() {    

    SpannableString spanString = new SpannableString("超链接");    

    URLSpan span = new URLSpan("http://www.baidu.com");    

    spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);    

    tv.append(spanString);    

}    





7) ImageSpan         图片



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);    

}   





8) ClickableSpan                 文本有点击事件



private TextView textView;  

textView = (TextView)this.findViewById(R.id.textView);  

String text = "显示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);  

    }  

    // 表示点击整个text的长度都有效触发这个事件  

}, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  

textView.setText(spannableString);  

textView.setMovementMethod(LinkMovementMethod.getInstance());  





9) UnderlineSpan         下划线



private void addUnderLineSpan() {    

    SpannableString spanString = new SpannableString("下划线");    

    UnderlineSpan span = new UnderlineSpan();    

    spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);    

    tv.append(spanString);    

}   





10) StrikethroughSpan 

        删除线



private void addStrikeSpan() {    

    SpannableString spanString = new SpannableString("删除线");    

    StrikethroughSpan span = new StrikethroughSpan();    

    spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);    

    tv.append(spanString);    

}    





11) SuggestionSpan 

相当于占位符





12) MaskFilterSpan 

修饰效果,如模糊(BlurMaskFilter)、浮雕(EmbossMaskFilter)





13) RasterizerSpan 

        光栅效果





14) AbsoluteSizeSpan 

        绝对大小(文本字体)



private void setAbsoluteFontSpan() {  

        SpannableString spannableString = new SpannableString("40号字体");  

        AbsoluteSizeSpan absoluteSizeSpan = new AbsoluteSizeSpan(40);  

        spannableString.setSpan(absoluteSizeSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  

        editText.append(spannableString);  

}  





15) DynamicDrawableSpan    设置图片,基于文本基线或底部对齐。

16) TextAppearanceSpan 

文本外貌(包含字体、大小、样式和颜色)



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);  

}

好了。通过以上的复习知识点,如今我们就能够来真正開始demo的实现,然后我们一起来一步一步封装我们的倒计时。

一、编写一个MikyouCountDownTimer基类,让它去继承CountDownTimer类,而且发布出initSpanData和setBackgroundSpan方法用于其它样式倒计时的子类使用。它能够实现最基本倒计时的功能。

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;//传入的上下文对象
protected TextView mDateTv;//一个TextView实现倒计时
private long mGapTime;//传入设置的时间间隔即倒计时的总时长
private long mCount = 1000;//倒计时的步长 一般为1000代表每隔1s跳一次
private String mTimePattern = "HH:mm:ss";//timePattern 传入的时间的样式 如: HH:mm:ss HH时mm分ss秒 dd天HH时mm分ss秒
private String mTimeStr;
protected List<MikyouBackgroundSpan> mBackSpanList;
protected List<ForegroundColorSpan> mTextColorSpanList;
private int mDrawableId;
private boolean flag = false;//设置标记flag,用于控制使得初始化Span的数据一次 protected String[] numbers;//此数组用于保存每一个倒计时字符拆分后的天,时,分,秒的数值
protected char[] nonNumbers;//保存了天,时,分,秒之间的间隔("天","时","分","秒"或者":")
//用于倒计时样式的内间距,字体大小,字体颜色,倒计时间隔的颜色
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;//倒计时总时长
this.mCount = mCount;//每次倒计时的步长,默认是1000
this.mDrawableId= mDrawableId;//用于设置背景的drawable的id
this.mTimePattern = mTimePattern;//时间的格式:如HH:mm:ss或者dd天HH时mm分ss秒等
mBackSpanList = new ArrayList<>();
mTextColorSpanList = new ArrayList<>();
mDateTv = new TextView(mContext,null);
}
//发布这些设置倒计时样式的方法。供外部调用,从而灵活定制倒计时的样式
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;
}
//设置倒计时的Span的样式,发布出给各个子类实现
public void setBackgroundSpan(String timeStr) {
if (!flag){
initSpanData(timeStr);
flag = true;
}
mDateTv.setText(timeStr);
}
//设置倒计时的Span的数据,发布出给各个子类实现
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);
//这是apache中的common的lang包中DurationFormatUtils类中的formatDuration。通过传入
//一个时间格式就会自己主动将倒计时转换成对应的mTimePattern的样式(HH:mm:ss或dd天HH时mm分ss秒)
setBackgroundSpan(mTimeStr);
}
} @Override
public void onFinish() {
mDateTv.setText("倒计时结束");
}
//用于返回显示倒计时的TextView的对象
public TextView getmDateTv() {
startTimer();
return mDateTv;
}
public void cancelTimer(){
this.cancel();
}
public void startTimer(){
this.start();
} public String getmTimeStr() {
return mTimeStr;
}
}

TimerUtils类用于保存不同倒计时的格式,比如HH:mm:ss、HH时mm分ss秒、dd天HH时mm分ss秒等。如今我们能够来看下简单的基本样式。

浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

二、自己定义MikyouBackgroundSpan去继承ImageSpan。这个类很重要是用于给倒计时的TextView加样式。为什么能够使用一个TextView来实现呢

别忘了还有个非常强悍的类就是SpannableString类,这个类就是能够设置一段字符串中的每一个字符的样式,非常多样式。最后通过TextView中有个setSpan方法就可以传入

一个SpannableString对象完毕设置。可是为什么须要自己定义一个Span呢?这是由于非常奇怪为什么android中的那么多Span样式中没有一个能够直接设置一个drawable对象文件呢。所以上网找了非常多都没有找到,最后在*上找到了一个外国人给了一个解决的方法,就是重写ImageSpan最后就能够实现了设置drawable文件就可以

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) {
//绘制文本的内容的背景
paint.setTextSize(mTextSize);
//測量文本的宽度和高度。通过mTextBound得到
paint.getTextBounds(text.toString(), start, end, mTextBound);
//设置文本背景的宽度和高度,传入的是left,top,right,bottom四个參数
maxWidth = maxWidth < mTextBound.width() ? mTextBound.width() : maxWidth;
maxHeight = maxHeight < mTextBound.height() ? mTextBound.height() : maxHeight;
//设置最大宽度和最大高度是为了防止在倒计时在数字切换的过程中会重绘,会导致倒计时边框的宽度和高度会抖动,
// 所以每次取得最大的高度和宽度而不是每次都去取測量的高度和宽度
getDrawable().setBounds(0,0, maxWidth+mPaddingLeft+mPaddingRight,mPaddingTop+mPaddingBottom+maxHeight);
//绘制文本背景
super.draw(canvas, text, start, end, x, top, y, bottom, paint);
//设置文本的颜色
paint.setColor(mTextColor);
//设置字体的大小
paint.setTextSize(mTextSize);
int mGapX = (getDrawable().getBounds().width() - maxWidth)/2;
int mGapY= (getDrawable().getBounds().height() - maxHeight)/2;
//绘制文本内容
canvas.drawText(text.subSequence(start, end).toString(), x + mGapX , y - mGapY + maxHeight/3, paint); }
}

三、样式一的倒计时实现。样式一指的是比如:12时36分27秒或者12:36:27就是将数值和时、分、秒或者":"分隔开。然后去自己定义每块数值(12  36 27)和间隔(时 分 秒 或 :)的样式,包含给数值块加背景和边框。在MikyouCountDownTimer中的number数组中保存着[12 36 27]而nonumer数组中保存着[时 分 秒 ]或[ : :]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; /**
* 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;
}
/**
* 重写父类的initSpanData方法
* 通过number数组得到每块数值相应的自己定义MikyouBackgroundSpan对象
* 然后通过MikyouBackgroundSpan对象定义每块数值的样式包含背景,边框,边框圆角样式,然后将这些对象增加到集合中去
* 通过nonNumber数组得到每一个间隔的ForegroundColorSpan对象
* 然后通过这些对象就能够定义每一个间隔块的样式,由于仅仅定义了ForegroundColorSpan所以仅仅能定义
* 每一个间隔块的字体颜色,setmGapSpanColor方式也是供外部*定制每一个间隔的样式
* 实际上还能够定义其它的Span,同理实现也是非常easy的。
* */
@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);
}
} /** 重写父类的setBackgroundSpan方法
* 我们知道设置Span的样式主要是控制两个变量start,end索引
* 以确定设置start到end位置的字符串的子串的样式
* mGapLen = 1,表示一个间隔块的长度,
* 比如:12时36分27秒的"时","分","秒"的间隔长度
* 所以通过遍历Span集合,给字符串设置Span,
* 通过分析不难得出每一个数值块的Span的start索引: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()){//这里为了就是防止12:36:27这样的样式,这样的样式间隔仅仅有2个所以须要做推断。防止数组越界
TimerUtils.setContentSpan(mSpan,mTextColorSpanList.get(i),end,end + mGapLen);
}
}
mDateTv.setMovementMethod(LinkMovementMethod.getInstance());//此方法非常重要须要调用。否则绘制出来的倒计时就是重叠的样式
mDateTv.setText(mSpan);
} }

四、样式二的倒计时实现,样式二不同于样式一的是比如:12时36分27秒或者12:36:27就是将每一个数值和时、分、秒或者":"分隔开,然后去自己定义每块数值(1 2 3 6 2 7)和间隔(时 分 秒 或 :)的样式。包含给数值块加背景和边框。在MikyouCountDownTimer中的vipNumber数组中保存着[1 2 3 6 2 7]而vipnonNumer数组中保存着[时 分 秒 ]或[
: :]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<>();
}
/** 重写父类的setBackgroundSpan方法
* 我们知道设置Span的样式主要是控制两个变量start,end索引
* 以确定设置start到end位置的字符串的子串的样式,表示每一个数字子串在整个字符串中的位置范围
* mGapLen = 1,表示一个间隔块的长度,
* 比如:12时36分27秒的"时","分","秒"的间隔长度
* 所以通过遍历Span集合,给字符串设置Span,
* 通过分析不难得出每一个数值块的Span的start索引: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);
}
//此时表示遍历完了某一块的数值,从而须要将此时该块数值去更新start变量
start = start + vipNumbers[i].toCharArray().length;
if (i < nonNumbers.length){
TimerUtils.setContentSpan(mSpan,mTextColorSpanList.get(i),start,start+mGapLen);
start = start +mGapLen;//假设是个间隔还得去加上每一个间隔长度最后去更新start变量
} }
mDateTv.setMovementMethod(LinkMovementMethod.getInstance());
mDateTv.setText(mSpan);
}
/**
* 重写父类的initSpanData方法
* 通过number数组得到每块数值相应的自己定义MikyouBackgroundSpan对象
* 然后通过MikyouBackgroundSpan对象定义每块数值的样式包含背景,边框,边框圆角样式,然后将这些对象增加到集合中去
* 通过nonNumber数组得到每一个间隔的ForegroundColorSpan对象
* 然后通过这些对象就能够定义每一个间隔块的样式,由于仅仅定义了ForegroundColorSpan所以仅仅能定义
* 每一个间隔块的字体颜色,setmGapSpanColor方式也是供外部*定制每一个间隔的样式
* 实际上还能够定义其它的Span,同理实现也是非常easy的。
* */
@Override
public void initSpanData(String timeStr) {
super.initSpanData(timeStr);
vipNumbers = TimerUtils.getNumInTimerStr(timeStr);//得到每一个数字注意不是每块数值。并增加数组
vipNonNumbers = TimerUtils.getNonNumInTimerStr(timeStr);//得到每一个间隔字符,并增加到数组
for (int i=0;i<vipNumbers.length;i++){
for (int j=0;j<vipNumbers[i].toCharArray().length;j++){//由于须要得到每一个数字所以还得遍历每块数值中的每一个数字,所以须要二层循环
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);
}
}
}

四、TimerUtils管理类。主要是提供不相同式的倒计时的对象给client,所以这个类直接与client建立关系,从而实现倒计时子类和基类对外界的隐藏体现了封装性。

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;
}
//得到倒计时字符串中的数值块部分
public static String[] getNumInTimerStr(String mTimerStr){
return mTimerStr.split("[^\\d]");
}
//得到倒计时中字符串中的非数值的字符串,并把数值过滤掉又一次组合成一个字符串。并把字符串拆分字符数组,也就是保存倒计时中间的间隔
public static char[] getNonNumInTimerStr(String mTimerStr){
return mTimerStr.replaceAll("\\d","").toCharArray();
}
//设置字体颜色
public static ForegroundColorSpan getTextColorSpan(String color){
ForegroundColorSpan mSpan = null;
if (mSpan == null){
mSpan = new ForegroundColorSpan(Color.parseColor(color));
}
return mSpan;
}
//设置内容的Span
public static void setContentSpan(SpannableString mSpan, Object span, int start,
int end) {
mSpan.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }

如今我们就来測试下我们使用一个TextView实现的倒计时。

使用该倒计时很easy很方便仅仅须要一行代码就能实现一个高仿京东和各种电商的APP的倒计时样式。

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);
//默认样式倒计时每种样式下又相应四种时间的格式
/**
* 默认+时间格式1: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);
/**
* 默认+时间格式2: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);
/**
* 默认+时间格式3:DEFAULT_STYLE <--> TIME_STYLE_THREE = "dd天HH时mm分ss秒"
* */
TextView tv2 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE,this,120000000,TimerUtils.TIME_STYLE_THREE,0)
.getmDateTv();
parent.addView(tv2);
setmLayoutParams(tv2);
/**
* 默认+时间格式4:DEFAULT_STYLE <--> TIME_STYLE_FOUR = "dd天HH时mm分"
* */
TextView tv3 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE,this,120000000,TimerUtils.TIME_STYLE_FOUR,0)
.getmDateTv();
parent.addView(tv3);
setmLayoutParams(tv3);
//样式一倒计时,就是每块数值和每一个间隔分开的样式,每种样式下又相应四种时间的格式
/**
* 样式一+时间格式1: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)//设置内间距
.setTimerTextColor(Color.BLACK)//设置字体颜色
.setTimerTextSize(40)//设置字体大小
.setTimerGapColor(Color.BLACK)//设置间隔的颜色
.getmDateTv();//拿到TextView对象
parent.addView(tv4);
setmLayoutParams(tv4);
/**
* 样式一+时间格式2: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);
/**
* 样式一+时间格式3:JD_STYLE <-->TIME_STYLE_THREE = "dd天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);
/**
* 样式一+时间格式4:JD_STYLE <-->TIME_STYLE_FOUR = "dd天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); /**
* 样式二+时间格式1: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); /**
* 样式二+时间格式2: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);
/**
* 样式二+时间格式3:VIP_STYLE <-->TIME_STYLE_THREE = "dd天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);
/**
* 样式二+时间格式4:VIP_STYLE <-->TIME_STYLE_FOUR = "dd天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);
}
}

两个drawable文件:

带边框样式

<?xml version="1.0" encoding="utf-8"?

>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<corners android:radius="5px"/>
<stroke android:color="#88000000" android:width="1dp"/>
</shape>

带背景和边框样式

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

如今就看看我们执行的成果吧。

浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时

看看执行结果还不错吧,事实上它的样式还能够定义非常多种主要看自己的创意和想法了,这个倒计时封装假设还有什么不足之处,请多多提出建议。

可是如今使用还是蛮方便和简单的,一行代码就能就能解决。

这个倒计时用到的地方还是蛮多的,大家有须要的话能够直接引入到自己的项目中。

PS:有人反应说Demo放入project跑起来有问题,可是我自己是没问题的,然后我索性将这个打成了一个aar包,仅仅须要将这个aar包放入project中。就可以在Activity中如博客上所写一样通过一行代码就可以实现倒计时。

注意:aar包仅仅适用于AndroidStudio

引入说明:

新建一个Module,将aar包放入libs文件夹,然后在build.gradle中加入:

repositories {
flatDir {
dirs 'libs'
}
}
repositories放入android{}内部
compile(name:'mikyoutimerlib',ext:'aar')
compile放入dependencies {}内部。name-->aar包名

aar包下载

DEMO下载