Android镜像翻转指的是将屏幕进行水平的翻转,达到所有内容显示都会反向的效果,就像是在镜子中看到的界面一样。这种应用的使用场景相对比较受限,主要用在一些需要使用Android手机界面进行镜面投影的地方,比如说车载手机hud导航之类的。

镜像翻转的效果如下:

    

镜像水平翻转前后效果

在没办法对硬件进行直接翻转的时候,只能从代码进行着手。最先想到的方法是一种比较弱的实现方案,就是对界面进行截图,然后对截图进行翻转,再让其替换掉原先的界面,这种方法是可行的,但是会出现很严重的内存问题,因为图片很耗内存,而且不利于动态界面的实现,比如控件会变动位置,控件内容会变化的情况。这就需要其他更靠谱的方案。

下面提供三种解决方案,能够解决一部分镜像翻转问题。

1.翻转动画

第一种方法是使用Android翻转动画进行实现。

该方法需要重写动画,实现翻转,并将该动画添加到布局中,之后只要将动画的时长设置到0就能忽略掉动画过程,从而直接获取到动画的最终效果。需要重写Animate类,用 android.graphics.Camera和 android.graphics.Matrix可以比较容易地实现翻转效果,代码实现如下:

  1.  /**
    * Created by obo on 15/11/26.
    */ import android.graphics.Camera;
    import android.graphics.Matrix;
    import android.view.animation.Animation;
    import android.view.animation.Transformation; public class Rotate3dAnimation extends Animation { // 中心点
    private final float mCenterX;
    private final float mCenterY;
    // 3D变换处理camera(不是摄像头)
    private Camera mCamera = new Camera(); /**
    * @param centerX 翻转中心x坐标
    * @param centerY 翻转中心y坐标
    */
    public Rotate3dAnimation(float centerX,
    float centerY) {
    mCenterX = centerX;
    mCenterY = centerY;
    } @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
    // 生成中间角度
    final Camera camera = mCamera;
    final Matrix matrix = t.getMatrix();
    camera.save();
    camera.rotateY(180);
    // 取得变换后的矩阵
    camera.getMatrix(matrix); camera.restore();
    matrix.preTranslate(-mCenterX, -mCenterY);
    matrix.postTranslate(mCenterX, mCenterY);
    }
    }

调用的方法如下:

View layoutView = findViewById(R.id.reverse_layout);
Animation animation = new Rotate3dAnimation(layoutView.getWidth() / 2, layoutView.getHeight() / 2);
animation.setFillAfter(true);
layoutView.startAnimation(animation);

这里的reverse_layout是一个RelativeLayout的布局,调用了该段代码之后能将Layout和layout所承载的内容都进行翻转。思路是将layoutView从中心点进行180度的水平翻转,需要设置setFillAfter为true来保持翻转后的最终状态。这里需要注意的是,这段代码不能直接放在onCreate里面调用,因为在onCreate的时候,layout的大小还没有被计算出来,如果想在onCreate里面使用可以这样:

  1.  final View layoutView = findViewById(R.id.reverse_layout);  
    
     ViewTreeObserver vto = layoutView.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() { Animation animation = new Rotate3dAnimation(layoutView.getMeasuredWidth() / 2, layoutView.getMeasuredHeight() / 2);
    animation.setFillAfter(true);
    layoutView.startAnimation(animation);
    }
    });

可以为layoutView加一个布局的监听,监听到layoutView布局加载了之后,就能正常获取layoutView的大小了,也就能正常输出效果了。

2.重写控件

对控件进行重写是另外一个实现的思路。假设承载界面的Layout是RelativeLayout,则可以对整个RelativeLayout进行重写,重写的代码可以如下:

 import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.RelativeLayout; /**
* Created by obo on 15/12/4.
*/
public class ReverseLayout extends RelativeLayout { public boolean isReverse = true; public ReverseLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
public void dispatchDraw(Canvas canvas) { if (isReverse)
canvas.scale(-1, 1, getWidth() / 2, getHeight() / 2); super.dispatchDraw(canvas);
}
}

之后,在布局xml中将最外层的RelativeLayout替换成ReverseLayout就能对界面进行翻转。这样的翻转能够将Layout里面所有的控件都进行翻转,如果需要翻转的仅仅只是一个TextView的话,则可以单单对一个TextView进行重写,这个时候,就不需要重写dispatchDraw方法,而应该重写onDraw方法,如下:

  1. import android.content.Context;
    import android.graphics.Canvas;
    import android.util.AttributeSet;
    import android.widget.TextView; /**
    * Created by obo on 15/12/6.
    */
    public class ReverseTextView extends TextView {
    public ReverseTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    } @Override
    public void onDraw(Canvas canvas) {
    canvas.scale(-1, 1, getWidth() / 2, getHeight() / 2);
    super.onDraw(canvas);
    }
    }

onDraw和dispatchDraw的区别是onDraw只对当前的View有效,而不会影响其所包含的SubView,而dispatchDraw则会将翻转效果传递到所有的SubView。

3.SurfaceView翻转

以上两种方法能实现大多数View的翻转,但是都对SurfaceView没有效果,因为SurfaceView是通过双缓冲机制进行绘制的,不会经过onDraw或是dispatchDraw方法,也就不能对我们所进行的操作进行响应了,对于自定义的SurfaceView来说,可以对在lockCanvas中获取的Canvas对象进行翻转处理。

下面给出SurfaceView翻转实现的代码:

  1. import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView; /**
    * Created by obo on 15/12/6.
    */
    public class TestSurfaceView extends SurfaceView implements SurfaceHolder.Callback{ SurfaceHolder surfaceHolder ; public TestSurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);
    surfaceHolder = this.getHolder();
    surfaceHolder.addCallback(this);
    } @Override
    public void surfaceCreated(SurfaceHolder holder) { Canvas canvas = surfaceHolder.lockCanvas(); //绘制之前先对画布进行翻转
    canvas.scale(-1,1, getWidth()/2,getHeight()/2); //开始自己的内容的绘制
    Paint paint = new Paint();
    canvas.drawColor(Color.WHITE);
    paint.setColor(Color.BLACK);
    paint.setTextSize(50);
    canvas.drawText("这是对SurfaceView的翻转",50,250,paint);
    surfaceHolder.unlockCanvasAndPost(canvas);
    } @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} @Override
    public void surfaceDestroyed(SurfaceHolder holder) {} }

采用该方法之后,对SurfaceView也能进行翻转了,效果如下:


实际上,该方法是借助了第二种方法的思路,直接对canvas进行预先的处理从而达到我们所需要的效果的,所以也可以作为第二种方法的扩展。

4.手势翻转

需要注意的是,以上这几种方法仅仅是实现了显示的翻转,手势操作的位置并没有发生翻转。所以使用以上翻转方式的话需要结合手势翻转的实现,其实现思路是重写外层的viewgroup的onInterceptTouchEvent方法,对下发的MotionEvent进行一次翻转操作,使得childView接收到的手势都是反过来的。实现代码如下 :

5.更优雅的方案

对于普通view(非SurfaceView),还有一个更加优雅的实现方案,而且不需要重写onInterceptTouchEvent方法,只需要调用父布局的setScaleY或者setScaleX方法即可。

  1. // 获取需要翻转的父布局
  2. layoutScale = findViewById(R.id.layout_scale);
  3. // 翻转
  4. layoutScale.setScaleY(-1);

6.总结

package com.obo.reverseview.views.touchreverse;  

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.RelativeLayout; /**
* Created by obo on 16/5/4.
* Email:obo1993@gmail.com
* Git:https://github.com/OboBear
* Blog:http://blog.csdn.net/leilba
*/
public class TouchReverseLayout extends RelativeLayout { public TouchReverseLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) { ev.setLocation(this.getWidth() - ev.getX(),ev.getY()); return super.onInterceptTouchEvent(ev);
}
}

采用动画和重写控件的方案都能实现界面翻转的效果,而且性能方面都十分不错。

但是这两种方法都会存在以下一些问题:

1.仅仅翻转显示内容,不会翻转点击的坐标位置。也就是说,如果布局内最左边存在着一个按钮,则翻转后,按钮将会显示在界面最右边,但是想要点击按钮的话,还是在界面原先按钮所在的最左边进行点击才会得到响应。这里可以采取在父布局中对坐标进行重新定位的方法。

2. 无法翻转已经封装好了的SurfaceView。比如说,当前要将某第三方地图界面进行水平镜像翻转,发现用第一种和第二种方法都无效,查看了部分源码之后发现其实质是用SurfaceView进行实现的,但是SurfaceView是作为该地图的一个subView存在的,所以不能直接获取到该subView,也不能到该SubView的绘制层获取canvas了,这个时候第三种方法也无法进行施展,这里需要采用动态代理的方式来解决,可见Android动态代理为SurfaceHolder添加Hook

代码例子发布在github:

AndroidReverseView

【Android】android镜像翻转的更多相关文章

  1. 中科院开源协会镜像站 Android SDK镜像

    中科院开源协会镜像站 Android SDK镜像测试发布 https://forum.opencas.org/t/184

  2. Android SDK 镜像站

    Android SDK镜像的介绍使用  http://www.androiddevtools.cn 镜像站地址   由于一些原因,Google相关很多服务都无法访问,所以在很多时候我们SDK也无法升级 ...

  3. Android 实现卡片翻转的动画(翻牌动画)

    Android 实现卡片翻转的动画(翻牌动画) 需求描述 点击卡片,卡片翻转过来显示内容. 点击左边的卡片,将卡片翻转显示右边的图片结果. 功能实现 因为要翻转所以使用动画来完成翻转的动画.动画分为两 ...

  4. Stack Overflow 排错翻译 - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder

    Stack Overflow 排错翻译  - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder 转自:ht ...

  5. [Android]Android端ORM框架——RapidORM(v2.1)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6020412.html [Android]Android端ORM ...

  6. [Android]Android端ORM框架——RapidORM(v2.0)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5626716.html [Android]Android端ORM ...

  7. Android android:gravity属性介绍及效果图

    转自: http://blog.csdn.net/aminfo/article/details/7784229 Android:gravity的属性官方说明如下: public static fina ...

  8. [转][Android][Android Studio] *.jar 与 *.aar 的生成与*.aar导入项目方法

     转自:http://blog.csdn.net/qiujuer/article/details/39754517?utm_source=tuicool [Android][Android Studi ...

  9. 图解Android - Android GUI 系统 (1) - 概论

    Android的GUI系统是Android最重要也最复杂的系统之一.它包括以下部分: 窗口和图形系统 - Window and View Manager System. 显示合成系统 - Surfac ...

随机推荐

  1. bzoj 2741: 【FOTILE模拟赛】L 分塊+可持久化trie

    2741: [FOTILE模拟赛]L Time Limit: 15 Sec  Memory Limit: 162 MBSubmit: 1116  Solved: 292[Submit][Status] ...

  2. NOR FLASH与NAND FLASH

    整理自NOR FLASH 与NAND FLASH 1:NandFlash与NorFlash典型电路图 Nor Flash接原理图 从上图可以看出,该NorFlash采用并行地址和数据总线, 其中,21 ...

  3. 居然还有WM_TIMECHANGE(只在用户手动改变系统时间时才会产生作用)

    unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  4. Delphi 版本号(D1到XE6),发现一个delphi.wikia.com网站

    Borland Compiler Conditional Defines  Edit  Talk1 2,909PAGES ONTHIS WIKI   Product Name Version Cond ...

  5. MySQL建立连接的过程

    数据库连接的一些知识: import java.sql.Connection ; import java.sql.DriverManager ; import java.sql.Statement ; ...

  6. Spark Streaming 结合FlumeNG使用实例

    SparkStreaming是一个对实时数据流进行高通量.容错处理的流式处理系统,可以对多种数据源(如Kdfka.Flume.Twitter.Zero和TCP 套接字)进行类似map.reduce.j ...

  7. VARCHAR2字段关联

    SQL> create table a1(id int,name varchar2(10)); Table created. SQL> create table a2(id int,nam ...

  8. 此集合已经采用方案 http 的地址。此集合中每个方案中最多只能包含一个地址。

    错误信息:此集合已经采用方案 http 的地址.此集合中每个方案中最多只能包含一个地址.如果服务承载于 IIS 中,则可以通过将“system.serviceModel/serviceHostingE ...

  9. C++ Prime:switch内部的变量定义

    如果需要为某个case分支定义并初始化一个变量,我们应该把变量定义在块内,从而确保后面的所有case标签都在变量的作用域之外. case true: { // 正确,声明语句位于语句块内部 strin ...

  10. eclipse中 com.sun.image.codec.jpeg.JPEGCodec 无法编译通过问题

    在Eclipse中处理图片,需要引入两个包:import com.sun.image.codec.jpeg.JPEGCodec;import com.sun.image.codec.jpeg.JPEG ...