扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
先上效果图
创新互联建站坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都做网站、网站建设、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的清流网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
源码
单点拖动图片对图片进行平移操作。双手缩放图片大小和旋转图片到一定的角度。图片缩放的时候 不能大于最大的缩放因子和小于最小的缩放因子。大于最大缩放因子或者小于最小缩放因子需要对图像进行回弹。图片旋转的角度只能为90度的倍数,不满足90度要进行回弹。图片回弹要一个渐变的效果。
大体思路: 首先,Android中提供了Matrix类可以对图像进行处理。其次,要显示一张图片最容易想到的就是ImageView。回弹要求渐变的过程,可以通过属性动画进行设置。所以大体的思路是:继承ImageView,重写onTouchEvent()方法,判断事件类型,在对应的事件使用Matrix对图像进行变换。
Matrix是一个已经封装好的矩阵,最重要的作用就是对坐标点进行变换。
举个栗子:
1.某个点(x0,y0,1)通过单位矩阵E映射得到的点还是(x0,y0,1)。
3.点(x0,y0,1)通过矩阵T映射得到的点就会做如下的变换
可以看到点(x0,y0,1)经过T矩阵在x轴方向上平移了dx,在y轴方向上平移了dy。
通过以上的变换可以得到具体的思路: 我们维护一个图像对应的矩阵mCurrentMatrix,该矩阵主要是对ImageView中的图像的各个点进行映射。ImageView在容器位置摆放完成之后,置mCurrentMatrix矩阵为单位矩阵。当onTouchEvent()方法中触发单点触控并且手指进行平移的时候,调用矩阵mCurrentMatrix的postTranslate(dx,dy),对mCurrentMatrix进行变换。当手指抬起,利用变换结束后的矩阵对图像的各个点进行映射,从而得到平移变换后的图像。同理可得,在两只手指进行缩放旋转的时候,我们对矩阵mCurrentMatrix进行各种变换,当缩放旋转的事件结束再利用变换完的矩阵去映射图像的各个点,从而得到缩放、旋转后的图像。
安卓自定义View进阶 - Matrix原理
安卓自定义View进阶 - Matrix详解
首先理清事件的逻辑:
初始化图像大小和位置
缩放图像大小和控件大小自适应,平移图像中心和控件中心重合
onTouchEvent()函数
平移操作
将图像对应的矩阵进行变换。
缩放操作
mBoundRectF为记录图像边界的矩形。缩放的时候选取图像的中心进行缩放。
旋转操作
旋转的时候旋转的旋转中心也是图像的中心
图像中各个点的映射
调用ImageView的setImageMatrix(Matrix matrix)会让ImageView根据设置的matrix去重新绘制图像。
更新图像的矩形边界
获得图像的矩形,并根据矩阵映射矩形各个点的坐标。
缩放回弹
旋转回弹
一些计算方法
要求图像的变换是一个渐变的过程,很容易想到的就是属性动画。因为属性动画本身就是对值进行不断set的过程。而我们维护的矩阵也是一个值,所以很自然可以想到,如果得到回弹之前的矩阵的值以及回弹之后矩阵的值,就可以根据动画监听器中动画当前的系数值去改变矩阵的值。
对animator对象设置完监听器之后,就可以在手指抬起的时候调用属性动画的start()方法开启动画。
自定义可平移、缩放、旋转的控件主要点有两个方面:一是onTouchEvent()中判断平移、旋转、缩放的触发条件,平移位移量、缩放比例因子、旋转角度的计算。二是Matrix矩阵的应用。
横屏180°旋转系统不会回调到到 onConfigurationChanged() ,只能使用其他的方案,目前有2个方案
1、使用 OrientationEventListener 监听屏幕的旋转,里面本质使用的是 TYPE_ACCELEROMETER 传感器,具体如下:
开启调用 mOrientationListener. enable() , 关闭调用 mOrientationListener. disable() ;这种方式对性能消耗比较大, 但是可以获取到手机当前的角度
2、使用监听 DisplayManager 方式,手机切换方向会导致UI 显示的改变,所以会回调到这里
这种方式不会耗性能
设备平放,屏幕朝正上方。以下四个常量分别代表:
private static final int ROTATION_0 = 0;//初始情况。这个时候设备是横屏还是竖屏与硬件设备安装时默认的显示方向有关。
private static final int ROTATION_90 = 1;//设置屏幕方向自动旋转后,右侧翻起侧立时,屏幕会旋转到这个方向。
private static final int ROTATION_270 = 2;//设置屏幕方向自动旋转后,左侧翻起度侧立时,屏幕会旋转到这个方向。
private static final int ROTATION_180 = 3;//设置屏幕方向自动旋转后,屏幕底部侧立时,屏幕会旋转到这个方向。
再看两个数组:
view plain
private static final int[][][] THRESHOLDS_WITH_180 = new int[][][] {
{{60, 165}, {165, 195}, {195, 300}},
{{0, 30}, {165, 195}, {195, 315}, {315, 360}},
{{0, 45}, {45, 165}, {165, 195}, {330, 360}},
{{0, 45}, {45, 135}, {225, 315}, {315, 360}},
};
private static final int[][] ROTATE_TO_WITH_180 = new int[][] {
{ROTATION_90, ROTATION_180, ROTATION_270},
{ROTATION_0, ROTATION_180, ROTATION_90, ROTATION_0},
{ROTATION_0, ROTATION_270, ROTATION_180, ROTATION_0},
{ROTATION_0, ROTATION_90, ROTATION_270, ROTATION_0},
};
当前屏幕旋转方向为ROTATION_0时,取int[][] threshold=THRESHOLDS_WITH_180[0];
当前屏幕旋转方向为ROTATION_90时,取int[][] threshold=THRESHOLDS_WITH_180[1];
当前屏幕旋转方向为ROTATION_270时,取int[][] threshold=THRESHOLDS_WITH_180[2];
当前屏幕旋转方向为ROTATION_180时,取int[][] threshold=THRESHOLDS_WITH_180[3];
其中,threshold中的每一个元素由两个值构成,用来表示一个范围。
WindowOrientationListener会注册一个Accelerator类型的SensorEventListener,当有新的SensorEvent产生时,调用filterOrientation产生一个int orientation值。这个值会在threshold的各个元素表示的范围中匹配,看会落在哪个范围。假设当前屏幕方向为ROTATION_0,那么threshold={{60, 165}, {165, 195}, {195, 300}},假设这个时候把屏幕左侧翻起90度。filterOrientation计算出的orientation值落在了第三个元素范围内,那么去ROTATE_TO_WITH_180中寻找与它对应的值,发现是ROTATION_270,那么就把当前屏幕旋转方向改变为270度。threshold的取值就变成了THRESHOLDS_WITH_180[2]。当把屏幕再次放平时,filterOrientation计算出的orientation值会落在第一个元素表示的范围内。去ROTATE_TO_WITH_180中寻找与它对应的值,发现是ROTATION_0,那么当前屏幕旋转方向被改变为0度。
还有一个变量比较重要,mAllow180Rotation,这个变量设置为false时,就不使用THRESHOLDS_WITH_180和ROTATE_TO_WITH_180这一对数组来做上面这些变的了,就使用THRESHOLDS和ROTATE_TO。
其实,我研究了半天也没有搞清filterOrientation的算法以及THRESHOLDS_WITH_180和THRESHOLDS这两个数组里面的每个数字代表的具体意义。最后只搞清了上面的这个流程,还有ROTATION_0, ROTATION_90, ROTATION_270, ROTATION_180这四个角度分别代表哪四个方向。但这足以应付我们要做的事情了。
比如,我想让屏幕最多只旋转90度和180度,不让它有旋转270度的机会。那就把ROTATE_TO_WITH_180里面的ROTATION_270全部变成90度。这样,应该旋转到270度时,就会旋转到90度了。如果不想让屏幕旋转,把所有值都改成ROTATION_0就可以了。
再深入挖掘一下这个话题
PhonwWindowManager是唯一实现WindowOrientationListener接口的类,它管理着整个设备界面的显示。当PhonwWindowManager通过WindowOrientationListener知道屏幕方向发生旋转时,会告诉WindowManagerService:
mWindowManager.setRotation(rotation, false, mFancyRotationAnimation);
而WindowManagerService得到这个通知后,会做两个比较重要的事情:
1、Surface.setOrientation(0, rotation, animFlags);
2、mRotationWatchers.get(i).onRotationChanged(rotation);
我们知道,每个Activity都有一个View树,每个View树都是绘画在一个Surface上面的。通过上面这两步,先把Surface给旋转了,再告诉Activity重新绘制View树,就完了整个屏幕的旋转。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流