Home » Android » How to display 2 views, with a gradient-fade effect on Android?

How to display 2 views, with a gradient-fade effect on Android?

Posted by: admin May 14, 2020 Leave a comment


I want to have 2 views displayed on the screen – one would be a camera preview, on the top, while other one would show an image or a google map – and live on the bottom of the screen.

I want to have a gradient-like transition between them though – so there’s no rough edge between them. Is that possible to have such an effect?

The effect I’d like to achieve should look like this (top part comes from the camera preview, while bottom part should be a map…):

Map blending into camera photo

On the iOS I got similar effect with CameraOverlay showing the map and setting the layer masp to the gradient:

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;
How to&Answers:

This possible, but perhaps a bit complicated. To keep it simple, I’ve put the core code for achieving this in the answer. As has been noted, you need two views to do this, one “on top of” the other. The “lower” one should be a SurfaceView, driven by the maps API. The “higher” one should show the camera image faded out over it.

EDIT: As mr_archano points out, the API is (now) defined such that without a SurfaceView the camera wont send out preview data. Ho hum, such is the nature of progress, However, this is also surmountable.

The code presents:

  • The “lower” SurfaceView is driven directly by the camera preview mechanism.
  • The “middle” SurfaceView is for the MAPS API.
  • The “upper” View is where the camera data is rendered to achieve the desired effect.

The core code therefore gives “camera preview” over “camera preview”, and the upper picture has been intentionally distorted so it’s clearly visible fully at the top, fading in the middle and gone at the bottom.

May I suggest that the best way to use this code is to implement these first four steps on their own and see it working, then add the two final steps and see that working, before then inserting the key concepts into another, doubtless larger and more complex, piece of code.

First Four steps:

  1. Create a custom view for display to top, camera, view. This class renders a bitmap over whatever is underneath it. The alpha value in each pixel in the bitmap will determine how much of the lower view is let through.

    public class CameraOverlayView extends View {
        private Paint  paint;
        private Size   incomingSize;
        private Bitmap bitmap = null;
        public CameraOverlayView(Context context) {
        public CameraOverlayView(Context context, AttributeSet attrs) {
            super(context, attrs);
        private void init() {
            paint = new Paint();
            paint.setTextSize((float) 20.0);
        protected void onDraw(Canvas canvas) {
            int width  = canvas.getWidth();
            int height = canvas.getHeight();
            canvas.drawBitmap(bitmap, 0.0f, 0.0f, paint);
  2. Put three views in a frame with them all set tofill_parent in both directions. The first one will be “underneath” (the SurfaceView so the the camera preview works). The second one “in the middle” (the surface view for Maps or whatever). The third “on top” (the view for the faded camera image).

        android:layout_height="fill_parent" />
        android:layout_height="fill_parent" />
        android:layout_height="fill_parent" />

  3. A stripped down main Activity which will set up the camera, and send the automatic preview image to the (bottom) SurfaceView and the preview image data to a processing routine. It sets a callback to catch the preview data. These two run in parallel.

    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;
        protected void onCreate(Bundle savedInstanceState) {
            // 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();
            // FRONT: For getting the data from the camera (for the front view)
            cameraCPCB = new Camera.PreviewCallback () {
                public void onPreviewFrame(byte[] data, Camera camera) {
                    cameraV.acceptCameraData(data, camera);
        // Making the camera run and stop with state changes
        public void onResume() {
            camera = Camera.open();
        public void onPause() {
        private void cameraImageToViewOn() {
            // FRONT
        private void cameraImageToViewOff() {
            // FRONT
        // The callbacks which mean that the Camera does stuff ...
        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
                } 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
                cameraImageToViewOn(); // FRONT
            } catch (Exception e){
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                camera.setPreviewDisplay(holder); //BACK
                cameraImageToViewOn(); // FRONT
            } catch (IOException e) {
        public void surfaceDestroyed(SurfaceHolder holder) {
        public void surfaceRedrawNeeded(SurfaceHolder holder) {

    Some things are missing:

    • Ensuring that the camera image is the right orientation
    • Ensuring that the camera preview image is the optimal size
  4. Now, add two functions to the View created in step one. The first ensures that the View knows the size of the incoming image data. The second receives the preview image data, turns it into a bitmap, distorting it along the way both for visibility and to demonstrate the alpha fade.

    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
        // 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
        // Push the pixel information from the buffer onto the bitmap.

    Notes on the second routine:

That code shows the basic idea. To then step to the next phase:

  1. Set the camera Surface view to be sufficiently small to hide behind the non-faded section of the top View. ie, change android:layout_height for it to, say, 60dp.

  2. Set the middle SurfaceView to receive the map information.


Unfortunately AFAIK you can’t crossfade between a camera preview and a map if both components have to be interactive/live. Like stated before in a previous comment, this is related to the nature of both widgets and the limitations of Android compositing.

Camera preview needs a SurfaceView in order to work properly. From the official docs:

the SurfaceView punches a hole in its window to allow its surface to
be displayed. The view hierarchy will take care of correctly
compositing with the Surface any siblings of the SurfaceView that
would normally appear on top of it. This can be used to place overlays
such as buttons on top of the Surface, though note however that it can
have an impact on performance since a full alpha-blended composite
will be performed each time the Surface changes.

Google Maps v2 use SurfaceView too (look here), so basically you have two SurfaceView instances one on top of the other, and you simply can’t apply a gradient mask in order achieve what you want, because you have no control on how each widget draws itself:

  • Camera preview SurfaceView receive camera buffer and render it natively
  • Maps SurfaceView is rendered in another process.

Furthermore, using two instances of SurfaceView together is highly discouraged like stated here:

The way surface view is implemented is that a separate surface is
created and Z-ordered behind its containing window, and transparent
pixels drawn into the rectangle where the SurfaceView is so you can
see the surface behind. We never intended to allow for multiple
surface views.

I think the only option you have is to choose only one of them to be live/interactive and draw the other as a static image gradient on top of it.


In order to validate further my previous statements, below a quote from official docs about Camera usage:

Important: Pass a fully initialized SurfaceHolder to
setPreviewDisplay(SurfaceHolder). Without a surface, the camera will be unable to start the preview.

So you are forced to use a SurfaceView in order to get the preview from Camera. Always.
And just to repeat myself: you have no control on how those pixels are rendered, because Camera writes directly the framebuffer using the preview SurfaceHolder.

In conclusion you have two fully-opaque SurfaceView instances and you simply can’t apply any fancy rendering to their content, so I think such effect is simply impractical in Android.