Comments added, code cleared
This commit is contained in:
@@ -4,31 +4,36 @@ import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.text.TextPaint
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
/**
|
||||
* Класс наследован от SurfaceView
|
||||
* В нем отрисовывается проход фразы Hello World за 20 кадров
|
||||
* в превью можно переключать кадры, меняя _frameNumber от 0 до 19
|
||||
* В режиме редактирования для удобной отладки строка заполняется красным фоном
|
||||
*
|
||||
*/
|
||||
class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
||||
|
||||
private var _helloWorldString: String = ""
|
||||
private var _textSize: Float = 100f
|
||||
private var _frameNumber: Int = 7
|
||||
private var _helloWorldString: String = "" //Строка для отображения
|
||||
private var _frameNumber: Int = 7 // Инициализация номера фрейма, видно на превью
|
||||
private var _frameRate: Int = 30 //Частота кадров
|
||||
|
||||
private lateinit var textPaint: TextPaint
|
||||
private lateinit var backgroundPaint: Paint
|
||||
private lateinit var backgroundPaint: Paint //Фон надписи в режиме редактирвоания
|
||||
private var textWidth: Float = 0f
|
||||
private var textHeight: Float = 0f
|
||||
private var textPositionVertical = 0f
|
||||
private var frameDelta = 0f
|
||||
|
||||
private var mainJob: Job? = null
|
||||
private var persistentSurface: Surface? = null
|
||||
|
||||
private var persistentSurface: Surface? = null //Surface для рендера и сохранения видео
|
||||
|
||||
/**
|
||||
* The text to draw
|
||||
* Текст для отрисовки
|
||||
*/
|
||||
var helloWorldString: String
|
||||
get() = _helloWorldString
|
||||
@@ -38,22 +43,24 @@ class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
/**
|
||||
* font Size
|
||||
* Частота кадров
|
||||
*/
|
||||
var textSize: Float
|
||||
get() = _textSize
|
||||
var frameRate: Int
|
||||
get() = _frameRate
|
||||
set(value) {
|
||||
_textSize = value
|
||||
invalidateTextPaintAndMeasurements()
|
||||
_frameRate = value
|
||||
}
|
||||
var frameNumber: Int
|
||||
|
||||
/**
|
||||
* Номер фрейма, используется только в режиме редактирования для превью
|
||||
*/
|
||||
var frameNumber: Int
|
||||
get() = _frameNumber
|
||||
set(value) {
|
||||
_frameNumber = value % 20
|
||||
invalidateTextPaintAndMeasurements()
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
holder.addCallback(this)
|
||||
}
|
||||
@@ -75,20 +82,14 @@ class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun attrsInit(attrs: AttributeSet?, defStyle: Int) {
|
||||
// Load attributes
|
||||
|
||||
val a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.TestView, defStyle, 0
|
||||
)
|
||||
|
||||
_helloWorldString = a.getString(R.styleable.TestView_helloString) ?: "Test String"
|
||||
_textSize = a.getDimension(
|
||||
R.styleable.TestView_textSize,
|
||||
textSize
|
||||
)
|
||||
_frameNumber = a.getInteger(
|
||||
R.styleable.TestView_frameNumber,
|
||||
frameNumber
|
||||
)
|
||||
_frameRate = a.getInteger(R.styleable.TestView_frameRate, 30)
|
||||
_frameNumber = a.getInteger(R.styleable.TestView_frameNumber, frameNumber)
|
||||
a.recycle()
|
||||
|
||||
textPaint = TextPaint().apply {
|
||||
@@ -103,17 +104,20 @@ class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
||||
|
||||
invalidateTextPaintAndMeasurements()
|
||||
}
|
||||
|
||||
fun setExternalSurface(surface: Surface) {
|
||||
persistentSurface = surface
|
||||
}
|
||||
|
||||
/**
|
||||
* Перегруженный onDraw остался только для превью
|
||||
*/
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
super.onDraw(canvas)
|
||||
if (isInEditMode)
|
||||
canvas?.let {
|
||||
drawHello(canvas, _frameNumber)
|
||||
}
|
||||
|
||||
}
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
@@ -121,38 +125,46 @@ class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
||||
}
|
||||
private fun invalidateTextPaintAndMeasurements() {
|
||||
val textBounds = Rect()
|
||||
textPaint.let {
|
||||
it.textSize = textSize
|
||||
}
|
||||
textPaint.getTextBounds(helloWorldString, 0, helloWorldString.length, textBounds)
|
||||
|
||||
textPaint.getTextBounds(_helloWorldString, 0, _helloWorldString.length, textBounds)
|
||||
textHeight = textBounds.height()+0f
|
||||
textWidth = textBounds.width()+0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Выделение красным фоном надписи в превью
|
||||
*/
|
||||
private fun drawBounds(canvas: Canvas, sx: Int, sy: Int) {
|
||||
val textBounds = Rect()
|
||||
textPaint.getTextBounds(helloWorldString, 0, helloWorldString.length, textBounds)
|
||||
textBounds.right = textPaint.measureText(helloWorldString).toInt()
|
||||
textPaint.getTextBounds(_helloWorldString, 0, _helloWorldString.length, textBounds)
|
||||
textBounds.right = textPaint.measureText(_helloWorldString).toInt()
|
||||
textBounds.offsetTo(sx, sy-textHeight.toInt()+textPaint.descent().toInt())
|
||||
canvas.drawRect(textBounds, backgroundPaint)
|
||||
}
|
||||
/**
|
||||
* todo comment
|
||||
*/
|
||||
|
||||
fun drawHello(canvas: Canvas, frameNumber: Int) {
|
||||
/**
|
||||
* Основной метод прорисовки надписи
|
||||
* Так как этот метод используется для различных размеров Canvas,
|
||||
* я решил сделать размер шрифта равным 1/20 от высоты Canvas
|
||||
*
|
||||
*/
|
||||
private fun drawHello(canvas: Canvas, frameN: Int) {
|
||||
|
||||
val textSize = canvas.height/20f
|
||||
textPaint.textSize = textSize
|
||||
textWidth = textPaint.measureText(helloWorldString)
|
||||
textWidth = textPaint.measureText(_helloWorldString)
|
||||
|
||||
textPositionVertical = canvas.height/2f+textSize/2f
|
||||
frameDelta = (canvas.width+textWidth)/20f
|
||||
val textPositionVertical = canvas.height/2f+textSize/2f
|
||||
val frameDelta = (canvas.width+textWidth)/20f
|
||||
|
||||
var xPos = frameNumber*frameDelta-textWidth
|
||||
val xPos = frameN*frameDelta-textWidth
|
||||
|
||||
if (isInEditMode) drawBounds(canvas, xPos.toInt(), textPositionVertical.toInt())
|
||||
if (isInEditMode) {
|
||||
invalidateTextPaintAndMeasurements()
|
||||
drawBounds(canvas, xPos.toInt(), textPositionVertical.toInt())
|
||||
}
|
||||
|
||||
canvas.drawText( helloWorldString, xPos, textPositionVertical, textPaint)
|
||||
canvas.drawText( _helloWorldString, xPos, textPositionVertical, textPaint)
|
||||
|
||||
}
|
||||
private fun clearCanvas(canvas: Canvas) {
|
||||
@@ -165,20 +177,31 @@ class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
||||
renderLoop()
|
||||
}
|
||||
}
|
||||
|
||||
@Volatile private var maxFramesRender = 120
|
||||
@Volatile private var framesRendered = -1
|
||||
|
||||
|
||||
/**
|
||||
* Метод, запускающий отсчет фреймов для записи
|
||||
*/
|
||||
fun startRecording(maxFrames: Int) {
|
||||
maxFramesRender = maxFrames
|
||||
framesRendered++
|
||||
}
|
||||
var onLastFrame : () -> Unit = @Synchronized {}
|
||||
|
||||
/**
|
||||
* Метод, который будет вызван из корутины после рендера последнего фрейма для записи
|
||||
* Он будет переназначен во время инициализации енкодера
|
||||
*/
|
||||
var onLastFrame : () -> Unit = {}
|
||||
|
||||
fun setOnLastFrameRecordedListener(listener: () -> Unit) {
|
||||
onLastFrame = listener
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрисовка превью и здесь же рендер фреймов
|
||||
*/
|
||||
private suspend fun renderLoop() {
|
||||
while(GlobalScope.isActive) {
|
||||
val timeInMillis = measureTimeMillis {
|
||||
@@ -193,13 +216,15 @@ class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
||||
preview()
|
||||
|
||||
_frameNumber++
|
||||
}
|
||||
if (_frameNumber >= 20) _frameNumber = 0
|
||||
delay(33-timeInMillis)
|
||||
}
|
||||
//Расчет паузы перед отрисовкой следующего фрейма
|
||||
delay(1000/_frameRate-timeInMillis)
|
||||
|
||||
}
|
||||
}
|
||||
fun render() {
|
||||
//Рендер фрейма для записи видео
|
||||
private fun render() {
|
||||
persistentSurface?.let {surface ->
|
||||
val pCanvas = surface.lockCanvas(null)
|
||||
pCanvas?.let {
|
||||
@@ -211,8 +236,8 @@ class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun preview() {
|
||||
//Такой же рендер, но видимый на View и работающий постоянно
|
||||
private fun preview() {
|
||||
val canvas = holder.lockCanvas()
|
||||
canvas?.let {
|
||||
clearCanvas(it)
|
||||
@@ -224,7 +249,7 @@ class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||
|
||||
}
|
||||
|
||||
//Остановка корутины при уничтожении surface
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
runBlocking {
|
||||
mainJob?.cancelAndJoin()
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
package su.rst10h.loopedworld
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import android.text.TextPaint
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
|
||||
/**
|
||||
* TODO: document your custom view class.
|
||||
*/
|
||||
class TestView : View {
|
||||
|
||||
private var _helloWorldString: String = ""
|
||||
private var _textSize: Float = 100f // TODO: use a default from R.dimen...
|
||||
private var _frameNumber: Int = 12
|
||||
|
||||
private lateinit var textPaint: TextPaint
|
||||
private var textWidth: Float = 0f
|
||||
private var textHeight: Float = 0f
|
||||
private var textPositionVertical = 0f
|
||||
private var frameDelta = 0f
|
||||
|
||||
/**
|
||||
* The text to draw
|
||||
*/
|
||||
var helloWorldString: String
|
||||
get() = _helloWorldString
|
||||
set(value) {
|
||||
_helloWorldString = value
|
||||
invalidateTextPaintAndMeasurements()
|
||||
}
|
||||
|
||||
/**
|
||||
* font Size
|
||||
*/
|
||||
var textSize: Float
|
||||
get() = _textSize
|
||||
set(value) {
|
||||
_textSize = value
|
||||
invalidateTextPaintAndMeasurements()
|
||||
}
|
||||
var frameNumber: Int
|
||||
get() = _frameNumber
|
||||
set(value) {
|
||||
_frameNumber = value
|
||||
invalidateTextPaintAndMeasurements()
|
||||
}
|
||||
constructor(context: Context) : super(context) {
|
||||
init(null, 0)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(attrs, 0)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyle
|
||||
) {
|
||||
init(attrs, defStyle)
|
||||
}
|
||||
|
||||
private fun init(attrs: AttributeSet?, defStyle: Int) {
|
||||
// Load attributes
|
||||
val a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.TestView, defStyle, 0
|
||||
)
|
||||
|
||||
_helloWorldString = a.getString(R.styleable.TestView_helloString) ?: "Test String Hello"
|
||||
_textSize = a.getDimension(
|
||||
R.styleable.TestView_textSize,
|
||||
textSize
|
||||
)
|
||||
_frameNumber = a.getInteger(
|
||||
R.styleable.TestView_frameNumber,
|
||||
frameNumber
|
||||
)
|
||||
a.recycle()
|
||||
|
||||
textPaint = TextPaint().apply {
|
||||
flags = Paint.ANTI_ALIAS_FLAG
|
||||
textAlign = Paint.Align.LEFT
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
}
|
||||
|
||||
|
||||
invalidateTextPaintAndMeasurements()
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
invalidateTextPaintAndMeasurements()
|
||||
}
|
||||
private fun invalidateTextPaintAndMeasurements() {
|
||||
textPaint.let {
|
||||
it.textSize = textSize
|
||||
textWidth = it.measureText(helloWorldString)
|
||||
textHeight = it.fontMetrics.bottom
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* todo commen
|
||||
*/
|
||||
private fun drawHelloWorld(canvas: Canvas, frameNumber: Int) {
|
||||
|
||||
textPositionVertical = canvas.height/2f+textHeight
|
||||
frameDelta = (width+textWidth)/20f
|
||||
var xPos = frameNumber*frameDelta-textWidth
|
||||
// if (isInEditMode) {
|
||||
// xPos = width/2f-textWidth/2f
|
||||
// }
|
||||
helloWorldString.let {
|
||||
canvas.drawText( it, xPos, textPositionVertical, textPaint)
|
||||
}
|
||||
}
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
|
||||
super.onDraw(canvas)
|
||||
|
||||
// TODO: consider storing these as member variables to reduce
|
||||
|
||||
val paddingLeft = paddingLeft
|
||||
val paddingTop = paddingTop
|
||||
val paddingRight = paddingRight
|
||||
val paddingBottom = paddingBottom
|
||||
|
||||
val contentWidth = width - paddingLeft - paddingRight
|
||||
val contentHeight = height - paddingTop - paddingBottom
|
||||
|
||||
drawHelloWorld(canvas, _frameNumber)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<su.rst10h.loopedworld.TestView
|
||||
<su.rst10h.loopedworld.HelloSurface
|
||||
style="@style/Widget.Theme.Inspiry.MyView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -11,6 +11,7 @@
|
||||
android:paddingBottom="40dp"
|
||||
app:frameNumber="8"
|
||||
app:helloString="Hello World"
|
||||
app:textSize="55sp" />
|
||||
app:frameRate = "30"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,7 +1,7 @@
|
||||
<resources>
|
||||
<declare-styleable name="TestView">
|
||||
<attr name="helloString" format="string" />
|
||||
<attr name="textSize" format="dimension" />
|
||||
<attr name="frameRate" format="integer" />
|
||||
<attr name="frameNumber" format="integer" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user