Как отобразить 2 представления, с эффектом уклонения от градиента на Android?

Я хочу, чтобы на экране отображалось 2 вида - один из них был бы предварительным просмотром камеры, а другой отображал изображение или карту Google - и жил в нижней части экрана.

Я хочу, чтобы между ними был переход между градиентами - так что между ними нет грубой грани. Возможно ли, чтобы такой эффект был?

Изменить: Эффект, который я хотел бы достичь, должен выглядеть так (верхняя часть поступает из предварительного просмотра камеры, а нижняя часть должна быть картой...):

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;

Ответ 1

Это возможно, но, возможно, немного сложно. Чтобы это было просто, я поставил основной код для достижения этого в ответе. Как уже отмечалось, для этого вам нужно два вида, один "поверх другого". "Нижний" должен быть SurfaceView, управляемый API карт. "Высший" должен показать, что изображение камеры исчезло над ним.

EDIT: Как указывает mr_archano, API (теперь) определен таким образом, что без SurfaceView камера не будет отправлять данные предварительного просмотра. Ho hum, такова природа прогресса, но это также невозможно преодолеть.

В коде представлен:

  • "Нижний" SurfaceView управляется непосредственно механизмом предварительного просмотра камеры.
  • "Средний" SurfaceView предназначен для API MAPS.
  • "Верхний" вид - это то, где данные камеры отображаются для достижения желаемого эффекта.

Таким образом, основной код дает "предварительный просмотр камеры" над "предварительным просмотром камеры", а верхняя часть изображения была преднамеренно искажена, поэтому она четко видна в верхней части, затухающей в середине и снижающейся внизу.

Могу ли я предложить, чтобы лучший способ использования этого кода состоял в том, чтобы реализовать эти первые четыре шага самостоятельно и увидеть, как он работает, а затем добавить два последних шага и увидеть, что они работают, прежде чем вставлять ключевые понятия в другие, несомненно более сложный и сложный фрагмент кода.

Первые четыре шага:

  • Создайте собственное представление для отображения сверху, камеры, просмотра. Этот класс отображает растровое изображение над всем, что находится под ним. Значение альфа в каждом пикселе в растровом изображении определит, сколько из нижнего представления пропускается.

    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);
        }
    }
    
  • Поместите три представления в кадр, и все они будут установлены в 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" />
    

  • Урезанная основная активность, которая будет настраивать камеру, и отправить автоматическое изображение предварительного просмотра в (снизу) 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) {
        }
    }
    

    Некоторые вещи отсутствуют:

    • Обеспечение правильной ориентации изображения камеры
    • Обеспечение оптимального размера изображения предварительного просмотра камеры

  • Теперь добавьте две функции в представление, созданные на первом шаге. Первое гарантирует, что View знает размер входящих изображений. Второй получает данные предварительного просмотра, превращает его в растровое изображение, искажая его по пути как для видимости, так и для демонстрации альфа-затухания.

    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 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();
    }
    

    Примечания по второй процедуре:

Этот код показывает основную идею. Затем перейдите к следующему этапу:

  • Установите вид поверхности камеры, чтобы быть достаточно маленьким, чтобы спрятаться за не выцветшим участком верхнего вида. т.е. измените android:layout_height на это, скажем, на 60dp.

  • Установите средний SurfaceView для получения информации о карте.

Ответ 2

К сожалению, AFAIK вы не можете перекреститься между предварительным просмотром камеры и картой, если оба компонента должны быть интерактивными/живыми. Как указано ранее в предыдущем комментарии, это связано с природой обоих виджетов и ограничениями компоновки Android.

Для правильной работы для просмотра камеры требуется SurfaceView. Из официальных документов:

SurfaceView пробивает отверстие в своем окне, чтобы его поверхность будет отображаться. Иерархия представления позаботится правильно композиция с поверхностью любых братьев и сестер SurfaceView, которая обычно появлялся бы поверх него. Это можно использовать для размещения наложений например, кнопки поверх поверхности, однако обратите внимание, что это может оказывают влияние на производительность, поскольку полный альфа-смешанный композит будет выполняться каждый раз при изменении поверхности.

Google Maps v2 тоже использует SurfaceView (смотрите здесь), поэтому в основном у вас есть два экземпляра SurfaceView один поверх другого, и вы просто не можете применить градиентную маску для достижения того, чего хотите, потому что вы не можете контролировать, как каждый виджет рисует себя:

  • Предварительный просмотр камеры SurfaceView принимает буфер камеры и выводит его изначально
  • Карты SurfaceView отображаются в другом процессе.

Кроме того, использование двух экземпляров SurfaceView вместе очень сильно обескуражено, как указано здесь:

Поверхностный вид пути реализован в том, что отдельная поверхность созданный и Z-упорядоченный позади его содержащего окна, и прозрачный пиксели, нарисованные в прямоугольнике, где SurfaceView так вы можете см. поверхность позади. Мы никогда не планировали допускать множественные поверхности.

Я думаю, что единственный вариант, который у вас есть, - это выбрать только один из них, чтобы быть живым/интерактивным, а другой - как статический градиент изображения поверх него.


ИЗМЕНИТЬ

Чтобы подтвердить дальнейшие мои предыдущие заявления, ниже цитаты из официальных документов об использовании камеры:

Важно: передайте полностью инициализированный SurfaceHolder для setPreviewDisplay (SurfaceHolder). Без поверхности камера не сможет запустить предварительный просмотр.

Итак, вам нужно использовать SurfaceView, чтобы получить предварительный просмотр с Camera. Всегда.
И просто повторить: у вас нет контроля над тем, как отображаются эти пиксели, потому что Camera записывает непосредственно фреймбуфер с помощью предварительного просмотра SurfaceHolder.

В заключение у вас есть два полностью непрозрачных SurfaceView экземпляра, и вы просто не можете применить какой-либо причудливый рендеринг к их контенту, поэтому я думаю, что такой эффект просто непрактичен в Android.