服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android画板开发之添加文本文字

Android画板开发之添加文本文字

2022-09-05 15:15tpnet Android

这篇文章主要为大家详细介绍了Android画板开发之添加文本文字功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

一、前言

添加文本,也是属于 一个比较简单的功能,在第二篇的时候,添加了橡皮擦,在橡皮擦里面通过一个模式的形式进行画笔的判断,当然文本也是如此,添加一个文本模式,在onTouchDown的时候,弹出PopupWindow,输入文本,然后PopupWindow消失的时候,利用staticLayout绘制到画布上即可。当然也有些需要注意的地方

Android画板开发之添加文本文字

下面一步步来实现

二、实现

2.1 添加文本模式

例如橡皮擦那样,添加多一个文本模式,然后setModel的时候,需要把画笔的样式修改为FILL,如果是STROKE进行文字绘制会变成空心文字。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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行可以看到。

?
1
2
3
4
5
6
7
8
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,它有三个构造方法,我们用最少那个即可:

?
1
2
3
4
5
6
7
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进行了绘制。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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。 在弹窗消失的时候添加到画笔列表,然后进行重绘。 在这里有三点注意点

  • 软键盘自动弹出
  • 编辑框显示在软键盘上面
  • 弹框显示的位置
  • 右边越界
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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)
 
  }

在触摸的时候,进行显示。 移动的时候不用操作,手指起来的时候也不用操作

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@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方法还是以前那样,不需要变化:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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)
 
  }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/niubitianping/article/details/78421214

延伸 · 阅读

精彩推荐