Skip to content

Commit 94c7d01

Browse files
committed
新增拖拽后半隐的悬浮窗使用案例
1 parent f0f8965 commit 94c7d01

File tree

6 files changed

+310
-1
lines changed

6 files changed

+310
-1
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ android {
55

66
defaultConfig {
77
applicationId "com.hjq.window.demo"
8-
minSdkVersion 16
8+
minSdkVersion 18
99
targetSdkVersion 28
1010
versionCode 1200
1111
versionName "12.0"

app/src/main/java/com/hjq/window/demo/MainActivity.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ protected void onCreate(Bundle savedInstanceState) {
6767
findViewById(R.id.btn_main_list).setOnClickListener(this);
6868
findViewById(R.id.btn_main_draggable).setOnClickListener(this);
6969
findViewById(R.id.btn_main_global).setOnClickListener(this);
70+
findViewById(R.id.btn_main_semi_stealth).setOnClickListener(this);
7071
findViewById(R.id.btn_main_utils).setOnClickListener(this);
7172
findViewById(R.id.btn_main_cancel_all).setOnClickListener(this);
7273

@@ -355,6 +356,11 @@ public void onDenied(@NonNull List<String> permissions, boolean doNotAskAgain) {
355356
}
356357
});
357358

359+
} else if (viewId == R.id.btn_main_semi_stealth) {
360+
361+
new SemiStealthWindow(this)
362+
.show();
363+
358364
} else if (viewId == R.id.btn_main_cancel_all) {
359365

360366
// 关闭当前正在显示的悬浮窗
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
package com.hjq.window.demo;
2+
3+
import android.animation.Animator;
4+
import android.app.Activity;
5+
import android.content.Context;
6+
import android.content.res.Resources;
7+
import android.graphics.Rect;
8+
import android.support.annotation.NonNull;
9+
import android.util.DisplayMetrics;
10+
import android.view.Gravity;
11+
import android.view.View;
12+
import android.view.WindowManager;
13+
import com.hjq.toast.Toaster;
14+
import com.hjq.window.EasyWindow;
15+
import com.hjq.window.OnWindowLifecycleCallback;
16+
import com.hjq.window.OnWindowViewClickListener;
17+
import com.hjq.window.draggable.AbstractWindowDraggableRule;
18+
import com.hjq.window.draggable.AbstractWindowDraggableRule.OnWindowDraggingListener;
19+
import com.hjq.window.draggable.SpringBackWindowDraggableRule;
20+
import com.hjq.window.draggable.SpringBackWindowDraggableRule.SpringBackAnimCallback;
21+
22+
/**
23+
* author : Android 轮子哥
24+
* github : https://github.com/getActivity/EasyWindow
25+
* time : 2025/06/27
26+
* desc : 拖拽后半隐的悬浮窗
27+
*/
28+
public final class SemiStealthWindow extends EasyWindow<SemiStealthWindow>
29+
implements OnWindowDraggingListener,
30+
SpringBackAnimCallback,
31+
OnWindowViewClickListener<View>,
32+
OnWindowLifecycleCallback {
33+
34+
private boolean mAnimatingFlag;
35+
private boolean mDraggingFlag;
36+
37+
public SemiStealthWindow(@NonNull Activity activity) {
38+
super(activity);
39+
}
40+
41+
@Override
42+
protected void initWindow(@NonNull Context context) {
43+
super.initWindow(context);
44+
45+
setContentView(R.layout.window_semi_stealth);
46+
47+
setGravity(Gravity.START | Gravity.TOP);
48+
setXOffset(0);
49+
setYOffset(200);
50+
51+
SpringBackWindowDraggableRule springBackWindowDraggableRule = new SpringBackWindowDraggableRule(
52+
SpringBackWindowDraggableRule.ORIENTATION_HORIZONTAL);
53+
springBackWindowDraggableRule.setAllowMoveToScreenNotch(false);
54+
springBackWindowDraggableRule.setWindowDraggingListener(this);
55+
springBackWindowDraggableRule.setSpringBackAnimCallback(this);
56+
setWindowDraggableRule(springBackWindowDraggableRule);
57+
58+
setOnClickListenerByView(android.R.id.icon, this);
59+
setOnWindowLifecycleCallback(this);
60+
}
61+
62+
/**
63+
* 发送贴边显示任务
64+
*/
65+
public void postStayEdgeRunnable() {
66+
removeRunnable(mStayEdgeRunnable);
67+
postDelayed(mStayEdgeRunnable, 3000);
68+
}
69+
70+
private final Runnable mStayEdgeRunnable = () -> {
71+
if (mAnimatingFlag || mDraggingFlag) {
72+
return;
73+
}
74+
75+
if (isLeftShow()) {
76+
if (isTopShow()) {
77+
hideHalfView(Gravity.TOP);
78+
} else {
79+
hideHalfView(Gravity.LEFT);
80+
}
81+
} else {
82+
if (isTopShow()) {
83+
hideHalfView(Gravity.TOP);
84+
} else {
85+
hideHalfView(Gravity.RIGHT);
86+
}
87+
}
88+
};
89+
90+
/**
91+
* 隐藏 View 一半显示
92+
*/
93+
private void hideHalfView(int gravity) {
94+
AbstractWindowDraggableRule windowDraggableRule = getWindowDraggableRule();
95+
if (windowDraggableRule == null) {
96+
return;
97+
}
98+
View windowRootLayout = getWindowRootLayout();
99+
if (windowRootLayout == null) {
100+
return;
101+
}
102+
103+
int viewWidth = getWindowContentWidth();
104+
int viewHeight = getWindowContentHeight();
105+
106+
// 创建一个矩形来定义裁剪区域
107+
Rect clipBounds = new Rect();
108+
switch (gravity) {
109+
case Gravity.LEFT:
110+
Rect safeInsetRect = windowDraggableRule.getSafeInsetRect();
111+
if (safeInsetRect != null && safeInsetRect.left > 0) {
112+
WindowManager.LayoutParams windowParams = getWindowParams();
113+
windowDraggableRule.updateLocation(windowParams.x - viewWidth / 2f, windowParams.y, true);
114+
} else {
115+
int offSet = getWindowContentWidth() / 2; //用小球来做偏移
116+
clipBounds.set(offSet, 0, viewWidth, viewHeight);
117+
// 设置画板偏移
118+
windowRootLayout.setTranslationX(-offSet);
119+
windowRootLayout.setTranslationY(0);
120+
// 设置裁剪区域
121+
windowRootLayout.setClipBounds(clipBounds);
122+
}
123+
break;
124+
case Gravity.RIGHT:
125+
int offSet = viewWidth / 2;
126+
clipBounds.set(0, 0, viewWidth - offSet, viewHeight);
127+
// 设置画板偏移
128+
windowRootLayout.setTranslationX(offSet);
129+
windowRootLayout.setTranslationY(0);
130+
// 设置裁剪区域
131+
windowRootLayout.setClipBounds(clipBounds);
132+
break;
133+
case Gravity.TOP:
134+
int offSetHeight = viewHeight / 2;
135+
clipBounds.set(0, offSetHeight, viewWidth, viewHeight);
136+
// 设置画板偏移
137+
windowRootLayout.setTranslationX(0);
138+
windowRootLayout.setTranslationY(-offSetHeight);
139+
// 设置裁剪区域
140+
windowRootLayout.setClipBounds(clipBounds);
141+
break;
142+
default:
143+
break;
144+
}
145+
}
146+
147+
private void showFullView() {
148+
View windowRootLayout = getWindowRootLayout();
149+
if (windowRootLayout == null) {
150+
return;
151+
}
152+
int viewWidth = windowRootLayout.getWidth();
153+
int viewHeight = windowRootLayout.getHeight();
154+
AbstractWindowDraggableRule windowDraggableRule = getWindowDraggableRule();
155+
if (windowDraggableRule == null) {
156+
return;
157+
}
158+
Rect safeInsetRect = windowDraggableRule.getSafeInsetRect();
159+
if (safeInsetRect != null && safeInsetRect.left > 0 && !isTopShow()) {
160+
WindowManager.LayoutParams windowParams = getWindowParams();
161+
windowDraggableRule.updateLocation(windowParams.x + viewWidth / 2f, windowParams.y, false);
162+
}
163+
// 设置画板偏移
164+
windowRootLayout.setTranslationX(0);
165+
windowRootLayout.setTranslationY(0);
166+
// 设置裁剪区域
167+
windowRootLayout.setClipBounds(new Rect(0, 0, viewWidth, viewHeight));
168+
}
169+
170+
/**
171+
* View 是否全屏显示
172+
*/
173+
private boolean isFullShowView() {
174+
View view = getWindowRootLayout();
175+
AbstractWindowDraggableRule windowDraggableRule = getWindowDraggableRule();
176+
if (windowDraggableRule == null) {
177+
return true;
178+
}
179+
Rect safeInsetRect = windowDraggableRule.getSafeInsetRect();
180+
if (safeInsetRect != null && safeInsetRect.left > 0 && !isTopShow()) {
181+
if (getWindowParams().x < safeInsetRect.left) {
182+
return false;
183+
}
184+
}
185+
if (view == null) {
186+
return true;
187+
}
188+
int viewWidth = view.getWidth();
189+
int viewHeight = view.getHeight();
190+
Rect clipBounds = view.getClipBounds();
191+
if (view.getTranslationX() != 0 && view.getTranslationY() != 0) {
192+
return false;
193+
}
194+
if (clipBounds == null) {
195+
return true;
196+
}
197+
return clipBounds.left == 0 && clipBounds.top == 0 &&
198+
clipBounds.right == viewWidth && clipBounds.bottom == viewHeight;
199+
}
200+
201+
/**
202+
* 悬浮球是否靠顶显示
203+
*/
204+
private boolean isTopShow() {
205+
WindowManager.LayoutParams windowParams = getWindowParams();
206+
if (isLeftShow()) {
207+
return windowParams.x > windowParams.y;
208+
} else {
209+
return getScreenWidth() - windowParams.x - getWindowContentWidth() > windowParams.y;
210+
}
211+
}
212+
213+
private int getScreenWidth() {
214+
Context context = getContext();
215+
if (context == null) {
216+
return 0;
217+
}
218+
Resources resources = getContext().getResources();
219+
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
220+
return displayMetrics.widthPixels;
221+
}
222+
223+
/**
224+
* 悬浮球是否靠左显示
225+
*/
226+
private boolean isLeftShow(){
227+
return (getWindowParams().x + getWindowContentWidth() / 2f) < getScreenWidth() / 2f;
228+
}
229+
230+
/** {@link OnWindowDraggingListener} */
231+
232+
@Override
233+
public void onWindowDraggingStart(@NonNull EasyWindow<?> easyWindow) {
234+
mDraggingFlag = true;
235+
if (!isFullShowView()) {
236+
showFullView();
237+
}
238+
}
239+
240+
@Override
241+
public void onWindowDraggingStop(@NonNull EasyWindow<?> easyWindow) {
242+
mDraggingFlag = false;
243+
}
244+
245+
/** {@link SpringBackAnimCallback} */
246+
247+
@Override
248+
public void onSpringBackAnimationStart(@NonNull EasyWindow<?> easyWindow, @NonNull Animator animator) {
249+
mAnimatingFlag = true;
250+
}
251+
252+
@Override
253+
public void onSpringBackAnimationEnd(@NonNull EasyWindow<?> easyWindow, @NonNull Animator animator) {
254+
mAnimatingFlag = false;
255+
postStayEdgeRunnable();
256+
}
257+
258+
/** {@link SpringBackAnimCallback} */
259+
260+
@Override
261+
public void onClick(@NonNull EasyWindow<?> easyWindow, @NonNull View view) {
262+
if (!isFullShowView()) {
263+
showFullView();
264+
return;
265+
}
266+
Toaster.show("我被点击了");
267+
}
268+
269+
/** {@link OnWindowLifecycleCallback} */
270+
271+
@Override
272+
public void onWindowShow(@NonNull EasyWindow<?> easyWindow) {
273+
postStayEdgeRunnable();
274+
}
275+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:width="40dp"
4+
android:height="40dp"
5+
android:viewportWidth="1024"
6+
android:viewportHeight="1024">
7+
8+
<path
9+
android:fillColor="#8fb647"
10+
android:pathData="M436.479831 275.352691a19.086154 19.086154 0 1 0 19.086154 19.086155 19.318912 19.318912 0 0 0-19.086154-19.086155zM589.285445 275.352691a19.086154 19.086154 0 1 0 0 38.172309 17.34047 17.34047 0 0 0 19.202534-19.086154 19.318912 19.318912 0 0 0-19.202534-19.086155z" />
11+
<path
12+
android:fillColor="#8fb647"
13+
android:pathData="M870.806223 150.012519a504.270166 504.270166 0 0 0-717.709234 0 514.977033 514.977033 0 0 0 0 723.993699 504.270166 504.270166 0 0 0 717.709234 0 514.977033 514.977033 0 0 0 0-723.993699zM309.626731 589.808724a40.034373 40.034373 0 1 1-79.952366 0V430.02037a40.849026 40.849026 0 0 1 39.917994-41.663679 39.801615 39.801615 0 0 1 39.917993 41.663679z m116.378991-350.999036l-33.051633-48.646418a7.681013 7.681013 0 0 1 1.745684-8.728424c3.49137-1.745685 6.982739 0 6.98274 1.745685l34.913697 50.392103a211.460626 211.460626 0 0 1 74.715312-13.8491 188.417586 188.417586 0 0 1 72.969627 13.8491l34.913697-50.392103a6.051708 6.051708 0 0 1 8.728424-1.745685c3.49137 1.745685 3.49137 5.237055 1.745685 8.728424l-33.051633 48.646418c53.883473 24.323209 90.310097 69.827394 93.801466 125.107415H332.204256a157.809911 157.809911 0 0 1 93.801466-125.107415zM691.815336 681.980884a31.189569 31.189569 0 0 1-31.305948 31.305949h-36.426624v88.564411a40.034373 40.034373 0 1 1-79.952367 0v-88.68079H481.634879v88.68079a40.034373 40.034373 0 1 1-79.952366 0v-88.68079H365.255889a31.189569 31.189569 0 0 1-31.305949-31.305949V389.985997h357.865396z m102.529891-92.055781a40.034373 40.034373 0 1 1-79.952367 0V430.02037a40.849026 40.849026 0 0 1 39.917994-41.663679 39.801615 39.801615 0 0 1 39.917994 41.663679z" />
14+
</vector>

app/src/main/res/layout/activity_main.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@
110110
android:layout_marginTop="12dp"
111111
android:text="全局可拖拽的悬浮窗" />
112112

113+
<Button
114+
android:id="@+id/btn_main_semi_stealth"
115+
android:layout_width="wrap_content"
116+
android:layout_height="wrap_content"
117+
android:layout_gravity="center_horizontal"
118+
android:layout_marginTop="12dp"
119+
android:text="拖拽后半隐的悬浮窗" />
120+
113121
<Button
114122
android:id="@+id/btn_main_cancel_all"
115123
android:layout_width="wrap_content"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:id="@android:id/icon"
4+
android:layout_width="40dp"
5+
android:layout_height="40dp"
6+
android:src="@drawable/android_ic" />

0 commit comments

Comments
 (0)