【Android】android镜像翻转

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

镜像翻转的效果如下:

    

镜像水平翻转前后效果

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

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

1.翻转动画

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

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

[java]  view plain  copy
  1. /** 
  2.  * Created by obo on 15/11/26. 
  3.  */  
  4.   
  5. import android.graphics.Camera;  
  6. import android.graphics.Matrix;  
  7. import android.view.animation.Animation;  
  8. import android.view.animation.Transformation;  
  9.   
  10. public class Rotate3dAnimation extends Animation {  
  11.   
  12.     // 中心点  
  13.     private final float mCenterX;  
  14.     private final float mCenterY;  
  15.     // 3D变换处理camera(不是摄像头)  
  16.     private Camera mCamera = new Camera();  
  17.   
  18.     /** 
  19.      * @param centerX 翻转中心x坐标 
  20.      * @param centerY 翻转中心y坐标 
  21.      */  
  22.     public Rotate3dAnimation(float centerX,  
  23.                              float centerY) {  
  24.         mCenterX = centerX;  
  25.         mCenterY = centerY;  
  26.     }  
  27.   
  28.     @Override  
  29.     protected void applyTransformation(float interpolatedTime, Transformation t) {  
  30.         // 生成中间角度  
  31.         final Camera camera = mCamera;  
  32.         final Matrix matrix = t.getMatrix();  
  33.         camera.save();  
  34.         camera.rotateY(180);  
  35.         // 取得变换后的矩阵  
  36.         camera.getMatrix(matrix);  
  37.   
  38.         camera.restore();  
  39.         matrix.preTranslate(-mCenterX, -mCenterY);  
  40.         matrix.postTranslate(mCenterX, mCenterY);  
  41.     }  
  42. }  

调用的方法如下:

[java]  view plain  copy
  1. View layoutView = findViewById(R.id.reverse_layout);  
  2. Animation animation = new Rotate3dAnimation(layoutView.getWidth() / 2, layoutView.getHeight() / 2);  
  3. animation.setFillAfter(true);  
  4. layoutView.startAnimation(animation);  

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

[java]  view plain  copy
  1. final View layoutView = findViewById(R.id.reverse_layout);  
  2.   
  3. ViewTreeObserver vto = layoutView.getViewTreeObserver();  
  4. vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {  
  5.     @Override  
  6.     public void onGlobalLayout() {  
  7.   
  8.         Animation animation = new Rotate3dAnimation(layoutView.getMeasuredWidth() / 2, layoutView.getMeasuredHeight() / 2);  
  9.         animation.setFillAfter(true);  
  10.         layoutView.startAnimation(animation);  
  11.     }     
  12. });  

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

2.重写控件

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

[java]  view plain  copy
  1. import android.content.Context;  
  2. import android.graphics.Canvas;  
  3. import android.util.AttributeSet;  
  4. import android.widget.RelativeLayout;  
  5.   
  6. /** 
  7.  * Created by obo on 15/12/4. 
  8.  */  
  9. public class ReverseLayout extends RelativeLayout {  
  10.   
  11.     public boolean isReverse = true;  
  12.   
  13.     public ReverseLayout(Context context, AttributeSet attrs) {  
  14.         super(context, attrs);  
  15.     }  
  16.   
  17.     @Override  
  18.     public void dispatchDraw(Canvas canvas) {  
  19.   
  20.         if (isReverse)  
  21.             canvas.scale(-11, getWidth() / 2, getHeight() / 2);  
  22.   
  23.         super.dispatchDraw(canvas);  
  24.     }  
  25. }  

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

[java]  view plain  copy
  1. import android.content.Context;  
  2. import android.graphics.Canvas;  
  3. import android.util.AttributeSet;  
  4. import android.widget.TextView;  
  5.   
  6. /** 
  7.  * Created by obo on 15/12/6. 
  8.  */  
  9. public class ReverseTextView extends TextView {  
  10.     public ReverseTextView(Context context, AttributeSet attrs) {  
  11.         super(context, attrs);  
  12.     }  
  13.   
  14.     @Override  
  15.     public void onDraw(Canvas canvas) {  
  16.         canvas.scale(-11, getWidth() / 2, getHeight() / 2);  
  17.         super.onDraw(canvas);  
  18.     }  
  19. }  

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

3.SurfaceView翻转

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

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

[java]  view plain  copy
  1. import android.content.Context;  
  2. import android.graphics.Canvas;  
  3. import android.graphics.Color;  
  4. import android.graphics.Paint;  
  5. import android.util.AttributeSet;  
  6. import android.view.SurfaceHolder;  
  7. import android.view.SurfaceView;  
  8.   
  9. /** 
  10.  * Created by obo on 15/12/6. 
  11.  */  
  12. public class TestSurfaceView extends SurfaceView implements SurfaceHolder.Callback{  
  13.   
  14.     SurfaceHolder surfaceHolder ;  
  15.   
  16.     public TestSurfaceView(Context context, AttributeSet attrs) {  
  17.         super(context, attrs);  
  18.         surfaceHolder = this.getHolder();  
  19.         surfaceHolder.addCallback(this);  
  20.     }  
  21.   
  22.     @Override  
  23.     public void surfaceCreated(SurfaceHolder holder) {  
  24.   
  25.         Canvas canvas = surfaceHolder.lockCanvas();  
  26.   
  27.         //绘制之前先对画布进行翻转  
  28.         canvas.scale(-1,1, getWidth()/2,getHeight()/2);  
  29.   
  30.         //开始自己的内容的绘制  
  31.         Paint paint = new Paint();  
  32.         canvas.drawColor(Color.WHITE);  
  33.         paint.setColor(Color.BLACK);  
  34.         paint.setTextSize(50);  
  35.         canvas.drawText("这是对SurfaceView的翻转",50,250,paint);  
  36.         surfaceHolder.unlockCanvasAndPost(canvas);  
  37.     }  
  38.   
  39.     @Override  
  40.     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}  
  41.   
  42.     @Override  
  43.     public void surfaceDestroyed(SurfaceHolder holder) {}  
  44.   
  45. }  
采用该方法之后,对SurfaceView也能进行翻转了,效果如下:


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

4.手势翻转

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

[java]  view plain  copy
  1. package com.obo.reverseview.views.touchreverse;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.view.MotionEvent;  
  6. import android.widget.RelativeLayout;  
  7.   
  8. /** 
  9.  * Created by obo on 16/5/4. 
  10.  * Email:obo1993@gmail.com 
  11.  * Git:https://github.com/OboBear 
  12.  * Blog:http://blog.csdn.net/leilba 
  13.  */  
  14. public class TouchReverseLayout extends RelativeLayout {  
  15.   
  16.     public TouchReverseLayout(Context context, AttributeSet attrs) {  
  17.         super(context, attrs);  
  18.     }  
  19.   
  20.     @Override  
  21.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  22.   
  23.         ev.setLocation(this.getWidth() - ev.getX(),ev.getY());  
  24.   
  25.         return super.onInterceptTouchEvent(ev);  
  26.     }  
  27. }  

5.更优雅的方案

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

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

6.总结

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

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

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

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

代码例子发布在github:

AndroidReverseView

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值