一、 前言
随着网购的持续发展,抢购类倒计时在各类电商应用中已十分常见,这种设计可以提高用户的点击率和下单率等。
但是国内的电商应用大部分都仅支持中文,不适配其他的语言,因此当倒计时与其他文案处于同一行展示时,无需考虑倒计时的展示方式。在海外应用中,由于需要适配各种语言,有些小语种的文案较长,因此当倒计时和其他文案处于同一行展示时,需要充分考虑多语言的适配,如何优雅地完成倒计时自适应显示是一个值得深思的问题。
为进一步优化倒计时效果,我们为倒计时增加了数字滚动动画,如下图所示。倒计时的功能必然会带来性能的消耗,如何避免倒计时带来的性能问题,本文也将给出相应的解决方案。
二、 实现倒计时基本功能
2.1 需求与原理分析
该控件预期展现两种状态,距离活动开始还有X天XX:XX:XX 和距离活动结束还有X天XX:XX:XX,因此需要一个活动状态属性,并通过这个活动开始与否的属性设置时间前的文案。具体时间时分秒之间相互独立,因此将它们拆分成独立的textview进行处理。
倒计时控件的核心是计时器,安卓中已经有现成的CountDownTimer类可供使用以实现倒计时功能。此外,还需要实现一些监听的接口。
2.2 具体实现
2.2.1 回调监听接口设计
首先,定义回调接口
public interface OnCountDownTimerListener {
/**
* 倒计时正在进行时调用的方法
*
* @param millisUntilFinished 剩余的时间(毫秒)
*/
void onRemain(long millisUntilFinished);
/** * 倒计时结束 */ void onFinish(); /** * 每过一分钟调用的方法 */ void onArrivalOneMinute(); }
在该接口中定义三个方法:
onRemain(long millisUntilFinished):倒计时进行中回调的方法,用于后续功能的拓展
onFinish():倒计时结束回调,用于活动状态的切换和计时的暂停等
onArrivalOneMinute():每过一分钟回调,用于定时上报的埋点
2.2.2 view的构建与绑定
其次,初始化自定义view,基于实际开发需求,将整个控件细分为修饰文案、天数、时、分、秒等几个独立的textview,并在自定义BaseCountDownTimerView中初始化:
private void init() {
mDayTextView = findViewById(R.id.days_tv);
mHourTextView = findViewById(R.id.hours_tv);
mMinTextView = findViewById(R.id.min_tv);
mSecondTextView = findViewById(R.id.sec_tv);
mHeaderText = findViewById(R.id.header_tv);
mDayText = findViewById(R.id.new_arrival_day);
}
2.2.3 构建内部使用的私有方法
首先构造设置剩余时间的方法,入参是剩余的毫秒数,在方法内部将时间转化为具体的天时分秒,并将结果赋予给textview
需要注意的是,当单位时间为个位数时,为了视觉效果的统一,要在数字前加“0”进行补位。
其次,构建一个创建倒计时的方法,其代码如下:
private void createCountDownTimer(final int eventStatus) {
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
}
mCountDownTimer = new CountDownTimer(mMillis, 1000) {
@Override
public void onTick(long millisUntilFinished) {
//策划要求:倒计时为00:00:01时,活动状态刷新,倒计时不展示00:00:00这个状态
if (millisUntilFinished 》= ONE_SEC) {
setSecond(millisUntilFinished);
//当活动状态为进行中时,每隔一分钟调用一次回调
if (eventStatus == HomeItemViewNewArrival.EVENT_START) {
mArrivalOneMinuteFlag–;
if (mArrivalOneMinuteFlag == Constant.ZERO) {
mArrivalOneMinuteFlag = Constant.SIXTY;
mOnCountDownTimerListener.onArrivalOneMinute();
}
}
}
}
在该方法中,创建一个倒计时实例CountDownTimer,CountDownTimer() 有两个参数,分别是剩余的总时间和刷新间隔。
在实例的onTick()方法中,调用setSecond()方法在每次间隔时间(也就是1s)后定期刷新view,完成倒计时控件的更新。此外,产品中还有一个一分钟定期上报埋点的需求,也可以在onTick()方法中完成。在实际项目事件中,若有定时的任务需求,也可在该方法中自由设置。最后,还需重写该CountDownTimer的onFinish()方法,触发listener接口里的onFinish()
2.2.4 构建公有方法供外部使用
首先是设置倒计时的监听事件:
public void setDownTimerListener(OnCountDownTimerListener listener) {
this.mOnCountDownTimerListener = listener;
}
其次是外露一个设置初始时间和活动开始或结束文案的方法:
public void setDownTime(long millis) {
this.mMillis = millis;
}
public void setHeaderText(int eventStatus) {
if (eventStatus == HomeItemViewNewArrival.EVENT_NOT_START) {
mHeaderText.setText(“Start in”);
} else {
mHeaderText.setText(“Ends in”);
}
}
最后,也是最重要的,需要给倒计时类设计开始与取消倒计时的方法:
public void startDownTimer(int eventStatus) {
mArrivalOneMinuteFlag = Constant.SIXTY;
mFirstSetTimer = true;
//设置需要倒计时的初始值
setSecond(mMillis);
createCountDownTimer(eventStatus);// 创建倒计时
mCountDownTimer.start();
}
public void cancelDownTimer() { mCountDownTimer.cancel(); } 在开始倒计时的方法中,初始化倒计时的初始值并创建倒计时,最后调用CountDownTimer实例的start()方法开始倒计时。在取消的方法中,直接调用CountDownTimer实例的cancel()方法取消倒计时。
2.3 倒计时类的实际调用
实际调用倒计时控件时,只需在具体布局中添加该倒计时类布局,在调用的类中实例化BaseCountDownTimerView。接着,使用实例的setDownTime()、setHeaderText()初始化数据,使用setDownTimerListener()给view实例设置监听。
最后调用startDownTimer()开启倒计时。
if (view != null) {
view.setDownTime(mDuration);
view.setHeaderText(mEventStatus);
view.startDownTimer(mEventStatus);
view.setDownTimerListener(new BaseCountDownTimerView.OnCountDownTimerListener() {
@Override
三、实现倒计时整体布局
3.1 需求描述
在多语言环境或者不同屏幕条件下,某些语种的控件长度过长,需要自适应控件进行折行显示以适应UI规范
3.2 实施方案
原本考虑只实例化一个自定义倒计时控件的对象,但是在设计对象布局的过程中发现,一个对象不方便同时实现在行尾展示或折行后在第二行行首显示。因此,本文采用了在布局的时候同时预置两个倒计时对象的方法,一个对象位于行尾,另一个位于第二行的行首。
在measure过程中,如果测量得到控件的宽度大于某一个宽度阈值,则初始化次行行首的view,并将行尾的view可见状态置为Gone,若小于某一个宽度阈值,则初始化行尾的view,并将次行行首的view可见状态置为Gone
首先来看一看xml布局文件,以下是标题加倒计时位于行尾的一个整体布局文件main_view_header_new_arrival
《?xml versio encodin?》
《com.example.website.general.ui.widget.TextView android: android:layout_widt android:layout_heigh android:layout_alignParentStar android:layout_centerInParen android:layout_marginStar android:tex android:textColor=“@color/common_color_de000000” android:textSiz android:textStyl /》 《com.example.website.widget.BaseCountDownTimerView android: android:layout_widt android:layout_heigh android:layout_alignParentEn android:layout_marginEn android:gravit /》 它的实际展示效果如下图所示 但是此布局只能展示单行能展示所有内容的情况,因此还需要在此布局上拓展双行展示的情况,再看一看main_list_item_home_new_arrival的布局
《?xml versio encodin?》
《LinearLayout android:layout_widt android:layout_heigh android:orientatio》 《include layou/》 《com.example.website.widget.BaseCountDownTimerView android: android:layout_widt android:layout_heigh android:layout_alignParentStar android:layout_marginStar android:layout_marginTo android:layout_marginEn android:layout_marginBotto android:gravit /》 《/LinearLayout》
它的实际展示效果如下图所示 在类中将以上两个view分别进行实例关联。
View.inflate(getContext(), R.layout.main_list_item_home_new_arrival, this);
mBaseCountDownTimerViewShort = findViewById(R.id.count_down_timer_short); //行尾倒计时view
mBaseCountDownTimerViewLong = findViewById(R.id.count_down_timer_long); //次行行首倒计时view
通过以上的步骤搞定了两种情况下倒计时控件的布局,接下来就该考虑折行展示的判断条件了。
在多语言环境中,标题textview与倒计时view的宽度都是不确定的,因此需要综合考虑两个控件的宽度。同时,因为策划要求,还需考虑某些语种特殊情况的展示要求。判断代码如下所示:
private boolean isShortCountDownTimerViewShow() {
String languageCode = LocaleManager.getInstance().getCurrentLanguage();
if (Constant.EN_US.equals(languageCode) || Constant.EN_GB.equals(languageCode) || Constant.EN_AU.equals(languageCode)) {
//因策划要求,美式英语、英国英语、澳大利亚英语,强制在New Arrivals标题栏右侧展示
return true;
} else {
View newArrivalHeader = inflate(mContext, R.layout.main_view_header_new_arrival, null);
TextView newArrivalTextView = newArrivalHeader.findViewById(R.id.new_arrival_txt);
LinearLayout countDownTimer = newArrivalHeader.findViewById(R.id.count_down_timer_short);
int measureSpecW = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int measureSpecH = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
newArrivalTextView.measure(measureSpecW, measureSpecH);
countDownTimer.measure(measureSpecW, measureSpecH);
VLog.i(TAG, countDownTimer.getMeasuredWidth() + “–” + newArrivalTextView.getMeasuredWidth());
if (countDownTimer.getMeasuredWidth() + newArrivalTextView.getMeasuredWidth() 《= mContext.getResources().getDimensionPixelSize(R.dimen.qb_px_302)) { return true; } else { return false; } } }
在代码中,可以根据实际需要定制具体某几款语言是否换行显示。
而对于剩下的大多数语言,可以使用MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)获取measureSpecW 和 measureSpecH ,第一个参数是系统测量该View后得到的规格值,这里使用0代表省略(在系统对该View绘制之前就直接调用了measure方法,所以宽高为0,该值与最终获取的宽高无关),第二个参数MeasureSpec.UNSPECIFIED代表父容器不对View有任何限制。获取完成后也就顺利完成具体view宽度的测量。
通过该方法的返回值,我们就可以控制两个倒计时view的展示与隐藏,从而达到自适应折行展示的效果。
if (isShortCountDownTimerViewShow()) {
initCountDownTimerView(mBaseCountDownTimerViewShort, bean);
mBaseCountDownTimerViewShort.setVisibility(VISIBLE);
mBaseCountDownTimerViewLong.setVisibility(GONE);
} else {
initCountDownTimerView(mBaseCountDownTimerViewLong, bean);
mBaseCountDownTimerViewShort.setVisibility(GONE);
mBaseCountDownTimerViewLong.setVisibility(VISIBLE);
}
此外,该方法也不局限于倒计时控件view,针对多语言中各种各样的自定义view,依然可以使用这种测量方法实现自适应换行的美观展示。
一、 前言
随着网购的持续发展,抢购类倒计时在各类电商应用中已十分常见,这种设计可以提高用户的点击率和下单率等。
但是国内的电商应用大部分都仅支持中文,不适配其他的语言,因此当倒计时与其他文案处于同一行展示时,无需考虑倒计时的展示方式。在海外应用中,由于需要适配各种语言,有些小语种的文案较长,因此当倒计时和其他文案处于同一行展示时,需要充分考虑多语言的适配,如何优雅地完成倒计时自适应显示是一个值得深思的问题。
为进一步优化倒计时效果,我们为倒计时增加了数字滚动动画,如下图所示。倒计时的功能必然会带来性能的消耗,如何避免倒计时带来的性能问题,本文也将给出相应的解决方案。
二、 实现倒计时基本功能
2.1 需求与原理分析
该控件预期展现两种状态,距离活动开始还有X天XX:XX:XX 和距离活动结束还有X天XX:XX:XX,因此需要一个活动状态属性,并通过这个活动开始与否的属性设置时间前的文案。具体时间时分秒之间相互独立,因此将它们拆分成独立的textview进行处理。
倒计时控件的核心是计时器,安卓中已经有现成的CountDownTimer类可供使用以实现倒计时功能。此外,还需要实现一些监听的接口。
2.2 具体实现
2.2.1 回调监听接口设计
首先,定义回调接口
public interface OnCountDownTimerListener {
/**
* 倒计时正在进行时调用的方法
*
* @param millisUntilFinished 剩余的时间(毫秒)
*/
void onRemain(long millisUntilFinished);
/** * 倒计时结束 */ void onFinish(); /** * 每过一分钟调用的方法 */ void onArrivalOneMinute(); }
在该接口中定义三个方法:
onRemain(long millisUntilFinished):倒计时进行中回调的方法,用于后续功能的拓展
onFinish():倒计时结束回调,用于活动状态的切换和计时的暂停等
onArrivalOneMinute():每过一分钟回调,用于定时上报的埋点
2.2.2 view的构建与绑定
其次,初始化自定义view,基于实际开发需求,将整个控件细分为修饰文案、天数、时、分、秒等几个独立的textview,并在自定义BaseCountDownTimerView中初始化:
private void init() {
mDayTextView = findViewById(R.id.days_tv);
mHourTextView = findViewById(R.id.hours_tv);
mMinTextView = findViewById(R.id.min_tv);
mSecondTextView = findViewById(R.id.sec_tv);
mHeaderText = findViewById(R.id.header_tv);
mDayText = findViewById(R.id.new_arrival_day);
}
2.2.3 构建内部使用的私有方法
首先构造设置剩余时间的方法,入参是剩余的毫秒数,在方法内部将时间转化为具体的天时分秒,并将结果赋予给textview
需要注意的是,当单位时间为个位数时,为了视觉效果的统一,要在数字前加“0”进行补位。
其次,构建一个创建倒计时的方法,其代码如下:
private void createCountDownTimer(final int eventStatus) {
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
}
mCountDownTimer = new CountDownTimer(mMillis, 1000) {
@Override
public void onTick(long millisUntilFinished) {
//策划要求:倒计时为00:00:01时,活动状态刷新,倒计时不展示00:00:00这个状态
if (millisUntilFinished 》= ONE_SEC) {
setSecond(millisUntilFinished);
//当活动状态为进行中时,每隔一分钟调用一次回调
if (eventStatus == HomeItemViewNewArrival.EVENT_START) {
mArrivalOneMinuteFlag–;
if (mArrivalOneMinuteFlag == Constant.ZERO) {
mArrivalOneMinuteFlag = Constant.SIXTY;
mOnCountDownTimerListener.onArrivalOneMinute();
}
}
}
}
在该方法中,创建一个倒计时实例CountDownTimer,CountDownTimer() 有两个参数,分别是剩余的总时间和刷新间隔。
在实例的onTick()方法中,调用setSecond()方法在每次间隔时间(也就是1s)后定期刷新view,完成倒计时控件的更新。此外,产品中还有一个一分钟定期上报埋点的需求,也可以在onTick()方法中完成。在实际项目事件中,若有定时的任务需求,也可在该方法中自由设置。最后,还需重写该CountDownTimer的onFinish()方法,触发listener接口里的onFinish()
2.2.4 构建公有方法供外部使用
首先是设置倒计时的监听事件:
public void setDownTimerListener(OnCountDownTimerListener listener) {
this.mOnCountDownTimerListener = listener;
}
其次是外露一个设置初始时间和活动开始或结束文案的方法:
public void setDownTime(long millis) {
this.mMillis = millis;
}
public void setHeaderText(int eventStatus) {
if (eventStatus == HomeItemViewNewArrival.EVENT_NOT_START) {
mHeaderText.setText(“Start in”);
} else {
mHeaderText.setText(“Ends in”);
}
}
最后,也是最重要的,需要给倒计时类设计开始与取消倒计时的方法:
public void startDownTimer(int eventStatus) {
mArrivalOneMinuteFlag = Constant.SIXTY;
mFirstSetTimer = true;
//设置需要倒计时的初始值
setSecond(mMillis);
createCountDownTimer(eventStatus);// 创建倒计时
mCountDownTimer.start();
}
public void cancelDownTimer() { mCountDownTimer.cancel(); } 在开始倒计时的方法中,初始化倒计时的初始值并创建倒计时,最后调用CountDownTimer实例的start()方法开始倒计时。在取消的方法中,直接调用CountDownTimer实例的cancel()方法取消倒计时。
2.3 倒计时类的实际调用
实际调用倒计时控件时,只需在具体布局中添加该倒计时类布局,在调用的类中实例化BaseCountDownTimerView。接着,使用实例的setDownTime()、setHeaderText()初始化数据,使用setDownTimerListener()给view实例设置监听。
最后调用startDownTimer()开启倒计时。
if (view != null) {
view.setDownTime(mDuration);
view.setHeaderText(mEventStatus);
view.startDownTimer(mEventStatus);
view.setDownTimerListener(new BaseCountDownTimerView.OnCountDownTimerListener() {
@Override
三、实现倒计时整体布局
3.1 需求描述
在多语言环境或者不同屏幕条件下,某些语种的控件长度过长,需要自适应控件进行折行显示以适应UI规范
3.2 实施方案
原本考虑只实例化一个自定义倒计时控件的对象,但是在设计对象布局的过程中发现,一个对象不方便同时实现在行尾展示或折行后在第二行行首显示。因此,本文采用了在布局的时候同时预置两个倒计时对象的方法,一个对象位于行尾,另一个位于第二行的行首。
在measure过程中,如果测量得到控件的宽度大于某一个宽度阈值,则初始化次行行首的view,并将行尾的view可见状态置为Gone,若小于某一个宽度阈值,则初始化行尾的view,并将次行行首的view可见状态置为Gone
首先来看一看xml布局文件,以下是标题加倒计时位于行尾的一个整体布局文件main_view_header_new_arrival
《?xml versio encodin?》
《com.example.website.general.ui.widget.TextView android: android:layout_widt android:layout_heigh android:layout_alignParentStar android:layout_centerInParen android:layout_marginStar android:tex android:textColor=“@color/common_color_de000000” android:textSiz android:textStyl /》 《com.example.website.widget.BaseCountDownTimerView android: android:layout_widt android:layout_heigh android:layout_alignParentEn android:layout_marginEn android:gravit /》 它的实际展示效果如下图所示 但是此布局只能展示单行能展示所有内容的情况,因此还需要在此布局上拓展双行展示的情况,再看一看main_list_item_home_new_arrival的布局
《?xml versio encodin?》
《LinearLayout android:layout_widt android:layout_heigh android:orientatio》 《include layou/》 《com.example.website.widget.BaseCountDownTimerView android: android:layout_widt android:layout_heigh android:layout_alignParentStar android:layout_marginStar android:layout_marginTo android:layout_marginEn android:layout_marginBotto android:gravit /》 《/LinearLayout》
它的实际展示效果如下图所示 在类中将以上两个view分别进行实例关联。
View.inflate(getContext(), R.layout.main_list_item_home_new_arrival, this);
mBaseCountDownTimerViewShort = findViewById(R.id.count_down_timer_short); //行尾倒计时view
mBaseCountDownTimerViewLong = findViewById(R.id.count_down_timer_long); //次行行首倒计时view
通过以上的步骤搞定了两种情况下倒计时控件的布局,接下来就该考虑折行展示的判断条件了。
在多语言环境中,标题textview与倒计时view的宽度都是不确定的,因此需要综合考虑两个控件的宽度。同时,因为策划要求,还需考虑某些语种特殊情况的展示要求。判断代码如下所示:
private boolean isShortCountDownTimerViewShow() {
String languageCode = LocaleManager.getInstance().getCurrentLanguage();
if (Constant.EN_US.equals(languageCode) || Constant.EN_GB.equals(languageCode) || Constant.EN_AU.equals(languageCode)) {
//因策划要求,美式英语、英国英语、澳大利亚英语,强制在New Arrivals标题栏右侧展示
return true;
} else {
View newArrivalHeader = inflate(mContext, R.layout.main_view_header_new_arrival, null);
TextView newArrivalTextView = newArrivalHeader.findViewById(R.id.new_arrival_txt);
LinearLayout countDownTimer = newArrivalHeader.findViewById(R.id.count_down_timer_short);
int measureSpecW = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int measureSpecH = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
newArrivalTextView.measure(measureSpecW, measureSpecH);
countDownTimer.measure(measureSpecW, measureSpecH);
VLog.i(TAG, countDownTimer.getMeasuredWidth() + “–” + newArrivalTextView.getMeasuredWidth());
if (countDownTimer.getMeasuredWidth() + newArrivalTextView.getMeasuredWidth() 《= mContext.getResources().getDimensionPixelSize(R.dimen.qb_px_302)) { return true; } else { return false; } } }
在代码中,可以根据实际需要定制具体某几款语言是否换行显示。
而对于剩下的大多数语言,可以使用MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)获取measureSpecW 和 measureSpecH ,第一个参数是系统测量该View后得到的规格值,这里使用0代表省略(在系统对该View绘制之前就直接调用了measure方法,所以宽高为0,该值与最终获取的宽高无关),第二个参数MeasureSpec.UNSPECIFIED代表父容器不对View有任何限制。获取完成后也就顺利完成具体view宽度的测量。
通过该方法的返回值,我们就可以控制两个倒计时view的展示与隐藏,从而达到自适应折行展示的效果。
if (isShortCountDownTimerViewShow()) {
initCountDownTimerView(mBaseCountDownTimerViewShort, bean);
mBaseCountDownTimerViewShort.setVisibility(VISIBLE);
mBaseCountDownTimerViewLong.setVisibility(GONE);
} else {
initCountDownTimerView(mBaseCountDownTimerViewLong, bean);
mBaseCountDownTimerViewShort.setVisibility(GONE);
mBaseCountDownTimerViewLong.setVisibility(VISIBLE);
}
此外,该方法也不局限于倒计时控件view,针对多语言中各种各样的自定义view,依然可以使用这种测量方法实现自适应换行的美观展示。
举报