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+ }
0 commit comments