问题来源:对于 Gallery 的 3D 效果大家并不陌生,如下图
此效果在 android4.0 以前可以继承 Gallery 类或 ViewGroup 类,在构造函数中设置
- setStaticTransformationsEnabled(true);
然后重载函数 getChildStaticTransformation 如下代码。
- @Override
- protected boolean getChildStaticTransformation(View child, Transformation t) {
- // TODO Auto-generated method stub
- t.clear();
- t.setTransformationType(Transformation.TYPE_MATRIX);
- final float offset = calculateOffsetOfCenter(child);
- transformViewRoom(child, t, offset);
- return true;
- }
对于 final float offset = calculateOffsetOfCenter(child); 函数代码如下。
- //获取父控件中心点 X 的位置
- protected int getCenterOfCoverflow() {
- return ((getWidth() - getPaddingLeft() - getPaddingRight()) >> 1) + getPaddingLeft();
- }
- //获取 child 中心点 X 的位置
- protected int getCenterOfView(View view) {
- return view.getLeft() + (view.getWidth() >> 1);
- }
- //计算 child 偏离 父控件中心的 offset 值, -1 <= offset <= 1
- protected float calculateOffsetOfCenter(View view){
- final int pCenter = getCenterOfCoverflow();
- final int cCenter = getCenterOfView(view);
- float offset = (cCenter - pCenter) / (pCenter * 1.0f);
- offset = Math.min(offset, 1.0f);
- offset = Math.max(offset, -1.0f);
- return offset;
- }
transformViewRoom(child, t, offset); 根据 offset 值设置 t 的不同效果值,比如 alpha 效果, 平移效果(立体效果),旋转效果,代码如下。
- void transformViewRoom(View child, Transformation t, float race){
- Camera mCamera = new Camera();
- mCamera.save();
- final Matrix matrix = t.getMatrix();
- final int halfHeight = child.getMeasuredHeight() >> 1;
- final int halfWidth = child.getMeasuredWidth() >> 1;
- // 平移 X、Y、Z 轴已达到立体效果
- mCamera.translate(-race * 50, 0.0f , Math.abs(race) * 200);
- //也可设置旋转效果
- mCamera.getMatrix(matrix);
- //以 child 的中心点变换
- matrix.preTranslate(-halfWidth, -halfHeight);
- matrix.postTranslate(halfWidth, halfHeight);
- mCamera.restore();
- //设置 alpha 变换
- t.setAlpha(1 - Math.abs(race));
- }
而后 android4.1 更改了 ViewGroup 以及 View 的相关代码,主要是把 ViewGroup 的 drawChild 的代码移到 View 里面执行。
并在调用 getChildStaticTransformation 时,不仅对 设置 setStaticTransformationsEnabled(true) 是否为 true 判断,而且加入了对 android 系统本身硬件加速是否开启 作以判断,即硬件加速开启时 getChildStaticTransformation 是没有效果的。
更要命的是 android4.1 后续版本默认开启硬件加速,即 android:targetSdkVersion 大于 15 , android:hardwareAccelerated 默认为 true。
针对此问题,个人有三个解决方案。
一、在 AndroidManifest.xml 的 application 标签里添加 android:hardwareAccelerated="false" 就 ok 了,但这样不开启硬件加速此效果在效果界面比较复杂会导致界面非常卡,不流畅。
二、利用代码中间一个小小的漏洞,通过利用 android Animation 动画绕过了此项判断。
重载 Gallery 类或 ViewGroup 类 的 drawChild 方法,具体代码如下。
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- // TODO Auto-generated method stub
- if(android.os.Build.VERSION.SDK_INT > 15){
- if(child.getAnimation() == null){
- TransformationAnimation ta = new TransformationAnimation(child);
- child.setAnimation(ta);
- }
- }
- return super.drawChild(canvas, child, drawingTime);
- }
对 android4.1 以上的版本做特殊处理,TransformationAnimation 类为 Gallery 类或 ViewGroup 类自定义的内部类,代码如下。
- final class TransformationAnimation extends Animation{
- View v;
- TransformationAnimation(View _v){
- v = _v;
- }
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- // TODO Auto-generated method stub
- super.applyTransformation(interpolatedTime, t);
- getChildStaticTransformation(v, t);
- }
- }
此处是通过代码中判断是否有动画加以修饰,对于以上代码用起来简单,而且看起来貌似没啥问题,运行正常,只是不知道为什么其实后台一直在调用drawChild 方法,导致CPU 占有率很高,所以本人建议一般用下面方案比较靠谱。如果有人可以提出修正的方法,本人表示欢迎。
三、通过在画 child 之前对 Canvas 进行必要的变换,如上 Transformation 变换类似,重载 drawChild 方法,相关代码如下。(注:此对Android4.1及以上版本,对于4.1以下版本还是用老方法比较OK的)
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- // TODO Auto-generated method stub
- boolean ret;
- //Android SDK 4.1
- if(android.os.Build.VERSION.SDK_INT > 15){
- final float offset = calculateOffsetOfCenter(child);
- getTransformationMatrix(child, offset);
- child.setAlpha(1 - Math.abs(offset));
- final int saveCount = canvas.save();
- canvas.concat(mMatrix);
- ret = super.drawChild(canvas, child, drawingTime);
- canvas.restoreToCount(saveCount);
- }else{
- ret = super.drawChild(canvas, child, drawingTime);
- }
- return ret;
- }
以上 getTransformationMatrix(View child, float offset) 方法如下,mCamera = new Camera() & mMatrix = new Matrix() 为全局对象。
- void getTransformationMatrix(View child, float offset) {
- final int halfWidth = child.getLeft() + (child.getMeasuredWidth() >> 1);
- final int halfHeight = child.getMeasuredHeight() >> 1;
- mCamera.save();
- mCamera.translate(-offset * 50, 0.0f , Math.abs(offset) * 200);
- mCamera.getMatrix(mMatrix);
- mCamera.restore();
- mMatrix.preTranslate(-halfWidth, -halfHeight);
- mMatrix.postTranslate(halfWidth, halfHeight);
- }
注意 final int halfWidth = child.getLeft() + (child.getMeasuredWidth() >> 1); 是 child 本身的在父控件中的中心,这个非常重要,不然会变换出错,达不到想要的效果。对于以竖向排列的 child 方法类似,只是对于 child 变换中心要加以变通。
另注:第三种方案在用时是对Android4.1以上版本做特殊处理的。也可以用不做特殊处理,看个人情况。特殊处理时还包含以下代码。
构造函数加
- if(android.os.Build.VERSION.SDK_INT <= 15){setStaticTransformationsEnabled(true);}
getChildStaticTransformation 如下代码
- @Override
- protected boolean getChildStaticTransformation(View child, Transformation t) {
- // TODO Auto-generated method stub
- if(android.os.Build.VERSION.SDK_INT > 15){
- return false;
- }else{
- t.clear();
- t.setTransformationType(Transformation.TYPE_MATRIX);
- final float offset = calculateOffsetOfCenter(child);
- transformViewRoom(child, t, offset);
- return true;
- }
- }
引申:由此可看出父控件 在画 child 之前,如果 对 Canvas 做一定的变换,界面就会有很好的视觉效果,比如 3D 效果等。