11

我想在屏幕上显示 2 个视图 - 一个是顶部的相机预览,而另一个将显示图像或谷歌地图 - 并位于屏幕底部。

我想在它们之间有一个类似渐变的过渡——所以它们之间没有粗糙的边缘。能有这样的效果吗?

编辑:我想要实现的效果应该是这样的(顶部来自相机预览,而底部应该是地图......):

地图融合到相机照片中

在 iOS 上,CameraOverlay 显示地图并将图层 masp 设置为渐变时,我得到了类似的效果:

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = self.map.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite: 1.0 alpha: 0.0] CGColor], (id)[[UIColor colorWithWhite: 1.0 alpha: 1.0] CGColor], nil];
gradient.startPoint = CGPointMake(0.5f, 0.0f);
gradient.endPoint = CGPointMake(0.5f, 0.5f);
self.map.layer.mask = gradient;
4

2 回答 2

4

不幸的是,如果两个组件都必须是交互式/实时的,那么您不能在相机预览和地图之间交叉淡入淡出。正如之前的评论中所述,这与小部件的性质和 Android 合成的局限性有关。

相机预览需要一个SurfaceView才能正常工作。来自官方文档:

SurfaceView 在其窗口中打了一个孔以显示其表面。视图层次结构将负责正确地将 SurfaceView 的任何兄弟姐妹与 Surface 合成,这些兄弟通常会出现在它的顶部。这可用于在 Surface 顶部放置按钮等叠加层,但请注意,它可能会对性能产生影响,因为每次 Surface 更改时都会执行完整的 alpha 混合合成。

Google Maps v2SurfaceView也使用(看这里),所以基本上你有两个SurfaceView实例一个在另一个之上,你根本无法应用渐变蒙版来实现你想要的,因为你无法控制每个小部件的绘制方式本身:

  • 相机预览SurfaceView接收相机缓冲区并本机渲染
  • 地图SurfaceView在另一个进程中呈现。

此外,强烈建议不要同时使用两个实例,如下SurfaceView所述:

实现表面视图的方式是在其包含的窗口后面创建一个单独的表面并按 Z 顺序排列,并将透明像素绘制到 SurfaceView 所在的矩形中,以便您可以看到后面的表面。我们从未打算允许多个表面视图。

我认为您唯一的选择是仅选择其中一个进行实时/交互,并将另一个作为静态图像渐变绘制在其上。


编辑

为了进一步验证我之前的陈述,下面引用官方文档中有关相机使用的内容:

重要提示:将完全初始化的 SurfaceHolder 传递给 setPreviewDisplay(SurfaceHolder)。没有表面,相机将无法开始预览

因此,您必须使用 aSurfaceView才能从中获取预览Camera。总是。
再说一遍:您无法控制这些像素的渲染方式,因为使用 previewCamera 直接写入帧缓冲区SurfaceHolder

总之,您有两个完全不透明的 SurfaceView实例,您根本无法对它们的内容应用任何精美的渲染,所以我认为这种效果在 Android 中根本不切实际。

于 2013-04-21T18:16:36.363 回答
0

这可能,但可能有点复杂。为简单起见,我已将实现此目的的核心代码放在答案中。如前所述,您需要两个视图来执行此操作,一个“在”另一个“之上”。“较低”的应该是由地图 API 驱动的 SurfaceView。“更高”的应该显示相机图像淡出它。

编辑:正如 mr_archano 指出的那样,API(现在)被定义为没有 SurfaceView,相机将不会发送预览数据。哼哼,这就是进步的本质,不过,这也是可以克服的。

代码显示:

  • “较低”的 SurfaceView 直接由相机预览机制驱动。
  • “中间” SurfaceView 用于 MAPS API。
  • “上部”视图是渲染相机数据以实现所需效果的位置。

因此,核心代码给出了“相机预览”而不是“相机预览”,并且上面的图片被故意扭曲,因此它在顶部完全清晰可见,在中间褪色并在底部消失。

我可以建议使用此代码的最佳方法是自己实现前四个步骤并查看其工作,然后添加最后两个步骤并查看其工作,然后将关键概念插入另一个,无疑更大更多复杂的,一段代码。

前四步:

  1. 创建自定义视图以显示到顶部、相机、视图。这个类在它下面的任何东西上渲染一个位图。位图中每个像素的 alpha 值将决定有多少下部视图可以通过。

    public class CameraOverlayView extends View {
        private Paint  paint;
        private Size   incomingSize;
        private Bitmap bitmap = null;
    
        public CameraOverlayView(Context context) {
            super(context);
            init();
        }
    
        public CameraOverlayView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            paint = new Paint();
            paint.setStyle(Style.FILL_AND_STROKE);
            paint.setColor(0xffffffff);
            paint.setTextSize((float) 20.0);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            int width  = canvas.getWidth();
            int height = canvas.getHeight();
    
            canvas.drawBitmap(bitmap, 0.0f, 0.0f, paint);
        }
    }
    
  2. 将三个视图放在一个框架中,并将它们都设置为fill_parent两个方向。第一个将在“下方”(SurfaceView,因此相机预览有效)。第二个“在中间”(地图的表面视图或其他)。第三个“在顶部”(褪色相机图像的视图)。

    <SurfaceView
        android:id="@+id/beneathSurfaceView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
    
    <SurfaceView
        android:id="@+id/middleSurfaceView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
    
    <com.blah.blah.blah.CameraOverlayView
        android:id="@+id/aboveCameraView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
    

  3. 一个精简的主要活动,它将设置相机,并将自动预览图像发送到(底部)SurfaceView,并将预览图像数据发送到处理例程。它设置一个回调来捕获预览数据。这两个并行运行。

    public class CameraOverlay extends Activity implements SurfaceHolder.Callback2 {
    
        private SurfaceView       backSV;
        private CameraOverlayView cameraV;
        private SurfaceHolder cameraH;
        private Camera        camera=null;
    
        private Camera.PreviewCallback cameraCPCB;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.camera_overlay);
    
            // Get the two views        
            backSV  = (SurfaceView) findViewById(R.id.beneathSurfaceView);
            cameraV = (CameraOverlayView) findViewById(R.id.aboveCameraView);
    
            // BACK: Putting the camera on the back SV (replace with whatever is driving that SV)
            cameraH  = backSV.getHolder();
            cameraH.addCallback(this);
    
            // FRONT: For getting the data from the camera (for the front view)
            cameraCPCB = new Camera.PreviewCallback () {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {
                    cameraV.acceptCameraData(data, camera);
                }
            };
        }
    
        // Making the camera run and stop with state changes
        @Override
        public void onResume() {
            super.onResume();
            camera = Camera.open();
            camera.startPreview();
        }
    
        @Override
        public void onPause() {
            super.onPause();
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            camera=null;
        }
    
        private void cameraImageToViewOn() {
            // FRONT
            cameraV.setIncomingSize(camera.getParameters().getPreviewSize());
            camera.setPreviewCallback(cameraCPCB);
        }
    
        private void cameraImageToViewOff() {
            // FRONT
            camera.setPreviewCallback(null);
        }
    
        // The callbacks which mean that the Camera does stuff ...
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            // If your preview can change or rotate, take care of those events here.
            // Make sure to stop the preview before resizing or reformatting it.
    
            if (holder == null) return;
    
            // stop preview before making changes
            try {
                cameraImageToViewOff(); // FRONT
                camera.stopPreview();
                } catch (Exception e){
                // ignore: tried to stop a non-existent preview
            }
    
            // set preview size and make any resize, rotate or reformatting changes here
    
            // start preview with new settings
            try {
                camera.setPreviewDisplay(holder); //BACK
                camera.startPreview();
                cameraImageToViewOn(); // FRONT
            } catch (Exception e){
            }
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                camera.setPreviewDisplay(holder); //BACK
                camera.startPreview();
                cameraImageToViewOn(); // FRONT
            } catch (IOException e) {
            }       
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
    }
    
        @Override
        public void surfaceRedrawNeeded(SurfaceHolder holder) {
        }
    }
    

    缺少一些东西:

    • 确保相机图像方向正确
    • 确保相机预览图像是最佳尺寸

  4. 现在,向第一步中创建的视图添加两个函数。第一个确保 View 知道传入图像数据的大小。第二个接收预览图像数据,将其转换为位图,沿途扭曲它以提高可见性和演示 alpha 渐变。

    public void setIncomingSize(Size size) {
        incomingSize = size;
        if (bitmap != null) bitmap.recycle();
        bitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
    }
    
    public void acceptCameraData(byte[] data, Camera camera) {
        int width  = incomingSize.width;
        int height = incomingSize.height;
    
        // the bitmap we want to fill with the image
        int numPixels = width*height;
    
        // the buffer we fill up which we then fill the bitmap with
        IntBuffer intBuffer = IntBuffer.allocate(width*height);
        // If you're reusing a buffer, next line imperative to refill from the start, - if not good practice
        intBuffer.position(0);
    
        // Get each pixel, one at a time
        int Y;
        int xby2, yby2;
        int R, G, B, alpha;
        float U, V, Yf;
        for (int y = 0; y < height; y++) {
            // Set the transparency based on how far down the image we are:
            if (y<200) alpha = 255;          // This image only at the top
            else if (y<455) alpha = 455-y;   // Fade over the next 255 lines
            else alpha = 0;                  // nothing after that
            // For speed's sake, you should probably break out of this loop once alpha is zero ...
    
            for (int x = 0; x < width; x++) {
                // Get the Y value, stored in the first block of data
                // The logical "AND 0xff" is needed to deal with the signed issue
                Y = data[y*width + x] & 0xff;
    
                // Get U and V values, stored after Y values, one per 2x2 block
                // of pixels, interleaved. Prepare them as floats with correct range
                // ready for calculation later.
                xby2 = x/2;
                yby2 = y/2;
                U = (float)(data[numPixels + 2*xby2 + yby2*width] & 0xff) - 128.0f;
                V = (float)(data[numPixels + 2*xby2 + 1 + yby2*width] & 0xff) - 128.0f;
    
                // Do the YUV -> RGB conversion
                Yf = 1.164f*((float)Y) - 16.0f;
                R = (int)(Yf + 1.596f*V);
                G = 2*(int)(Yf - 0.813f*V - 0.391f*U); // Distorted to show effect
                B = (int)(Yf + 2.018f*U);
    
                // Clip rgb values to 0-255
                R = R < 0 ? 0 : R > 255 ? 255 : R;
                G = G < 0 ? 0 : G > 255 ? 255 : G;
                B = B < 0 ? 0 : B > 255 ? 255 : B;
    
                // Put that pixel in the buffer
                intBuffer.put(Color.argb(alpha, R, G, B));
            }
        }
    
        // Get buffer ready to be read
        intBuffer.flip();
    
        // Push the pixel information from the buffer onto the bitmap.
        bitmap.copyPixelsFromBuffer(intBuffer);
    
        this.invalidate();
    }
    

    关于第二个例程的注意事项:

该代码显示了基本思想。然后进入下一阶段:

  1. 将相机表面视图设置为足够小以隐藏在顶视图的非褪色部分后面。即,将android:layout_height其更改为60dp.

  2. 设置中间的 SurfaceView 接收地图信息。

于 2013-04-21T14:31:03.997 回答