Skip to content

Commit f97cbf7

Browse files
committed
Initial support for overlay draggable view (over other app)
- Update usage readme - Update example Solve #5
1 parent 4439948 commit f97cbf7

File tree

13 files changed

+347
-26
lines changed

13 files changed

+347
-26
lines changed

.idea/codeStyles/Project.xml

-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/gradle.xml

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+88-8
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ dependencies {
3232

3333
**Note:** check the number on Jitpack badge above for latest version
3434

35+
---
36+
3537
## Usage
3638

37-
### CustomView (XML)
39+
### Draggable Inside App
40+
#### CustomView (XML)
3841

3942
Currently i've only provide CustomView that extends ImageView. For other view, see **Programmatically** usage below
4043

41-
#### Customizable Attributes
44+
##### Customizable Attributes
4245

4346
**DraggableImageView**
4447

@@ -47,7 +50,7 @@ Attribute | Value (Default) | XML | Code
4750
Animate | true, false (false) | animate | setAnimate(boolean isAnimate)
4851
Sticky Axis | NON_STICKY, STICKY_AXIS_X, STICKY_AXIS_Y, STICKY_AXIS_XY (NON_STICKY) | sticky | setStickyAxis(int axis)
4952

50-
#### On Layout XML file
53+
##### On Layout XML file
5154
```xml
5255
<io.github.hyuwah.draggableviewlib.DraggableImageView
5356
android:src="@mipmap/ic_launcher_round"
@@ -56,7 +59,7 @@ Sticky Axis | NON_STICKY, STICKY_AXIS_X, STICKY_AXIS_Y, STICKY_AXIS_XY (NON_STIC
5659
android:layout_height="wrap_content"/>
5760
```
5861

59-
#### On Activity / Fragment file
62+
##### On Activity / Fragment file
6063
```kotlin
6164
var dv = findViewById<DraggableImageView>(R.id.draggableView)
6265
dv.setOnClickListener {
@@ -66,9 +69,9 @@ dv.setOnClickListener {
6669

6770
You can add a DraggableListener programmatically via `setListener()` method directly on the view, see below explanation about the listener
6871

69-
### Programmatically
72+
#### Programmatically
7073

71-
#### Using extension (Kotlin)
74+
##### Using extension (Kotlin)
7275

7376
You can extent any view or viewgroup to be draggable (i.e. Button, FrameLayout, Linearlayout, LottieView, etc)
7477

@@ -114,7 +117,7 @@ tv.makeDraggable() // all default
114117
// - DraggableListener implementation (default is null)
115118
```
116119

117-
#### Using DraggableUtils (Java)
120+
##### Using DraggableUtils (Java)
118121

119122
If you're on java class, you could do it with the help of DraggableUtils
120123

@@ -167,7 +170,7 @@ DraggableUtils.makeDraggable(button) // all default
167170
// - DraggableListener implementation (default is null)
168171
```
169172

170-
#### DraggableListener
173+
##### DraggableListener
171174
There's an interface `DraggableListener` to listen to the `View` while being dragged / moved
172175

173176
```kotlin
@@ -189,6 +192,83 @@ someView.makeDraggable(object: DraggableListener{
189192

190193
Check example module [kotlin](https://github.com/hyuwah/DraggableView/blob/master/example/src/main/java/io/github/hyuwah/draggableview/MainActivity.kt), [java](https://github.com/hyuwah/DraggableView/blob/master/example/src/main/java/io/github/hyuwah/draggableview/JavaMainActivity.java) for actual implementation
191194

195+
### Draggable over other App (Overlay)
196+
197+
> Tested working on API 25, 28 & 29
198+
> Not working as of now on API 19 (on investigation)
199+
200+
This is the simplest way to setup an overlay draggable view, assuming it will be started from an activity.
201+
202+
Some notes:
203+
* On the activity, implement `OverlayDraggableListener`
204+
* We need to make the view programmatically, here i'm creating a TextView, you can also inflate a layout
205+
* You need to make the view as global variable
206+
* Here i'm omitting the params / using default params for `makeOverlayDraggable()`
207+
208+
This will create an overlay draggable view that tied to the activity's lifecycle, from `onCreate` until `onDestroy`.
209+
It is recommended to create the overlay draggable view on a `Service` instead of an activity, because of that reason.
210+
211+
```kotlin
212+
class ExampleActivity: AppCompatActivity(), OverlayDraggableListener {
213+
214+
private lateinit var overlayView: TextView
215+
216+
override fun onCreate(savedInstanceState: Bundle?) {
217+
super.onCreate(savedInstanceState)
218+
setContentView(R.layout.activity_example)
219+
overlayView = TextView(this)
220+
overlayView.text = "Overlay Text View"
221+
overlayView.textSize = 32f
222+
overlayView.setShadowLayer(10f, 5f, 5f, Color.rgb(56, 56, 56))
223+
overlayView.setOnClickListener {
224+
Toast.makeText(this, "Overlay view clicked", Toast.LENGTH_SHORT).show()
225+
}
226+
var params = overlayView.makeOverlayDraggable(this)
227+
windowManager.addView(overlayView, params) // Show overlay view
228+
}
229+
230+
// From OverlayDraggableListener
231+
override fun onParamsChanged(updatedParams: WindowManager.LayoutParams) {
232+
windowManager.updateViewLayout(overlayView, updatedParams) // Move overlay view
233+
}
234+
235+
override fun onDestroy() {
236+
super.onDestroy()
237+
windowManager.removeViewImmediate(overlayView) // Remove overlay view
238+
}
239+
}
240+
```
241+
242+
You also need to add some permission
243+
244+
```xml
245+
<uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />
246+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
247+
```
248+
249+
Also, before adding the view to WindowManager, you need to check if the device support overlay and required permission
250+
251+
```kotlin
252+
private fun checkOverlayPermission() {
253+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
254+
!Settings.canDrawOverlays(this)
255+
) {
256+
// Get permission first on Android M & above
257+
val intent = Intent(
258+
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
259+
Uri.parse("package:$packageName")
260+
)
261+
startActivityForResult(intent, 1234)
262+
} else {
263+
// overlayView & params are global variable
264+
windowManager.addView(overlayView, params)
265+
}
266+
}
267+
```
268+
269+
Check the example here: [Kotlin](https://github.com/hyuwah/DraggableView/blob/master/example/src/main/java/io/github/hyuwah/draggableview/OverlayDraggableActivity.kt)
270+
271+
---
192272
## Accompanying Article
193273

194274
* [Implementasi DraggableView di Android (Bahasa)](https://medium.com/@hyuwah/implementasi-draggable-view-di-android-eb84e50fbba9)

build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// Top-level build file where you can add configuration options common to all sub-projects/modules.
22

33
buildscript {
4-
ext.kotlin_version = '1.3.50'
4+
ext.kotlin_version = '1.3.71'
55
repositories {
66
google()
77
jcenter()
88

99
}
1010
dependencies {
11-
classpath 'com.android.tools.build:gradle:3.5.1'
11+
classpath 'com.android.tools.build:gradle:3.6.3'
1212
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1313
// NOTE: Do not place your application dependencies here; they belong
1414
// in the individual module build.gradle files

draggableviewlib/src/main/java/io/github/hyuwah/draggableviewlib/Extensions.kt

+67
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
package io.github.hyuwah.draggableviewlib
44

5+
import android.graphics.PixelFormat
6+
import android.os.Build
57
import android.view.MotionEvent
68
import android.view.View
9+
import android.view.WindowManager
710
import io.github.hyuwah.draggableviewlib.Draggable.DRAG_TOLERANCE
811
import io.github.hyuwah.draggableviewlib.Draggable.DURATION_MILLIS
912
import kotlin.math.abs
1013
import kotlin.math.max
1114
import kotlin.math.min
15+
import kotlin.math.roundToInt
1216

1317
@JvmOverloads
1418
fun View.makeDraggable(
@@ -138,4 +142,67 @@ fun View.makeDraggable(
138142
}
139143
true
140144
}
145+
}
146+
147+
/**
148+
* Make floating draggable overlay view (on top of other application).
149+
* You still have to manually manage adding, updating & removing the view via Window Manager from where
150+
* you call this function
151+
* @param listener callback for new LayoutParams, do `windowManager.updateViewLayout()` here
152+
* @param layoutParams if you need to customize the layout params (e.g. Gravity),
153+
* note that you still have to use correct Layout Flag, can be omitted for default value
154+
* @return layoutParams to be used when adding the view on window manager (outside this function)
155+
*/
156+
@JvmOverloads
157+
fun View.makeOverlayDraggable(
158+
listener: OverlayDraggableListener,
159+
layoutParams: WindowManager.LayoutParams? = null
160+
): WindowManager.LayoutParams {
161+
var widgetInitialX = 0
162+
var widgetDX = 0f
163+
var widgetInitialY = 0
164+
var widgetDY = 0f
165+
166+
val layoutFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
167+
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
168+
} else {
169+
WindowManager.LayoutParams.TYPE_PHONE
170+
}
171+
val params = layoutParams
172+
?: WindowManager.LayoutParams(
173+
WindowManager.LayoutParams.WRAP_CONTENT,
174+
WindowManager.LayoutParams.WRAP_CONTENT,
175+
layoutFlag,
176+
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
177+
PixelFormat.TRANSLUCENT
178+
)
179+
180+
setOnTouchListener { _, event ->
181+
when (event.actionMasked) {
182+
MotionEvent.ACTION_DOWN -> {
183+
widgetInitialX = params.x
184+
widgetInitialY = params.y
185+
widgetDX = widgetInitialX - event.rawX
186+
widgetDY = widgetInitialY - event.rawY
187+
return@setOnTouchListener true
188+
}
189+
MotionEvent.ACTION_MOVE -> {
190+
val newX = event.rawX + widgetDX
191+
val newY = event.rawY + widgetDY
192+
params.x = newX.roundToInt()
193+
params.y = newY.roundToInt()
194+
listener.onParamsChanged(params)
195+
return@setOnTouchListener true
196+
}
197+
MotionEvent.ACTION_UP -> {
198+
if (abs(params.x - widgetInitialX) <= DRAG_TOLERANCE && abs(params.y - widgetInitialY) <= DRAG_TOLERANCE) {
199+
performClick()
200+
}
201+
return@setOnTouchListener true
202+
}
203+
else -> return@setOnTouchListener false
204+
}
205+
}
206+
207+
return params
141208
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.github.hyuwah.draggableviewlib
2+
3+
import android.view.WindowManager
4+
5+
interface OverlayDraggableListener {
6+
fun onParamsChanged(updatedParams: WindowManager.LayoutParams)
7+
}

example/src/main/AndroidManifest.xml

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="io.github.hyuwah.draggableview">
44

5+
<!-- These permission is only used by overlay draggable view-->
6+
<uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />
7+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
8+
59
<application
610
android:allowBackup="true"
711
android:icon="@mipmap/ic_launcher"
812
android:label="@string/app_name"
913
android:roundIcon="@mipmap/ic_launcher_round"
1014
android:supportsRtl="true"
1115
android:theme="@style/AppTheme">
12-
<activity android:name=".JavaMainActivity"></activity>
16+
<activity
17+
android:name=".OverlayDraggableActivity"
18+
android:parentActivityName=".MainActivity" />
19+
<activity
20+
android:name=".JavaMainActivity"
21+
android:parentActivityName=".MainActivity" />
1322
<activity android:name=".MainActivity">
1423
<intent-filter>
1524
<action android:name="android.intent.action.MAIN" />

example/src/main/java/io/github/hyuwah/draggableview/JavaMainActivity.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package io.github.hyuwah.draggableview;
22

3-
import android.support.v4.view.ViewPropertyAnimatorListener;
4-
import android.support.v7.app.AppCompatActivity;
53
import android.os.Bundle;
4+
import android.support.v7.app.AppCompatActivity;
65
import android.view.View;
76
import android.widget.Button;
87
import android.widget.TextView;
@@ -20,6 +19,7 @@ public class JavaMainActivity extends AppCompatActivity implements DraggableList
2019
protected void onCreate(Bundle savedInstanceState) {
2120
super.onCreate(savedInstanceState);
2221
setContentView(R.layout.activity_java_main);
22+
setTitle("Java Activity");
2323

2424
Button button = findViewById(R.id.btn_java);
2525

example/src/main/java/io/github/hyuwah/draggableview/MainActivity.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ class MainActivity : AppCompatActivity() {
117117
// Set click listener
118118
dvTest.setOnClickListener {
119119
Toast.makeText(this@MainActivity, "Clicked", Toast.LENGTH_SHORT).show()
120-
startActivity(Intent(this, JavaMainActivity::class.java))
121120
}
122121

123122
ll_test_draggable.setOnClickListener {
@@ -130,5 +129,13 @@ class MainActivity : AppCompatActivity() {
130129
.show()
131130
}
132131

132+
btn_java_activity.setOnClickListener {
133+
startActivity(Intent(this, JavaMainActivity::class.java))
134+
}
135+
136+
btn_overlay_activity.setOnClickListener {
137+
startActivity(Intent(this, OverlayDraggableActivity::class.java))
138+
}
139+
133140
}
134141
}

0 commit comments

Comments
 (0)