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

Posted

技术标签:

【中文标题】如何在Android上显示2个视图,并具有渐变效果?【英文标题】:How to display 2 views, with a gradient-fade effect on Android? 【发布时间】:2013-04-12 01:05:37 【问题描述】:

我想在屏幕上显示 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;

【问题讨论】:

你能解释一下你所说的类似渐变的过渡是什么意思吗? 我认为只有使用静态内容才能轻松实现这样的“过渡”(静态我是指图像之类的东西)。不幸的是,相机预览和地图都使用 SurfaceView(或类似/后代),因此在这种情况下合成非常有限。 如果可能的话,展示你想要的图片 @StinePike 编辑了问题以显示我想得到什么:) 预览和地图都应该是交互式/实时的? 【参考方案1】:

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

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

SurfaceView 在它的窗口上打了一个洞来让它的表面 被显示。视图层次结构将正确处理 将 SurfaceView 的任何兄弟姐妹与 Surface 合成 通常会出现在它上面。这可用于放置叠加层 例如 Surface 顶部的按钮,但请注意,它可以 对性能有影响,因为完整的 alpha 混合复合材料 将在每次 Surface 更改时执行。

Google Maps v2 也使用SurfaceView(看here),所以基本上你有两个SurfaceView 实例一个在另一个之上,你根本无法应用渐变蒙版来实现你想要的,因为您无法控制每个小部件如何绘制自己:

摄像头预览SurfaceView接收摄像头缓冲区并原生渲染 地图SurfaceView 在另一个进程中呈现。

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

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

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

编辑

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

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

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

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

【讨论】:

您好 mr_archano,如果您坚持使用 SurfaceViews,您显然是正确的。但是还有另一种在屏幕上渲染相机预览图像的方法,您可以在其中捕获预览数据并自己进行渲染... 是的,您可以在另一个视图中渲染相机位,但您无法摆脱绑定到相机的表面视图。这就是我的观点:你不能只渲染褪色的框架并隐藏/删除用于开始预览的表面视图。 有趣的是,自从我使用以来,API 发生了变化。但是,解决方案相对简单:您将相机使用的 SurfaceView 设置在另一个视图后面,使其变小并隐藏等等。我会相应地更正我的答案。 我不认为 API 改变了 AFAICT。当我在 2011 年 2 月在这个演示 (play.google.com/store/apps/details?id=com.ssd.app.demo.nfd) 中使用它时,它已经是这样了。 好吧,几年前我写了一段代码,它在不使用支架的情况下工作得非常好,我今天重新运行了代码,它需要一个支架 - 哼哼。但是,正如我添加的那样,解决方案是将相机的 SurfaceView 隐藏在某处并使用它...【参考方案2】:

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

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

代码呈现:

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

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

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

前四个步骤:

    创建自定义视图以显示到顶部、相机、视图。这个类在它下面的任何东西上渲染一个位图。位图中每个像素的 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);
        
    
    

    将三个视图放在一个框架中,并在两个方向上都设置为fill_parent。第一个将在“下方”(SurfaceView,因此相机预览有效)。第二个“在中间”(地图的表面视图或其他)。第三个“顶部”(褪色相机图像的视图)。

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

    一个精简的主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) 
        
    
    

    有些东西不见了:

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

    现在,向第一步创建的视图添加两个函数。第一个确保 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();
    
    

    关于第二个例程的注释:

    它假定传入的相机格式为 NV21。其他人可能可用,这个保证在那里,即使它是一种痛苦。见Converting YUV->RGB(Image processing)->YUV during onPreviewFrame in android?。 使用更新的 Android 版本和一些代码优化可能会更快或更好。

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

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

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

【讨论】:

如何修改相机输出?到目前为止我使用的代码类似于***.com/questions/2456802/android-camera-preview,将相机输出放在SurfaceView上 你用的是SurfaceView还是ImageView?还是各有一个?如果各有一个,哪个是哪个? 现在我正在使用 2 个 SurfaceViews。我想我可以像每 0.1 秒一样向相机索要一个位图并将其放在 ImageView 上,但不确定这是否有意义...... 您需要自己进行预览图像筛选,正如我在更新的答案中总结的那样。如果您认为这可能是一条可接受的路线,我会提供更多详细信息。 是的,这听起来像是要走的路(显然是唯一可以接受的方法:)

以上是关于如何在Android上显示2个视图,并具有渐变效果?的主要内容,如果未能解决你的问题,请参考以下文章

具有渐变的可绘制视图背景在预览中正确显示但在模拟器中不正确

在表格视图单元格背景上放置渐变

如何在颤动中显示具有顶部和底部渐变阴影的图像?

PS如何画环形渐变

Cordova 无法处理的具有线性渐变的 SVG - iOS

如何通过android实现alpha渐变动画效果