如何在Android上显示两个视图,并带有渐变淡出效果?

11

我希望在屏幕上显示两个视图-一个是位于顶部的相机预览,另一个则显示一张图片或谷歌地图,并实时呈现在屏幕底部。

我想让它们之间有渐变式的过渡,这样它们之间就没有明显的粗糙边缘。是否可能实现这种效果?

编辑:我想要实现的效果应该像这样(上半部分来自相机预览,下半部分应该是一张地图...):

地图混合到照片中

在iOS上,我使用CameraOverlay显示地图并将图层设置为渐变色获得了类似的效果:

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(或类似/后代),因此在这种情况下合成非常有限。 - a.bertucci
如果可能,请展示您想要的图像请将以下与编程有关的内容翻译成中文。只返回已翻译的文本: - stinepike
@StinePike编辑了问题以展示我想要得到的内容 :) - kender
是的,两个都应该是在线的。 - kender
请 @kender 明确指定地图是否应该是交互式的(缩放、平移等)还是仅仅是一个简单的图片。这会有很大的区别。 - a.bertucci
显示剩余2条评论
2个回答

4
很遗憾,据我所知,如果两个组件都需要交互/实时,则无法在相机预览和地图之间进行淡入淡出。正如先前在先前的评论中所述,这与两个小部件的性质以及Android合成的限制有关。
相机预览需要一个SurfaceView才能正常工作。根据官方文档:
“SurfaceView在其窗口中打洞,以允许其表面显示。视图层次结构将负责正确地与Surface合成任何SurfaceView的同级别的兄弟姐妹,这些兄弟姐妹通常会出现在其上面。这可以用于放置覆盖物,例如按钮,但请注意,这可能会影响性能,因为每次Surface更改时都会执行完整的alpha混合复合。”
Google Maps v2也使用SurfaceView(请参见此处),因此基本上您有两个SurfaceView实例,一个叠在另一个上面,您不能简单地应用渐变掩码以实现所需的效果,因为您无法控制每个小部件如何绘制自己:
  • 相机预览SurfaceView接收相机缓冲区并在本地渲染。
  • 地图SurfaceView在另一个进程中呈现。

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

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

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


编辑

为了进一步验证我之前的说法,以下是官方文档有关相机使用的说明的引用:

重要提示:将一个完全初始化的SurfaceHolder传递给setPreviewDisplay(SurfaceHolder)。没有surface,相机将无法开始预览
因此,您被迫使用SurfaceView才能从Camera获取预览。总是这样。 并且再次重申:您无法控制这些像素的呈现方式,因为Camera使用预览SurfaceHolder直接写入帧缓冲区。
总之,您有两个完全不透明的SurfaceView实例,并且无法对其内容应用任何花哨的渲染效果,因此我认为在Android中实现这种效果是不切实际的。

你好,mr_archano先生,你显然是正确的,如果你坚持使用SurfaceView。但还有另一种在屏幕上渲染相机预览图像的方法,即捕获预览数据并自己进行渲染... - Neil Townsend
是的,您可以在另一个视图中呈现相机位,但您无法摆脱绑定到相机的SurfaceView。这就是我的观点:您无法仅呈现已淡化的帧并隐藏/删除用于启动预览的SurfaceView。 - a.bertucci
有趣的是,自从我使用它以来,API已经发生了变化。然而,解决方案相对简单:将相机使用的SurfaceView设置为另一个视图的后面,使其变小并隐藏起来等等。我会相应地更正我的答案。 - Neil Townsend
我认为API没有改变,就我所知道的。当我在2011年2月为这个演示(https://play.google.com/store/apps/details?id=com.ssd.app.demo.nfd)使用它时,它已经是这样了。 - a.bertucci
1
好的,我几年前写了一段代码,完全没有使用 holder 就可以正常工作,但今天重新运行代码时需要一个 holder - 哎呀。然而,我添加的解决方案是将相机的 SurfaceView 隐藏在某个地方并使用它... - Neil Townsend

0
这是可行的,但可能有点复杂。为了简单起见,我已经在答案中放置了实现此目的的核心代码。正如已经注意到的那样,您需要两个视图来完成此操作,一个位于另一个“之上”。较低的视图应该是由地图API驱动的SurfaceView。而“较高”的视图应显示相机图像在其上方逐渐失去透明度。
编辑:正如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. 将三个视图放入一个框架中,它们都设置为在两个方向上都填充父级。第一个视图将位于“下方”(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. 一个简化的主 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

    这段代码展示了基本的思路。然后进入下一阶段:

    1. 将相机表面视图设置为足够小,以隐藏在顶部未淡化部分后面。例如,更改其 android:layout_height60dp

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


我该如何修改相机输出?到目前为止,我使用了类似于https://dev59.com/CHE95IYBdhLWcg3wGqEH的代码,将相机输出放在SurfaceView上。 - kender
你使用SurfaceView还是ImageView?或者两个都有用到吗?如果两个都用到,哪一个是哪一个? - Neil Townsend
现在我正在使用2个SurfaceView。我想我可以每0.1秒向相机请求一个位图,并将其放在ImageView上,但不确定这是否有意义... - kender
是的,这听起来像是正确的方式(显然是唯一可接受的方式 :) - kender
获取梯度的方法在之前的版本中,它都在一行代码里而且没有注释 - 我没有表达清楚,所以在你的评论后我进行了更明确的说明。我已经尝试并运行了答案中的代码(因此在编辑将所有代码放入之前有很长的间隔),它按照我的说法正常工作:基本SurfaceView显示直接相机预览(但应该可以由任何驱动SurfaceView的东西来驱动),覆盖的视图显示扭曲和淡化的相机图片版本覆盖在原始图片上。这是一个非常有趣的效果! - Neil Townsend
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接