{"id":793,"date":"2025-01-24T15:24:18","date_gmt":"2025-01-24T07:24:18","guid":{"rendered":"https:\/\/iichen.cn\/?p=793"},"modified":"2025-01-24T15:39:47","modified_gmt":"2025-01-24T07:39:47","slug":"android-%e8%bf%9e%e7%82%b9%e5%99%a8","status":"publish","type":"post","link":"https:\/\/iichen.cn\/?p=793","title":{"rendered":"Android-\u8fde\u70b9\u5668"},"content":{"rendered":"\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><p>\u8fde\u70b9\u5668\u60ac\u6d6e\u9762\u677f\u5168\u90e8\u9644\u52a0\u4e8eActivity\u7684DecorView\u5b9e\u73b0\u3002\u5177\u4f53\u5206\u4e3a\uff1a\u70b9\u4f4d\u8fde\u70b9\u5668\u548c\u8def\u5f84\u8fde\u70b9\u5668\u70b9\u4f4d\u8fde\u70b9\u5668\u4f7f\u7528\u7684While+Thread\u5b9e\u73b0\u8f6e\u8be2\u70b9\u4f4d\u5206\u53d1\u8def\u5f84\u8fde\u70b9\u5668\u4f7f\u7528\u7684\u662f\u72b6\u6001\u673a\u70b9\u4f4d\u5206\u53d1<\/p><\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">1. \u70b9\u4f4d\u8fde\u70b9\u5668<\/h2>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><div class='fancybox-wrapper lazyload-container-unload' data-fancybox='post-images' href='https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click-1024x798.png'><img class=\"lazyload lazyload-style-11\" src=\"data:image\/svg+xml;base64,PCEtLUFyZ29uTG9hZGluZy0tPgo8c3ZnIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjZmZmZmZmMDAiPjxnPjwvZz4KPC9zdmc+\"  decoding=\"async\" width=\"1024\" height=\"798\" data-original=\"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click-1024x798.png\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB\/AAffA0nNPuCLAAAAAElFTkSuQmCC\" alt=\"\" class=\"wp-image-797\" style=\"width:384px;height:auto\"  sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/div><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">1.1 \u5355\u51fb\u3001\u53cc\u51fb\u3001\u957f\u6309<\/h3>\n\n\n\n<pre class=\"wp-block-code language-dart lines-number has-small-font-size\"><code>class GmTouchSpotView : LinearLayout {\n    constructor(context: Context) : super(context) {\n        init()\n    }\n\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {\n        init()\n    }\n\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n            context,\n            attrs,\n            defStyleAttr\n    ) {\n        init()\n    }\n\n    constructor(\n            context: Context,\n            attrs: AttributeSet?,\n            defStyleAttr: Int,\n            defStyleRes: Int\n    ) : super(context, attrs, defStyleAttr, defStyleRes) {\n        init()\n    }\n\n    private fun init() {\n        layoutParams = ViewGroup.LayoutParams(-2, -2)\n        Ext.getLayoutView(\"gamehelper_point\", this)\n        clipChildren = false\n        clipToPadding = false\n        setWillNotDraw(false)\n\n        GmLifecycleUtils.getTopActivity()?.window?.decorView?.apply {\n            screenMin = measuredWidth.coerceAtMost(measuredHeight)\n            screenMax = measuredWidth.coerceAtLeast(measuredHeight)\n        }\n\n        translationX = x\n        translationY = y\n\n        post {\n            if (spotType == SPOT_START) {\n                for (listener in moveListener) {\n                    listener.onSizeChange(measuredWidth)\n                }\n            }\n        }\n    }\n\n    private var screenMin: Int = 0\n    private var screenMax: Int = 0\n    private var x = 0f\n    private var y = 0f\n    private var moveListener: ArrayList&lt;GmSpotMoveListener> = arrayListOf()\n    private var spotType: Int = SPOT_START\n\n    constructor (\n            context: Context,\n            x: Float,\n            y: Float,\n            @SpotType spotType: Int,\n            moveListener: GmSpotMoveListener? = null\n    ) : super(\n            context\n    ) {\n        this.x = x\n        this.y = y\n        this.spotType = spotType\n        moveListener?.let {\n            this.moveListener.add(it)\n        }\n        init()\n    }\n\n    fun addMoveListener(moveListener: GmSpotMoveListener? = null) {\n        moveListener?.let {\n            this.moveListener.add(it)\n        }\n    }\n\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        moveListener.clear()\n    }\n\n    constructor (\n            context: Context,\n            x: Float,\n            y: Float,\n            moveListener: GmSpotMoveListener? = null\n    ) : super(\n            context\n    ) {\n        this.x = x\n        this.y = y\n        moveListener?.let {\n            this.moveListener.add(it)\n        }\n        init()\n    }\n\n    \/\/ \u8d77\u70b9\u5750\u6807\n    private var lastX = 0f\n    private var lastY = 0f\n\n    fun updateSpotCoordinate(newX: Float, newY: Float) {\n        x = newX\n        y = newY\n        translationX = x\n        translationY = y\n        for (listener in moveListener) {\n            listener.onMove(newX, newY, spotType)\n        }\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        if (config?.playStatus == true) {\n            return super.onTouchEvent(event)\n        }\n\n        val action: Int = event.action\n        val realX: Float\n        if (action == MotionEvent.ACTION_DOWN) {\n            lastX = event.rawX\n            lastY = event.rawY\n        } else if (action == MotionEvent.ACTION_UP) {\n            \/\/ \u66f4\u65b0\u5750\u6807\n            for (listener in moveListener) {\n                listener.onMoveEnd(x, y)\n            }\n        } else if (action == MotionEvent.ACTION_MOVE) {\n            val rawX: Float = event.rawX\n            val rawY: Float = event.rawY\n            val dx: Float = rawX - this.lastX\n            val dy: Float = rawY - this.lastY\n\n            val curX = x + dx\n            val curY = y + dy\n\n            val h: Int\n            if (resources.configuration.orientation == 1) {\n                realX = max(0f, min(curX, (screenMin - width) * 1.0F))\n                h = screenMax\n            } else {\n                realX = max(0f, min(curX, (screenMax - width) * 1.0F))\n                h = screenMin\n            }\n            val realY = max(0.0f, min(curY, (h - height) * 1.0f))\n\n            translationX = realX\n            translationY = realY\n            for (listener in moveListener) {\n                listener.onMove(realX, realY, spotType)\n            }\n            x = realX\n            y = realY\n\n            lastX = rawX\n            lastY = rawY\n        }\n        return true\n    }\n\n    private var orientation = resources.configuration.orientation\n    override fun onConfigurationChanged(newConfig: Configuration?) {\n        super.onConfigurationChanged(newConfig)\n        newConfig?.let { config ->\n            if (orientation != config.orientation) {\n                orientation = config.orientation\n                val screenW = DisplayUtils.getScreenWidth(context)\n                val screenH = DisplayUtils.getScreenHeight(context)\n                val xRatio = x \/ screenH\n                x = (screenW - measuredWidth) * xRatio\n                val yRatio = y \/ screenW\n                y = (screenH - measuredHeight) * yRatio\n\n                translationX = x\n                translationY = y\n                for (listener in moveListener) {\n                    listener.onMove(x, y, spotType)\n                }\n            }\n        }\n    }\n\n    fun add(gmTouchSpotView: GmTouchSpotView) {\n        GmLifecycleUtils.getTopActivity()?.window?.decorView?.let { decorView ->\n            (decorView as ViewGroup).apply {\n                clipChildren = false\n                addView(gmTouchSpotView)\n            }\n        }\n    }\n\n    fun hide() {\n        visibility = View.INVISIBLE\n    }\n\n    fun remove() {\n        GmLifecycleUtils.getTopActivity()?.window?.decorView?.let { decorView ->\n            (decorView as ViewGroup).removeView(this)\n        }\n    }\n\n    fun show() {\n        visibility = View.VISIBLE\n    }\n\n    private var config: GmSpaceLinkerConfig? = null\n    fun setConfig(config: GmSpaceLinkerConfig) {\n        this.config = config\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1.2 \u6ed1\u52a8<\/h3>\n\n\n\n<p>\u6ed1\u52a8\u9700\u8981\u4e24\u4e2a\u70b9\u4f4d\u5e76\u4e14\u9700\u8981\u4e00\u6bb5\u8fde\u63a5\u7ebf,\u5982\u4e0b<\/p>\n\n\n\n<pre class=\"wp-block-code language-dart lines-number has-small-font-size\"><code>class GmTouchSwipeSpotView : FrameLayout, GmSpotMoveListener {\n    constructor(context: Context) : super(context) {\n        init()\n    }\n\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {\n        init()\n    }\n\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context, attrs, defStyleAttr\n    ) {\n        init()\n    }\n\n    constructor(\n        context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int\n    ) : super(context, attrs, defStyleAttr, defStyleRes) {\n        init()\n    }\n\n    private fun init() {\n\/\/        layoutParams = ViewGroup.LayoutParams(-2,-2)\n        setWillNotDraw(false)\n        isClickable = false\n        isFocusable = false\n        setFocusableInTouchMode(false)\n        setClipChildren(false)\n\n        mArrowBitmap =\n            BitmapFactory.decodeResource(Ext.resource.resource, Ext.resource.getDrawableId(\"gamehelper_slide_arrow\"))?.apply {\n                mArrowHeight = height\n                mArrowWidth = width\n            }\n    }\n\n    private var mArrowWidth: Int = 0\n    private var mArrowHeight: Int = 0\n    private var mArrowBitmap: Bitmap? = null\n\n    private var x = 0f\n    private var y = 0f\n    private var x2 = 0f\n    private var y2 = 0f\n\n    constructor (context: Context, x: Float, y: Float, x2: Float, y2: Float) : super(context) {\n        this.x = x\n        this.y = y\n        this.x2 = x2\n        this.y2 = y2\n        init()\n    }\n\n    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {\n        return false\n    }\n\n    private var pointSize = -1\n    private var animationX = 0f\n    private var animationY = 0f\n    override fun draw(canvas: Canvas) {\n        try {\n            super.draw(canvas)\n            if (pointSize == -1) return\n            canvas.apply {\n                val halfSize = pointSize \/ 2f\n                drawLine(x + halfSize, y + halfSize, x2 + halfSize, y2 + halfSize, paint)\n                drawArrow(\n                    canvas,\n                    x + halfSize,\n                    y + halfSize,\n                    if (isRunStatus) animationX + halfSize else x2 + halfSize,\n                    if (isRunStatus) animationY + halfSize else y2 + halfSize,\n                    mArrowWidth * 1.0f \/ 2,\n                    mArrowHeight * 1.0f \/ 2\n                )\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    private val paint = Paint().apply {\n        color = Color.argb(125, 0, 0, 0)\n        strokeWidth = 14f.dpToPx(context).toFloat()\n        style = Paint.Style.FILL\n    }\n\n    fun add(gmTouchSpotView: GmTouchSwipeSpotView) {\n        GmLifecycleUtils.getTopActivity()?.window?.decorView?.let { decorView ->\n            (decorView as ViewGroup).apply {\n                clipChildren = false\n                addView(gmTouchSpotView)\n            }\n        }\n    }\n\n    private fun drawArrow(\n        canvas: Canvas,\n        fromX: Float,\n        fromY: Float,\n        toX: Float,\n        toY: Float,\n        width: Float,\n        height: Float\n    ) {\n\n        val juli =\n            sqrt(((toX - fromX) * (toX - fromX) + (toY - fromY) * (toY - fromY)).toDouble()).toFloat()\n        if (juli &lt;= (width * 4)) {\n            return\n        }\n        val juliX = toX - fromX\n        val juliY = toY - fromY\n\n        val dianX = juliX \/ (if (isRunStatus) 1 else 2) + fromX - (width \/ juli * juliX)\n        val dianY = juliY \/ (if (isRunStatus) 1 else 2) + fromY - (width \/ juli * juliY)\n        val arrowStartX = dianX + (height \/ juli * juliY)\n        val arrowStartY = dianY - (height \/ juli * juliX)\n        val matrix = Matrix()\n        matrix.postRotate(getAngle(fromX, fromY, toX, toY))\n        matrix.postTranslate(arrowStartX, arrowStartY)\n        canvas.drawBitmap(mArrowBitmap!!, matrix, (if (isRunStatus) arrowBitmapPaint else null))\n    }\n\n    private fun getAngle(fromX: Float, fromY: Float, toX: Float, toY: Float): Float {\n        var angle = 0f\n        if (toX != fromX &amp;&amp; toY != fromY) {\n            angle =\n                (atan(abs(((toY - fromY) * 1.0f \/ (toX - fromX)).toDouble())) * 180 \/ Math.PI).toFloat()\n        }\n        if (toX > fromX &amp;&amp; toY &lt; fromY) {  \/\/\u7b2c\u4e00\u8c61\u9650\n            angle = 360 - angle\n        } else if (toX > fromX &amp;&amp; toY > fromY) { \/\/\u7b2c\u56db\u8c61\u9650\n        } else if (toX &lt; fromX &amp;&amp; toY > fromY) { \/\/\u7b2c\u4e09\u8c61\u9650\n            angle = 180 - angle\n        } else if (toX &lt; fromX &amp;&amp; toY &lt; fromY) { \/\/\u7b2c\u4e8c\u8c61\u9650\n            angle = 180 + angle\n        } else if (toX == fromX &amp;&amp; toY &lt; fromY) {\n            angle = 270f\n        } else if (toX == fromX &amp;&amp; toY > fromY) {\n            angle = 90f\n        } else if (toX &lt; fromX &amp;&amp; toY == fromY) {\n            angle = 180f\n        } else if (toX > fromX &amp;&amp; toY == fromY) {\n            angle = 0f\n        }\n        return angle\n    }\n\n    override fun onMove(newX: Float, newY: Float, @SpotType type: Int) {\n        if (type == SPOT_START) {\n            x = newX\n            y = newY\n        } else {\n            x2 = newX\n            y2 = newY\n        }\n        postInvalidate()\n    }\n\n    override fun onSizeChange(spotSize: Int) {\n        pointSize = spotSize\n        postInvalidate()\n    }\n\n    fun createListener(): GmSpotMoveListener {\n        return this\n    }\n\n    fun hide() {\n        visibility = View.INVISIBLE\n    }\n\n    fun remove() {\n        GmLifecycleUtils.getTopActivity()?.window?.decorView?.let { decorView ->\n            (decorView as ViewGroup).removeView(this)\n        }\n    }\n\n    fun show() {\n        visibility = View.VISIBLE\n    }\n\n    private var config: GmSpaceLinkerConfig? = null\n    fun setConfig(config: GmSpaceLinkerConfig) {\n        this.config = config\n    }\n\n    private val arrowBitmapPaint = Paint()\n    private var isRunStatus = false\n    private var arrowAnimation: ValueAnimator? = null\n\n    fun moveUpdate(newX: Float,newY: Float) {\n        if(config?.playStatus == true) {\n            isRunStatus = true\n            val f = (newX - x) \/ (x2 - x)\n            arrowBitmapPaint.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply {\n                \/\/ 1 ~ 2\n\/\/            val scale = if (f &lt;= 0.5) 1f + 2f * f else 2 - 2 * (f - 0.5f)\n                \/\/ 1 ~ 3\n\/\/            val scale = if (f &lt;= 0.5) 1f + 4 * f else 3 - 4 * (f - 0.5f)\n                \/\/ 1 ~ 5\n                val scale = if (f &lt;= 0.5) 1f + 8 * f else 5 - 8 * (f - 0.5f)\n\n                setScale(scale, scale, scale, 1.0f)\n            })\n            arrowBitmapPaint.alpha =\n                    if (f &lt;= 0.5) (f * 255 * 2).toInt() else ((1 - (f - 0.5) * 2) * 255).toInt()\n            animationX = newX\n            animationY = newY\n            postInvalidate()\n        } else {\n            resetArrowPosition()\n        }\n    }\n\n    fun startArrowAnimation(slideDuration: Long) {\n        if (arrowAnimation == null) {\n            arrowAnimation = GmAnimationUtil.fraction(duration = slideDuration) { f ->\n                if (f != 1f) {\n                    animationX = x + (x2 - x) * f\n                    animationY = y + (y2 - y) * f\n                    isRunStatus = true\n                    arrowBitmapPaint.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply {\n                        \/\/ 1 ~ 2\n\/\/            val scale = if (f &lt;= 0.5) 1f + 2f * f else 2 - 2 * (f - 0.5f)\n                        \/\/ 1 ~ 3\n\/\/            val scale = if (f &lt;= 0.5) 1f + 4 * f else 3 - 4 * (f - 0.5f)\n                        \/\/ 1 ~ 5\n                        val scale = if (f &lt;= 0.5) 1f + 8 * f else 5 - 8 * (f - 0.5f)\n\n                        setScale(scale, scale, scale, 1.0f)\n                    })\n                    arrowBitmapPaint.alpha =\n                        if (f &lt;= 0.5) (f * 255 * 2).toInt() else ((1 - (f - 0.5) * 2) * 255).toInt()\n                } else { \/\/ \u6267\u884c\u5b8c\u4e86 \u5c06\u7bad\u5934\u91cd\u7f6e\n                    isRunStatus = false\n                }\n                postInvalidate()\n            }\n        } else {\n            arrowAnimation?.setDuration(slideDuration)\n        }\n        arrowAnimation?.start()\n    }\n\n    fun resetArrowPosition() {\n        arrowAnimation?.cancel()\n        isRunStatus = false\n        postInvalidate()\n    }\n\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        arrowAnimation?.cancel()\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1.3 \u4e8b\u4ef6\u5206\u53d1<\/h3>\n\n\n\n<pre class=\"wp-block-code language-dart lines-number has-small-font-size\"><code>mThread = Thread {\n\twhile (config.playStatus &amp;&amp; !Thread.currentThread().isInterrupted) {\n\t\ttry {\n\t\t\tfor ((index, recordBean) in config.recordBeanList.withIndex()) {\n\t\t\t\tThread.sleep(wrapSpeedTime(gapTime))\n\t\t\t\tif (recordBean.type == 4) {\n\t\t\t\t\tplaySwipe(config, recordBean)\n\t\t\t\t} else {\n\t\t\t\t\tplayNormal(config, recordBean)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e: InterruptedException) {\n\t\t\te.printStackTrace()\n\t\t\tbreak\n\t\t} catch (e: Exception) {\n\t\t\te.printStackTrace()\n\t\t}\n\t}\n}\n\nprivate fun dispatchEvent(event: MotionEvent, call: (() -> Unit)? = null) {\n\tHandler(Looper.getMainLooper()).post {\n\t\tevent.source = 4098\n\t\ttry {\n\t\t\tGmLifecycleUtils.getTopActivity()?.window?.decorView?.apply {\n\t\t\t\tdispatchTouchEvent(event)\n\t\t\t}\n\t\t\tcall?.invoke()\n\t\t} catch (e: Exception) {\n\t\t\te.printStackTrace()\n\t\t}\n\t}\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">2. \u8def\u5f84\u8fde\u70b9\u5668<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><div class='fancybox-wrapper lazyload-container-unload' data-fancybox='post-images' href='https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/path_click.png'><img class=\"lazyload lazyload-style-11\" src=\"data:image\/svg+xml;base64,PCEtLUFyZ29uTG9hZGluZy0tPgo8c3ZnIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjZmZmZmZmMDAiPjxnPjwvZz4KPC9zdmc+\"  decoding=\"async\" width=\"991\" height=\"677\" data-original=\"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/path_click.png\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB\/AAffA0nNPuCLAAAAAElFTkSuQmCC\" alt=\"\" class=\"wp-image-803\" style=\"width:487px;height:auto\"  sizes=\"(max-width: 991px) 100vw, 991px\" \/><\/div><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">2.1 \u8def\u5f84\u7ed8\u5236\u4e0e\u6e32\u67d3<\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u901a\u8fc7\u8bbe\u7f6eActivity\u7684setWindowCallback\u83b7\u53d6\u4e8b\u4ef6\u8fdb\u884c\u7ed8\u5236\u8def\u5f84\uff0c\u7ed8\u5236\u7c7bGmPathRecordSpotView\u66b4\u9732handleTouchEvent(event: MotionEvent\u7ed8\u5236\u65f6\u4f20\u5165\u7684\u662fsetWindowCallback\u83b7\u53d6\u7684\u4e8b\u4ef6\uff0c\u6e32\u67d3\u65f6\u4f20\u5165\u7684\u662f\u7ed8\u5236\u65f6\u4fdd\u5b58\u7684\u4e8b\u4ef6\u5e76\u901a\u8fc7\u72b6\u6001\u673a\u7684\u65b9\u5f0f\u6765\u5faa\u73af\u5904\u7406\u4e0d\u540c\u7684\u4e8b\u4ef6\u903b\u8f91\u3002<\/p>\n<\/blockquote>\n\n\n\n<pre class=\"wp-block-code language-dart lines-number has-small-font-size\"><code>class GmPathRecordSpotView(context: Context, val listener: GmPathRecordStatusListener) :\n    FrameLayout(context) {\n    private val path = Path()\n    private val paint = Paint().apply {\n        color = Color.argb(125, 0, 0, 0)\n        strokeWidth = 14f.dpToPx(context).toFloat()\n        style = Paint.Style.STROKE\n        strokeJoin = Paint.Join.ROUND\n        strokeCap = Paint.Cap.ROUND\n        isAntiAlias = true\n    }\n\n    init {\n        setWillNotDraw(false)\n        isClickable = false\n        isFocusable = false\n        isFocusableInTouchMode = false\n        clipChildren = false\n    }\n\n    private val pathRecordList: ArrayList&lt;GmPathRecordModel> = arrayListOf()\n    private var cacheRecordList: ArrayList&lt;GmPathRecordModel> = arrayListOf()\n    private var index: Int = 0\n\n    private var lastX = 0f\n    private var lastY = 0f\n\n    private fun isOverMinSlidingDistance(f: Float, f2: Float): Boolean {\n        return f.pow(2.0f) + f2.pow(2.0f) >= (30f).pow(2.0f)\n    }\n\n    \/\/ \u5f55\u5236\u8def\u5f84\n    fun handleWindowTouchEvent(event: MotionEvent) {\n        if (type == 0) {\n            handleTouchEvent(event)\n        }\n    }\n\n    override fun onTouchEvent(event: MotionEvent?): Boolean {\n        return false\n    }\n\n    private fun handleTouchEvent(event: MotionEvent) {\n        val action = event.action\n        if (action != MotionEvent.ACTION_DOWN) {\n            if (action != MotionEvent.ACTION_UP) {\n                if (action == MotionEvent.ACTION_CANCEL) {\n                    \">>>>>>>>>>>>>>>>>>>>>>>> MotionEvent.ACTION_CANCEL \".logHelper()\n                    pathRecordList&#91;index - 1].let { record ->\n                        record.coordinates.apply {\n                            if (size > 1) {\n                                lastX = event.x\n                                lastY = event.y\n                                record.tagFollow = \"follow_${index}\"\n                                addPointView(record.tagFollow, index, true)\n                            }\n                            add(\n                                GmPathRecordModel.PathRecordCoordinateBean(\n                                    event.x,\n                                    event.y,\n                                    MotionEvent.ACTION_UP\n                                )\n                            )\n                            val curTime = System.currentTimeMillis()\n                            this&#91;this.size - 1].nextGap = curTime - lastTime\n                            lastTime = curTime\n                        }\n                    }\n                } else if (action == MotionEvent.ACTION_MOVE) {\n                    val curX = event.x\n                    val curY = event.y\n                    val dx: Float = curX - lastX\n                    val dy: Float = curY - lastY\n\n                    if (isOverMinSlidingDistance(dx, dy)) {\n                        pathRecordList&#91;index - 1].coordinates.apply {\n                            add(\n                                GmPathRecordModel.PathRecordCoordinateBean(\n                                    curX,\n                                    curY,\n                                    MotionEvent.ACTION_MOVE\n                                )\n                            )\n\n                            val curTime = System.currentTimeMillis()\n                            this&#91;this.size - 1].nextGap = curTime - lastTime\n                            lastTime = curTime\n                        }\n                    }\n                }\n            } else {\n                pathRecordList&#91;index - 1].let { record ->\n                    record.coordinates.apply {\n                        if (size > 1) {\n                            lastX = event.x\n                            lastY = event.y\n                            record.tagFollow = \"follow_${index}\"\n                            addPointView(record.tagFollow, index, true)\n                        }\n                        add(\n                            GmPathRecordModel.PathRecordCoordinateBean(\n                                event.x,\n                                event.y,\n                                MotionEvent.ACTION_UP\n                            )\n                        )\n                        val curTime = System.currentTimeMillis()\n                        this&#91;this.size - 1].nextGap = curTime - lastTime\n                        lastTime = curTime\n                    }\n                }\n            }\n        } else {\n            lastX = event.x\n            lastY = event.y\n\n            val curTime = System.currentTimeMillis()\n\n            val model = GmPathRecordModel(++index)\n            model.coordinates.add(\n                GmPathRecordModel.PathRecordCoordinateBean(\n                    lastX,\n                    lastY,\n                    MotionEvent.ACTION_DOWN\n                )\n            )\n            model.startTime = curTime\n            model.tag = \"$index\"\n\n            if (pathRecordList.isNotEmpty()) {\n                pathRecordList&#91;pathRecordList.size - 1].apply {\n                    totalTime = curTime - this.startTime\n                    if (type == 1) {\n                        canShow = false\n                    }\n                }\n            }\n\n            model.coordinates&#91;model.coordinates.size - 1].nextGap = curTime - lastTime\n\n            pathRecordList.add(model)\n            lastTime = curTime\n\n            addPointView(model.tag, index, false)\n        }\n        postInvalidate()\n    }\n\n    private val spotMap: ArrayMap&lt;String, View> = ArrayMap()\n    private fun dismissPointViewByTag(tag: String, index: Int) {\n        if (spotMap.containsKey(tag)) {\n            spotMap&#91;tag]?.apply {\n                visibility = View.INVISIBLE\n            }\n        }\n    }\n\n\n    private fun showPointViewByTag(tag: String) {\n        if (spotMap.containsKey(tag)) {\n            spotMap&#91;tag]?.apply {\n                visibility = View.VISIBLE\n                handlerSpotAnimation(this, tag)\n            }\n        }\n    }\n\n    private fun dismissAllPointView() {\n        for (spotView in spotMap) {\n            spotView.value.visibility = View.INVISIBLE\n        }\n    }\n\n    private fun showAllPointView() {\n        for (spotView in spotMap) {\n            spotView.value.visibility = View.VISIBLE\n        }\n    }\n\n    private fun removeAllPointView() {\n        for (spotView in spotMap) {\n            val view = spotView.value\n            if (view.parent != null) {\n                (view.parent as ViewGroup).removeView(view)\n            }\n        }\n        spotMap.clear()\n    }\n\n    private var pointViewSize = 40f.dpToPx(context)\n    private var isCalcPointViewSize = false\n    private fun addPointView(tag: String, index: Int, isUp: Boolean) {\n        if (spotMap.containsKey(tag)) {\n            showPointViewByTag(tag)\n            return\n        }\n        getLayoutView(\"gamehelper_point\")\n            .let { pointView ->\n                if(!isCalcPointViewSize) {\n                    isCalcPointViewSize = true\n                    pointView.post {\n                        pointViewSize = pointView.measuredWidth\n                    }\n                }\n                (getWidgetView(pointView, \"num\") as TextView).text = \"$index\"\n                val size = 40f.dpToPx(context)\n                pointView.translationX = lastX - size \/ 2\n                pointView.translationY = lastY - size \/ 2\n                spotMap&#91;tag] = pointView\n\n                this.addView(pointView, ViewGroup.LayoutParams(-2, -2))\n\n                handlerSpotAnimation(pointView, tag)\n            }\n    }\n\n    private fun handlerSpotAnimation(view: View, tag: String) {\n        var duration = 150L\n        val isTagFollow = tag.contains(\"follow_\")\n        var isSwipe = if (isTagFollow) true else spotMap.containsKey(\"follow_$tag\")\n        try {\n            var totalTime = cacheRecordList&#91;index - 1].totalTime\n            var endTime = 0L\n            if ((index + 1) &lt; cacheRecordList.size) {\n                endTime = cacheRecordList&#91;index].coordinates&#91;0].nextGap\n            } else {\n                endTime = GmPathRecordManager.pathRecordListModel?.endRemainTime ?: 150\n            }\n            totalTime -= endTime\n\/\/            if ((totalTime + endTime) >= duration * 2) {\n\/\/            } else {\n\/\/                duration = ((totalTime + endTime)) \/ 2\n\/\/            }\n\n            if (totalTime &lt; duration) {\n                duration = totalTime\n            }\n\n            if (isTagFollow) {\n                GmAnimationUtil.scaleBrightness(\n                    (getWidgetView(view, \"point\") as ImageView),\n                    isRunning,\n                    \/\/ 150 * 2 \u653e\u5927\u548c\u7f29\u5c0f\n                    duration = if (endTime > 300) 300 else endTime,\n                    scaleParams = floatArrayOf(\n                        1.0f,\n                        1.2f,\n                        1.0f\n                    ),\n                    brightNessParams = floatArrayOf(1.0f, 2.0f, 1.0f),\n                )\n            } else {\n                handler.postDelayed({\n                    GmAnimationUtil.scaleBrightness(\n                        (getWidgetView(view, \"point\") as ImageView),\n                        isRunning,\n                        duration = duration,\n                        scaleParams = floatArrayOf(0.8f, 1.0f),\n                        brightNessParams = floatArrayOf(2.0f, 1.0f),\n                    )\n                }, if(isSwipe) totalTime else totalTime - duration)\n                GmAnimationUtil.scaleBrightness(\n                    (getWidgetView(view, \"point\") as ImageView),\n                    isRunning,\n                    duration = duration,\n                    scaleParams = floatArrayOf(1.0f, 0.8f),\n                    brightNessParams = floatArrayOf(1.0f, 2.0f),\n                )\n            }\n        } catch (e: Exception) {\n        }\n    }\n\n    @Volatile\n    private var isRunning = false\n\n    fun pause() {\n        isRunning = false\n        handler.removeCallbacksAndMessages(null)\n        sleepTime = sleepRemainTime\n    }\n\n    fun resume() {\n        isRunning = true\n        sleepStartTime = SystemClock.elapsedRealtime()\n        if (runnableType == 0) {\n            handler.post(countDownRunnable)\n        } else if (runnableType == 1) {\n            handler.post(remainCountDownRunnable)\n        } else if (runnableType == 2) {\n            handler.post(loopFinishCountDownRunnable)\n        }\n    }\n\n    private val handler = Handler(Looper.getMainLooper())\n\n    \/\/ 0 \u8bbe\u7f6e\u8def\u5f84  1 \u64ad\u653e\u8def\u5f84 2 \u9690\u85cf\u8def\u5f84\n    private var lastTime = 0L\n    private var startTime = 0L\n    private var type = -1\n\n\n    fun hidePath() {\n        setType(2)\n    }\n\n    fun stopPlay() {\n        isRunning = false\n        hidePath()\n    }\n\n    fun stopRecord() {\n        hidePath()\n        val endRemainTime = System.currentTimeMillis() - lastTime\n        GmPathRecordManager.pathRecordListModel = GmPathRecordList().apply {\n            isPathRecord = true\n            recordModelList = ArrayList(pathRecordList)\n            this.endRemainTime = endRemainTime\n            val curTime = System.currentTimeMillis()\n            if (pathRecordList.isNotEmpty()) {\n                \/\/ \u8bb0\u5f55\u6700\u540e\u4e00\u4e2a\u8def\u5f84\u7684\u603b\u65f6\u95f4\n                pathRecordList&#91;pathRecordList.size - 1].apply {\n                    totalTime = curTime - this.startTime\n                }\n            }\n            totalTime = curTime - startTime\n            \">>>>>>>>>> \u7ed3\u675f\u5f55\u5236 \u603b\u65f6\u957f\uff1a$totalTime\".logHelper()\n            name = \"\u9ed8\u8ba4\u65b9\u6848${GmSpaceRuleSpUtils.getAllRecordList().size + 1}\"\n        }\n    }\n\n\n    \/\/ OUT_LOOP, INNER_LOOP, REVERSE_ANIMATION, ANIMATION, LOGIC\n    private val OUT_LOOP = 0\n    private val INNER_LOOP = 1\n    private val REVERSE_ANIMATION = 2\n    private val ANIMATION = 3\n    private val LOGIC = 4\n\n\n    private var currentStep = OUT_LOOP\n    private var outLoopIndex = 0\n    private var innerLoopIndex = 0\n    private var curloopCount = 1\n    private var sleepTime = 0L\n    private var sleepStartTime = 0L\n\n    private var runnableType = 0\n\n    \/\/ \u7761\u7720\u5269\u4f59\u65f6\u95f4\n    private var sleepRemainTime = 0L\n    private val countDownRunnable: Runnable = object : Runnable {\n        override fun run() {\n            runnableType = 0\n            val dur = SystemClock.elapsedRealtime() - sleepStartTime\n            sleepRemainTime = sleepTime - dur\n            if (sleepRemainTime &lt;= 0) {\n                currentStep = LOGIC\n                exeRecord()\n                return\n            }\n            handler.postDelayed(this, 1)\n        }\n    }\n\n    private val remainCountDownRunnable: Runnable = object : Runnable {\n        override fun run() {\n            runnableType = 1\n            val dur = SystemClock.elapsedRealtime() - sleepStartTime\n            sleepRemainTime = sleepTime - dur\n\/\/            \">>>>>>>>>>>>>>>>> remainCountDownRunnable $sleepTime\".logHelper()\n            if (sleepRemainTime &lt;= 0) {\n                pathRecordList.clear()\n                dismissAllPointView()\n                listener.onRunPathIndex(outLoopIndex + 1)\n                currentStep = INNER_LOOP\n                exeRecord()\n                return\n            }\n            handler.postDelayed(this, 1)\n        }\n    }\n\n    private val loopFinishCountDownRunnable: Runnable = object : Runnable {\n        override fun run() {\n            runnableType = 2\n            val dur = SystemClock.elapsedRealtime() - sleepStartTime\n            sleepRemainTime = sleepTime - dur\n\/\/            \">>>>>>>>>>>>>>>>> loopFinishCountDownRunnable $sleepTime\".logHelper()\n            if (sleepRemainTime &lt;= 0) {\n                curloopCount = 1\n                listener.playFinish()\n                return\n            }\n            handler.postDelayed(this, 1)\n        }\n    }\n\n    private fun exeRecord() {\n        if (!isRunning)\n            return\n        when (currentStep) {\n            OUT_LOOP -> {\n                if (outLoopIndex >= cacheRecordList.size) {\n                    \/\/ \u662f\u5426\u9700\u8981\u91cd\u65b0\u5f00\u59cb\n                    outLoopIndex = 0\n                    val loopCount = GmPathRecordManager.pathRecordListModel?.loopCount ?: 0\n                    val loopType = GmPathRecordManager.pathRecordListModel?.loopType ?: 0\n                    if (loopType == 0) {\n                        index = 0\n                        isFromStart = false\n                        sleepStartTime = SystemClock.elapsedRealtime()\n                        sleepTime = GmPathRecordManager.pathRecordListModel?.endRemainTime ?: 0\n                        handler.post(remainCountDownRunnable)\n                    } else {\n                        if (curloopCount >= loopCount) {\n                            sleepStartTime = SystemClock.elapsedRealtime()\n                            sleepTime = GmPathRecordManager.pathRecordListModel?.endRemainTime ?: 0\n                            handler.post(loopFinishCountDownRunnable)\n                            return\n                        }\n                        index = 0\n                        isFromStart = false\n                        curloopCount += 1\n                        listener.playRoundFinish(curloopCount)\n\n                        sleepStartTime = SystemClock.elapsedRealtime()\n                        sleepTime = GmPathRecordManager.pathRecordListModel?.endRemainTime ?: 0\n                        handler.post(remainCountDownRunnable)\n                    }\n                } else {\n                    listener.onRunPathIndex(outLoopIndex + 1)\n                    currentStep = INNER_LOOP\n                    exeRecord()\n                }\n            }\n\n            INNER_LOOP -> {\n                val size = cacheRecordList&#91;outLoopIndex].coordinates.size\n                val recordModel = cacheRecordList&#91;outLoopIndex]\n                if (innerLoopIndex &lt; size) {\n                    val coordinateBean = recordModel.coordinates&#91;innerLoopIndex]\n                    sleepTime = coordinateBean.nextGap\n                    val gapTime = GmPathRecordManager.pathRecordListModel?.gapTime ?: 300\n                    if (innerLoopIndex == 0) { \/\/ \u5904\u7406\u6bcf\u6bb5\u7684\u5f00\u5934\n                        if (outLoopIndex == 0 &amp;&amp; !isFromStart) {\n                            sleepTime += gapTime\n                            listener.playSleep(sleepTime)\n                        } else {\n                            listener.playSleep(sleepTime)\n                        }\n                    }\n\n                    \/\/ \u5ef6\u8fdf\u6267\u884c \u5206\u53d1\u903b\u8f91  \u4ee5\u53ca\u52a8\u753b\n                    sleepStartTime = SystemClock.elapsedRealtime()\n                    handler.post(countDownRunnable)\n                } else {\n                    \/\/ \u79fb\u9664point\n\n                    \/\/ \u4e0b\u4e00\u6761\u8def\u5f84\n                    innerLoopIndex = 0\n                    currentStep = OUT_LOOP\n                    outLoopIndex += 1\n                    exeRecord()\n                }\n            }\n\n            REVERSE_ANIMATION -> {\n\n            }\n\n            ANIMATION -> {\n\n            }\n\n            LOGIC -> {\n\/\/                cacheRecordList&#91;outLoopIndex].totalTime\n\n                val coordinateBean = cacheRecordList&#91;outLoopIndex].coordinates&#91;innerLoopIndex]\n\n                listener.playNotify()\n                \/\/ \u6dfb\u52a0point\n\n                val uptimeMillis = SystemClock.uptimeMillis()\n                val motionEvent = MotionEvent.obtain(\n                    uptimeMillis,\n                    uptimeMillis,\n                    coordinateBean.action,\n                    coordinateBean.x,\n                    coordinateBean.y,\n                    0\n                )\n                motionEvent.source = 4098\n                if (context is Activity) {\n                    (context as Activity).apply {\n                        runOnUiThread {\n                            window.decorView.dispatchTouchEvent(\n                                motionEvent\n                            )\n                        }\n                    }\n                }\n                handleTouchEvent(motionEvent)\n                innerLoopIndex += 1\n                currentStep = INNER_LOOP\n                exeRecord()\n            }\n        }\n    }\n\n    fun setType(type: Int) {\n        this.type = type\n        index = 0\n        if (type == 0) {\n            pathRecordList.clear()\n            cacheRecordList.clear()\n            startTime = System.currentTimeMillis()\n            lastTime = startTime\n        }\n        if (type == 1) {\n            isRunning = true\n            isFromStart = true\n            curloopCount = 1\n            currentStep = OUT_LOOP\n            if (cacheRecordList.isEmpty()) {\n                cacheRecordList = ArrayList(pathRecordList)\n            }\n            pathRecordList.clear()\n\n            exeRecord()\n        }\n        dismissAllPointView()\n        postInvalidate()\n    }\n\n    private var isFromStart = true\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        mSingleThreadExecutor.shutdownNow()\n        handler.removeCallbacksAndMessages(null)\n    }\n\n    private var mSingleThreadExecutor = Executors.newSingleThreadExecutor()\n\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n        \/\/ 0 \u5904\u7406MotionEvent  1 \u7ed8\u5236 2 \u9690\u85cf\n        if (type == 2) {\n            return\n        }\n        val drawPathModel: ArrayList&lt;GmPathRecordModel> = ArrayList(pathRecordList)\n        for ((index, pathModel) in drawPathModel.withIndex()) {\n            if (pathModel.canShow) {\n                drawLinePath(pathModel, canvas)\n            } else {\n                dismissPointViewByTag(pathModel.tag, index)\n                dismissPointViewByTag(pathModel.tagFollow, index)\n            }\n        }\n    }\n\n    private fun drawLinePath(pathModel: GmPathRecordModel, canvas: Canvas) {\n        val size = pathModel.coordinates.size\n        if (size > 0) {\n            val firstCoordinateBean = pathModel.coordinates&#91;0]\n            path.reset()\n            path.moveTo(firstCoordinateBean.x, firstCoordinateBean.y)\n            if (size > 1) {\n                for (i in 1 until pathModel.coordinates.size) {\n                    val lastCoordinateBean = pathModel.coordinates&#91;i - 1]\n                    val coordinateBean = pathModel.coordinates&#91;i]\n                    val x = lastCoordinateBean.x\n                    val y = lastCoordinateBean.y\n                    path.quadTo(x, y, (coordinateBean.x + x) \/ 2.0f, (coordinateBean.y + y) \/ 2.0f)\n                }\n            }\n            canvas.drawPath(path, paint)\n        }\n    }\n\n    fun setPathModel(pathRecordList: ArrayList&lt;GmPathRecordModel>) {\n        cacheRecordList = ArrayList(pathRecordList)\n    }\n}\n<\/code><\/pre>\n<style>language-dart lines-number<\/style>","protected":false},"excerpt":{"rendered":"<p>\u8fde\u70b9\u5668\u60ac\u6d6e\u9762\u677f\u5168\u90e8\u9644\u52a0\u4e8eActivity\u7684DecorView\u5b9e\u73b0\u3002\u5177\u4f53\u5206\u4e3a\uff1a\u70b9\u4f4d\u8fde\u70b9\u5668\u548c\u8def\u5f84\u8fde\u70b9\u5668\u70b9\u4f4d\u8fde\u70b9\u5668\u4f7f [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,8],"tags":[],"class_list":["post-793","post","type-post","status-publish","format-standard","hentry","category-android","category-8"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Android-\u8fde\u70b9\u5668 - IIchen<\/title>\n<meta name=\"description\" content=\"Android-\u8fde\u70b9\u5668\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/iichen.cn\/?p=793\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Android-\u8fde\u70b9\u5668 - IIchen\" \/>\n<meta property=\"og:description\" content=\"Android-\u8fde\u70b9\u5668\" \/>\n<meta property=\"og:url\" content=\"https:\/\/iichen.cn\/?p=793\" \/>\n<meta property=\"og:site_name\" content=\"IIchen\" \/>\n<meta property=\"article:published_time\" content=\"2025-01-24T07:24:18+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-01-24T07:39:47+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click-1024x798.png\" \/>\n<meta name=\"author\" content=\"iichen\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"iichen\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data2\" content=\"1 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/iichen.cn\/?p=793#article\",\"isPartOf\":{\"@id\":\"https:\/\/iichen.cn\/?p=793\"},\"author\":{\"name\":\"iichen\",\"@id\":\"https:\/\/iichen.cn\/#\/schema\/person\/4a47edf85ab49841df9e8f6aee40b77c\"},\"headline\":\"Android-\u8fde\u70b9\u5668\",\"datePublished\":\"2025-01-24T07:24:18+00:00\",\"dateModified\":\"2025-01-24T07:39:47+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/iichen.cn\/?p=793\"},\"wordCount\":12,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/iichen.cn\/#\/schema\/person\/4a47edf85ab49841df9e8f6aee40b77c\"},\"image\":{\"@id\":\"https:\/\/iichen.cn\/?p=793#primaryimage\"},\"thumbnailUrl\":\"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click-1024x798.png\",\"articleSection\":[\"Android\",\"\u7b14\u8bb0\"],\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/iichen.cn\/?p=793#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/iichen.cn\/?p=793\",\"url\":\"https:\/\/iichen.cn\/?p=793\",\"name\":\"Android-\u8fde\u70b9\u5668 - IIchen\",\"isPartOf\":{\"@id\":\"https:\/\/iichen.cn\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/iichen.cn\/?p=793#primaryimage\"},\"image\":{\"@id\":\"https:\/\/iichen.cn\/?p=793#primaryimage\"},\"thumbnailUrl\":\"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click-1024x798.png\",\"datePublished\":\"2025-01-24T07:24:18+00:00\",\"dateModified\":\"2025-01-24T07:39:47+00:00\",\"description\":\"Android-\u8fde\u70b9\u5668\",\"breadcrumb\":{\"@id\":\"https:\/\/iichen.cn\/?p=793#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/iichen.cn\/?p=793\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\/\/iichen.cn\/?p=793#primaryimage\",\"url\":\"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click.png\",\"contentUrl\":\"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click.png\",\"width\":1075,\"height\":838},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/iichen.cn\/?p=793#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9875\",\"item\":\"https:\/\/iichen.cn\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Android-\u8fde\u70b9\u5668\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/iichen.cn\/#website\",\"url\":\"https:\/\/iichen.cn\/\",\"name\":\"IIchen\",\"description\":\"Just do it!\",\"publisher\":{\"@id\":\"https:\/\/iichen.cn\/#\/schema\/person\/4a47edf85ab49841df9e8f6aee40b77c\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/iichen.cn\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"zh-Hans\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/iichen.cn\/#\/schema\/person\/4a47edf85ab49841df9e8f6aee40b77c\",\"name\":\"iichen\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\/\/iichen.cn\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/avatar.jpg\",\"contentUrl\":\"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/avatar.jpg\",\"width\":940,\"height\":940,\"caption\":\"iichen\"},\"logo\":{\"@id\":\"https:\/\/iichen.cn\/#\/schema\/person\/image\/\"},\"sameAs\":[\"https:\/\/www.iichen.cn\"],\"url\":\"https:\/\/iichen.cn\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Android-\u8fde\u70b9\u5668 - IIchen","description":"Android-\u8fde\u70b9\u5668","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/iichen.cn\/?p=793","og_locale":"zh_CN","og_type":"article","og_title":"Android-\u8fde\u70b9\u5668 - IIchen","og_description":"Android-\u8fde\u70b9\u5668","og_url":"https:\/\/iichen.cn\/?p=793","og_site_name":"IIchen","article_published_time":"2025-01-24T07:24:18+00:00","article_modified_time":"2025-01-24T07:39:47+00:00","og_image":[{"url":"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click-1024x798.png","type":"","width":"","height":""}],"author":"iichen","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"iichen","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"1 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/iichen.cn\/?p=793#article","isPartOf":{"@id":"https:\/\/iichen.cn\/?p=793"},"author":{"name":"iichen","@id":"https:\/\/iichen.cn\/#\/schema\/person\/4a47edf85ab49841df9e8f6aee40b77c"},"headline":"Android-\u8fde\u70b9\u5668","datePublished":"2025-01-24T07:24:18+00:00","dateModified":"2025-01-24T07:39:47+00:00","mainEntityOfPage":{"@id":"https:\/\/iichen.cn\/?p=793"},"wordCount":12,"commentCount":0,"publisher":{"@id":"https:\/\/iichen.cn\/#\/schema\/person\/4a47edf85ab49841df9e8f6aee40b77c"},"image":{"@id":"https:\/\/iichen.cn\/?p=793#primaryimage"},"thumbnailUrl":"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click-1024x798.png","articleSection":["Android","\u7b14\u8bb0"],"inLanguage":"zh-Hans","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/iichen.cn\/?p=793#respond"]}]},{"@type":"WebPage","@id":"https:\/\/iichen.cn\/?p=793","url":"https:\/\/iichen.cn\/?p=793","name":"Android-\u8fde\u70b9\u5668 - IIchen","isPartOf":{"@id":"https:\/\/iichen.cn\/#website"},"primaryImageOfPage":{"@id":"https:\/\/iichen.cn\/?p=793#primaryimage"},"image":{"@id":"https:\/\/iichen.cn\/?p=793#primaryimage"},"thumbnailUrl":"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click-1024x798.png","datePublished":"2025-01-24T07:24:18+00:00","dateModified":"2025-01-24T07:39:47+00:00","description":"Android-\u8fde\u70b9\u5668","breadcrumb":{"@id":"https:\/\/iichen.cn\/?p=793#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/iichen.cn\/?p=793"]}]},{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/iichen.cn\/?p=793#primaryimage","url":"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click.png","contentUrl":"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/normal_click.png","width":1075,"height":838},{"@type":"BreadcrumbList","@id":"https:\/\/iichen.cn\/?p=793#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9875","item":"https:\/\/iichen.cn\/"},{"@type":"ListItem","position":2,"name":"Android-\u8fde\u70b9\u5668"}]},{"@type":"WebSite","@id":"https:\/\/iichen.cn\/#website","url":"https:\/\/iichen.cn\/","name":"IIchen","description":"Just do it!","publisher":{"@id":"https:\/\/iichen.cn\/#\/schema\/person\/4a47edf85ab49841df9e8f6aee40b77c"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/iichen.cn\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"zh-Hans"},{"@type":["Person","Organization"],"@id":"https:\/\/iichen.cn\/#\/schema\/person\/4a47edf85ab49841df9e8f6aee40b77c","name":"iichen","image":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/iichen.cn\/#\/schema\/person\/image\/","url":"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/avatar.jpg","contentUrl":"https:\/\/iichen.cn\/wp-content\/uploads\/2025\/01\/avatar.jpg","width":940,"height":940,"caption":"iichen"},"logo":{"@id":"https:\/\/iichen.cn\/#\/schema\/person\/image\/"},"sameAs":["https:\/\/www.iichen.cn"],"url":"https:\/\/iichen.cn\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/iichen.cn\/index.php?rest_route=\/wp\/v2\/posts\/793","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/iichen.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/iichen.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/iichen.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/iichen.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=793"}],"version-history":[{"count":16,"href":"https:\/\/iichen.cn\/index.php?rest_route=\/wp\/v2\/posts\/793\/revisions"}],"predecessor-version":[{"id":814,"href":"https:\/\/iichen.cn\/index.php?rest_route=\/wp\/v2\/posts\/793\/revisions\/814"}],"wp:attachment":[{"href":"https:\/\/iichen.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=793"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/iichen.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=793"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/iichen.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=793"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}