English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Сегодня я покажу, как с помощью одного TextView можно реализовать обратный отсчет, имитирующий различные торговые приложения, такие как JD.com, Taobao, VIP.com и т.д. В последнее время в компании было много работы, и я не успел подготовить это, но сегодня я难得休息 и хочу поделиться этим с вами, чтобы мы могли вместе учиться и复习. Почему я решил использовать TextView? Потому что в последнее время в компании занимаются оптимизацией, и среди них есть функция обратного отсчета, которую коллега, разрабатывавший этот компонент, реализовал с использованием нескольких TextView, что привело к значительному избыточному коду. Поэтому项目经理 сказал: "Хуан Хонг, это твоя задача оптимизировать, и还要 обеспечить определенную расширяемость, и я был ошарашен. Не знал, с чего начать оптимизацию. Затем я проверил обратный отсчет в приложениях JD.com, Ele.me, VIP.com и т.д., и открыл уровень интерфейса разработчика, чтобы увидеть, что у них есть один общий признак: один View, без использования нескольких TextView. Думаю, все знают, что использование одного TextView лучше, чем использование нескольких TextView для их спайки. Давайте посмотрим на несколько интерфейсов, чтобы понять это.
Увидев это, естественно, возникает мысль о создании пользовательского View для реализации. Да, пользовательский View действительно可以实现 такой эффект. Но сегодня мы не будем использовать пользовательский View, а вместо этого используем TextView.
Поскольку项目经理 потребовал, чтобы код, который мы оптимизируем, был расширяемым, мы добавили в этот код некоторые знания об объектно-ориентированном программировании. Есть свои собственные идеи и архитектурные思路.
Концепция дизайна этого demo:
1. Напишите базовый класс для обратного отсчета, который реализует наиболее обычные и базовые функции обратного отсчета, без никакого стиля, чтобы этот базовый класс наследовался от класса CountDownTimer, и в этом базовом классе
Сохраните объект TextView и отображайте данные каждого обратного отсчета в TextView, а затем公布 метод getmDateTv(), который возвращает объект TextView. Затем вы можете просто получить этот объект TextView и отобразить его в макете интерфейса. Это очень удобно.
2. Then, for different styles of countdown, you only need to write different subclasses to inherit the most common countdown base class, and then overwrite the two methods of setting data and setting style. Then you can add different styles to the most common countdown. If you need to expand a new countdown style next time, you don't need to change the code of other classes. Just write a derived class of a common countdown and overwrite the two methods, making the extensibility more flexible.
3. Then, through a TimerUtils management class, take on the pressure of subclasses and superclass collectively, so that the functions required by subclasses and superclass are distributed 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, all through this management class. This avoids the client from directly interacting with the base class and subclasses of countdown. Thus, the encapsulation and concealment of the class are reflected.
Below is a simple UML class diagram of this Demo design:
Through the above analysis, let's see what knowledge points are needed for the implementation of this Demo.
1. Usage 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 see its source code to understand its usage naturally.
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(); }
Как видно, общее время обратного отсчета равно millisFuture, и интервал CountDownInterval по умолчанию составляет 1000ms, поэтому данные инициализируются через конструктор, затем необходимо переопределить回调-метод onTick,其中一个 из параметров - это оставшееся время в миллисекундах через каждый соответствующий шаг. Затем нам нужно только форматировать время в onTick методе каждые 1000ms миллисекунд, чтобы получить соответствующий формат времени обратного отсчета. Форматирование обратного отсчета выполняется с помощью метода formatDuration из класса DurationFormatUtils пакета common lang apache,传入 формат времени, автоматически преобразует обратный отсчет в соответствующий формат 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); } // означает, что весь текст можно нажать для запуска этого события }, 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 spannableString = new SpannableString("Strike-through"); 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, и我们一起一步一步封装 наш обратный отсчет.
1. Написать базовый класс 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; /** * Создано mikyou 16-10-22. */ public class MikyouCountDownTimer extends CountDownTimer{ private Context mContext;//传入的对象 контекста protected TextView mDateTv;//TextView для реализации обратного отсчета private long mGapTime;//Введенный设置的 интервал времени, то есть общее время обратного отсчета private long mCount = 1000;//Шаг обратного отсчета: обычно 1000 означает, что раз в 1 секунду private String mTimePattern = "HH:mm:ss";//timePattern传入的时间样式: HH:mm:ss HHч-mm:ss ssсек HHд-ч-mm:ss 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; // Используется для установки id drawable фона 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); // Это класс formatDuration в пакете lang пакета common Apache, через传入 //Одна форма времени автоматически преобразует обратный отсчет в соответствующий стиль 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 сек. и т.д. Теперь мы можем看一下 простые базовые стили.
Во второй части мы создаем пользовательский MikyouBackgroundSpan, наследующийся от ImageSpan. Этот класс очень важен и используется для добавления стиля к TextView для обратного отсчета. Почему можно использовать один TextView для этого?
Не забудьте, что есть очень мощный класс SpannableString, который позволяет настраивать стиль каждого символа в строке, множество стилей. И в конце концов, через метод setSpan в TextView можно передать
Объект SpannableString завершает настройку. Но почему нужно создавать пользовательский Span? Потому что, почему-то в Android так много типов Span, и ни один из них не позволяет напрямую устанавливать объект файла Drawable. Поэтому я много времени провел в поисках в Интернете, но не нашел ничего, и в конце концов нашел решение на stackOverFlow. Иностранец предложил решение, заключающееся в переписывании 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; /** * Создано mikyou 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); }
Третий способ реализации таймера обратного отсчета с стилем one, стиль one指的是, например: 12 часов 36 минут 27 секунд или 12:36:27, это разделение значений и часов, минут, секунд или разделителя ":", затем определение стиля каждого блока значений (12 36 27) и интервала (часы минуты секунды или :), включая добавление фона и рамки для блоков значений. В массиве number MikyouCountDownTimer сохраняются [12 36 27], а в массиве nonumer сохраняются [часы минуты секунды ] или [ : :] разделители.
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; /** * Создано mikyou 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 родительского класса * Получить объект MikyouBackgroundSpan для каждого блока чисел с помощью массива number * Затем с помощью объекта MikyouBackgroundSpan определить стиль каждого блока чисел, включая фон, рамку, стиль закругленных углов, и добавить эти объекты в коллекцию * Получить объект ForegroundColorSpan для каждого интервала с помощью массива nonNumber * Затем с помощью этих объектов можно определить стиль каждого интервала блока, так как определен только ForegroundColorSpan, можно определить только * Цвет шрифта каждого интервала блока, способ setmGapSpanColor также позволяет внешнему интерфейсу свободно настраивать стиль каждого интервала * На самом деле можно определить и другие Span, реализация также очень проста. * */ @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 индексами * Определить стиль подстроки строки от position start до position 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); } }
Четвертый способ реализации таймера в стиле 2, который отличается от стиля 1, например: 12 часов 36 минут 27 секунд или 12:36:27 означает разделение каждого значения и часов, минут, секунд или «:» на части, а затем определение стиля каждого блока значений (1 2 3 6 2 7) и интервала (часы минуты секунды или :), включая добавление фона и рамки для блоков значений. В массиве vipNumber класса MikyouCountDownTimer сохраняется [1 2 3 6 2 7], а в массиве vipnonNumer сохраняется间隔 символы [часы минуты секунды ] или [ : :].
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; /** * Создано mikyou 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 индексами * Чтобы определить стиль substrинга от start до end в строке, показывающий положение каждого числового substrинга в целой строке * 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 родительского класса * Получить объект MikyouBackgroundSpan для каждого блока чисел с помощью массива number * Затем с помощью объекта MikyouBackgroundSpan определить стиль каждого блока чисел, включая фон, рамку, стиль закругленных углов, и добавить эти объекты в коллекцию * Получить объект ForegroundColorSpan для каждого интервала с помощью массива nonNumber * Затем с помощью этих объектов можно определить стиль каждого интервала блока, так как определен только ForegroundColorSpan, можно определить только * Цвет шрифта каждого интервала блока, способ setmGapSpanColor также позволяет внешнему интерфейсу свободно настраивать стиль каждого интервала * На самом деле можно определить и другие Span, реализация также очень проста. * */ @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,主要负责向客户端提供不同样式的倒计时对象,因此这个类直接与客户端建立关系,从而实现倒计时子类和基类对外界的隐藏体现了封装性。
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; /** * Создано mikyou 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 для реализации обратного отсчета.
Использование этого таймера очень простое и удобное, чтобы реализовать стиль обратного отсчета, аналогичный JD.com и другим интернет-магазинами, всего одним строкой кода.
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 has four time formats corresponding to each style /** * Default + time format 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); /** * Default + time format 2: DEFAULT_STYLE <-> TIME_STYLE_TWO = "HH hours mm minutes ss seconds" * */ TextView tv1 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE, this, 120000000, TimerUtils.TIME_STYLE_TWO, 0) .getmDateTv(); parent.addView(tv1); setmLayoutParams(tv1); /** * Default + time format 3: DEFAULT_STYLE <-> TIME_STYLE_THREE = "dd days HH hours mm minutes ss seconds" * */ TextView tv2 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE, this, 120000000, TimerUtils.TIME_STYLE_THREE, 0) .getmDateTv(); parent.addView(tv2); setmLayoutParams(tv2); /** * Default + time format 4: DEFAULT_STYLE <-> TIME_STYLE_FOUR = "dd days HH hours mm minutes" * */ TextView tv3 = TimerUtils.getTimer(TimerUtils.DEFAULT_STYLE, this, 120000000, TimerUtils.TIME_STYLE_FOUR, 0) .getmDateTv(); parent.addView(tv3); setmLayoutParams(tv3); // стиль 1 обратный отсчет, это стиль, где каждый блок значения и каждый интервал разделены, и每种 стиля соответствует四种 форматам времени /** * стиль 1 + формат времени 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); /** * стиль 1 + формат времени 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); /** * стиль 1 + формат времени 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); /** * Стylе один + формат времени 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); /** * Стylе два + формат времени 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); /** * Стylе два + формат времени 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); /** * Стylе два + формат времени 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>
Теперь посмотрим на результаты, которые мы получили.
Проверьте результаты выполнения, они выглядят不错, на самом деле стиль можно определить многими способами, это зависит от вашей фантазии и идей. Если у вас есть какие-либо недостатки в этом封装 обратного отсчета, пожалуйста, дайте много советов. Но на данный момент использование очень удобно и просто, всего一行 кода можно решить. Этот обратный отсчет используется очень часто, если вам нужно, вы можете напрямую включить его в свой проект.
Как уже было сказано, редактор介绍了, как использовать TextView в Android для имитации эффектов обратного отсчета Jingdong Taobao, надеюсь, это поможет вам. Если у вас есть какие-либо вопросы, пожалуйста, оставляйте комментарии, редактор будет своевременно отвечать на ваши вопросы. В этом разделе также очень благодарим всех за поддержку呐喊 учебного сайта!
Заявление: содержимое этой статьи взято из Интернета, авторские права принадлежат соответствующему автору, контент предоставлен пользователями Интернета, сайт не обладает правами собственности, не был обработан вручную и не несет ответственности за соответствующие юридические последствия. Если вы обнаружите контент,涉嫌侵犯版权, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (во время отправки письма, пожалуйста, замените # на @) для сообщения о нарушении,并提供相关证据. При обнаружении факта нарушения, сайт незамедлительно удаляет涉嫌侵权的内容。