前言
最近review驾驶证的上传模块业务,里面部分实现写的不错;效果如下,特此记录。
1、视图的结构
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/cert_camera_textureView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.xxx.view.RecordMaskView
android:id="@+id/cert_mask"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
2、TextureView的使用,扩大到全屏
简化了视图接口的定义,只拿一个举例子
cert_camera_textureView.surfaceTextureListener =
object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(
surface: SurfaceTexture?,
width: Int,
height: Int
) {
if (hasVideoRecordPermission()) {
startPreview()
}
}
override fun onSurfaceTextureSizeChanged(
surface: SurfaceTexture?,
width: Int,
height: Int
) {
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
return true
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
}
}
fun startPreview(holder: SurfaceTexture, width: Int, height: Int) {
initCamera(width, height)
try {
mCamera.setPreviewTexture(holder)
mCamera.startPreview()
} catch (e: Exception) {
e.printStackTrace()
}
}
3、 底部视图的精美实现
涉及到不少画布的操纵,实现确实比较精美
class RecordMaskView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var currentStatus = DEFAULT
private val blackColor by lazy {
ContextCompat.getColor(context, R.color.sc_common_color_000000)
}
private val whiteColor by lazy {
ContextCompat.getColor(context, R.color.color_white_bg)
}
private val grayColor by lazy {
ContextCompat.getColor(context, R.color.sc_common_color_879394)
}
private val bigDottedBoxBitmap by lazy {
BitmapFactory.decodeResource(resources, R.drawable.sc_mask_big_dotted_box)
}
private val smallDottedBoxBitmap by lazy {
BitmapFactory.decodeResource(resources, R.drawable.sc_mask_small_dotted_box)
}
companion object {
const val DEFAULT = 0
const val PREVIEW = 1
const val PLAYING = 2
}
private var paint: Paint = Paint()
init {
paint.isAntiAlias = true
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.let {
drawByStatus(it)
}
}
private fun drawByStatus(canvas: Canvas) {
when (currentStatus) {
DEFAULT -> drawDefaultView(canvas)
PREVIEW -> drawPreviewView(canvas)
PLAYING -> drawPlayingView(canvas)
else -> {
}
}
}
fun showDefault() {
currentStatus = DEFAULT
postInvalidate()
}
fun showPreview() {
currentStatus = PREVIEW
postInvalidate()
}
fun showPlaying() {
currentStatus = PLAYING
postInvalidate()
}
/**
* 默认样式
*/
private fun drawDefaultView(canvas: Canvas) {
drawBlackBg(canvas)
paint.color = grayColor
val transparentX = dip(16F).toFloat()
val transparentY = dip(151F).toFloat()
val transparentWidth = width - 2 * dip(16F).toFloat()
val transparentHeight = dip(251F).toFloat()
canvas.drawRoundRect(
transparentX, transparentY,
transparentX + transparentWidth,
transparentY + transparentHeight,
dip(8F).toFloat(),
dip(8F).toFloat(),
paint
)
drawVirtualBox(canvas)
}
/**
* 预览样式
*/
private fun drawPreviewView(canvas: Canvas) {
val layer = canvas.saveLayer(
0f, 0f, width.toFloat(), height.toFloat(),
null, Canvas.ALL_SAVE_FLAG
)
drawBlackBg(canvas)
drawTranCenter(canvas)
canvas.restoreToCount(layer)
drawVirtualBox(canvas)
}
/**
* 播放样式
*/
private fun drawPlayingView(canvas: Canvas) {
val layer = canvas.saveLayer(
0f, 0f, width.toFloat(), height.toFloat(),
null, Canvas.ALL_SAVE_FLAG
)
drawBlackBg(canvas)
drawTranCenter(canvas)
canvas.restoreToCount(layer)
}
private fun drawTranCenter(canvas: Canvas) {
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
val transparentX = dip(16F).toFloat()
val transparentY = dip(151F).toFloat()
val transparentWidth = width - 2 * dip(16F).toFloat()
val transparentHeight = dip(251F).toFloat()
canvas.drawRoundRect(
transparentX, transparentY,
transparentX + transparentWidth,
transparentY + transparentHeight,
dip(8F).toFloat(),
dip(8F).toFloat(),
paint
)
paint.xfermode = null
}
/**
* 虚线框&&文字
*/
private fun drawVirtualBox(canvas: Canvas) {
//外部虚线矩形
paint.alpha = 0xFF
val outBoxX = dip(34F).toFloat()
val outBoxY = dip(173F).toFloat()
val outBoxWidth = width - 2 * dip(34F)
val outBoxHeight = dip(207F)
val outMatrix = Matrix()
outMatrix.postScale(
outBoxWidth / (bigDottedBoxBitmap.width * 1.0f),
outBoxHeight / (bigDottedBoxBitmap.height * 1.0f)
)
outMatrix.postTranslate(outBoxX, outBoxY)
canvas.drawBitmap(bigDottedBoxBitmap, outMatrix, paint)
//内部虚线矩形
val innerBoxX = dip(125F).toFloat()
val innerBoxY = dip(292F).toFloat()
val innerBoxWidth = width - 2 * dip(125F).toFloat()
val innerBoxHeight = dip(42F).toFloat()
val innerMatrix = Matrix()
innerMatrix.postScale(
innerBoxWidth / (smallDottedBoxBitmap.width * 1.0f),
innerBoxHeight / (smallDottedBoxBitmap.height * 1.0f)
)
innerMatrix.postTranslate(innerBoxX, innerBoxY)
canvas.drawBitmap(smallDottedBoxBitmap, innerMatrix, paint)
//文字
paint.textAlign = Paint.Align.CENTER
paint.color = whiteColor
paint.textSize = sp(16).toFloat()
val fontMetrics = paint.fontMetrics
val distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom
val baseline = innerBoxY + innerBoxHeight / 2 + distance
canvas.drawText(
context.getString(R.string.sc_cert_mask_tip),
width / 2f,
baseline,
paint
)
}
/**
* 黑色背景
*/
private fun drawBlackBg(canvas: Canvas) {
paint.color = blackColor
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
}
}
4、总结
纸上得来终觉浅,绝知此事要躬行。
- View基础绘制中画布,画笔的使用需要勤加练习方能掌握;
- View的自定义控件实在不是一件简单的事儿。