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. March of the Penguins

    poj3498:http://poj.org/problem?id=3498 题意:某个冰块上有a只企鹅,总共可以跳出去b只,问是否可能所有的企鹅都跳到某一块冰块上,输出所有的可能的冰块的编号. 由于 ...

  2. Katu Puzzle

    poj3678:http://poj.org/problem?id=3678 题意:给你一些数,然后这些要么是0要么是1,然后回给出一些数之间的and,or,xor的值,问你是否存在一组解. 题解:2 ...

  3. iOS-NSTimer-pause-暂停-引用循环

    7月26日更新: 今天更新的主要目的是因为暂停!!!! 注:不推荐使用,并不是这样有错,而是因为这样写代码的规范问题,代码要有可读性,遵循代码即文档,使用暂停在团队合作中可能会带来误会,非必要不建议使 ...

  4. CAD文件导入AD09

    1.首先将CAD图纸倒出为DXF格式的文件. 2.在altium designer 的pcb编辑中点菜单文件--导入,在弹出的对话框中,选择导入文件的类型,选择 DWG,DXF类型.然后确定,再弹出的 ...

  5. Tomcat默认打开项目设置

    Tomcat设置默认启动项目 Tomcat设置默认启动项目,顾名思义,就是让可以在浏览器的地址栏中输入ip:8080,就能访问到我们的项目.具体操作如下: 1.打开tomcat的安装根目录,找到Tom ...

  6. Features of Spring Web MVC

    21.1.1 Features of Spring Web MVC Spring Web Flow Spring Web Flow (SWF) aims to be the best solution ...

  7. poj 2431 Expedition 贪心

    简单的说说思路,如果一开始能够去到目的地那么当然不需要加油,否则肯定选择能够够着的油量最大的加油站加油,,不断重复这个贪心的策略即可. #include <iostream> #inclu ...

  8. Linux驱动的两种加载方式过程分析

    一.概念简述 在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载. 静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用.静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新 ...

  9. (转载)MySQL关键字GROUP BY的使用

    例子: mysql> select * from employee; +------+------+-------+------+-------+----------+ | num | d_id ...

  10. Linux学习笔记7——linux中的静态库和动态库

    一.静态库的编译 静态库的编译过程如下: 1.编译成目标文件 这里有一个可选项-static,调用格式:gcc -c -static 代码文件名.c 2.归档成静态库 A.归档的工具是ar工具,使用a ...