fragment 变成悬浮窗window

原文链接:https://blog.csdn.net/zxd_Android/article/details/87642998
背景:
项目中UI层有SurfaceView,其渲染展示的是摄像机等采集画面,但是测试提了一个问题单,如果在当前页面中跳出到其他页面,会crash,经过log分析,是由于surfaceview 在失去焦点的时候会走到onDestroy方法,也就是surfaceview会失效。
解决思路:surfaceview不失去焦点就可以了,改用悬浮窗实现。

UI层 最主要的页面结构如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
   // 摄像机画面
    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
   //叠在摄像机画面上的fragment
    <FrameLayout
        android:id="@+id/fragment_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#20000000" />

页面底层是摄像机画面,叠在上面的是fragment,用 framelayout 作为容器去承载,surfaceview既然要作为悬浮窗中去展示,因为悬浮窗的层级比Activity页面高,所以fragment层页面当然也要放到悬浮窗,否则页面就没法操作了。

1、先检查悬浮窗权限。

       if (!Settings.canDrawOverlays(this)) {
         //启动权限页面
            startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 0);
        } else {
            //添加悬浮窗
            addWindowSurfaceView();
            }

2、创建悬浮窗。

// add window 
  private void addWindowSurfaceView() {
        WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        mRootViewLayout = new RootViewLayout(this);
        mRootViewLayout.setOrientation(LinearLayout.VERTICAL);
        LayoutInflater.from(this).inflate(R.layout.activity_main, mRootViewLayout);
        WindowManager.LayoutParams layoutParams = createDefaultWindowLayoutParams();
        windowManager.addView(mRootViewLayout, layoutParams);
    }

 // 添加默认的悬浮窗参数
    private WindowManager.LayoutParams createDefaultWindowLayoutParams() {
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        layoutParams.height = ScreenUtils.getScreenHeight(this);
        layoutParams.width = ScreenUtils.getScreenWidth(this);
        return layoutParams;
    }

这块代码还是比较简单的,在跳到其他页面的时候,将悬浮窗设置成1x1 px的大小,再次回来又恢复成默认大小。

 // 退到后台
  private void updateWindowSufaceViewOnStop() {
        WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        layoutParams.height = 1;
        layoutParams.width = 1;
        //设置成不可获取焦点
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        windowManager.updateViewLayout(mRootViewLayout, layoutParams);
    }
   //再次回来
    private void updateWindowSufaceViewOnResume() {
        WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        WindowManager.LayoutParams layoutParams = createDefaultWindowLayoutParams();
        windowManager.updateViewLayout(mRootViewLayout, layoutParams);
    }

不过需要注意的是,在退到后台的时候,需要将虚浮窗设置成不可获取焦点

以为就这么简单就结束了?No,绝非那么simple,重点来了。

【问题一】run 一下项目,crash掉了,报如下的错误。

No view found for id 0x7f080088 (com.xxxxxx:id/fragment_layout) for fragment LauncherFragment{ab9831c #0 id=0x7f080088 LauncherFragment}
                                                     at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1413)
                                                     at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
                                                     at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)
                                                     at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799)
                                                     at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580)

从日志中看,是没有找到承载fragment的layout的资源,我们知道fragment是依附于Activity的,所以这个view没有被找到,是不是用Activity上下文findviewbyId没有获取到?下面继续我一贯的源码分析,当然这次比较不贴很多代码~~.

FragmentManager中moveState方法中关键添加fragment 视图的代码如下:

           // 1、寻找装载fragment的容器
         container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
         f.mContainer = container;
           // XX 省略了无用的代码
           // 2、创建fragmentView
           f.mView = f.performCreateView(f.getLayoutInflater(
          f.mSavedFragmentState), container, f.mSavedFragmentState);
           // 3、添加到容器中
          if (container != null) {
                container.addView(f.mView);
         }

从这段源码以及日志中看到,container 是通过mContainer(Activity)去找的,日志报的错,说明findViewbyId没找到,我们去Activity中看下这个方法

  @Nullable
        @Override
        public View onFindViewById(int id) {
            return Activity.this.findViewById(id);
        }

         /**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link #onCreate}.
     *
     * @return The view if found or null otherwise.
     */
    @Nullable
    public View findViewById(@IdRes int id) {
        return getWindow().findViewById(id);
    }

从这段源码中,可以清楚的看到,是从Activity的setContentView中加载资源的,由于我们这里采用了悬浮窗,所以自然没有办法从中获取到资源,那怎么改?

既然我们知道了原因,那就好入手了啊,实现 override findViewById方法,然后从我们的悬浮窗根view中去加载。

 @Override
    public <T extends View> T findViewById(int id) {
        return mRootViewLayout.findViewById(id);
    }
**mRootViewLayout 就是悬浮窗根layout**。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Android 应用实现后置摄像头悬浮窗,你需要进行以下步骤: 1. 添加权限:在 AndroidManifest.xml 文件添加摄像头权限。 ```xml <uses-permission android:name="android.permission.CAMERA" /> ``` 2. 创建后置摄像头的预览界面:在你的 Activity 或 Fragment 创建一个 SurfaceView,并在 `surfaceCreated` 回调打开相机并设置预览界面。 ```java public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder holder; private Camera camera; public CameraPreview(Context context) { super(context); // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. holder = getHolder(); holder.addCallback(this); } public void surfaceCreated(SurfaceHolder holder) { // Open the camera and set the preview display camera = Camera.open(); try { camera.setPreviewDisplay(holder); } catch (IOException e) { e.printStackTrace(); } } public void surfaceDestroyed(SurfaceHolder holder) { // Release the camera when the surface is destroyed camera.stopPreview(); camera.release(); camera = null; } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { // Start the camera preview after the surface is changed camera.startPreview(); } } ``` 3. 创建悬浮窗:在你的 Activity 或 Service 创建一个悬浮窗,并将预览界面添加到悬浮窗。 ```java public class FloatingCameraService extends Service { private WindowManager windowManager; private CameraPreview cameraPreview; public void onCreate() { super.onCreate(); // Create a new SurfaceView and add it to the window manager cameraPreview = new CameraPreview(this); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); windowManager.addView(cameraPreview, layoutParams); } public void onDestroy() { super.onDestroy(); // Remove the SurfaceView from the window manager when the service is destroyed windowManager.removeView(cameraPreview); } public IBinder onBind(Intent intent) { // Not used return null; } } ``` 4. 启动悬浮窗:在你的 Activity 启动悬浮窗服务。 ```java Intent intent = new Intent(this, FloatingCameraService.class); startService(intent); ``` 注意:在 Android 6.0 及以上版本,你需要动态请求摄像头权限。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值