如何在Android上显示2个具有渐变效果的视图?

时间:2013-04-18 18:59:03

标签: android android-layout google-maps-android-api-2

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

我希望它们之间有一个类似渐变的过渡 - 所以它们之间没有粗糙的边缘。这有可能产生这样的效果吗?

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

Map blending into camera photo

在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;

2 个答案:

答案 0 :(得分:4)

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

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

  

SurfaceView在其窗口中打孔以允许其表面   显示。视图层次结构将正确处理   与Surface合成SurfaceView的任何兄弟姐妹   通常会出现在它上面。这可用于放置叠加层   比如Surface顶部的按钮,但是请注意它可以   自完全α混合复合材料以来对性能产生影响   每次Surface更改时都会执行。

谷歌地图v2也使用SurfaceView(看here),所以基本上你有两个SurfaceView实例一个在另一个上面,你根本无法应用渐变蒙版为了实现你想要的,你无法控制每个小部件的绘制方式:

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

此外,非常不鼓励使用SurfaceView的两个实例,如here所述:

  

实现表面视图的方式是单独的表面   在其包含的窗口后面创建并且Z-ordered,并且透明   像素被绘制到SurfaceView所在的矩形中   看到背后的表面。我们从不打算允许多个   表面视图。

我认为你唯一的选择就是只选择其中一个进行实时/互动,然后将另一个绘制为静态图像渐变。


修改

为了进一步验证我之前的陈述,请参阅官方文档about Camera usage的引用:

  

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

因此,您必须使用SurfaceView才能从Camera获取预览。始终。
只是重复一遍:你无法控制这些像素的呈现方式,因为Camera 使用预览SurfaceHolder直接写入帧缓冲区

总之,你有两个完全不透明的 SurfaceView个实例,你根本无法对其内容应用任何花哨的渲染,所以我认为这样的效果在Android中是不切实际的。< / p>

答案 1 :(得分:0)

这可能,但也许有点复杂。为了简单起见,我将核心代码放在答案中。正如已经指出的那样,你需要两个视图才能做到这一点,一个视图在另一个视图之上。 “lower”应该是SurfaceView,由maps API驱动。 “更高”的应该显示相机图像逐渐消失。

编辑:正如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();
    }
    

    关于第二个例程的说明:

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

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

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