Android Activity 
    
  
    
    
    
      悬浮窗 
    
  
 
Android 应用内悬浮窗 Activity 
更新日期: 2022-5-17 
2022-5-17 增加扩展阅读 
2022-1-4 创建文档 
 
 
悬浮窗是一种比较常见的需求。例如把视频通话界面缩小成一个悬浮窗,然后用户可以在其他界面上处理事情。
本文给出一个简单的应用内悬浮窗 实现。可缩小activity和还原大小。可悬浮在同一个app的其他activity上。使用TouchListener监听触摸事件,拖动悬浮窗。
本文链接 
缩放方法 
缩放activity需要使用WindowManager.LayoutParams ,控制window的宽高
在activity中调用
android . view . WindowManager . LayoutParams   p   =   getWindow (). getAttributes (); 
p . height   =   480 ;   // 高度 
p . width   =   360 ;    // 宽度 
p . dimAmount   =   0.0f ;   // 不让下面的界面变暗 (1) 
getWindow (). setAttributes ( p ); 
dim: adj. 暗淡的; 昏暗的; 微弱的; 不明亮的; 光线暗淡的;  
 
修改了WindowManager.LayoutParams 的宽高,activity的window大小会发生变化。
要变回默认大小,在activity中调用
getWindow (). setLayout ( ViewGroup . LayoutParams . MATCH_PARENT ,   ViewGroup . LayoutParams . MATCH_PARENT ); 
如果缩小时改变了位置,需要把window的位置置为0
WindowManager . LayoutParams   lp   =   getWindow (). getAttributes (); 
lp . x   =   0 ; 
lp . y   =   0 ; 
getWindow (). setAttributes ( lp ); 
activity变小时,后面可能是黑色的背景。这需要进行下面的操作。
悬浮样式 
在styles.xml里新建一个MeTranslucentAct。
styles.xml <resources> 
     <!-- Base application theme. --> 
     <style   name= "AppTheme"   parent= "Theme.AppCompat.Light.DarkActionBar" > 
         <!-- Customize your theme here. --> 
         <item   name= "colorPrimary" > @color/colorPrimary</item> 
         <item   name= "colorPrimaryDark" > @color/colorPrimaryDark</item> 
         <item   name= "colorAccent" > @color/colorAccent</item> 
         <item   name= "windowNoTitle" > true</item> 
     </style> 
     <style   name= "TranslucentAct"   parent= "AppTheme" > 
         <item   name= "android:windowBackground" > #80000000</item> 
         <item   name= "android:windowIsTranslucent" > true</item> 
         <item   name= "android:windowAnimationStyle" > @android:style/Animation.Translucent</item> 
     </style> 
</resources> 
指定一个window的背景android:windowBackground
使用的Activity继承自androidx.appcompat.app.AppCompatActivity
activity缩小后,背景是透明的,可以看到后面的其他页面
点击穿透空白 
activity缩小后,点击旁边空白处,其他组件能接到点击事件
在onCreate方法的setContentView之前,给WindowManager.LayoutParams 添加标记FLAG_LAYOUT_NO_LIMITS和FLAG_NOT_TOUCH_MODAL
WindowManager . LayoutParams   layoutParams   =   getWindow (). getAttributes (); 
layoutParams . flags   =   WindowManager . LayoutParams . FLAG_LAYOUT_NO_LIMITS   | 
         WindowManager . LayoutParams . FLAG_NOT_TOUCH_MODAL ; 
mBinding   =   DataBindingUtil . setContentView ( this ,   R . layout . act_float_scale ); 
移动悬浮窗 
监听触摸事件,计算出手指移动的距离,然后移动悬浮窗。
activity private   boolean   mIsSmall   =   false ;   // 当前是否小窗口 
private   float   mLastTx   =   0 ;   // 手指的上一个位置x 
private   float   mLastTy   =   0 ; 
// .... 
     mBinding . root . setOnTouchListener (( v ,   event )   ->   { 
         switch   ( event . getAction ())   { 
             case   MotionEvent . ACTION_DOWN : 
                 Log . d ( TAG ,   "down "   +   event ); 
                 mLastTx   =   event . getRawX (); 
                 mLastTy   =   event . getRawY (); 
                 return   true ; 
             case   MotionEvent . ACTION_MOVE : 
                 Log . d ( TAG ,   "move "   +   event ); 
                 float   dx   =   event . getRawX ()   -   mLastTx ; 
                 float   dy   =   event . getRawY ()   -   mLastTy ; 
                 mLastTx   =   event . getRawX (); 
                 mLastTy   =   event . getRawY (); 
                 Log . d ( TAG ,   "  dx: "   +   dx   +   ", dy: "   +   dy ); 
                 if   ( mIsSmall )   { 
                     WindowManager . LayoutParams   lp   =   getWindow (). getAttributes (); 
                     lp . x   +=   dx ; 
                     lp . y   +=   dy ; 
                     getWindow (). setAttributes ( lp ); 
                 } 
                 break ; 
             case   MotionEvent . ACTION_UP : 
                 Log . d ( TAG ,   "up "   +   event ); 
                 return   true ; 
             case   MotionEvent . ACTION_CANCEL : 
                 Log . d ( TAG ,   "cancel "   +   event ); 
                 return   true ; 
         } 
         return   false ; 
     }); 
mIsSmall用来记录当前activity是否变小(悬浮)。
在触摸监听器中返回true,表示消费这个触摸事件。
event.getX()和event.getY()获取到的是当前View的触摸坐标。
event.getRawX()和event.getRawY()获取到的是屏幕的触摸坐标。即触摸点在屏幕上的位置。
例子的完整代码 
启用了databinding
android   { 
     dataBinding   { 
         enabled   =   true 
     } 
} 
styles.xml 
新建一个样式
styles.xml      <style   name= "TranslucentAct"   parent= "AppTheme" > 
         <item   name= "android:windowBackground" > #80000000</item> 
         <item   name= "android:windowIsTranslucent" > true</item> 
         <item   name= "android:windowAnimationStyle" > @android:style/Animation.Translucent</item> 
     </style> 
layout 
act_float_scale.xml 里面放一些按钮,控制放大和缩小。
ConstraintLayout拿来监听触摸事件。
act_float_scale.xml <?xml version="1.0" encoding="utf-8"?> 
<layout   xmlns:android= "http://schemas.android.com/apk/res/android" 
     xmlns:app= "http://schemas.android.com/apk/res-auto" > 
     <androidx.constraintlayout.widget.ConstraintLayout 
         android:id= "@+id/root" 
         android:layout_width= "match_parent" 
         android:layout_height= "match_parent" 
         android:background= "#555555" > 
         <LinearLayout 
             android:layout_width= "match_parent" 
             android:layout_height= "wrap_content" 
             android:gravity= "center" 
             android:orientation= "vertical" 
             app:layout_constraintTop_toTopOf= "parent" > 
             <Button 
                 android:id= "@+id/to_small" 
                 style= "@style/NormalBtn" 
                 android:layout_width= "wrap_content" 
                 android:layout_height= "wrap_content" 
                 android:text= "变小"   /> 
             <Button 
                 android:id= "@+id/to_reset" 
                 style= "@style/NormalBtn" 
                 android:layout_width= "wrap_content" 
                 android:layout_height= "wrap_content" 
                 android:layout_marginTop= "12dp" 
                 android:text= "还原"   /> 
         </LinearLayout> 
     </androidx.constraintlayout.widget.ConstraintLayout> 
</layout> 
activity 
FloatingScaleAct 
FloatingScaleAct import   android.os.Bundle ; 
import   android.util.Log ; 
import   android.view.Display ; 
import   android.view.MotionEvent ; 
import   android.view.ViewGroup ; 
import   android.view.WindowManager ; 
import   androidx.appcompat.app.AppCompatActivity ; 
import   androidx.databinding.DataBindingUtil ; 
import   com.rustfisher.tutorial2020.R ; 
import   com.rustfisher.tutorial2020.databinding.ActFloatScaleBinding ; 
public   class  FloatingScaleAct   extends   AppCompatActivity   { 
     private   static   final   String   TAG   =   "rfDevFloatingAct" ; 
     ActFloatScaleBinding   mBinding ; 
     private   boolean   mIsSmall   =   false ;   // 当前是否小窗口 
     private   float   mLastTx   =   0 ;   // 手指的上一个位置 
     private   float   mLastTy   =   0 ; 
     @Override 
     protected   void   onCreate ( Bundle   savedInstanceState )   { 
         super . onCreate ( savedInstanceState ); 
         WindowManager . LayoutParams   layoutParams   =   getWindow (). getAttributes (); 
         layoutParams . flags   =   WindowManager . LayoutParams . FLAG_LAYOUT_NO_LIMITS   | 
                 WindowManager . LayoutParams . FLAG_NOT_TOUCH_MODAL ; 
         mBinding   =   DataBindingUtil . setContentView ( this ,   R . layout . act_float_scale ); 
         mBinding . toSmall . setOnClickListener ( v   ->   toSmall ()); 
         mBinding . toReset . setOnClickListener ( v   ->   { 
             WindowManager . LayoutParams   lp   =   getWindow (). getAttributes (); 
             lp . x   =   0 ; 
             lp . y   =   0 ; 
             getWindow (). setAttributes ( lp ); 
             getWindow (). setLayout ( ViewGroup . LayoutParams . MATCH_PARENT ,   ViewGroup . LayoutParams . MATCH_PARENT ); 
             mIsSmall   =   false ; 
         }); 
         mBinding . root . setOnTouchListener (( v ,   event )   ->   { 
             switch   ( event . getAction ())   { 
                 case   MotionEvent . ACTION_DOWN : 
                     Log . d ( TAG ,   "down "   +   event ); 
                     mLastTx   =   event . getRawX (); 
                     mLastTy   =   event . getRawY (); 
                     return   true ; 
                 case   MotionEvent . ACTION_MOVE : 
                     Log . d ( TAG ,   "move "   +   event ); 
                     float   dx   =   event . getRawX ()   -   mLastTx ; 
                     float   dy   =   event . getRawY ()   -   mLastTy ; 
                     mLastTx   =   event . getRawX (); 
                     mLastTy   =   event . getRawY (); 
                     Log . d ( TAG ,   "  dx: "   +   dx   +   ", dy: "   +   dy ); 
                     if   ( mIsSmall )   { 
                         WindowManager . LayoutParams   lp   =   getWindow (). getAttributes (); 
                         lp . x   +=   dx ; 
                         lp . y   +=   dy ; 
                         getWindow (). setAttributes ( lp ); 
                     } 
                     break ; 
                 case   MotionEvent . ACTION_UP : 
                     Log . d ( TAG ,   "up "   +   event ); 
                     return   true ; 
                 case   MotionEvent . ACTION_CANCEL : 
                     Log . d ( TAG ,   "cancel "   +   event ); 
                     return   true ; 
             } 
             return   false ; 
         }); 
     } 
     private   void   toSmall ()   { 
         mIsSmall   =   true ; 
         WindowManager   m   =   getWindowManager (); 
         Display   d   =   m . getDefaultDisplay (); 
         WindowManager . LayoutParams   p   =   getWindow (). getAttributes (); 
         p . height   =   ( int )   ( d . getHeight ()   *   0.35 ); 
         p . width   =   ( int )   ( d . getWidth ()   *   0.4 ); 
         p . dimAmount   =   0.0f ; 
         getWindow (). setAttributes ( p ); 
     } 
} 
manifest里注册这个activity
<activity 
     android:name= ".act.FloatingScaleAct" 
     android:theme= "@style/TranslucentAct"   /> 
运行效果 
在红米9A(Android 10,MIUI 12.5.1 稳定版)和荣耀(Android 5.1)上运行OK
悬浮窗效果-红米9A 
 
小结 
为实现悬浮窗效果,思路是改变activity大小,将activity所在window的背景设置透明,监听触摸事件改变window的位置。
主要使用的类 WindowManager.LayoutParams 
悬浮窗效果
本文展示的是应用内的悬浮Activity,我们也可以利用SYSTEM_ALERT_WINDOW 实现能在其他App上出现的悬浮窗效果 
 
本文也发布在 
华为云社区 
cnblog 
掘金 
oschina 
csdn 
 
  
  
                
  本站说明 
    
    一起在知识的海洋里呛水吧。广告内容与本站无关。如果喜欢本站内容,欢迎投喂作者,谢谢支持服务器。如有疑问和建议,欢迎在下方评论~
    
      📖AndroidTutorial 
      📚AndroidTutorial 
      🙋反馈问题 
      🔥最近更新 
      🍪投喂作者 
    
    
  Ads