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

ken*_*der 11 android android-layout google-maps-android-api-2

我想在屏幕上显示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;
Run Code Online (Sandbox Code Playgroud)

Nei*_*end 0

这是可能的,但可能有点复杂。为了简单起见,我将实现此目的的核心代码放在答案中。正如已经指出的,您需要两个视图来执行此操作,一个“位于”另一个视图之上。“较低”的应该是 SurfaceView,由地图 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);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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" />
    
    Run Code Online (Sandbox Code Playgroud)

  3. 一个精简的主 Activity,它将设置相机,并将自动预览图像发送到(底部)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) {
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    有些东西缺失了:

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

  4. 现在,将两个函数添加到第一步中创建的视图中。第一个确保视图知道传入图像数据的大小。第二个接收预览图像数据,将其转换为位图,并沿途扭曲它以提高可见性并演示 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();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    第二个例程的注意事项:

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

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

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