Android imitation wechat video suspended window effect

In the project, Tencent cloud audio and video that needs to be accessed can be displayed in the floating window, which can be dragged, and the focus of other activities will not be affected in the floating window.

This great God's article Android is based on Tencent cloud real-time audio and video imitation wechat video call to minimize suspension. He talks about displaying the remote video in the form of suspension window during video call. I have partially simplified it according to his code

1. Suspended window effect: click the zoom out button to load the current remote video screen into the suspended window, and the suspended window can be dragged without affecting other interface focus; Click the suspended window to return to the original activity

2. The realization of suspended window requires:

Apply for suspended window permission in Android manifest < uses permission Android: name = "Android. Permission. System_alert_window" / >

Register floatwindowservice in Android manifest

3. Realization of Video Activity:

-Put the activity in the background key code: movetasktoback (true)// Put activity in the background - open the floating window

/**
   * 定义服务绑定的回调 开启视频通话服务连接
   */
  private ServiceConnection mVideoCallServiceConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName name,IBinder service) {
      // 获取服务的操作对象
      FloatWindowService.MyBinder binder = (FloatWindowService.MyBinder) service;
      binder.getService();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
  };

/*
   * 开启悬浮Video服务
   */
  private void startVideoService() {
    //最小化Activity
    moveTaskToBack(true);//将activity置于后台
    //开启服务显示悬浮框
    Intent serviceVideoIntent = new Intent(this,FloatWindowService.class);
    mServiceBound = bindService(serviceVideoIntent,mVideoCallServiceConnection,Context.BIND_AUTO_CREATE);//绑定Service
  }

-At the end of the suspended window

//在onDestroy()与onReStart()中解绑并销毁相关内容
if (mServiceBound) {
      unbindService(mVideoCallServiceConnection);//解绑
      mServiceBound = false;
    }

4. Code related to the realization of suspended window:

/**
 * 视频悬浮窗服务
 */
public class FloatWindowService extends Service implements View.OnTouchListener {
  private WindowManager mWindowManager;
  private WindowManager.LayoutParams wmParams;
  private LayoutInflater inflater;
  //浮动布局view
  private View mFloatingLayout;
  //容器父布局
  private View mMainVIew;

  //开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
  private int mTouchStartX,mTouchStartY,mTouchCurrentX,mTouchCurrentY;
  //开始时的坐标和结束时的坐标(相对于自身控件的坐标)
  private int mStartX,mStartY,mStopX,mStopY;
  //判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
  private boolean isMove;

  @Override
  public void onCreate() {
    super.onCreate();
    initWindow();//设置悬浮窗基本参数(位置、宽高等)

  }

  @Nullable
  @Override
  public IBinder onBind(Intent intent) {
    currentBigUserId = intent.getStringExtra("localUserId");
    remoteUserId = intent.getStringExtra("remoteUserId");
    initFloating();//悬浮框点击事件的处理
    return new MyBinder();
  }

  public class MyBinder extends Binder {
    public FloatWindowService getService() {
      return FloatWindowService.this;
    }
  }

  @Override
  public int onStartCommand(Intent intent,int flags,int startId) {
    return super.onStartCommand(intent,flags,startId);
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    if (mFloatingLayout != null) {
      // 移除悬浮窗口
      mWindowManager.removeView(mFloatingLayout);
      mFloatingLayout = null;
    }
  }

  /**
   * 设置悬浮框基本参数(位置、宽高等)
   */
  private void initWindow() {
    mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
    //设置好悬浮窗的参数
    wmParams = getParams();
    // 悬浮窗显示左上角为起始坐标
    wmParams.gravity = Gravity.RIGHT | Gravity.TOP;
    //悬浮窗的开始位置,因为设置的是从右上角开始,所以屏幕左上角是x=屏幕最大值;y=0
    wmParams.x = 10;
    wmParams.y = 120;
    //得到容器,通过这个inflater来获得悬浮窗控件
    inflater = LayoutInflater.from(getApplicationContext());
    // 获取浮动窗口视图所在布局
    mFloatingLayout = inflater.inflate(R.layout.dlg_floatview,null);
    // 添加悬浮窗的视图
    mWindowManager.addView(mFloatingLayout,wmParams);
  }

  private WindowManager.LayoutParams getParams() {
    wmParams = new WindowManager.LayoutParams();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    } else {
      wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
    }
    //设置可以显示在状态栏上
    wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;

    //设置悬浮窗口长宽数据
    wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    return wmParams;
  }

  //加载远端视屏:在这对悬浮窗内内容做操作
  private void initFloating() {
    //将子View加载进悬浮窗View
    mMainView = mFloatingLayout.findViewById(R.id.trtc_video_view_layout_float);//悬浮窗父布局
    View mChildView = renderView.getChildView();//加载进悬浮窗的子View,这个VIew来自天转过来的那个Activity里面的那个需要加载的View
    mMainView.addView(mChildView);//将需要悬浮显示的Viewadd到mTXCloudVideoView中

    //悬浮框触摸事件,设置悬浮框可拖动
    mTXCloudVideoView.setOnTouchListener(this::onTouch);
    //悬浮框点击事件
    mTXCloudVideoView.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        //在这里实现点击重新回到Activity
        Intent intent =
        new Intent(FloatWindowService.this,RtcActivity.class);//从该service跳转至该activity会将该activity从后台唤醒,所以activity会走onReStart()
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//从Service跳转至RTCActivity,需要Intent.FLAG_ACTIVITY_NEW_TASK,不然会崩溃
        startActivity(intent);
      }
    });

  }

  //触摸事件
  @Override
  public boolean onTouch(View v,MotionEvent event) {
    int action = event.getAction();
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        isMove = false;
        mTouchStartX = (int) event.getRawX();
        mTouchStartY = (int) event.getRawY();
        mStartX = (int) event.getX();
        mStartY = (int) event.getY();
        break;
      case MotionEvent.ACTION_MOVE:
        mTouchCurrentX = (int) event.getRawX();
        mTouchCurrentY = (int) event.getRawY();
        wmParams.x += mTouchStartX - mTouchCurrentX;
        wmParams.y += mTouchCurrentY - mTouchStartY;
        ALog.dTag("FloatingListener() onTouch",mTouchStartX,mTouchCurrentY,mTouchStartY);
        mWindowManager.updateViewLayout(mFloatingLayout,wmParams);

        mTouchStartX = mTouchCurrentX;
        mTouchStartY = mTouchCurrentY;
        break;
      case MotionEvent.ACTION_UP:
        mStopX = (int) event.getX();
        mStopY = (int) event.getY();
        if (Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1) {
          isMove = true;
        }
        break;
      default:
        break;
    }
    //如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
    return isMove;
  }

}

PS: the purpose of using service as the carrier of the suspension window is to associate the opening and closing of the suspension frame with the binding and unbinding of the service. Opening the service is equivalent to opening our suspension frame, and unbinding the service is equivalent to closing the suspension frame, so as to achieve better control effect.

The above is the whole content of this article. I hope it will help you in your study, and I hope you will support us a lot.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>