android调试助手基于recyclerView

android调试助手基于recyclerView

效果图如下
调试助手

一 、自定义View

fun Context.dip(value: Float): Int = (value * resources.displayMetrics.density).toInt()
fun Context.dip(value: Int): Int = dip(value.toFloat())
fun Context.sp(value: Float): Int = (value * resources.displayMetrics.scaledDensity).toInt()
fun Context.sp(value: Int): Int = sp(value.toFloat())
fun Context.width(): Int = resources.displayMetrics.widthPixels
fun Context.height(): Int = resources.displayMetrics.heightPixels
class DebugView constructor(context: Context, attrs: AttributeSet? = null, defStyleId: Int = 0) :
    ConstraintLayout(context, attrs, defStyleId), View.OnClickListener,
    CoroutineScope by MainScope() {
    private val container: FrameLayout
    private val recyclerView: RecyclerView
    private val navigationBackView: ImageView
    private val titleView: TextView
    private val toggleView: ImageView

    private val viewController: ViewController

    init {
        background = GradientDrawable().apply {
            setColor(Color.WHITE)
            cornerRadius = context.dip(5).toFloat()
        }
        inflate(context, R.layout.debug_layout_main, this)
        navigationBackView = findViewById(R.id.back)
        container = findViewById(R.id.container)
        recyclerView = findViewById(R.id.recycler)
        titleView = findViewById(R.id.title)
        toggleView = findViewById(R.id.toggle)
        viewController = ViewController(container)

        viewController.onStackChangeListener = object : ViewController.OnStackChangeListener {
            override fun onStackChanged(controller: ViewController) {
                if (controller.isRoot()) {
                    navigationBackView.visibility = View.GONE
                } else {
                    navigationBackView.visibility = View.VISIBLE
                }
            }
        }
        navigationBackView.setOnClickListener {
            if (!viewController.isRoot()) {
                viewController.pop()
            }
        }

        val items = buildItems()
        val adapter = DebugAdapter(context, items)
        adapter.registerFactory(ExitItem::class.java, ExitItem.Factory)
        recyclerView.adapter = adapter
        val spanCount = 2
        recyclerView.layoutManager = GridLayoutManager(context, spanCount).apply {
            spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    return if (items.size - 1 == position) {
                        spanCount
                    } else {
                        1
                    }
                }
            }
        }
        recyclerView.addItemDecoration(SimpleDividerDecoration())
        toggleView.clipToOutline = true
        toggleView.outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View, outline: Outline) {
                outline.setOval(0, 0, view.width, view.height)
            }
        }
        toggleView.setBackgroundColor(Color.GREEN)
        toggleView.setOnClickListener(this)
    }
    // 右上角的绿色开关   隐藏和显示
    private fun toggle() {
        if (container.visibility == View.VISIBLE) {
            container.visibility = View.GONE
            toggleView.setBackgroundColor(Color.BLUE)
        } else {
            container.visibility = View.VISIBLE
            toggleView.setBackgroundColor(Color.GREEN)
        }
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.toggle -> toggle()
        }
    }
	private fun buildItems(): List<DebugItem> {
        val items: MutableList<DebugItem> = mutableListOf()
        items.add(DefaultDebugItem("ADB相关") {
        // TODO 添加点击item的操作 })
        items.add(ExitItem(context))
        return items
    }
    
	
}    

二、布局XML

<?xml version="1.0" encoding="utf-8"?>
<merge 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="wrap_content"
    android:layout_height="wrap_content"
    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">

    <ImageView
        android:id="@+id/back"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        android:src="@drawable/navigation_back_black_18dp"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="@id/container"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/title"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:paddingTop="12dp"
        android:paddingBottom="12dp"
        android:text="调试助手"
        android:textColor="#333"
        app:layout_constraintBottom_toTopOf="@id/container"
        app:layout_constraintLeft_toRightOf="@id/back"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_min="140dp"
        app:layout_goneMarginStart="12dp" />

    <ImageView
        android:id="@+id/toggle"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginEnd="12dp"
        app:layout_constraintBottom_toBottomOf="@id/title"
        app:layout_constraintRight_toRightOf="@id/title"
        app:layout_constraintTop_toTopOf="@id/title" />

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constrainedHeight="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintWidth_min="250dp">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </FrameLayout>

</merge>

三、工具类和Adapter

ViewController

class ViewController(private val container: FrameLayout) {

    interface OnStackChangeListener {

        fun onStackChanged(controller: ViewController)

    }

    var onStackChangeListener: OnStackChangeListener? = null

    fun isRoot(): Boolean {
        return container.childCount <= 1
    }

    fun push(view: View) {
        if (container.indexOfChild(view) != -1) {
            return
        }

        container.addView(view)
        onStackChangeListener?.onStackChanged(this)
    }

    fun pop() {
        val top = container.getChildAt(container.childCount - 1) ?: return

        container.removeView(top)
        onStackChangeListener?.onStackChanged(this)
    }

}

DebugAdapter

class DebugAdapter(
    private val context: Context,
    private val items: List<DebugItem>
) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var baseViewType = 0
    private val itemTypes = mutableMapOf<String, Int>()
    private fun getViewTypeOf(item: DebugItem): Int = getViewTypeOf(item.javaClass)

    private fun getViewTypeOf(clazz: Class<out DebugItem>): Int {
        return itemTypes[clazz.name] ?: run {
            val newType = baseViewType++
            itemTypes[clazz.name] = newType
            newType
        }
    }

    private val factories = mutableMapOf<Int, DebugItem.Factory>()
    private fun getFactoryOf(viewType: Int): DebugItem.Factory {
        return factories[viewType] ?: DebugItem.DefaultFactory
    }

    fun registerFactory(item: Class<out DebugItem>, factory: DebugItem.Factory) {
        factories[getViewTypeOf(item)] = factory
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val factory = getFactoryOf(viewType)
        return object : RecyclerView.ViewHolder(factory.createView(parent)) {}
    }

    override fun getItemCount(): Int {
        return items.size
    }

    override fun getItemViewType(position: Int): Int {
        return getViewTypeOf(items[position])
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = items[position]
        item.render(holder.itemView)
        if (item is View.OnClickListener) {
            holder.itemView.setOnClickListener(item)
        }
    }

}

RecyclerView分割线

class SimpleDividerDecoration : RecyclerView.ItemDecoration() {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = 0xFFF5F5F5.toInt()
    }

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        val position = parent.getChildLayoutPosition(view)
        val bottom = if (position == state.itemCount - 1) 0 else view.context.dip(1)
        outRect.set(0, 0, 0, bottom)
    }

    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        super.onDraw(c, parent, state)
        (0 until parent.childCount - 1).forEach {
            val child = parent.getChildAt(it)
            val params = child.layoutParams as ViewGroup.MarginLayoutParams
            val left = child.left - params.leftMargin
            val right = child.right + params.rightMargin
            val top = child.bottom + params.bottomMargin
            val bottom = top + parent.context.dip(1)
            c.drawRect(
                left.toFloat(),
                top.toFloat(),
                right.toFloat(),
                bottom.toFloat(),
                paint
            )
        }
    }

}

DebugItem

abstract class DebugItem(val name: String) {

    interface Factory {
        fun createView(parent: ViewGroup): View
    }

    abstract fun render(view: View)

    companion object {
        val DefaultFactory = object : Factory {
            override fun createView(parent: ViewGroup): TextView {
                return TextView(parent.context).apply {
                    layoutParams = FrameLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT
                    )
                    textSize = 16F
                    gravity = Gravity.CENTER
                    setTextColor(0xFF333333.toInt())
                    setBackgroundResource(R.drawable.debug_item_background)
                    setPadding(context.dip(16), context.dip(16), context.dip(16), context.dip(16))
                }
            }
        }
    }
}

open class DefaultDebugItem(
    name: String,
    private val onClick: ((View) -> Unit)? = null
) :
    DebugItem(name), View.OnClickListener {

    override fun render(view: View) {
        (view as TextView).text = name
    }

    override fun onClick(v: View) {
        this.onClick?.invoke(v)
    }
}

class ExitItem(val context: Context) : DefaultDebugItem("关闭浮窗", onClick = {
    context.stopService(Intent(context, DebugService::class.java))
}) {

    companion object Factory :
        DebugItem.Factory {
        override fun createView(parent: ViewGroup): TextView {
            return TextView(parent.context).apply {
                layoutParams = FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
                )
                textSize = 16F
                gravity = Gravity.CENTER
                setTextColor(0xFFFFFFFF.toInt())
                setBackgroundResource(R.drawable.debug_item_background_red)
                setPadding(context.dip(16), context.dip(16), context.dip(16), context.dip(16))
            }
        }
    }
}

ScrollTouchListener

class ScrollTouchListener(
    private val wm: WindowManager
) : View.OnTouchListener {

    private var x = 0
    private var y = 0

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
        when (motionEvent.action) {
            MotionEvent.ACTION_DOWN -> {
                x = motionEvent.rawX.toInt()
                y = motionEvent.rawY.toInt()
            }
            MotionEvent.ACTION_MOVE -> {
                val nowX = motionEvent.rawX.toInt()
                val nowY = motionEvent.rawY.toInt()
                val movedX = nowX - x
                val movedY = nowY - y
                x = nowX
                y = nowY
                (view.layoutParams as? WindowManager.LayoutParams)?.apply {
                    x += movedX
                    y += movedY
                    wm.updateViewLayout(view, this)
                }
            }
            else -> {

            }
        }
        return false
    }
}

四、添加view

private val wm by lazy {
	getSystemService(WINDOW_SERVICE) as WindowManager
}
private val entrance by lazy {
	DebugView(applicationContext)
}
override fun onCreate() {
        super.onCreate()

        wm.addView(
            entrance, WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
                } else {
                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
                },
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT
            )
        )
        entrance.setOnTouchListener(ScrollTouchListener(wm))
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值