携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情
前言
之前的文章又讲到过Android的倒计时实现和Android的定时任务实现。我们基于它们除了一些常规的计时显示之外还能做什么操作呢?
产品看到竞品App有这样的功能,希望我们的App也能加上,文字自动打印字符的功能。
这还不简单,拿到需要展示的文本之后一个一个展示不就行了吗?
一、最简单的做法Handler计时
fun printText() {
sendTextMessage()
}
private val text = "Hello World."
private var curIndex = 0
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if (msg.what == 101) {
val text: String = msg.obj as String
YYLogUtils.w("当前展示的字符串:"+text)
mBinding.tvPrint.text = text
sendTextMessage()
}
}
}
private fun sendTextMessage() {
val message = handler.obtainMessage()
message.what = 101
curIndex += 1
if (curIndex <= text.length) {
val text = text.substring(0, curIndex)
message.obj = text
handler.sendMessage(message)
} else {
YYLogUtils.w("写完了")
curIndex = 0
}
}
我们把每一个字符发送给Handler不就能完成了吗?咦?为什么打印正常,但是没有打印效果呢?
因为执行速度太快了,16.6毫秒之内就执行完毕了,一次就全丢缓冲区了,那不得一次上屏了吗?这样就直接显示最终的效果了,肯定是没有打印效果的啦。
我们修改一下,延时17毫秒执行,确保每次只有当前需要显示的字符上屏展示即可:
fun printText() {
sendTextMessageDelay(0)
}
private val text = "Hello World."
private var curIndex = 0
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if (msg.what == 101) {
val text: String = msg.obj as String
YYLogUtils.w("当前展示的字符串:"+text)
mBinding.tvPrint.text = text
sendTextMessageDelay(17)
}
}
}
private fun sendTextMessageDelay(delay: Long) {
val message = handler.obtainMessage()
message.what = 101
curIndex += 1
if (curIndex <= text.length) {
val text = text.substring(0, curIndex)
message.obj = text
handler.sendMessageDelayed(message, delay)
} else {
YYLogUtils.w("写完了")
curIndex = 0
}
}
这样就可以完成了往前打印的效果
一、往前打印与往后打印
效果图是往前打印完了,再往回收回,再打印下一个文本,其实往回收回也时一样的逻辑,从文本的lenght开始往前打印
private val text = "Hello World."
private var curIndex = 0
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if (msg.what == 101) {
val text: String = msg.obj as String
YYLogUtils.w("当前展示的字符串:" + text)
mBinding.tvPrint.text = text
printForwardMessage(17L)
} else if (msg.what == 102) {
val text: String = msg.obj as String
YYLogUtils.w("当前展示的字符串:" + text)
mBinding.tvPrint.text = text
printBackMessage(17L)
}
}
}
private fun printForwardMessage(delay: Long) {
val message = handler.obtainMessage()
message.what = 101
curIndex += 1
if (curIndex <= text.length) {
val text = text.substring(0, curIndex)
message.obj = text
handler.sendMessageDelayed(message, delay)
} else {
YYLogUtils.w("写完了")
curIndex = text.length
printBackMessage(delay)
}
}
private fun printBackMessage(delay: Long) {
val message = handler.obtainMessage()
message.what = 102
curIndex -= 1
if (curIndex >= 0) {
val text = text.substring(0, curIndex)
message.obj = text
handler.sendMessageDelayed(message, delay)
} else {
YYLogUtils.w("写完了")
curIndex = 0
printForwardMessage(delay)
}
}
//打印文本
fun printText() {
printForwardMessage(0)
}
效果:
当然,你觉得打印速度太快了,可以调节速度的,只要是大于17ms以上都可以达到打印的效果。
三、其他方式的定时任务能不能实现?
道理是一样的,之前讲的倒计时与App内的定时任务都可以完成这样的效果,例如我们使用Thread+Handler一样能实现这样的效果。
并且我们还需要优化一下,绑定生命周期让它可以暂停、恢复、销毁等功能。
完整代码如下:
/**
* 仿制EditText 一个一个字符的添加 和删除的效果,并轮播展示
*/
public class AutoLoopTextView extends AppCompatTextView implements ILifecycleObserver {
private ExecutorService mSingleThreadExecutor;
public AutoLoopTextView(@NonNull Context context) {
super(context);
}
public AutoLoopTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public AutoLoopTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private List<String> mLoopDatas;
private int mTextCount = 0;
private int mCurIndex = 0;
private long charTimeMilles = 50L; //文本打印的时间间隔 默认50毫秒
private long clearTimeMilles = 2500L; //清空的时间间隔 默认2.5秒
private Handler mHandler;
/**
* 设置下一个文本
*/
@SuppressLint("SetTextI18n")
private void setNextText(String str, int index) {
if (TextUtils.isEmpty(str)) return;
setTag(str); //取值从Tag取
if (mSingleThreadExecutor == null) return;
mSingleThreadExecutor.execute(() -> {
int n = index;
int nn;
try {
String stv;
if (str.length() < n) {
stv = str.substring(0, str.length());
} else {
stv = str.substring(0, n);
}
post(() -> {
if (!TextUtils.isEmpty(stv)) {
setText(stv);
}
});
Thread.sleep(charTimeMilles);//休息片刻
nn = n + 1; //n+1;多截取一个
if (nn <= str.length()) { //如果还有汉字,那么继续开启线程,相当于递归的感觉
try {
setNextText(str, nn);
} catch (Exception e) {
e.printStackTrace();
}
} else {
//写完了,开始延时清除
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
mHandler.sendEmptyMessageDelayed(102, clearTimeMilles);
} else {
resetHandle();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
/**
* 清空上一个文本
*/
private void clearPreText(int index) {
String str = getText().toString();
if (TextUtils.isEmpty(str)) return;
if (mSingleThreadExecutor == null) return;
mSingleThreadExecutor.execute(() -> {
int n = index == -1 ? str.length() : index;
int nn;
try {
if (n == 0) {
post(() -> {
setText("");
});
} else {
String stv;
if (str.length() < n) {
stv = str.substring(0, str.length());
} else {
stv = str.substring(0, n);
}
post(() -> {
if (!TextUtils.isEmpty(stv)) {
setText(stv);
}
});
}
Thread.sleep(charTimeMilles);//休息片刻
nn = n - 1;//n-1;少截取一个
if (nn >= 0) { //如果还有汉字,那么继续开启线程,相当于递归的感觉
try {
clearPreText(nn);
} catch (Exception e) {
e.printStackTrace();
}
} else {
//清除完了,开始写入
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
mHandler.sendEmptyMessage(101);
} else {
resetHandle();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
/**
* 设置轮播数据
*/
public void setLoopDate(List<String> list) {
mSingleThreadExecutor = Executors.newSingleThreadExecutor();
initHandle();
mLoopDatas = list;
mTextCount = list.size();
}
@SuppressLint("HandlerLeak")
private void initHandle() {
//主线程创建Handle
if (mHandler == null) {
setupHandler();
}
}
private void setupHandler() {
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull @NotNull Message msg) {
if (msg.what == 101) {
//开始写入
if (mCurIndex >= mTextCount) {
mCurIndex = 0;
}
try {
setNextText(mLoopDatas.get(mCurIndex), 0);
} catch (Exception e) {
e.printStackTrace();
}
mCurIndex++;
} else if (msg.what == 102) {
//开始清除
try {
clearPreText(-1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
}
/**
* 重启Handle
*/
@SuppressLint("HandlerLeak")
private void resetHandle() {
if (mSingleThreadExecutor != null) {
//先停止线程池
mSingleThreadExecutor.execute(() -> {
YYLogUtils.w("auto loop 停止线程");
if (!CheckUtil.isEmpty(mLoopDatas)) {
mTextCount = mLoopDatas.size();
}
});
}
post(() -> {
//主线程创建Handle
if (mHandler == null) {
setupHandler();
}
mHandler.sendEmptyMessage(101);
});
}
/**
* 开始轮播
*/
public void startTextLoop() {
YYLogUtils.w("auto loop 开始轮播 startTextLoop");
if (mLoopDatas == null || mLoopDatas.isEmpty()) return;
//从写入文本开始
mSingleThreadExecutor.execute(() -> {
YYLogUtils.w("auto loop 停止线程");
if (!CheckUtil.isEmpty(mLoopDatas)) {
mTextCount = mLoopDatas.size();
}
});
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
mHandler.sendEmptyMessage(101);
} else {
resetHandle();
}
}
/**
* 停止轮播
*/
public void stopTextLoop() {
YYLogUtils.w("auto loop 停止轮播 stopTextLoop");
if (mLoopDatas == null || mLoopDatas.isEmpty()) return;
mSingleThreadExecutor.execute(() -> {
YYLogUtils.w("auto loop 停止线程");
if (!CheckUtil.isEmpty(mLoopDatas)) {
mTextCount = mLoopDatas.size();
}
});
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
/**
* 销毁资源
*/
public void destroyTextLoop() {
YYLogUtils.w("auto loop 销毁资源 destroyTextLoop");
stopTextLoop();
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
if (mSingleThreadExecutor != null && !mSingleThreadExecutor.isShutdown()) {
mSingleThreadExecutor.shutdown();
}
mSingleThreadExecutor = null;
}
/**
* **********************************************************************
* ------------------------ 生命周期控制 --------------------------------*
* **********************************************************************
*/
public void addLoopLifecycleObserver(LifecycleOwner owner) {
if (owner != null) {
owner.getLifecycle().addObserver(new LifecycleObserverAdapter(owner, this));
}
}
@Override
public void onStart(LifecycleOwner owner) {
startTextLoop();
}
@Override
public void onStop(LifecycleOwner owner) {
stopTextLoop();
}
@Override
public void onDestroy(LifecycleOwner owner) {
destroyTextLoop();
}
}
效果图中,往前打印与往后打印的时长是不同我们分别控制。
需要注意的是我们没有直接使用Thread,而是使用单线程池来管理线程。
然后我们绑定生命周期进行暂停与恢复。暴露公共方法设置数据源即可开始打印效果。
使用的时候:
List<String> datas = new ArrayList<>();
for (PartTimeSearchKeywords item : list) {
datas.add(item.employer_name);
}
mTvAutoLoop.setLoopDate(datas);
mTvAutoLoop.addLoopLifecycleObserver(this);
mTvAutoLoop.startTextLoop();
最终效果即为最开始的效果图样子,这里就不再贴出。
总结
其实原理不复杂,就是利用了之前所学的定时任务的机制,加上屏幕刷新机制的原理,就可以实现文本的打印效果。
代码都全部贴出来了,如果有需求的话,自取即可。
好了,如有讲解不到位或错漏的地方,希望同学们可以指出交流。
如果感觉本文对你有一点点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。
Ok,完结。