ps: 不想看代码的滑到最下面有apk包百度网盘下载地址
1. 先看效果图 不然都是耍流氓
2.项目目录
3.一些配置
build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
android {
compileSdkVersion 31
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.znan.autoclick"
minSdkVersion 24
targetSdkVersion 31
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'
//协程
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 注册权限和服务
<?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 无障碍服务
class AutoClickService : AccessibilityService() {
private val TAG = javaClass.canonicalName
var mainScope: CoroutineScope? = null
private val broadcastReceiver = BroadcastHandler(this)
//点击间隔
private var mInterval = -1L
//点击坐标xy
private var mPointX = -1f
private var mPointY = -1f
//悬浮窗视图
private lateinit var mFloatingView: FloatingClickView
companion object {
//打开悬浮窗
val ACTION_SHOW = "action_show"
//自动点击事件 开启/关闭
val ACTION_PLAY = "action_play"
val ACTION_STOP = "action_stop"
//关闭悬浮窗
val ACTION_CLOSE = "action_close"
}
private inner class BroadcastHandler(val context: Context) : BroadcastReceiver() {
fun register() {
context.registerReceiver(
this,
IntentFilter().apply {
addAction(BroadcastConstants.BROADCAST_ACTION_AUTO_CLICK)
//息屏关闭自动点击事件
addAction(Intent.ACTION_SCREEN_OFF)
}
)
}
fun unregister() {
context.unregisterReceiver(this)
}
override fun onReceive(p0: Context?, intent: Intent?) {
intent?.apply {
when(action) {
Intent.ACTION_SCREEN_OFF -> {
mFloatingView.remove()
mainScope?.cancel()
}
BroadcastConstants.BROADCAST_ACTION_AUTO_CLICK -> {
when (getStringExtra(BroadcastConstants.KEY_ACTION)) {
ACTION_SHOW -> {
mFloatingView.remove()
mainScope?.cancel()
mInterval = getLongExtra(BroadcastConstants.KEY_INTERVAL, 5000)
mFloatingView.show()
}
ACTION_PLAY -> {
mPointX = getFloatExtra(BroadcastConstants.KEY_POINT_X, 0f)
mPointY = getFloatExtra(BroadcastConstants.KEY_POINT_Y, 0f)
mainScope = MainScope()
autoClickView(mPointX, mPointY)
}
ACTION_STOP -> {
mainScope?.cancel()
}
ACTION_CLOSE -> {
mFloatingView.remove()
mainScope?.cancel()
}
else -> {
Log.e(TAG, "action error")
}
}
}
}
}
}
}
override fun onCreate() {
super.onCreate()
startForegroundNotification()
mFloatingView = FloatingClickView(this)
broadcastReceiver.register()
}
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, "自动点击完成")
}
override fun onCancelled(gestureDescription: GestureDescription?) {
super.onCancelled(gestureDescription)
Log.d(TAG, "自动点击取消")
}
},
null
)
}
}
}
override fun onInterrupt() {
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
}
override fun onDestroy() {
super.onDestroy()
broadcastReceiver.unregister()
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
}
/**
* 更新悬浮窗参数
*
* @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
class FloatingClickView(private val mContext: Context) : FrameLayout(mContext) {
private lateinit var mWindowManager: FloatingManager
private var mParams: WindowManager.LayoutParams? = null
private lateinit var mView: View
//按下坐标
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().apply {
action = BroadcastConstants.BROADCAST_ACTION_AUTO_CLICK
when (mCurrentState) {
STATE_NORMAL -> {
mCurrentState = STATE_CLICKING
putExtra(BroadcastConstants.KEY_ACTION, AutoClickService.ACTION_PLAY)
putExtra(BroadcastConstants.KEY_POINT_X, (location[0] - 1).toFloat())
putExtra(BroadcastConstants.KEY_POINT_Y, (location[1] - 1).toFloat())
ivIcon?.setImageResource(R.drawable.ic_auto_click_icon_green_24)
}
STATE_CLICKING -> {
mCurrentState = STATE_NORMAL
putExtra(BroadcastConstants.KEY_ACTION, AutoClickService.ACTION_STOP)
ivIcon?.setImageResource(R.drawable.ic_auto_click_icon_gray_24)
}
}
}
context.sendBroadcast(intent)
}
}
fun show() {
mParams = WindowManager.LayoutParams()
mParams?.apply {
gravity = Gravity.CENTER
//总是出现在应用程序窗口之上
type = if (Build.VERSION.SDK_INT >= 26) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
}
//设置图片格式,效果为背景透明
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() {
mCurrentState = STATE_NORMAL
ivIcon?.setImageResource(R.drawable.ic_auto_click_icon_gray_24)
mWindowManager.removeView(mView)
}
}
页面事件
MainActivity.kt
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()
startAutoClickService()
}
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(et_interval.text.toString().toLong())
}
btn_close_window.setOnClickListener {
closeFloatWindow()
}
btn_test.setOnClickListener {
Log.d(TAG, "btn_test on click")
}
}
/**
* 跳转设置开启无障碍
*/
private fun checkAccessibility() {
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)
}
/**
* 跳转设置顶层悬浮窗
*/
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 startAutoClickService() {
val intent = Intent(this, AutoClickService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}
private fun showFloatingWindow(interval: Long) {
sendBroadcast(Intent().apply {
action = BroadcastConstants.BROADCAST_ACTION_AUTO_CLICK
putExtra(BroadcastConstants.KEY_ACTION, AutoClickService.ACTION_SHOW)
putExtra(BroadcastConstants.KEY_INTERVAL, interval)
})
}
private fun closeFloatWindow() {
sendBroadcast(Intent().apply {
action = BroadcastConstants.BROADCAST_ACTION_AUTO_CLICK
putExtra(BroadcastConstants.KEY_ACTION, AutoClickService.ACTION_CLOSE)
})
}
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"
}
object BroadcastConstants {
val BROADCAST_ACTION_AUTO_CLICK = "BROADCAST_ACTION_AUTO_CLICK"
val KEY_ACTION = "KEY_ACTION"
val KEY_INTERVAL = "KEY_INTERVAL"
val KEY_POINT_X = "KEY_POINT_X"
val KEY_POINT_Y = "KEY_POINT_Y"
}
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="先打开无障碍权限 和 悬浮窗顶层权限\n点击位置在图标左上角xy偏移一个px\n程序将会在延迟一个间隔后开始执行"
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="无障碍选项"
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="悬浮窗选项"
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="点击的间隔(建议大于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="关闭悬浮视图"
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="测试点击按钮"
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.app-auto-click.zip
提取码: 6p2u
告辞~