Android -- [SelfView] 自定义多动画效果文本显示器
package com.nepalese.harinetest.player;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.text.Html;
import android.text.Layout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.Nullable;
import com.nepalese.harinetest.config.Constants;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2024/10/15.
* Usage: 滚动文本,不限长度
* 显示效果:
* 1. 静态显示(左对齐、居中、右对齐)
* 2. 向左滚动;(纵向居中)
* 3. 向上滚动(一条一条);
* 4. 向上滚动(整体);
* <p>
* 可控接口:
* 1. 字体大小、颜色;
* 2. 背景颜色(可透明);
* 3. 滚动速度(1-20);
* 4. 修饰符(斜体、粗体、下划线、删除线);
*/
public class VirgoScrollTextView extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "VirgoScrollTextView";
//速度: 1--20;
private static final int MAX_SPEED = 20;
private static final int MIN_SPEED = 1;
public static final int MODE_STATIC_DRAW = 1;//无动画静态显示
public static final int MODE_SCROLL_UP = 2;//纵向向上滚动
public static final int MODE_SCROLL_UP_ALL = 3;//纵向向上滚动(整体)
public static final int MODE_SCROLL_LEFT = 4;//横向向左滚动
public static final int ALIGN_LEFT = 1;//左对齐
public static final int ALIGN_CENTER = 2;//居中
public static final int ALIGN_RIGHT = 3;//右对齐
protected static final String TEXT_SPACE = "\u0020";
protected static final String TXT_ENTER = "\u3000\u3000";
//半角空格(英文符号)\u0020,代码中常用的;
//全角空格(中文符号)\u3000,中文文章中使用;
//默认样式
protected static final int DEF_BG = Color.TRANSPARENT;
protected static final int DEF_TEXT_COLOR = Color.BLACK;
private static final int DEF_SPEED = 10;//默认速度
protected static final float DEF_TEXT_SIZE = 30f;//px
private static final float SPEED_RATIO = 0.30f;//速度使用率
private static final long DELAY_FLASH = 40L;//刷新频率
private Paint paint;//画笔
private List<BaseText> textList;//分块文本列表
private SurfaceHolder surfaceHolder;//绘制画布载体
private DrawRun runnable;
private Handler drawHandler;//绘制线程
protected int width, height;//控件宽高
private String cont;//文本内容
private int animMode;//动画模式
private int alignMode;//对齐方式
private int bgColr;//背景色
private int pageIndex;//分页播放页
private int padding;//内偏移
private float textSpeed;//滚动速度
private float lineSpace;//行间隔
private float textSize;//字体大小 px
private float textHeight;//字体高度,与字体大小关联
private float startY;//与字体高度绑定
private float offset;//已滚动值
private float allHeight;//竖向滚动文本总高度
private boolean isHolderReady;// surfaceCreated
public VirgoScrollTextView(Context context) {
this(context, null);
}
public VirgoScrollTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VirgoScrollTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
setZOrderOnTop(true);//使surfaceview放到最顶层 看是否需要
surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);//使窗口支持透明度
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setFakeBoldText(false);
textList = new ArrayList<>();
setDefault();
}
private void setDefault() {
width = 0;
height = 0;
animMode = MODE_STATIC_DRAW;
offset = 0f;
pageIndex = 0;
padding = 0;
lineSpace = 1.3f;
alignMode = ALIGN_CENTER;
allHeight = 0;
if (runnable != null) {
runnable.isRolling = false;
}
isHolderReady = false;
setBgColr(DEF_BG);
setTextColor(DEF_TEXT_COLOR);
setTextSize(DEF_TEXT_SIZE);
setTextSpeed(DEF_SPEED);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0) {
initLayout(w, h);
setTextCont(this.cont);
}
}
//
/**
* 设置|更改布局时调用
*
* @param width 容器宽
* @param height 容器高
*/
private void initLayout(int width, int height) {
this.width = width;
this.height = height;
this.allHeight = height;
Log.d(TAG, "initLayout: " + width + " - " + height);
}
/**
* 停止滚动(调用继续滚动不影响滚动位置)
*/
public void stopRolling() {
if (drawHandler != null) {
if (runnable != null) {
runnable.isRolling = false;
runnable = null;
}
Looper mLooper = drawHandler.getLooper();
if (Build.VERSION.SDK_INT >= 18) {
mLooper.quitSafely();
} else {
mLooper.quit();
}
drawHandler = null;
}
}
/**
* 开始|继续滚动
* 文本、画布校验
*/
public void startRolling() {
if (!isHolderReady) {
Log.w(TAG, "还没准备好");
stopRolling();
return;
}
if (textList == null || textList.isEmpty()) {
Log.w(TAG, "文本为空");
stopRolling();
return;
}
if (animMode == MODE_STATIC_DRAW) {
stopRolling();
drawStatic();
} else {
if (drawHandler == null) {
HandlerThread mHandlerThread = new HandlerThread("scheduleText_" + System.currentTimeMillis());
mHandlerThread.start();
drawHandler = new Handler(mHandlerThread.getLooper());
}
if (runnable != null && !runnable.isRolling) {
runnable.isRolling = true;
drawHandler.post(runnable);
} else if (runnable == null) {
runnable = new DrawRun();
runnable.isRolling = true;
drawHandler.post(runnable);
}
}
}
/**
* 设置需显示的文本
* 在设置完字体样式之后调用
*
* @param cont str
*/
public void setTextCont(String cont) {
if (TextUtils.isEmpty(cont)) {
//todo 暂不处理
return;
}
if (width <= 0) {
//未设置宽度
this.cont = cont;
return;
}
//停止滚动
stopRolling();
//处理文本,缓存
textList.clear();
textList.addAll(parseCont(cont));
//重置
offset = 0f;
pageIndex = 0;
if (animMode == MODE_SCROLL_LEFT) {
startY = height / 2f + textHeight / 3;
} else {
if (textList.size() > 0) {
allHeight = textList.size() * (textHeight + lineSpace);
} else {
allHeight = height;
}
offset = -height;//从底部开始
}
}
/**
* 设置背景颜色
*
* @param bgColr def white
*/
public void setBgColr(int bgColr) {
this.bgColr = bgColr;
}
/**
* 设置字体颜色
*
* @param textColor def black
*/
public void setTextColor(int textColor) {
if (paint != null) {
paint.setColor(textColor);
}
}
/**
* 设置滚动速度
*
* @param textSpeed [1,20] def 10
*/
public void setTextSpeed(int textSpeed) {
if (textSpeed > MAX_SPEED || textSpeed < MIN_SPEED) {
//不在支持范围,设为默认值
this.textSpeed = DEF_SPEED * SPEED_RATIO;
} else {
this.textSpeed = textSpeed * SPEED_RATIO;
}
}
/**
* 设置字体大小
*
* @param textSize px def 25
*/
public void setTextSize(float textSize) {
if (this.textSize == textSize) {
//相同
return;
}
this.textSize = textSize;
if (paint != null) {
paint.setTextSize(textSize);
}
//获取字体高度
this.textHeight = getContHeight2();
}
public void setAnimMode(int animMode) {
this.animMode = animMode;
}
public void setAlignMode(int alignMode) {
this.alignMode = alignMode;
}
public void setPadding(int padding) {
this.padding = padding;
}
public void setLineSpace(float lineSpace) {
this.lineSpace = lineSpace;
}
//修饰: 下划线,删除线
public void setTextDec(String dec) {
if (TextUtils.isEmpty(dec) || dec.equals(Constants.DEC.NONE)) {
if (paint != null) {
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
}
return;
}
if (dec.equals(Constants.DEC.DELETE)) {
if (paint != null) {
paint.setFlags(Paint.STRIKE_THRU_TEXT_FLAG);//删除线
}
} else if (dec.equals(Constants.DEC.UNDERLINE)) {
if (paint != null) {
paint.setFlags(Paint.UNDERLINE_TEXT_FLAG);//下划线
}
}
}
//斜体
public void setTextItalic(boolean isItalic) {
if (paint != null) {
paint.setTextSkewX(isItalic ? -0.25f : 0);
}
}
//粗体
public void setTextBold(boolean isBold) {
if (paint != null) {
paint.setFakeBoldText(isBold);// 1 + 1
}
}
/**
* 注销
*/
public void release() {
stopRolling();
if (textList != null) {
textList.clear();
textList = null;
}
}
/**
* 重置至初始值
*/
public void reset() {
stopRolling();
textList.clear();
setDefault();
}
/
//surfaceHolder.lockCanvas(); 不能连续调用
//静态显示
private void drawStatic() {
Log.d(TAG, "drawStatic: ");
if (!textList.isEmpty() && surfaceHolder != null) {
//多行就多行画
Canvas canvas = surfaceHolder.lockCanvas();
//支持透明背景
canvas.drawColor(bgColr, PorterDuff.Mode.CLEAR);
canvas.drawColor(bgColr);
for (int i = 0; i < textList.size(); i++) {
BaseText baseText = textList.get(i);
float startX;
switch (alignMode) {
case ALIGN_LEFT:
startX = padding;
break;
case ALIGN_RIGHT:
startX = width - baseText.getLenght() - padding;
break;
case ALIGN_CENTER:
default:
startX = (width - baseText