• Androidandroid镜像翻转 2016-05-23 16:47:14
    Android镜像翻转指的是将屏幕进行水平的翻转,达到所有内容显示都会反向的效果,就像是在镜子中看到的界面一样。这种应用的使用场景相对比较受限,主要用在一些需要使用Android手机界面进行镜面投影的地方,比如说...

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

    镜像翻转的效果如下:

        

    镜像水平翻转前后效果

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

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

    1.翻转动画

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

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

    /**
     * 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里面使用可以这样:

    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方法,如下:

    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翻转实现的代码:

    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接收到的手势都是反过来的。实现代码如下 :

    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);
        }
    }
    

    5.更优雅的方案

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

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

    6.总结

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

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

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

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

    代码例子发布在github:

    AndroidReverseView

    展开全文
  • Android车载方案公司,为WinCE工厂服务,这是一种悲哀。 1、你要解释什么是时区,什么是UTC时间,Android的时区设置有什么好处,以及去掉时区设置的坏处。 2、你要解释为什么凯立德中显示的时间和系统时间有可能不...
  • Application 标签android:allowTaskReparenting android:allowTaskReparenting=[“true” | “false”]  表明了这个应用在 reset task 时,它的所有 activity 是否可以从打开它们的 task 栈中迁移到它们声明的 ...
  • Android 横竖屏切换小结(实践) 首先我们先创建一个Activity 名为Main2Activity 写出相应的Activity 的生命周期,并且在Androidmanifest.xm中什么都没有配置如下: activity android:name=".land_portrait.Main...
  • Android横竖屏切换的时候相关信息。
  • 前言:前面几篇介绍了android四大组件的知识,可以发现,四大组件都必须在一个叫AndroidManifest.xml文件中进行注册,那么该文件的作用是什么呢?你们的内容各有什么意义呢?带着这些疑问,来开始下面内容的学习。...
  • Android Activity标签属性 2017-02-22 14:28:31
    Activity 是 Android 系统四大应用组件之一,用户可与 Activity 提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件等操作开发者必须在清单文件中声明要使用的 Activity,这样系统才能访问它。声明方式是...
  • Android 清单文件属性大全 2017-06-16 17:22:23
    最近得空,再回顾一下android api文档,其中有些地方自己做了总结,如想看api的,链接在此:https://developer.android.google.cn/guide/topics/manifest/activity-element.html#reparentalwaysRetainTaskState...
  • APP为上层的车载导航软件,通过ADBClient跟对端的ADBD进行数据交互。包括adb所支持的所有命令(push/pull file, shell, tcp socket, udp-socket以及组合命令实现install uninstall等命令)。ADBClient与对端的...
  • Android设备研发术语表 2019-03-15 14:31:36
    A 术语 简介 APSS Applications Subsystem ACDB Audio Calibration Database ADC Analog-to-Digital Conversion,模拟 - ... ...
  • MTK Android Driver知识大全 2019-07-24 23:31:16
    一、Display 1.lcm 相关概念 1.1) MIPI接口:一共有三种接口:DBI(也做CPU或MCU接口)、DPI(也叫RGB接口)、DSI. 在使用DSI接口时,目前75/77都只支持到2条data lane,加上一条clock lane. 使用DPI接口时...
  • 就是在新建安卓窗口要在AndroidMainfest清单中添加Activity的时候可以为这个Activity设置的属性。 本文来源安卓官方文档(需翻墙):...最后有原文截图语法:<activity android:allowEmbedded=["tru
  • Android架构组件-WorkMannager 2019-07-17 23:53:28
    WorkManager是一个针对后台任务处理的方案,无论在应用或设备重启的情况下都能可靠地帮助您的应该管理和执行延迟的后台任务,同时能兼容到Android 4.0以及以后的版本,能减轻适配工作。我们知...
  • ...作为一个长期使用 Android 的用户,我在使用 Android 应用的时候经常遇到各种各样的交互上的问题,并且早就想整理它们写一篇文章了。但是由于懒惰和拖延,这篇文章一直处于草稿的状态。正...
1 2 3 4 5 6
收藏数 103
精华内容 41