【Android】android鏡像翻轉


Android鏡像翻轉指的是將屏幕進行水平的翻轉,達到所有內容顯示都會反向的效果,就像是在鏡子中看到的界面一樣。這種應用的使用場景相對比較受限,主要用在一些需要使用Android手機界面進行鏡面投影的地方,比如說車載手機hud導航之類的。

鏡像翻轉的效果如下:

    

鏡像水平翻轉前后效果

在沒辦法對硬件進行直接翻轉的時候,只能從代碼進行着手。最先想到的方法是一種比較弱的實現方案,就是對界面進行截圖,然后對截圖進行翻轉,再讓其替換掉原先的界面,這種方法是可行的,但是會出現很嚴重的內存問題,因為圖片很耗內存,而且不利於動態界面的實現,比如控件會變動位置,控件內容會變化的情況。這就需要其他更靠譜的方案。

下面提供三種解決方案,能夠解決一部分鏡像翻轉問題。

1.翻轉動畫

第一種方法是使用Android翻轉動畫進行實現。

該方法需要重寫動畫,實現翻轉,並將該動畫添加到布局中,之后只要將動畫的時長設置到0就能忽略掉動畫過程,從而直接獲取到動畫的最終效果。需要重寫Animate類,用 android.graphics.Camera和 android.graphics.Matrix可以比較容易地實現翻轉效果,代碼實現如下:

 
  1.  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 }  

     

 

調用的方法如下:

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.  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進行重寫,重寫的代碼可以如下:

 
 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(-1, 1, getWidth() / 2, getHeight() / 2);  
22   
23         super.dispatchDraw(canvas);  
24     }  
25 }  

 

 

之后,在布局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方法即可。

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM