Android中使用TextView實(shí)現(xiàn)高仿京東淘寶各種倒計(jì)時(shí)效果
今天給大家?guī)?lái)的是僅僅使用一個(gè)TextView實(shí)現(xiàn)一個(gè)高仿京東、淘寶、唯品會(huì)等各種電商APP的活動(dòng)倒計(jì)時(shí)。最近公司一直加班也沒(méi)來(lái)得及時(shí)間去整理,今天難得休息想把這個(gè)分享給大家,只求共同學(xué)習(xí),以及自己后續(xù)的復(fù)習(xí)。為什么會(huì)想到使用一個(gè)TextView來(lái)實(shí)現(xiàn)呢?因?yàn)樽罱驹谧鲆恍﹥?yōu)化的工作,其中就有一個(gè)倒計(jì)時(shí)樣式,原來(lái)開發(fā)的這個(gè)控件的同事使用了多個(gè)TextView拼接在一起的,實(shí)現(xiàn)的代碼冗余比較大,故此項(xiàng)目經(jīng)理就說(shuō):小宏這個(gè)就交給你來(lái)優(yōu)化了,并且還要保證有一定的擴(kuò)展性,當(dāng)時(shí)就懵逼了。不知道從何處開始優(yōu)化。然后我就查看京東,餓了么,唯品會(huì)等各個(gè)APP的倒計(jì)時(shí),并在開發(fā)者中打開層級(jí)界面顯示,發(fā)現(xiàn)他們都有一個(gè)共同的特點(diǎn)就是一個(gè)View,沒(méi)有使用多個(gè)TextView來(lái)拼接。相信大家都知道僅僅使用一個(gè)TextView比使用多個(gè)TextView拼接去實(shí)現(xiàn)的優(yōu)勢(shì)吧,下面不妨來(lái)看看幾個(gè)界面就知道了。




看到這個(gè),大家心里自然就想到了自定義View來(lái)實(shí)現(xiàn)吧。對(duì),自定義View確實(shí)可以實(shí)現(xiàn)這樣的效果。但是今天我們不采用自定義View來(lái)做。而是使用一個(gè)TextView來(lái)實(shí)現(xiàn)。
由于項(xiàng)目經(jīng)理要求此次優(yōu)化的代碼具有可擴(kuò)展性。所以此次代碼的設(shè)計(jì)加了一些面向?qū)ο蟮闹R(shí)。有一些自己的設(shè)計(jì)和架構(gòu)的思路。
此次demo的設(shè)計(jì)思路:
?。?、編寫一個(gè)倒計(jì)時(shí)的基類作為實(shí)現(xiàn)最普通和最基本的倒計(jì)時(shí)的功能,沒(méi)有任何樣式,讓這個(gè)基類去繼承CountDownTimer類,并且在該基類中
保存一個(gè)TextView的對(duì)象,并且把每次倒計(jì)時(shí)的數(shù)據(jù),顯示在TextView中,然后公布一個(gè)getmDateTv()方法返回一個(gè)TextView對(duì)象即可。然后只要拿到這個(gè)TextView對(duì)象顯示界面的布局中即可。非常方便。
?。病⑷缓蟛煌瑯邮降牡褂?jì)時(shí),只需要編寫不同的子類去繼承最普通的倒計(jì)時(shí)基類即可,然后重寫其中的設(shè)置數(shù)據(jù)和設(shè)置樣式的兩個(gè)方法即可,然后就能給最普通的倒計(jì)時(shí)添加不同的樣式。下次如果需要擴(kuò)展新的倒計(jì)時(shí)樣式,不需要改變其他類的代碼,只需編寫一個(gè)普通倒計(jì)時(shí)的派生類重寫兩個(gè)方法即可,使得可擴(kuò)展性更靈活。
?。场⑷缓笸ㄟ^(guò)一個(gè)TimerUtils管理類,去集中承擔(dān)子類和父類壓力,讓子類和父類所需實(shí)現(xiàn)功能分擔(dān)到TimerUtils類中,并且該TimerUtils管理類是與客戶端唯一打交道的類,比如獲得倒計(jì)時(shí)對(duì)象以及獲得倒計(jì)時(shí)的TextView對(duì)象都通過(guò)這個(gè)管理類分配,避免客戶端直接與倒計(jì)時(shí)的基類和子類打交道。從而使得類的封裝性和隱藏性得到體現(xiàn)。
下面可以看下這個(gè)Demo設(shè)計(jì)的簡(jiǎn)單的UML類圖:

通過(guò)以上思路分析下面我們就看看此次Demo的實(shí)現(xiàn)需要用到哪些知識(shí)點(diǎn).
1、CountDownTimer類的用法。
?。?、SpannableString的用法。
3、MikyouCountDownTimer的封裝。
?。?、自定義MikyouBackgroundSpan的實(shí)現(xiàn)。
一、通過(guò)以上的分析我們首先得復(fù)習(xí)一下有關(guān)CountDownTimer的知識(shí),CountDownTimer是一個(gè)很簡(jiǎn)單的類我們可以看下的它的源碼,它的用法自然就知道了。
CountDownTimer是一個(gè)抽象類。
//
// 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();
}
可以看到倒計(jì)時(shí)的總時(shí)長(zhǎng)就是millisFuture,和countDownInterVal間隔步長(zhǎng)默認(rèn)是1000ms,所以數(shù)據(jù)都是通過(guò)其構(gòu)造器進(jìn)行初始化,然后需要去重寫一個(gè)回調(diào)方法onTick,里面的一個(gè)參數(shù)就是每隔相應(yīng)的步長(zhǎng)后剩余的時(shí)間毫秒數(shù)。然后我們只需要在onTick方法中將每隔1000ms時(shí)間毫秒數(shù)進(jìn)行時(shí)間格式化即可得到相應(yīng)時(shí)間格式的倒計(jì)時(shí)這就是實(shí)現(xiàn)了最基本倒計(jì)時(shí)樣式。格式化倒計(jì)時(shí)格式采用的是apache中的common的lang包中DurationFormatUtils類中的formatDuration,通過(guò)傳入一個(gè)時(shí)間格式就會(huì)自動(dòng)將倒計(jì)時(shí)轉(zhuǎn)換成相應(yīng)的mTimePattern的樣式(HH:mm:ss或dd天HH時(shí)mm分ss秒).
二、復(fù)習(xí)一下有關(guān)SpannableString的用法。
在Android中EditText用于編輯文本,TextView用于顯示文本,但是有時(shí)候我們需要對(duì)其中的文本進(jìn)行樣式等方面的設(shè)置。Android為我們提供了SpannableString類來(lái)對(duì)指定文本進(jìn)行處理。
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 相對(duì)大小
private void setRelativeFontSpan() {
SpannableString spanString = new SpannableString("字體相對(duì)大小");
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 文本有點(diǎn)擊事件
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);
}
// 表示點(diǎn)擊整個(gè)text的長(zhǎng)度都有效觸發(fā)這個(gè)事件
}, 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
相當(dāng)于占位符
12) MaskFilterSpan
修飾效果,如模糊(BlurMaskFilter)、浮雕(EmbossMaskFilter)
13) RasterizerSpan
光柵效果
14) AbsoluteSizeSpan
絕對(duì)大小(文本字體)
private void setAbsoluteFontSpan() {
SpannableString spannableString = new SpannableString("40號(hào)字體");
AbsoluteSizeSpan absoluteSizeSpan = new AbsoluteSizeSpan(40);
spannableString.setSpan(absoluteSizeSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
editText.append(spannableString);
}
15) DynamicDrawableSpan 設(shè)置圖片,基于文本基線或底部對(duì)齊。
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);
}
好了,通過(guò)以上的復(fù)習(xí)知識(shí)點(diǎn),現(xiàn)在我們就可以來(lái)真正開始demo的實(shí)現(xiàn),然后我們一起來(lái)一步一步封裝我們的倒計(jì)時(shí)。
一、編寫一個(gè)MikyouCountDownTimer基類,讓它去繼承CountDownTimer類,并且公布出initSpanData和setBackgroundSpan方法用于其他樣式倒計(jì)時(shí)的子類使用,它可以實(shí)現(xiàn)最基本倒計(jì)時(shí)的功能。
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;//傳入的上下文對(duì)象
protected TextView mDateTv;//一個(gè)TextView實(shí)現(xiàn)倒計(jì)時(shí)
private long mGapTime;//傳入設(shè)置的時(shí)間間隔即倒計(jì)時(shí)的總時(shí)長(zhǎng)
private long mCount = 1000;//倒計(jì)時(shí)的步長(zhǎng) 一般為1000代表每隔1s跳一次
private String mTimePattern = "HH:mm:ss";//timePattern 傳入的時(shí)間的樣式 如: HH:mm:ss HH時(shí)mm分ss秒 dd天HH時(shí)mm分ss秒
private String mTimeStr;
protected List<MikyouBackgroundSpan> mBackSpanList;
protected List<ForegroundColorSpan> mTextColorSpanList;
private int mDrawableId;
private boolean flag = false;//設(shè)置標(biāo)記flag,用于控制使得初始化Span的數(shù)據(jù)一次
protected String[] numbers;//此數(shù)組用于保存每個(gè)倒計(jì)時(shí)字符拆分后的天,時(shí),分,秒的數(shù)值
protected char[] nonNumbers;//保存了天,時(shí),分,秒之間的間隔("天","時(shí)","分","秒"或者":")
//用于倒計(jì)時(shí)樣式的內(nèi)間距,字體大小,字體顏色,倒計(jì)時(shí)間隔的顏色
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;//倒計(jì)時(shí)總時(shí)長(zhǎng)
this.mCount = mCount;//每次倒計(jì)時(shí)的步長(zhǎng),默認(rèn)是1000
this.mDrawableId= mDrawableId;//用于設(shè)置背景的drawable的id
this.mTimePattern = mTimePattern;//時(shí)間的格式:如HH:mm:ss或者dd天HH時(shí)mm分ss秒等
mBackSpanList = new ArrayList<>();
mTextColorSpanList = new ArrayList<>();
mDateTv = new TextView(mContext,null);
}
//公布這些設(shè)置倒計(jì)時(shí)樣式的方法,供外部調(diào)用,從而靈活定制倒計(jì)時(shí)的樣式
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;
}
//設(shè)置倒計(jì)時(shí)的Span的樣式,公布出給各個(gè)子類實(shí)現(xiàn)
public void setBackgroundSpan(String timeStr) {
if (!flag){
initSpanData(timeStr);
flag = true;
}
mDateTv.setText(timeStr);
}
//設(shè)置倒計(jì)時(shí)的Span的數(shù)據(jù),公布出給各個(gè)子類實(shí)現(xiàn)
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,通過(guò)傳入
//一個(gè)時(shí)間格式就會(huì)自動(dòng)將倒計(jì)時(shí)轉(zhuǎn)換成相應(yīng)的mTimePattern的樣式(HH:mm:ss或dd天HH時(shí)mm分ss秒)
setBackgroundSpan(mTimeStr);
}
}
@Override
public void onFinish() {
mDateTv.setText("倒計(jì)時(shí)結(jié)束");
}
//用于返回顯示倒計(jì)時(shí)的TextView的對(duì)象
public TextView getmDateTv() {
startTimer();
return mDateTv;
}
public void cancelTimer(){
this.cancel();
}
public void startTimer(){
this.start();
}
public String getmTimeStr() {
return mTimeStr;
}
}
TimerUtils類用于保存不同倒計(jì)時(shí)的格式,例如HH:mm:ss、HH時(shí)mm分ss秒、dd天HH時(shí)mm分ss秒等?,F(xiàn)在我們可以來(lái)看下簡(jiǎn)單的基本樣式。

二、自定義MikyouBackgroundSpan去繼承ImageSpan,這個(gè)類非常重要是用于給倒計(jì)時(shí)的TextView加樣式,為什么可以使用一個(gè)TextView來(lái)實(shí)現(xiàn)呢
別忘了還有個(gè)很強(qiáng)悍的類就是SpannableString類,這個(gè)類就是可以設(shè)置一段字符串中的每個(gè)字符的樣式,很多樣式。最后通過(guò)TextView中有個(gè)setSpan方法即可傳入
一個(gè)SpannableString對(duì)象完成設(shè)置。但是為什么需要自定義一個(gè)Span呢?這是因?yàn)楹芷婀譃槭裁碼ndroid中的那么多Span樣式中沒(méi)有一個(gè)可以直接設(shè)置一個(gè)drawable對(duì)象文件呢,所以上網(wǎng)找了很多都沒(méi)有找到,最后在stackOverFlow上找到了一個(gè)外國(guó)人給了一個(gè)解決辦法,就是重寫ImageSpan最后就可以實(shí)現(xiàn)了設(shè)置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) {
//繪制文本的內(nèi)容的背景
paint.setTextSize(mTextSize);
//測(cè)量文本的寬度和高度,通過(guò)mTextBound得到
paint.getTextBounds(text.toString(), start, end, mTextBound);
//設(shè)置文本背景的寬度和高度,傳入的是left,top,right,bottom四個(gè)參數(shù)
maxWidth = maxWidth < mTextBound.width() ? mTextBound.width() : maxWidth;
maxHeight = maxHeight < mTextBound.height() ? mTextBound.height() : maxHeight;
//設(shè)置最大寬度和最大高度是為了防止在倒計(jì)時(shí)在數(shù)字切換的過(guò)程中會(huì)重繪,會(huì)導(dǎo)致倒計(jì)時(shí)邊框的寬度和高度會(huì)抖動(dòng),
// 所以每次取得最大的高度和寬度而不是每次都去取測(cè)量的高度和寬度
getDrawable().setBounds(0,0, maxWidth+mPaddingLeft+mPaddingRight,mPaddingTop+mPaddingBottom+maxHeight);
//繪制文本背景
super.draw(canvas, text, start, end, x, top, y, bottom, paint);
//設(shè)置文本的顏色
paint.setColor(mTextColor);
//設(shè)置字體的大小
paint.setTextSize(mTextSize);
int mGapX = (getDrawable().getBounds().width() - maxWidth)/2;
int mGapY= (getDrawable().getBounds().height() - maxHeight)/2;
//繪制文本內(nèi)容
canvas.drawText(text.subSequence(start, end).toString(), x + mGapX , y - mGapY + maxHeight/3, paint); }
}
三、樣式一的倒計(jì)時(shí)實(shí)現(xiàn),樣式一指的是例如:12時(shí)36分27秒或者12:36:27就是將數(shù)值和時(shí)、分、秒或者":"分隔開,然后去自定義每塊數(shù)值(12 36 27)和間隔(時(shí) 分 秒 或 :)的樣式,包括給數(shù)值塊加背景和邊框。在MikyouCountDownTimer中的number數(shù)組中保存著[12 36 27]而nonumer數(shù)組中保存著[時(shí) 分 秒 ]或[ :?。篯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方法
* 通過(guò)number數(shù)組得到每塊數(shù)值對(duì)應(yīng)的自定義MikyouBackgroundSpan對(duì)象
* 然后通過(guò)MikyouBackgroundSpan對(duì)象定義每塊數(shù)值的樣式包括背景,邊框,邊框圓角樣式,然后將這些對(duì)象加入到集合中去
* 通過(guò)nonNumber數(shù)組得到每個(gè)間隔的ForegroundColorSpan對(duì)象
* 然后通過(guò)這些對(duì)象就可以定義每個(gè)間隔塊的樣式,因?yàn)橹欢x了ForegroundColorSpan所以只能定義
* 每個(gè)間隔塊的字體顏色,setmGapSpanColor方式也是供外部自由定制每個(gè)間隔的樣式
* 實(shí)際上還可以定義其他的Span,同理實(shí)現(xiàn)也是很簡(jiǎn)單的。
* */
@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方法
* 我們知道設(shè)置Span的樣式主要是控制兩個(gè)變量start,end索引
* 以確定設(shè)置start到end位置的字符串的子串的樣式
* mGapLen = 1,表示一個(gè)間隔塊的長(zhǎng)度,
* 例如:12時(shí)36分27秒的"時(shí)","分","秒"的間隔長(zhǎng)度
* 所以通過(guò)遍歷Span集合,給字符串設(shè)置Span,
* 通過(guò)分析不難得出每個(gè)數(shù)值塊的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個(gè)所以需要做判斷,防止數(shù)組越界
TimerUtils.setContentSpan(mSpan,mTextColorSpanList.get(i),end,end + mGapLen);
}
}
mDateTv.setMovementMethod(LinkMovementMethod.getInstance());//此方法很重要需要調(diào)用,否則繪制出來(lái)的倒計(jì)時(shí)就是重疊的樣式
mDateTv.setText(mSpan);
}
}
四、樣式二的倒計(jì)時(shí)實(shí)現(xiàn),樣式二不同于樣式一的是例如:12時(shí)36分27秒或者12:36:27就是將每個(gè)數(shù)值和時(shí)、分、秒或者":"分隔開,然后去自定義每塊數(shù)值(1 2 3 6 2 7)和間隔(時(shí) 分 秒 或 :)的樣式,包括給數(shù)值塊加背景和邊框。在MikyouCountDownTimer中的vipNumber數(shù)組中保存著[1 2 3 6 2 7]而vipnonNumer數(shù)組中保存著[時(shí) 分 秒 ]或[ : :]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方法
* 我們知道設(shè)置Span的樣式主要是控制兩個(gè)變量start,end索引
* 以確定設(shè)置start到end位置的字符串的子串的樣式,表示每個(gè)數(shù)字子串在整個(gè)字符串中的位置范圍
* mGapLen = 1,表示一個(gè)間隔塊的長(zhǎng)度,
* 例如:12時(shí)36分27秒的"時(shí)","分","秒"的間隔長(zhǎng)度
* 所以通過(guò)遍歷Span集合,給字符串設(shè)置Span,
* 通過(guò)分析不難得出每個(gè)數(shù)值塊的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);
}
//此時(shí)表示遍歷完了某一塊的數(shù)值,從而需要將此時(shí)該塊數(shù)值去更新start變量
start = start + vipNumbers[i].toCharArray().length;
if (i < nonNumbers.length){
TimerUtils.setContentSpan(mSpan,mTextColorSpanList.get(i),start,start+mGapLen);
start = start +mGapLen;//如果是個(gè)間隔還得去加上每個(gè)間隔長(zhǎng)度最后去更新start變量
}
}
mDateTv.setMovementMethod(LinkMovementMethod.getInstance());
mDateTv.setText(mSpan);
}
/**
* 重寫父類的initSpanData方法
* 通過(guò)number數(shù)組得到每塊數(shù)值對(duì)應(yīng)的自定義MikyouBackgroundSpan對(duì)象
* 然后通過(guò)MikyouBackgroundSpan對(duì)象定義每塊數(shù)值的樣式包括背景,邊框,邊框圓角樣式,然后將這些對(duì)象加入到集合中去
* 通過(guò)nonNumber數(shù)組得到每個(gè)間隔的ForegroundColorSpan對(duì)象
* 然后通過(guò)這些對(duì)象就可以定義每個(gè)間隔塊的樣式,因?yàn)橹欢x了ForegroundColorSpan所以只能定義
* 每個(gè)間隔塊的字體顏色,setmGapSpanColor方式也是供外部自由定制每個(gè)間隔的樣式
* 實(shí)際上還可以定義其他的Span,同理實(shí)現(xiàn)也是很簡(jiǎn)單的。
* */
@Override
public void initSpanData(String timeStr) {
super.initSpanData(timeStr);
vipNumbers = TimerUtils.getNumInTimerStr(timeStr);//得到每個(gè)數(shù)字注意不是每塊數(shù)值,并加入數(shù)組
vipNonNumbers = TimerUtils.getNonNumInTimerStr(timeStr);//得到每個(gè)間隔字符,并加入到數(shù)組
for (int i=0;i<vipNumbers.length;i++){
for (int j=0;j<vipNumbers[i].toCharArray().length;j++){//因?yàn)樾枰玫矫總€(gè)數(shù)字所以還得遍歷每塊數(shù)值中的每個(gè)數(shù)字,所以需要二層循環(huán)
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管理類,主要是提供不同樣式的倒計(jì)時(shí)的對(duì)象給客戶端,所以這個(gè)類直接與客戶端建立關(guān)系,從而實(shí)現(xiàn)倒計(jì)時(shí)子類和基類對(duì)外界的隱藏體現(xiàn)了封裝性。
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時(shí)mm分ss秒";
public static final String TIME_STYLE_THREE = "dd天HH時(shí)mm分ss秒";
public static final String TIME_STYLE_FOUR = "dd天HH時(shí)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;
}
//得到倒計(jì)時(shí)字符串中的數(shù)值塊部分
public static String[] getNumInTimerStr(String mTimerStr){
return mTimerStr.split("[^\\d]");
}
//得到倒計(jì)時(shí)中字符串中的非數(shù)值的字符串,并把數(shù)值過(guò)濾掉重新組合成一個(gè)字符串,并把字符串拆分字符數(shù)組,也就是保存倒計(jì)時(shí)中間的間隔
public static char[] getNonNumInTimerStr(String mTimerStr){
return mTimerStr.replaceAll("\\d","").toCharArray();
}
//設(shè)置字體顏色
public static ForegroundColorSpan getTextColorSpan(String color){
ForegroundColorSpan mSpan = null;
if (mSpan == null){
mSpan = new ForegroundColorSpan(Color.parseColor(color));
}
return mSpan;
}
//設(shè)置內(nèi)容的Span
public static void setContentSpan(SpannableString mSpan, Object span, int start,
int end) {
mSpan.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
現(xiàn)在我們就來(lái)測(cè)試下我們使用一個(gè)TextView實(shí)現(xiàn)的倒計(jì)時(shí)。
使用該倒計(jì)時(shí)非常簡(jiǎn)單非常方便只需要一行代碼就能實(shí)現(xiàn)一個(gè)高仿京東和各種電商的APP的倒計(jì)時(shí)樣式。
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);
//默認(rèn)樣式倒計(jì)時(shí)每種樣式下又對(duì)應(yīng)四種時(shí)間的格式
/**
* 默認(rèn)+時(shí)間格式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);
/**
* 默認(rèn)+時(shí)間格式2:DEFAULT_STYLE <--> TIME_STYLE_TWO = "HH時(shí)mm分ss秒"
* */
TextView tv1 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE,this,120000000,TimerUtils.TIME_STYLE_TWO,0)
.getmDateTv();
parent.addView(tv1);
setmLayoutParams(tv1);
/**
* 默認(rèn)+時(shí)間格式3:DEFAULT_STYLE <--> TIME_STYLE_THREE = "dd天HH時(shí)mm分ss秒"
* */
TextView tv2 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE,this,120000000,TimerUtils.TIME_STYLE_THREE,0)
.getmDateTv();
parent.addView(tv2);
setmLayoutParams(tv2);
/**
* 默認(rèn)+時(shí)間格式4:DEFAULT_STYLE <--> TIME_STYLE_FOUR = "dd天HH時(shí)mm分"
* */
TextView tv3 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE,this,120000000,TimerUtils.TIME_STYLE_FOUR,0)
.getmDateTv();
parent.addView(tv3);
setmLayoutParams(tv3);
//樣式一倒計(jì)時(shí),就是每塊數(shù)值和每個(gè)間隔分開的樣式,每種樣式下又對(duì)應(yīng)四種時(shí)間的格式
/**
* 樣式一+時(shí)間格式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)//設(shè)置內(nèi)間距
.setTimerTextColor(Color.BLACK)//設(shè)置字體顏色
.setTimerTextSize(40)//設(shè)置字體大小
.setTimerGapColor(Color.BLACK)//設(shè)置間隔的顏色
.getmDateTv();//拿到TextView對(duì)象
parent.addView(tv4);
setmLayoutParams(tv4);
/**
* 樣式一+時(shí)間格式2:JD_STYLE <--> TIME_STYLE_TWO = "HH時(shí)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);
/**
* 樣式一+時(shí)間格式3:JD_STYLE <-->TIME_STYLE_THREE = "dd天HH時(shí)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);
/**
* 樣式一+時(shí)間格式4:JD_STYLE <-->TIME_STYLE_FOUR = "dd天HH時(shí)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);
/**
* 樣式二+時(shí)間格式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);
/**
* 樣式二+時(shí)間格式2:VIP_STYLE <-->TIME_STYLE_TWO = "HH時(shí)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);
/**
* 樣式二+時(shí)間格式3:VIP_STYLE <-->TIME_STYLE_THREE = "dd天HH時(shí)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);
/**
* 樣式二+時(shí)間格式4:VIP_STYLE <-->TIME_STYLE_FOUR = "dd天HH時(shí)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);
}
}
兩個(gè)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>
現(xiàn)在就看看我們運(yùn)行的成果吧。

看看運(yùn)行結(jié)果還不錯(cuò)吧,其實(shí)它的樣式還可以定義很多種主要看自己的創(chuàng)意和想法了,這個(gè)倒計(jì)時(shí)封裝如果還有什么不足之處,請(qǐng)多多提出建議。但是現(xiàn)在使用還是蠻方便和簡(jiǎn)單的,一行代碼就能就能解決。這個(gè)倒計(jì)時(shí)用到的地方還是蠻多的,大家有需要的話可以直接引入到自己的項(xiàng)目中。
以上所述是小編給大家介紹的Android中使用TextView實(shí)現(xiàn)高仿京東淘寶各種倒計(jì)時(shí)效果,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)歡迎給我留言,小編會(huì)及時(shí)回復(fù)大家的,在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- android自定義倒計(jì)時(shí)控件示例
- android實(shí)現(xiàn)倒計(jì)時(shí)功能代碼
- Android實(shí)現(xiàn)計(jì)時(shí)與倒計(jì)時(shí)的常用方法小結(jié)
- Android實(shí)現(xiàn)加載廣告圖片和倒計(jì)時(shí)的開屏布局
- Android 實(shí)現(xiàn)閃屏頁(yè)和右上角的倒計(jì)時(shí)跳轉(zhuǎn)實(shí)例代碼
- Android實(shí)現(xiàn)時(shí)間倒計(jì)時(shí)功能
- Android自定義圓形倒計(jì)時(shí)進(jìn)度條
- Android自帶倒計(jì)時(shí)控件Chronometer使用方法詳解
- Android 列表倒計(jì)時(shí)的實(shí)現(xiàn)的示例代碼(CountDownTimer)
- Android實(shí)現(xiàn)圓圈倒計(jì)時(shí)
相關(guān)文章
Android退出應(yīng)用最優(yōu)雅的方式(改進(jìn)版)
這篇文章主要介紹了Android退出應(yīng)用最優(yōu)雅的方式,改進(jìn)版,感興趣的小伙伴們可以參考一下2016-01-01
Android仿新浪微博/QQ空間滑動(dòng)自動(dòng)播放視頻功能
相信用過(guò)新浪微博或者QQ空間的朋友都看到過(guò)滑動(dòng)自動(dòng)播放視頻的效果,那么這篇文章跟大家分享下如何利用Android實(shí)現(xiàn)這一個(gè)功能,有需要的朋友們可以參考借鑒。2016-09-09
關(guān)于Android中自定義ClassLoader耗時(shí)問(wèn)題的追查
熱修復(fù)和插件化是目前比較熱門的技術(shù),要想更好的掌握它們需要了解ClassLoader,下面這篇文章主要給大家介紹了關(guān)于Android中自定義ClassLoader耗時(shí)問(wèn)題追查的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧2018-06-06
Android Zipalign工具優(yōu)化Android APK應(yīng)用
本文主要介紹Android Zipalign工具優(yōu)化Android APK應(yīng)用,這里整理了相關(guān)資料及簡(jiǎn)單優(yōu)化實(shí)例,有需要的小伙伴可以參考下2016-09-09
Android手勢(shì)操作簡(jiǎn)單實(shí)例講解
這篇文章主要為大家詳細(xì)介紹了Android手勢(shì)操作簡(jiǎn)單實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android編程開發(fā)之打開文件的Intent及使用方法
這篇文章主要介紹了Android編程開發(fā)之打開文件的Intent及使用方法,已實(shí)例形式分析了Android打開文件Intent的相關(guān)布局及功能實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android自定義ImageView實(shí)現(xiàn)點(diǎn)擊兩張圖片切換效果
這篇文章主要為大家詳細(xì)介紹了Android自定義ImageView實(shí)現(xiàn)點(diǎn)擊兩張圖片切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android編程實(shí)現(xiàn)兩點(diǎn)觸控功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)兩點(diǎn)觸控功能的方法,涉及Android事件響應(yīng)與處理相關(guān)操作技巧,需要的朋友可以參考下2017-08-08

