一、前言
添加文本,也是属于 一个比较简单的功能,在第二篇的时候,添加了橡皮擦,在橡皮擦里面通过一个模式的形式进行画笔的判断,当然文本也是如此,添加一个文本模式,在onTouchDown的时候,弹出PopupWindow,输入文本,然后PopupWindow消失的时候,利用staticLayout绘制到画布上即可。当然也有些需要注意的地方
下面一步步来实现
二、实现
2.1 添加文本模式
例如橡皮擦那样,添加多一个文本模式,然后setModel的时候,需要把画笔的样式修改为FILL,如果是STROKE进行文字绘制会变成空心文字。
companion object {
const val EDIT_MODE_PEN = 0x1L //画笔模式
const val EDIT_MODE_ERASER = 0x2L //橡皮擦模式
const val EDIT_MODE_TEXT = 0x3L //文字模式
}
@Retention(AnnotationRetention.SOURCE)
@IntDef(EDIT_MODE_PEN, EDIT_MODE_ERASER, EDIT_MODE_TEXT)
annotation class EditMode
/**
* 设置画笔模式
*/
fun setModel(@EditMode model: Long) {
mMode = model
when (model) {
EDIT_MODE_PEN -> {
//画线
mPaint.xfermode = null
mPaint.style = Paint.Style.STROKE
}
EDIT_MODE_ERASER -> {
mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
EDIT_MODE_TEXT -> {
mPaint.style = Paint.Style.FILL
}
}
}
2.2 修改bean类型
StaticLayout 是一个为不可编辑的文本布局的类,这意味着一旦布局完成,文本内容就不可以改变。在单纯地使用TextView来展示静态文本的时候,创建的就是 StaticLayout,在api25,Textview源码6858行可以看到。
StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
mHint.length(), mTextPaint, hintWidth)
.setAlignment(alignment)
.setTextDirection(mTextDir)
.setLineSpacing(mSpacingAdd, mSpacingMult)
.setIncludePad(mIncludePad)
.setBreakStrategy(mBreakStrategy)
.setHyphenationFrequency(mHyphenationFrequency);
我们画板的绘制文字也是用到了这个StaticLayout,它有三个构造方法,我们用最少那个即可:
public StaticLayout(CharSequence source, //字符串
TextPaint paint, //画笔对象
int width, //layout的宽度,字符串超出宽度时自动换行。
Layout.Alignment align, //layout的对其方式,有ALIGN_CENTER, ALIGN_NORMAL, ALIGN_OPPOSITE 三种。
float spacingmult, //相对行间距,相对字体大小,1.5f表示行间距为1.5倍的字体高度。
float spacingadd, //在基础行距上添加多少
boolean includepad) //文本顶部和底部是否留白
所以,bean类在之前的基础上,添加了文本、宽度、xy轴的偏移,然后绘制的时候,利用staticLayout进行了绘制。
data class PaintBean(
var mPaint: Paint, //保存画笔
var mPath: Path?, //保存路径
var mText: String, //文本
var mWidth: Int,
var mOffX: Float,
var mOffY: Float,
private @TPTextView.EditMode var mMode:Long
) {
constructor(mPaint: Paint, mPath: Path) : this(mPaint,mPath,"",0,0f,0f,TPTextView.EDIT_MODE_PEN)
/**
* 撤销和反撤销之后 重新绘制
* @param canvas 绘制的画布
*/
fun draw(canvas: Canvas){
when(mMode){
TPTextView.EDIT_MODE_TEXT -> {
if(!TextUtils.isEmpty(mText)){
//调节画布起始坐标进行绘制
canvas.translate(mOffX,mOffY)
//利用staticLayout生成文字,不然不能换行
val staticLayout = StaticLayout(mText,mPaint as TextPaint,mWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false)
staticLayout.draw(canvas)
//Log.e("@@","长度:"+staticLayout.width)
//canvas.drawText(mText,mTextOffX,mTextOffY,mPaint)
//恢复画布坐标
canvas.translate(-mOffX,-mOffY)
}
}
else -> {
canvas.drawPath(mPath,mPaint)
}
}
}
fun getMode():Long = mMode
}
2.3 弹窗处理
接下来,设置一个弹框PopupWindow进行文本的输入,弹窗里面的控件就是一个EditText。 在弹窗消失的时候添加到画笔列表,然后进行重绘。 在这里有三点注意点
private var mTextPopup: PopupWindow? = null
private var mTextView: EditText? = null
/**
* 显示popup文本输入弹窗
*/
private fun showTextPopup() {
if (null == mTextPopup) {
mTextView = EditText(context)
mTextView?.hint = "文字"
mTextPopup = PopupWindow(mTextView,
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
true)
mTextPopup?.setOnDismissListener {
if (!TextUtils.isEmpty(mTextView?.text)) {
//添加到列表
mPaintedList.add(
PaintBean(TextPaint(mPaint), null, mTextView?.text.toString(), (width - preX).toInt(),preX,preY - mTextView!!.height / 2, EDIT_MODE_TEXT))
invalidate()
}
}
//让popup显示在软键盘上面
mTextPopup?.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
}
mTextView?.requestFocus()
//自动弹出软键盘,会导致布局变化,重测量、绘制
val imm = context.getSystemService(Service.INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS)
mTextPopup?.showAtLocation(this, Gravity.TOP and Gravity.LEFT, preX.toInt(), preY.toInt()+mTextView!!.height)
}
在触摸的时候,进行显示。 移动的时候不用操作,手指起来的时候也不用操作
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> { //手指按下的时候
//记录上次触摸的坐标,注意ACTION_DOWN方法只会执行一次
preX = event.x
preY = event.y
when (mMode) {
EDIT_MODE_TEXT -> {
//弹出popupWidnwo输入text
showTextPopup()
//文字在隐藏的时候添加到list
}
else -> {
//将起始点移动到当前坐标
mPath.moveTo(event.x, event.y)
mPaintedList.add(PaintBean(Paint(mPaint), Path(mPath)))
}
}
}
MotionEvent.ACTION_MOVE -> { //手指移动的时候
when (mMode) {
EDIT_MODE_TEXT -> {
}
else -> {
//绘制圆滑曲线,即贝塞尔曲线,贝塞尔曲线这个知识自行了解
mPaintedList.get(mPaintedList.size - 1).mPath?.quadTo(preX, preY, event.x, event.y)
preX = event.x
preY = event.y
//重新绘制,会调用onDraw方法
invalidate()
}
}
}
MotionEvent.ACTION_UP -> {}
}
return true
}
因为绘制在bean类里面,所以view的onDraw方法还是以前那样,不需要变化:
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//超出缓存的就固化到缓存bitmap
while (mPaintedList.size > PAINT_RECORED_NUM) {
val paint = mPaintedList.removeAt(0)
paint.draw(mHoldCanvas!!)
}
//绘制固化的内容到缓存Canvas
mBufferCanvas?.drawBitmap(mHoldBitmap, 0f, 0f, null)
//绘制记录的画笔
for (paint in mPaintedList) {
paint.draw(mBufferCanvas!!)
}
//画出缓存bitmap的内容
canvas.drawBitmap(mBufferBitmap, 0f, 0f, null)
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持亿速云。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。