Android 大小两个Surface切换

在视频聊天app中一般会有这样的场景,一个大的Surface显示对方的画面,一个略小的Surface显示自己的画面(类似于微信视频聊天),然后点击一下小的那个Surface,会把两个画面对调。今天就来看看,这个是怎么实现的。

先给大家一个直观的认识,来两张截图。
这里写图片描述
这里写图片描述
就是这两个画面,一大一小,大的是自己摄像头的预览画面,小的是对面传过来的画面(现在暂时是这样的。)点击小的画面里面右上角按钮,就可以切换两个换面。具体如何实现,会碰到哪些问题呢?
####方案一:采用切换数据源
这是什么意思呢?就是说,Surface的大小和位置不去改变他,数据绘制的位置,来实现,这个方案且不说实现起来复杂度和可行性,有一个致命的问题,就是在切换Surface的过程中,必然会有重新加载的过程,会导致短暂的黑屏(取决于网络和缓存时间),所以果断放弃了。
####方案二:改变Surface大小,并改变层级
这个方案理论上是可行的,只是改变下Surface的大小和位置,大的变小,小的变大,然后大的把小的覆盖掉了,再把小的显示到上面来。但是有个问题,修改层级的方法SurfaceView#setZOrderMediaOverlay有个限制,只能在初始的的时候调用,不信可以看官方注释:

/**
     * Control whether the surface view's surface is placed on top of another
     * regular surface view in the window (but still behind the window itself).
     * This is typically used to place overlays on top of an underlying media
     * surface view.
     *
     * <p>Note that this must be set before the surface view's containing
     * window is attached to the window manager.
     *
     * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
     */
    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
        mWindowType = isMediaOverlay
                ? WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
                : WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
    }

Note that this must be set before the surface view’s containing window is attached to the window manager.应该可以看得懂吧。
所以就得想个办法来绕开这个这个限制,最简单的就是先removeView(View)再addView(View)这样就相当于重新做了attach操作,setZOrderMediaOverlay操作也就可以生效了。接下来看看代码具体如何实现

private RelativeLayout.LayoutParams getSmallLayoutParams() {
        RelativeLayout.LayoutParams lpSmall = null;
        lpSmall = new RelativeLayout.LayoutParams(mWindowWidth / 2, mWindowHeight / 2);
        lpSmall.width = mWindowWidth / 2;
        lpSmall.height = mWindowHeight / 2;
        lpSmall.topMargin = mWindowHeight / 2;
        lpSmall.leftMargin = 0;
        return lpSmall;
    }

    private RelativeLayout.LayoutParams getNormalLayoutParams() {
        RelativeLayout.LayoutParams lpNormal = null;
        lpNormal = new RelativeLayout.LayoutParams(mWindowWidth / 2, mWindowHeight / 2);
        lpNormal.width = mWindowWidth;
        lpNormal.height = mWindowHeight;
        lpNormal.topMargin = 0;
        lpNormal.leftMargin = 0;
        return lpNormal;
    }
    
private void changeScreen() {
        mIsChangeScreen = !mIsChangeScreen;
        RelativeLayout.LayoutParams lpSmall = getSmallLayoutParams();
        RelativeLayout.LayoutParams lpNormal = getNormalLayoutParams();

        mRlRoot.removeView(mSurfaceViewSmall);
        mRlRoot.removeView(mSurfaceView);
        mRlRoot.removeView(mRlFun);

        if (mIsChangeScreen) {
            mSurfaceView.setLayoutParams(lpSmall);
            mRlRoot.addView(mSurfaceView);

            mSurfaceViewSmall.setLayoutParams(lpNormal);
            mRlRoot.addView(mSurfaceViewSmall);

//            mSurfaceView.setZOrderOnTop(true);
            mSurfaceView.setZOrderMediaOverlay(true);
        } else {
            mSurfaceViewSmall.setLayoutParams(lpSmall);
            mRlRoot.addView(mSurfaceViewSmall);

            mSurfaceView.setLayoutParams(lpNormal);
            mRlRoot.addView(mSurfaceView);

//            mSurfaceViewSmall.setZOrderOnTop(true);
            mSurfaceViewSmall.setZOrderMediaOverlay(true);
        }
        mRlRoot.addView(mRlFun);
    }

点击小画面右上角按钮的时候,调用changeScreen()方法,这个代码其实不难理解,先removeView调大小两个Surface,修改LayoutParams后一次addView进去,然后通过调用setZOrderMediaOverlay方法修改层级关系,就实现了大小和层级的切换。
这里还有一个mRlRoot.removeView(mRlFun);和mRlRoot.addView(mRlFun);其实是把其他控件的移除和加入操作,目的是防止Surface把其他控件覆盖调。

####tips: setZOrderOnTop&setZOrderMediaOverlay
这里再简单讲解下这两个方法的区别,我再网上查到很多关于多个Surface重叠显示,放置遮盖其他控件的解决方法,都是说先调用setZOrderOnTop(true)方法,再调用setZOrderMediaOverlay(true),先不评价这个方法的正确性(因为这样确实可以达到目的),我们先看看这两个方法的含义。
setZOrderMediaOverlay方法的源码注释我已经在前面有贴,意思就是"把目标Surface显示在其他Surface之前,如果以前有其他控件覆盖,任然会覆盖在它上面"
然后setZOrderOnTop方法,

/**
     * Control whether the surface view's surface is placed on top of its
     * window.  Normally it is placed behind the window, to allow it to
     * (for the most part) appear to composite with the views in the
     * hierarchy.  By setting this, you cause it to be placed above the
     * window.  This means that none of the contents of the window this
     * SurfaceView is in will be visible on top of its surface.
     *
     * <p>Note that this must be set before the surface view's containing
     * window is attached to the window manager.
     *
     * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
     */
    public void setZOrderOnTop(boolean onTop) {
        if (onTop) {
            mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
            // ensures the surface is placed below the IME
            mLayout.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
        } else {
            mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
            mLayout.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
        }
    }

这个方法的意思就是“调用之后,会把目标Surface显示到window的最前面,包括其他控件”
意思已经很明显了 ,我们在Surface上要显示一下其他控件,比如小画面右上角的按钮,所以调用setZOrderMediaOverlay(true)是最好的,进过试验,只调用setZOrderMediaOverlay(true)就可以达到想要的结果。
所以我就不太理解,为什么那么多博文要说同时调用两个方法呢。总结下自己的经验,希望对大家有所帮助。


最近研究了一下,只需要setZOrderMediaOverlay,来切换SurfaceView的层级关系。不过需要注意的是:
1.上层的SurfaceView需要调用setZOrderMediaOverlay(true),底层的SurfaceView需要调用setZOrderMediaOverlay(false)
2.需要切换的两个SurfaceView必须要有大小变化
3.自测安卓8 9 10版本都是可以的,但是安卓6会有问题

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
好的,下面是一个简单的使用SurfaceView绘制图形的Android示例程序: 首先,在XML布局文件中定义一个SurfaceView: ```xml <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 然后,在Java代码中创建SurfaceView类并实现SurfaceHolder.Callback接口: ```java public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder holder; private Paint paint; private boolean isDrawing; public MySurfaceView(Context context, AttributeSet attrs) { super(context, attrs); holder = getHolder(); holder.addCallback(this); paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(5); isDrawing = false; } @Override public void surfaceCreated(SurfaceHolder holder) { isDrawing = true; new Thread(new Runnable() { @Override public void run() { Canvas canvas = null; try { canvas = holder.lockCanvas(); canvas.drawCircle(100, 100, 50, paint); } finally { if (canvas != null) { holder.unlockCanvasAndPost(canvas); } } } }).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { isDrawing = false; } } ``` 在这个例子中,我们创建了一个自定义视图MySurfaceView,实现了SurfaceHolder.Callback接口。在构造函数中,我们获取SurfaceHolder对象,并设置为回调接口。在surfaceCreated方法中,我们创建一个线程,在线程中获取Canvas对象并绘制一个圆形。在surfaceDestroyed方法中,我们停止绘制线程。 这个例子只是一个简单的使用SurfaceView绘制图形的示例,你可以根据自己的需求和应用场景进行更加复杂和丰富的图形绘制。同时需要注意的是,在使用SurfaceView绘制图形时,需要考虑线程同步和资源释放的问题。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值