当前位置: 移动技术网 > 移动技术>移动开发>Android > Android自定义view之潜艇大作战

Android自定义view之潜艇大作战

2020年07月27日  | 移动技术网移动技术  | 我要评论

效果如下:

在这里插入图片描述
根目录 build.gradle

 ext.kotlin_version = '1.3.61'
 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

model的build.gradle

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'


android{
    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}


  implementation 'androidx.core:core-ktx:1.2.0'


障碍物基类

sealed class Bar(context: Context) {

    protected open val bmp = context.getDrawable(R.mipmap.bar)!!.toBitmap()

    protected abstract val srcRect: Rect

    private lateinit var dstRect: Rect

    private val paint = Paint()

    var h = 0F
        set(value) {
            field = value
            dstRect = Rect(0, 0, w.toInt(), h.toInt())
        }

    var w = 0F
        set(value) {
            field = value
            dstRect = Rect(0, 0, w.toInt(), h.toInt())
        }

    var x = 0F
        set(value) {
            view.x = value
            field = value
        }

    val y
        get() = view.y

    internal val view by lazy {
        BarView(context) {
            it?.apply {
                drawBitmap(
                    bmp,
                    srcRect,
                    dstRect,
                    paint
                )
            }
        }
//            .apply {
//                setBackgroundColor(context.getColor(R.color.colorAccent))
//            }
    }

}

/**
 * 屏幕上方障碍物
 */
class UpBar(context: Context, container: ViewGroup) : Bar(context) {

    private val _srcRect by lazy(LazyThreadSafetyMode.NONE) {
        Rect(0, (bmp.height * (1 - (h / container.height))).toInt(), bmp.width, bmp.height)
    }
    override val srcRect: Rect
        get() = _srcRect

}

/**
 * 屏幕下方障碍物
 */
class DnBar(context: Context, container: ViewGroup) : Bar(context) {

    override val bmp = super.bmp.let {
        Bitmap.createBitmap(
            it, 0, 0, it.width, it.height,
            Matrix().apply { postRotate(-180F) }, true
        )
    }

    private val _srcRect by lazy(LazyThreadSafetyMode.NONE) {
        Rect(0, 0, bmp.width, (bmp.height * (h / container.height)).toInt())
    }

    override val srcRect: Rect
        get() = _srcRect
}


@SuppressLint("ViewConstructor")
internal class BarView(context: Context?, private val block: (Canvas?) -> Unit) :
    View(context) {

    override fun onDraw(canvas: Canvas?) {
        block((canvas))
    }
}

后景容器类

class BackgroundView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {

    private lateinit var _timer: Timer

    private var _random = Random()

    private val _anims = mutableListOf<ValueAnimator>()

    private val _barWidth by lazy { width * BAR_WIDTH_FACTOR } // 障碍物默认宽度

    private val _barHeight by lazy { height * BAR_HEIGHT_FACTOR } // 障碍物默认高度

    private val _gap by lazy { height * BAR_GAP_FACTOR } // 障碍物空洞间隙

    private val _step by lazy { height * 0.1F } // 高度调整步进单位

    internal val barsList = mutableListOf<Bars>()

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        barsList.flatMap { listOf(it.up, it.down) }.forEach {
            val w = it.view.measuredWidth
            val h = it.view.measuredHeight
            when (it) {
                is UpBar -> it.view.layout(0, 0, w, h)
                else -> it.view.layout(0, height - h, w, height)
            }
        }
    }

    /**
     * 游戏结束,停止所有障碍物的移动
     */
    @UiThread
    fun stop() {
        _timer.cancel()
        _anims.forEach { it.cancel() }
        _anims.clear()
    }

    /**
     * 定时刷新障碍物:
     * 1. 创建
     * 2. 添加到视图
     * 3. 移动
     */
    @UiThread
    fun start() {
        _clearBars()
        Timer().also { _timer = it }.schedule(object : TimerTask() {
            override fun run() {
                post {
                    _createBars(context, barsList.lastOrNull()).let {
                        _addBars(it)
                        _moveBars(it)
                    }
                }
            }

        },
            FIRST_APPEAR_DELAY_MILLIS,
            BAR_APPEAR_INTERVAL_MILLIS
        )
    }

    /**
     * 创建障碍物(上下两个为一组)
     */
    private fun _createBars(context: Context, pre: Bars?) = run {
        val up = UpBar(context, this).apply {
            h = pre?.let {
                val step = when {
                    it.up.h >= height - _gap - _step -> -_step
                    it.up.h <= _step -> _step
                    _random.nextBoolean() -> _step
                    else -> -_step
                }
                it.up.h + step
            } ?: _barHeight
            w = _barWidth
        }

        val down = DnBar(context, this).apply {
            h = height - up.h - _gap
            w = _barWidth
        }

        Bars(up, down)

    }

    /**
     * 添加到屏幕
     */
    private fun _addBars(bars: Bars) {
        barsList.add(bars)
        bars.asArray().forEach {
            addView(
                it.view,
                ViewGroup.LayoutParams(
                    it.w.toInt(),
                    it.h.toInt()
                )
            )
        }
    }

    /**
     * 使用属性动画移动障碍物(从屏幕左侧到右侧)
     */
    private fun _moveBars(bars: Bars) {
        _anims.add(
            ValueAnimator.ofFloat(width.toFloat(), -_barWidth)
                .apply {
                    addUpdateListener {
                        bars.asArray().forEach { bar ->
                            bar.x = it.animatedValue as Float
                            if (bar.x + bar.w <= 0) {
                                post { removeView(bar.view) }
                            }
                        }
                    }

                    duration = BAR_MOVE_DURATION_MILLIS
                    interpolator = LinearInterpolator()
                    start()
                })
    }


    /**
     * 游戏重启时,清空障碍物
     */
    private fun _clearBars() {
        barsList.clear()
        removeAllViews()
    }

}


internal data class Bars(val up: Bar, val down: Bar)

private fun Bars.asArray() = arrayOf(up, down)

private const val BAR_WIDTH_FACTOR = 0.22F

private const val BAR_HEIGHT_FACTOR = 0.35F

private const val BAR_GAP_FACTOR = BAR_HEIGHT_FACTOR

private const val BAR_MOVE_DURATION_MILLIS = 4000L

private const val BAR_APPEAR_INTERVAL_MILLIS = (BAR_MOVE_DURATION_MILLIS / 2.2).toLong()

internal const val FIRST_APPEAR_DELAY_MILLIS = 3000L

潜艇类

class Boat(context: Context) {

    internal val view by lazy { BoatView(context) }

    val h
        get() = view.height.toFloat()

    val w
        get() = view.width.toFloat()

    val x
        get() = view.x

    val y
        get() = view.y

    /**
     * 移动到指定坐标
     */
    fun moveTo(x: Int, y: Int) {
        view.smoothMoveTo(x, y)
    }

}


internal class BoatView(context: Context?) : AppCompatImageView(context) {

    private val _scroller by lazy { OverScroller(context) }

    private val _res = arrayOf(
        R.mipmap.boat_000,
        R.mipmap.boat_002
    )

    private var _rotationAnimator: ObjectAnimator? = null

    private var _cnt = 0
        set(value) {
            field = if (value > 1) 0 else value
        }

    init {
        scaleType = ScaleType.FIT_CENTER
        _startFlashing()
    }

    private fun _startFlashing() {
        postDelayed({
            setImageResource(_res[_cnt++])
            _startFlashing()
        }, 500)
    }

    override fun computeScroll() {
        super.computeScroll()

        if (_scroller.computeScrollOffset()) {

            x = _scroller.currX.toFloat()
            y = _scroller.currY.toFloat()

            // Keep on drawing until the animation has finished.
            postInvalidateOnAnimation()
        }

    }

    /**
     * 移动更加顺换
     */
    internal fun smoothMoveTo(x: Int, y: Int) {
        if (!_scroller.isFinished) _scroller.abortAnimation()
        _rotationAnimator?.let { if (it.isRunning) it.cancel() }

        val curX = this.x.toInt()
        val curY = this.y.toInt()

        val dx = (x - curX)
        val dy = (y - curY)
        _scroller.startScroll(curX, curY, dx, dy, 250)

        _rotationAnimator = ObjectAnimator.ofFloat(
            this,
            "rotation",
            rotation,
            Math.toDegrees(atan((dy / 100.toDouble()))).toFloat()
        ).apply {
            duration = 100
            start()
        }

        postInvalidateOnAnimation()
    }
}

前景容器类

class ForegroundView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs),
    CameraHelper.FaceDetectListener {

    private var _isStop: Boolean = false

    internal var boat: Boat? = null

    private val _width by lazy { (width * BOAT_WIDTH_FACTOR).toInt() }

    private val _widthOffset by lazy { dip2px(context, 50F) }

    private val _heightOffset by lazy { dip2px(context, 150F) }

    /**
     * 游戏停止,潜艇不再移动
     */
    @MainThread
    fun stop() {
        _isStop = true
    }

    /**
     * 游戏开始时通过动画进入
     */
    @MainThread
    fun start() {
        _isStop = false
        if (boat == null) {
            boat = Boat(context).also {
                post {
                    addView(it.view, _width, _width)
                    AnimatorSet().apply {
                        play(
                            ObjectAnimator.ofFloat(
                                it.view,
                                "y",
                                0F,
                                this@ForegroundView.height / 2f
                            )
                        ).with(
                            ObjectAnimator.ofFloat(it.view, "rotation", 0F, 360F)
                        )
                        doOnEnd { _ -> it.view.rotation = 0F }
                        duration = 1000
                    }.start()
                }
            }
        }
    }

    /**
     * 接受人脸识别的回调,移动位置
     */
    override fun onFaceDetect(faces: Array<Face>, facesRect: ArrayList<RectF>) {
        if (_isStop) return
        if (facesRect.isNotEmpty()) {
            boat?.run {
                val face = facesRect.first()
                val x = (face.left - _widthOffset).toInt()
                val y = (face.top + _heightOffset).toInt()
                moveTo(x, y)
            }
            _face = facesRect.first()
        }
    }


    //debug
    private val _paint by lazy {
        Paint().apply {
            color = Color.parseColor("#42ed45")
            style = Paint.Style.STROKE
            strokeWidth = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                1f,
                context.resources.displayMetrics
            )
            isAntiAlias = true
        }
    }

    private var _face: RectF? = null
        set(value) {
            field = value
            invalidate()
        }

    override fun dispatchDraw(canvas: Canvas) {
        super.dispatchDraw(canvas)
        _face?.let {
            canvas.drawRect(it, _paint)
        }
    }

}

private const val BOAT_WIDTH_FACTOR = 0.2F

private fun dip2px(context: Context, dp: Float): Float {
    val scale = context.resources.displayMetrics.density
    return dp * scale * 0.5f
}

相机帮助类


fun Context.toast(msg: String) {
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}

class CameraHelper(val mActivity: Activity, private val mTextureView: XKAutoFitTextureView) {

    private lateinit var mCameraManager: CameraManager
    private var mCameraDevice: CameraDevice? = null
    private var mCameraCaptureSession: CameraCaptureSession? = null

    private var mCameraId = "0"
    private lateinit var mCameraCharacteristics: CameraCharacteristics

    private var mCameraSensorOrientation = 0                                            //摄像头方向
    private var mCameraFacing = CameraCharacteristics.LENS_FACING_BACK             //默认使用前置摄像头
    private var mFaceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF     //人脸检测模式

    private var canExchangeCamera = false                                               //是否可以切换摄像头
    private var mFaceDetectMatrix = Matrix()                                            //人脸检测坐标转换矩阵
    private var mFacesRect = ArrayList<RectF>()                                         //保存人脸坐标信息
    private var mFaceDetectListener: FaceDetectListener? = null                         //人脸检测回调

    private var mCameraHandler: Handler
    private val handlerThread = HandlerThread("CameraThread")

    private lateinit var mPreviewSize: Size

    interface FaceDetectListener {
        fun onFaceDetect(faces: Array<Face>, facesRect: ArrayList<RectF>)
    }

    fun setFaceDetectListener(listener: FaceDetectListener) {
        this.mFaceDetectListener = listener
    }

    init {
        handlerThread.start()
        mCameraHandler = Handler(handlerThread.looper)

        mTextureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
            override fun onSurfaceTextureSizeChanged(
                    surface: SurfaceTexture?,
                    width: Int,
                    height: Int
            ) {
                configureTransform(width, height)
            }

            override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
            }

            override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
                releaseCamera()
                return true
            }

            override fun onSurfaceTextureAvailable(
                    surface: SurfaceTexture?,
                    width: Int,
                    height: Int
            ) {
                initCameraInfo()
                configureTransform(width, height)
            }
        }
    }

    /**
     * 初始化
     */
    private fun initCameraInfo() {
        mCameraManager = mActivity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        val cameraIdList = mCameraManager.cameraIdList
        if (cameraIdList.isEmpty()) {
            mActivity.toast("没有可用相机")
            return
        }

        for (id in cameraIdList) {
            val cameraCharacteristics = mCameraManager.getCameraCharacteristics(id)
            val facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)

            if (facing == mCameraFacing) {
                mCameraId = id
                mCameraCharacteristics = cameraCharacteristics
            }
        }

        val supportLevel =
                mCameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
        if (supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
            mActivity.toast("相机硬件不支持新特性")
        }

        //获取摄像头方向
        mCameraSensorOrientation =
                mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
        //获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
        val configurationMap =
                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!

        val previewSize = configurationMap.getOutputSizes(SurfaceTexture::class.java) //预览尺寸

        // 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高
        mPreviewSize = getBestSize(
                mTextureView.height,
                mTextureView.width,
                previewSize.toList()
        )

        mTextureView.surfaceTexture.setDefaultBufferSize(mPreviewSize.width, mPreviewSize.height)
        mTextureView.setAspectRatio(mPreviewSize.height, mPreviewSize.width)

        initFaceDetect()
        openCamera()
    }


    private fun getBestSize(
            targetWidth: Int,
            targetHeight: Int,
            sizeList: List<Size>
    ): Size {
        val bigEnough = ArrayList<Size>()     //比指定宽高大的Size列表
        val notBigEnough = ArrayList<Size>()  //比指定宽高小的Size列表

        for (size in sizeList) {

            //宽高比 == 目标值宽高比
            if (size.width == size.height * targetWidth / targetHeight
            ) {
                if (size.width >= targetWidth && size.height >= targetHeight)
                    bigEnough.add(size)
                else
                    notBigEnough.add(size)
            }
        }

        //选择bigEnough中最小的值  或 notBigEnough中最大的值
        return when {
            bigEnough.size > 0 -> Collections.min(bigEnough, CompareSizesByArea())
            notBigEnough.size > 0 -> Collections.max(notBigEnough, CompareSizesByArea())
            else -> sizeList[0]
        }
    }

    /**
     * 初始化人脸检测相关信息
     */
    private fun initFaceDetect() {

        val faceDetectModes =
                mCameraCharacteristics.get(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES)  //人脸检测的模式

        mFaceDetectMode = when {
            faceDetectModes!!.contains(CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL) -> CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL
            faceDetectModes!!.contains(CaptureRequest.STATISTICS_FACE_DETECT_MODE_SIMPLE) -> CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL
            else -> CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF
        }

        if (mFaceDetectMode == CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF) {
            mActivity.toast("相机硬件不支持人脸检测")
            return
        }

        val activeArraySizeRect =
                mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)!! //获取成像区域
        val scaledWidth = mPreviewSize.width / activeArraySizeRect.width().toFloat()
        val scaledHeight = mPreviewSize.height / activeArraySizeRect.height().toFloat()

        val mirror = mCameraFacing == CameraCharacteristics.LENS_FACING_FRONT

        mFaceDetectMatrix.setRotate(mCameraSensorOrientation.toFloat())
        mFaceDetectMatrix.postScale(if (mirror) -scaledHeight else scaledHeight, scaledWidth)// 注意交换width和height的位置!
        mFaceDetectMatrix.postTranslate(
                mPreviewSize.height.toFloat(),
                mPreviewSize.width.toFloat()
        )

    }

    /**
     * 打开相机
     */
    @SuppressLint("MissingPermission")
    private fun openCamera() {
        mCameraManager.openCamera(mCameraId, object : CameraDevice.StateCallback() {
            override fun onOpened(camera: CameraDevice) {
                mCameraDevice = camera
                createCaptureSession(camera)
            }

            override fun onDisconnected(camera: CameraDevice) {
            }

            override fun onError(camera: CameraDevice, error: Int) {
                mActivity.toast("打开相机失败!$error")
            }
        }, mCameraHandler)
    }

    /**
     * 创建预览会话
     */
    private fun createCaptureSession(cameraDevice: CameraDevice) {

        val captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)

        val surface = Surface(mTextureView.surfaceTexture)
        captureRequestBuilder.addTarget(surface)  // 将CaptureRequest的构建器与Surface对象绑定在一起
        captureRequestBuilder.set(
                CaptureRequest.CONTROL_AE_MODE,
                CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
        )      // 闪光灯
        captureRequestBuilder.set(
                CaptureRequest.CONTROL_AF_MODE,
                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
        ) // 自动对焦
        if (mFaceDetectMode != CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF)
            captureRequestBuilder.set(
                    CaptureRequest.STATISTICS_FACE_DETECT_MODE,
                    CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE
            )//人脸检测

        // 为相机预览,创建一个CameraCaptureSession对象
        cameraDevice.createCaptureSession(
                arrayListOf(surface),
                object : CameraCaptureSession.StateCallback() {
                    override fun onConfigureFailed(session: CameraCaptureSession) {
                        mActivity.toast("开启预览会话失败!")
                    }

                    override fun onConfigured(session: CameraCaptureSession) {
                        mCameraCaptureSession = session
                        session.setRepeatingRequest(
                                captureRequestBuilder.build(),
                                mCaptureCallBack,
                                mCameraHandler
                        )
                    }

                },
                mCameraHandler
        )
    }

    private val mCaptureCallBack = object : CameraCaptureSession.CaptureCallback() {
        override fun onCaptureCompleted(
                session: CameraCaptureSession,
                request: CaptureRequest,
                result: TotalCaptureResult
        ) {
            super.onCaptureCompleted(session, request, result)
            if (mFaceDetectMode != CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF)
                handleFaces(result)

            canExchangeCamera = true
        }


        override fun onCaptureFailed(
                session: CameraCaptureSession,
                request: CaptureRequest,
                failure: CaptureFailure
        ) {
            super.onCaptureFailed(session, request, failure)
            mActivity.toast("开启预览失败!")
        }
    }

    /**
     * 处理人脸信息
     */
    private fun handleFaces(result: TotalCaptureResult) {
        val faces = result.get(CaptureResult.STATISTICS_FACES)!!
        mFacesRect.clear()

        for (face in faces) {
            val bounds = face.bounds

            val left = bounds.left
            val top = bounds.top
            val right = bounds.right
            val bottom = bounds.bottom

            val rawFaceRect =
                    RectF(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
            mFaceDetectMatrix.mapRect(rawFaceRect)

            var resultFaceRect = if (mCameraFacing == CaptureRequest.LENS_FACING_FRONT) {
                rawFaceRect
            } else {
                RectF(
                        rawFaceRect.left,
                        rawFaceRect.top - mPreviewSize.width,
                        rawFaceRect.right,
                        rawFaceRect.bottom - mPreviewSize.width
                )
            }

            mFacesRect.add(resultFaceRect)

        }

        mActivity.runOnUiThread {
            mFaceDetectListener?.onFaceDetect(faces, mFacesRect)
        }
    }

    /**
     * 切换摄像头
     */
    fun exchangeCamera() {
        if (mCameraDevice == null || !canExchangeCamera || !mTextureView.isAvailable) return

        mCameraFacing = if (mCameraFacing == CameraCharacteristics.LENS_FACING_FRONT)
            CameraCharacteristics.LENS_FACING_BACK
        else
            CameraCharacteristics.LENS_FACING_FRONT

        releaseCamera()
        initCameraInfo()
    }


    fun releaseCamera() {
        mCameraCaptureSession?.close()
        mCameraCaptureSession = null

        mCameraDevice?.close()
        mCameraDevice = null

        canExchangeCamera = false
    }

    fun releaseThread() {
        handlerThread.quitSafely()
        handlerThread.join()
    }


    /**
     * Configures the necessary [android.graphics.Matrix] transformation to `mTextureView`.
     * This method should be called after the camera preview size is determined in
     * setUpCameraOutputs and also the size of `mTextureView` is fixed.
     *
     * @param viewWidth  The width of `mTextureView`
     * @param viewHeight The height of `mTextureView`
     */
    private fun configureTransform(viewWidth: Int, viewHeight: Int) {
        val rotation = mActivity.windowManager.defaultDisplay.rotation
        val matrix = Matrix()
        val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
        val bufferRect = RectF(0f, 0f, mPreviewSize.height.toFloat(), mPreviewSize.width.toFloat())
        val centerX = viewRect.centerX()
        val centerY = viewRect.centerY()
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
            val scale = Math.max(
                    viewHeight.toFloat() / mPreviewSize.height,
                    viewWidth.toFloat() / mPreviewSize.width
            )
            matrix.postScale(scale, scale, centerX, centerY)
            matrix.postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
        } else if (Surface.ROTATION_180 == rotation) {
            matrix.postRotate(180f, centerX, centerY)
        }
        mTextureView.setTransform(matrix)
    }

    private class CompareSizesByArea : Comparator<Size> {
        override fun compare(size1: Size, size2: Size): Int {
            return java.lang.Long.signum(size1.width.toLong() * size1.height - size2.width.toLong() * size2.height)
        }
    }
}

调整到指定的高宽比

public class XKAutoFitTextureView extends TextureView {
    private int mRatioWidth =0;
    private int mRatioHeight =0;


    public XKAutoFitTextureView(Context context) {
        this(context,null);
    }

    public XKAutoFitTextureView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public XKAutoFitTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setAspectRatio(int width,int height){
        if(width <0 || height <0){
            throw  new IllegalArgumentException("Size cannot be negative.");
        }
        mRatioWidth =width;
        mRatioHeight =height;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if(0 == mRatioHeight || 0==mRatioWidth){
            setMeasuredDimension(width,height);
        }else if (width == height * mRatioWidth/mRatioHeight){
            setMeasuredDimension(width,height);
        }else if (width<height * mRatioWidth / mRatioHeight){
            setMeasuredDimension(height * mRatioWidth/mRatioHeight,height);
        }else {
            setMeasuredDimension(width,width*mRatioHeight/mRatioWidth);
        }
    }
}

游戏控制类

class GameController(
        private val activity: AppCompatActivity,
        private val textureView: XKAutoFitTextureView,
        private val bg: BackgroundView,
        private val fg: ForegroundView
) {
    init {
        activity.lifecycle.addObserver(object : LifecycleObserver {

            @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
            fun onDestroy() {
                cameraHelper?.releaseCamera()
                cameraHelper?.releaseThread()
            }
        })
    }

    private val handler by lazy {
        android.os.Handler(activity.mainLooper)
    }

    private var cameraHelper: CameraHelper? = null

    private var _score: Long = 0L

    /**
     * 游戏停止
     */
    fun stop() {
        bg.stop()
        fg.stop()
        _state.value = GameState.Over(_score)
        _score = 0L
    }

    /**
     * 游戏再开
     */
    fun start() {
        initCamera()
        fg.start()
        bg.start()
        _state.value = GameState.Start
        handler.postDelayed({
            startScoring()
        }, FIRST_APPEAR_DELAY_MILLIS)
    }

    /**
     * 开始计分
     */
    private fun startScoring() {
        handler.postDelayed(
                {
                    fg.boat?.run {
                        bg.barsList.flatMap { listOf(it.up, it.down) }
                                .forEach { bar ->
                                    if (isCollision(
                                                    bar.x, bar.y, bar.w, bar.h,
                                                    this.x, this.y, this.w, this.h
                                            )
                                    ) {
                                        stop()
                                        return@postDelayed
                                    }
                                }
                    }
                    _score++
                    _state.value = GameState.Score(_score)
                    startScoring()
                }, 100
        )
    }

    /**
     * 碰撞检测
     */
    private fun isCollision(
            x1: Float,
            y1: Float,
            w1: Float,
            h1: Float,
            x2: Float,
            y2: Float,
            w2: Float,
            h2: Float
    ): Boolean {
        if (x1 > x2 + w2 || x1 + w1 < x2 || y1 > y2 + h2 || y1 + h1 < y2) {
            return false
        }
        return true
    }

    /**
     * 相机初始化
     */
    private fun initCamera() {
        cameraHelper ?: run {
            cameraHelper = CameraHelper(activity, textureView).apply {
                setFaceDetectListener(object : CameraHelper.FaceDetectListener {
                    override fun onFaceDetect(faces: Array<Face>, facesRect: ArrayList<RectF>) {
                        if (facesRect.isNotEmpty()) {
                            fg.onFaceDetect(faces, facesRect)
                        }
                    }
                })
            }
        }
    }

    /**
     * 切换摄像头
     */
    fun switchCamera() {
        cameraHelper?.exchangeCamera()
    }

    /**
     * 游戏状态
     */
    private val _state = MutableLiveData<GameState>()
    internal val gameState: LiveData<GameState>
        get() = _state
}


sealed class GameState(open val score: Long) {

    object Start : GameState(0)

    data class Over(override val score: Long) : GameState(score)

    data class Score(override val score: Long) : GameState(score)

}

权限管理类

object PermissionUtils {

    const val PERMISSION_REQUEST_CODE = 100
    const val REQUEST_PERMISSION = Manifest.permission.CAMERA
    const val PERMISSION_SETTING_CODE = 101

    private var permissionExplainDialog: AlertDialog? = null
    private var permissionSettingDialog: AlertDialog? = null


    fun checkPermission(
            activity: AppCompatActivity,
            callBack: Runnable
    ) {
        if (ContextCompat.checkSelfPermission(
                        activity,
                        REQUEST_PERMISSION
                ) == PackageManager.PERMISSION_GRANTED
        ) callBack.run()
        else startRequestPermission(activity)
    }

    /**
     * 如果用户之前拒绝过,展示需要权限的提示框,否则直接请求相关权限
     */
    private fun startRequestPermission(activity: AppCompatActivity) {

        if (ActivityCompat.shouldShowRequestPermissionRationale(
                        activity,
                        REQUEST_PERMISSION
                )
        ) {
            showPermissionExplainDialog(activity)
        } else {
            requestPermission(activity)
        }
    }


    private fun requestPermission(activity: AppCompatActivity) {
        ActivityCompat.requestPermissions(
                activity,
                arrayOf(REQUEST_PERMISSION),
                PERMISSION_REQUEST_CODE
        )
    }


    /**
     * 展示一个对话框,解释为什么需要此权限
     */
    private fun showPermissionExplainDialog(
            activity: AppCompatActivity
    ) {
        if (permissionExplainDialog == null) {
            permissionExplainDialog = AlertDialog.Builder(activity).setTitle("权限申请")
                    .setMessage(
                            "您刚才拒绝了相关权限,开始游戏需要重新申请权限"
                    )
                    .setPositiveButton("申请权限") { dialog, _ ->
                        requestPermission(activity)
                        dialog.cancel()
                    }
                    .setNegativeButton("退出游戏") { dialog, _ ->
                        dialog.cancel()
                        activity.finish()
                    }
                    .create()
        }

        permissionExplainDialog?.let {
            if (!it.isShowing) {
                it.show()
            }
        }
    }

    fun showPermissionSettingDialog(activity: AppCompatActivity) {
        if (permissionSettingDialog == null) {
            permissionSettingDialog = AlertDialog.Builder(activity)
                    .setTitle("权限设置")
                    .setMessage("您刚才拒绝了相关的权限,请到应用设置页面更改应用的权限")
                    .setPositiveButton("确定") { dialog, _ ->
                        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        val uri = Uri.fromParts("package", activity.packageName, null)
                        intent.data = uri
                        activity.startActivityForResult(intent, PERMISSION_SETTING_CODE)
                        dialog.cancel()
                    }
                    .setNegativeButton("取消") { dialog, _ ->
                        dialog.cancel()
                        activity.finish()
                    }
                    .create()

        }

        permissionSettingDialog?.let {
            if (!it.isShowing) {
                it.show()
            }
        }
    }
}

activity_two.xml

<FrameLayout 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:id="@+id/root"
    android:layout_height="match_parent"
    tools:context=".GameActivity">
    <TextView
        android:id="@+id/score"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:keepScreenOn="true"
        android:textColor="#33b5e5"
        android:textSize="50sp"
        android:textStyle="bold" />

    <com.xiaokexin.testvideodemo.game.BackgroundView
        android:id="@+id/background"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <com.xiaokexin.testvideodemo.game.ForegroundView
        android:id="@+id/foreground"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>


    <!-- This FrameLayout insets its children based on system windows using
         android:fitsSystemWindows. -->
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <LinearLayout
            android:id="@+id/fullscreen_content_controls"
            style="?metaButtonBarStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|center_horizontal"
            android:background="@color/black_overlay"
            android:orientation="horizontal"
            tools:ignore="UselessParent">

            <Button
                android:id="@+id/switch_button"
                style="?metaButtonBarButtonStyle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="切换摄像头" />

        </LinearLayout>

    </FrameLayout>
</FrameLayout>

TwoActivity

class GameActivity : AppCompatActivity() {

    private val gameController by lazy {
        XKAutoFitTextureView(this).let {
            root.addView(
                    it,
                    0,
                    ViewGroup.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.MATCH_PARENT
                    )
            )
            GameController(this, it, background, foreground)
        }
    }

    private val mHideHandler = Handler()
    private val mHidePart2Runnable = Runnable {
        // Delayed removal of status and navigation bar

        // Note that some of these constants are new as of API 16 (Jelly Bean)
        // and API 19 (KitKat). It is safe to use them, as they are inlined
        // at compile-time and do nothing on earlier devices.
        root.systemUiVisibility =
                View.SYSTEM_UI_FLAG_LOW_PROFILE or
                        View.SYSTEM_UI_FLAG_FULLSCREEN or
                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

    }
    private val mShowPart2Runnable = Runnable {
        // Delayed display of UI elements
        fullscreen_content_controls.visibility = View.VISIBLE
    }
    private var mVisible: Boolean = false
    private val mHideRunnable = Runnable { hide() }
    /**
     * Touch listener to use for in-layout UI controls to delay hiding the
     * system UI. This is to prevent the jarring behavior of controls going away
     * while interacting with activity UI.
     */
    private val mDelayHideTouchListener = View.OnTouchListener { _, _ ->
        if (AUTO_HIDE) {
            delayedHide(AUTO_HIDE_DELAY_MILLIS)
        }
        false
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        window.setStatusBarColor(Color.TRANSPARENT);

        setContentView(R.layout.activity_two)

        mVisible = true

        // Set up the user interaction to manually show or hide the system UI.
        root.setOnClickListener { toggle() }

        // Upon interacting with UI controls, delay any scheduled hide()
        // operations to prevent the jarring behavior of controls going away
        // while interacting with the UI.
        switch_button.setOnTouchListener { v, event ->
            gameController.switchCamera()
            mDelayHideTouchListener.onTouch(v, event)
        }

        mHidePart2Runnable.run()
    }


    override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (permissions[0] == REQUEST_PERMISSION && requestCode == PERMISSION_REQUEST_CODE) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startGame()
            } else {
                PermissionUtils.showPermissionSettingDialog(this)
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            PERMISSION_SETTING_CODE -> {
                startGame()
            }
        }
    }


    override fun onPostCreate(savedInstanceState: Bundle?) {
        super.onPostCreate(savedInstanceState)

        // Trigger the initial hide() shortly after the activity has been
        // created, to briefly hint to the user that UI controls
        // are available.
        delayedHide(100)
        startGame()
    }

    @SuppressLint("SetTextI18n")
    private fun startGame() {
        PermissionUtils.checkPermission(this, Runnable {
            gameController.start()
            gameController.gameState.observe(this, Observer {
                when (it) {
                    is GameState.Start ->
                        score.text = "DANGER\nAHEAD"
                    is GameState.Score ->
                        score.text = "${it.score / 10f} m"
                    is GameState.Over ->
                        AlertDialog.Builder(this)
                                .setMessage("游戏结束!成功推进 ${it.score / 10f} 米! ")
                                .setNegativeButton("结束游戏") { _: DialogInterface, _: Int ->
                                    finish()
                                }.setCancelable(false)
                                .setPositiveButton("再来一把") { _: DialogInterface, _: Int ->
                                    gameController.start()
                                }.show()
                }
            })
        })
    }

    private fun toggle() {
        if (mVisible) {
            hide()
        } else {
            show()
        }
    }

    private fun hide() {
        // Hide UI first
        fullscreen_content_controls.visibility = View.GONE
        mVisible = false

        // Schedule a runnable to remove the status and navigation bar after a delay
        mHideHandler.removeCallbacks(mShowPart2Runnable)
        mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY.toLong())
    }

    private fun show() {
        // Show the system bar
        root.systemUiVisibility =
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        mVisible = true

        // Schedule a runnable to display UI elements after a delay
        mHideHandler.removeCallbacks(mHidePart2Runnable)
        mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY.toLong())
    }

    /**
     * Schedules a call to hide() in [delayMillis], canceling any
     * previously scheduled calls.
     */
    private fun delayedHide(delayMillis: Int) {
        mHideHandler.removeCallbacks(mHideRunnable)
        mHideHandler.postDelayed(mHideRunnable, delayMillis.toLong())
    }

    companion object {
        /**
         * Whether or not the system UI should be auto-hidden after
         * [AUTO_HIDE_DELAY_MILLIS] milliseconds.
         */
        private val AUTO_HIDE = true

        /**
         * If [AUTO_HIDE] is set, the number of milliseconds to wait after
         * user interaction before hiding the system UI.
         */
        private val AUTO_HIDE_DELAY_MILLIS = 3000

        /**
         * Some older devices needs a small delay between UI widget updates
         * and a change of the status and navigation bar.
         */
        private val UI_ANIMATION_DELAY = 300
    }

}

Androidmanifest.xml

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.permission.camera"/>

感谢观看,记得点赞哟!

本文地址:https://blog.csdn.net/qq_42795723/article/details/107579555

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网