Android實(shí)現(xiàn)自動(dòng)點(diǎn)擊無障礙服務(wù)功能的實(shí)例代碼
ps: 不想看代碼的滑到最下面有apk包百度網(wǎng)盤下載地址
1. 先看效果圖 不然都是耍流氓

2.項(xiàng)目目錄

3.一些配置
build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.znan.autoclick"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//協(xié)程
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
}
accessibility.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackAllMask"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_desc" />
AndroidManifest.xml 注冊權(quán)限和服務(wù)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.znan.autoclick">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AutoClick">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".AutoClickService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility" />
</service>
</application>
</manifest>
4.代碼
AutoClickService.kt 無障礙服務(wù)
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.app.Notification
import android.content.Intent
import android.graphics.Path
import android.os.Build
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.*
class AutoClickService : AccessibilityService() {
private val TAG = javaClass.canonicalName
var mainScope: CoroutineScope? = null
//點(diǎn)擊間隔
private var mInterval = -1L
//點(diǎn)擊坐標(biāo)xy
private var mPointX = -1f
private var mPointY = -1f
//懸浮窗視圖
private lateinit var mFloatingView: FloatingClickView
companion object {
val FLAG_ACTION = "flag_action"
//打開懸浮窗
val ACTION_SHOW = "action_show"
//自動(dòng)點(diǎn)擊事件 開啟/關(guān)閉
val ACTION_PLAY = "action_play"
val ACTION_STOP = "action_stop"
//關(guān)閉懸浮窗
val ACTION_CLOSE = "action_close"
}
override fun onCreate() {
super.onCreate()
startForegroundNotification()
mFloatingView = FloatingClickView(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand " + intent?.extras)
intent?.apply {
val action = getStringExtra(FLAG_ACTION)
Log.d(TAG, "action " + action)
when (action) {
ACTION_SHOW -> {
mInterval = getLongExtra("interval", 5000)
mFloatingView.show()
}
ACTION_PLAY -> {
mPointX = getFloatExtra("pointX", 0f)
mPointY = getFloatExtra("pointY", 0f)
mainScope = MainScope()
autoClickView(mPointX, mPointY)
}
ACTION_STOP -> {
mainScope?.cancel()
}
ACTION_CLOSE -> {
mFloatingView.remove()
mainScope?.cancel()
}
else -> {
Log.e(TAG, "action error")
}
}
}
return super.onStartCommand(intent, flags, startId)
}
private fun startForegroundNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationBuilder =
NotificationCompat.Builder(this, NotificationConstants.CHANNEL_ID)
val notification = notificationBuilder.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setCategory(Notification.CATEGORY_SERVICE)
.build()
startForeground(-1, notification)
} else {
startForeground(-1, Notification())
}
}
@RequiresApi(Build.VERSION_CODES.N)
private fun autoClickView(x: Float, y: Float) {
mainScope?.launch {
while (true) {
delay(mInterval)
Log.d(TAG, "auto click x:$x y:$y")
val path = Path()
path.moveTo(x, y)
val gestureDescription = GestureDescription.Builder()
.addStroke(GestureDescription.StrokeDescription(path, 100L, 100L))
.build()
dispatchGesture(
gestureDescription,
object : AccessibilityService.GestureResultCallback() {
override fun onCompleted(gestureDescription: GestureDescription?) {
super.onCompleted(gestureDescription)
Log.d(TAG, "自動(dòng)點(diǎn)擊完成")
}
override fun onCancelled(gestureDescription: GestureDescription?) {
super.onCancelled(gestureDescription)
Log.d(TAG, "自動(dòng)點(diǎn)擊取消")
}
},
null
)
}
}
}
override fun onInterrupt() {
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
}
override fun onDestroy() {
super.onDestroy()
mainScope?.cancel()
}
}
懸浮窗
SingletonHolder.kt
open class SingletonHolder<out T, in A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
@Volatile private var instance: T? = null
fun getInstance(arg: A): T {
val i = instance
if (i != null) {
return i
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}
FloatingManager.kt
import android.content.Context
import android.view.View
import android.view.WindowManager
class FloatingManager private constructor(context: Context) {
//獲得WindowManager對象
private var mWindowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
companion object : SingletonHolder<FloatingManager, Context>(::FloatingManager)
/**
* 添加懸浮窗
* @param view
* @param params
* @return
*/
fun addView(view: View, params: WindowManager.LayoutParams): Boolean {
try {
mWindowManager.addView(view, params)
return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
/**
* 移除懸浮窗
*
* @param view
* @return
*/
fun removeView(view: View): Boolean {
try {
mWindowManager.removeView(view)
return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
/**
* 更新懸浮窗參數(shù)
*
* @param view
* @param params
* @return
*/
fun updateView(view: View, params: WindowManager.LayoutParams): Boolean {
try {
mWindowManager.updateViewLayout(view, params)
return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
}
FloatingClickView.kt
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.view.*
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatImageView
class FloatingClickView(private val mContext: Context) : FrameLayout(mContext) {
private lateinit var mWindowManager: FloatingManager
private var mParams: WindowManager.LayoutParams? = null
private lateinit var mView: View
//按下坐標(biāo)
private var mTouchStartX = -1f
private var mTouchStartY = -1f
val STATE_CLICKING = "state_clicking"
val STATE_NORMAL = "state_normal"
private var mCurrentState = STATE_NORMAL
private var ivIcon: AppCompatImageView? = null
init {
initView()
}
private fun initView() {
mView = LayoutInflater.from(context).inflate(R.layout.view_floating_click, null)
ivIcon = mView.findViewById(R.id.iv_icon)
mWindowManager = FloatingManager.getInstance(mContext)
initListener()
}
@SuppressLint("ClickableViewAccessibility")
private fun initListener() {
mView.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mTouchStartX = event.rawX
mTouchStartY = event.rawY
}
MotionEvent.ACTION_MOVE -> {
mParams?.let {
it.x += (event.rawX - mTouchStartX).toInt()
it.y += (event.rawY - mTouchStartY).toInt()
mWindowManager.updateView(mView, it)
}
mTouchStartX = event.rawX
mTouchStartY = event.rawY
}
}
false
}
mView.setOnClickListener {
val location = IntArray(2)
it.getLocationOnScreen(location)
val intent = Intent(context, AutoClickService::class.java)
when (mCurrentState) {
STATE_NORMAL -> {
mCurrentState = STATE_CLICKING
intent.putExtra(AutoClickService.FLAG_ACTION, AutoClickService.ACTION_PLAY)
intent.putExtra("pointX", (location[0] - 1).toFloat())
intent.putExtra("pointY", (location[1] - 1).toFloat())
ivIcon?.setImageResource(R.drawable.ic_auto_click_icon_green_24)
}
STATE_CLICKING -> {
mCurrentState = STATE_NORMAL
intent.putExtra(AutoClickService.FLAG_ACTION, AutoClickService.ACTION_STOP)
ivIcon?.setImageResource(R.drawable.ic_auto_click_icon_gray_24)
}
}
context.startService(intent)
}
}
fun show() {
mParams = WindowManager.LayoutParams()
mParams?.apply {
gravity = Gravity.CENTER
//總是出現(xiàn)在應(yīng)用程序窗口之上
type = if (Build.VERSION.SDK_INT >= 26) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
}
//設(shè)置圖片格式,效果為背景透明
format = PixelFormat.RGBA_8888
flags =
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
width = LayoutParams.WRAP_CONTENT
height = LayoutParams.WRAP_CONTENT
if (mView.isAttachedToWindow) {
mWindowManager.removeView(mView)
}
mWindowManager.addView(mView, this)
}
}
fun remove() {
mWindowManager.removeView(mView)
}
}
頁面事件
MainActivity.kt
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings
import android.text.TextUtils
import android.util.Log
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val TAG = javaClass::class.java.canonicalName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initNotification()
initListener()
}
private fun initListener() {
btn_accessibility.setOnClickListener {
checkAccessibility()
}
btn_floating_window.setOnClickListener {
checkFloatingWindow()
}
btn_show_window.setOnClickListener {
hideKeyboard()
if (TextUtils.isEmpty(et_interval.text.toString())) {
Snackbar.make(et_interval, "請輸入間隔", Snackbar.LENGTH_SHORT).show()
return@setOnClickListener
}
showFloatingWindow()
}
btn_close_window.setOnClickListener {
closeFloatWindow()
}
btn_test.setOnClickListener {
Log.d(TAG, "btn_test on click")
}
}
/**
* 跳轉(zhuǎn)設(shè)置開啟無障礙
*/
private fun checkAccessibility() {
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)
}
/**
* 跳轉(zhuǎn)設(shè)置頂層懸浮窗
*/
private fun checkFloatingWindow() {
if (Build.VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(this)) {
Toast.makeText(this, "已開啟", Toast.LENGTH_SHORT).show()
} else {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
startActivity(intent)
}
}
}
private fun showFloatingWindow() {
val intent = Intent(this, AutoClickService::class.java)
intent.apply {
putExtra(AutoClickService.FLAG_ACTION, AutoClickService.ACTION_SHOW)
putExtra("interval", et_interval.text.toString().toLong())
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}
private fun closeFloatWindow() {
val intent = Intent(this, AutoClickService::class.java)
intent.putExtra(AutoClickService.FLAG_ACTION, AutoClickService.ACTION_CLOSE)
startService(intent)
}
private fun initNotification() {
//注冊渠道id
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = NotificationConstants.CHANNEl_NAME
val descriptionText = NotificationConstants.CHANNEL_DES
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel =
NotificationChannel(NotificationConstants.CHANNEL_ID, name, importance).apply {
description = descriptionText
}
channel.enableLights(true)
channel.lightColor = Color.GREEN
// Register the channel with the system
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
override fun onDestroy() {
val intent = Intent(this, AutoClickService::class.java)
stopService(intent)
super.onDestroy()
}
//收起輸入法
fun hideKeyboard() {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (imm.isActive && currentFocus != null) {
imm.hideSoftInputFromWindow(currentFocus!!.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
}
}
}
object NotificationConstants {
val CHANNEL_ID = "auto_channel_id"
val CHANNEl_NAME ="Auto Click"
val CHANNEL_DES = "Auto Click Service"
}
5.布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginTop="20dp"
android:text="先打開無障礙權(quán)限 和 懸浮窗頂層權(quán)限\n點(diǎn)擊位置在圖標(biāo)左上角xy偏移一個(gè)px\n程序?qū)?huì)在延遲一個(gè)間隔后開始執(zhí)行"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_accessibility"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginTop="20dp"
android:text="無障礙選項(xiàng)"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_message" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_floating_window"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="懸浮窗選項(xiàng)"
app:layout_constraintStart_toEndOf="@id/btn_accessibility"
app:layout_constraintTop_toTopOf="@id/btn_accessibility" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="50dp"
android:text="ms"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="@id/et_interval"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/et_interval" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/et_interval"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="50dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="10dp"
android:hint="點(diǎn)擊的間隔(建議大于100)(毫秒)"
android:inputType="number"
android:textColor="#FF0000"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@id/tv_unit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_accessibility" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_show_window"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="打開懸浮視圖"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_interval" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_close_window"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="關(guān)閉懸浮視圖"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_show_window" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="100dp"
android:text="測試點(diǎn)擊按鈕"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_auto_click_icon_gray_24" />
</FrameLayout>
6.debug.apk
鏈接: https://pan.baidu.com/s/1ukuJofO3SOfdOw5vgfMG4g
提取碼: va43
到此這篇關(guān)于Android實(shí)現(xiàn)自動(dòng)點(diǎn)擊無障礙服務(wù)功能的文章就介紹到這了,更多相關(guān)android無障礙自動(dòng)點(diǎn)擊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android Kotlin環(huán)境使用ButterKnife的方法
本篇文章主要介紹了Android Kotlin環(huán)境使用ButterKnife的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03
Android實(shí)現(xiàn)簡單的下拉刷新pulltorefresh
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡單的下拉刷新pulltorefresh的相關(guān)代碼,具有一定的實(shí)用性和操作價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07
Android使用listview實(shí)現(xiàn)分頁刷新(線程休眠模擬)
這篇文章主要為大家詳細(xì)介紹了Android使用listview實(shí)現(xiàn)分頁刷新,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android編程之界面跳動(dòng)提示動(dòng)畫效果實(shí)現(xiàn)方法
這篇文章主要介紹了Android編程之界面跳動(dòng)提示動(dòng)畫效果實(shí)現(xiàn)方法,實(shí)例分析了Android動(dòng)畫效果的布局及功能相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Android 5.0 實(shí)現(xiàn)水波擴(kuò)散效果
這篇文章主要為大家詳細(xì)介紹了Android 5.0 實(shí)現(xiàn)水波擴(kuò)散效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
詳解Android studio 動(dòng)態(tài)fragment的用法
這篇文章主要介紹了Android studio 動(dòng)態(tài)fragment的用法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10

