oppo手机的界面设计也是很漂亮的。在很多界面中使用了3D技术塑造出了大量华丽的效果。在蝴蝶解锁中使用了两个对称的三D变幻,宛如蝴蝶翅膀上美丽的花纹。在受到用户点击后,随风缓慢上下扇动,充满浪漫的动感色彩。这里只在技术角度做一些探索。
这个效果由两个子view合成,每个各占整个屏幕的一半。左边子view以右边界为旋转中心,手指向右滑动距离转为绕Y轴施转的角度,角度为正。右边子view以左边界为旋转中心,手指向左滑动距离转为绕Y轴旋转的角度,角度为负,这样恰好与x轴方向一致。这种效果经过转为,可以转为像两扇门一样开关,也是很有意思的。为了保证两个view的对称性,我这里使用一张图片,沿中线分割为两个view的背景。
可以使用函数Bitmap.createBitmap实现.
左view的背景,LeftView.java:
package com.magcomm.lockscrenn; import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Camera; import android.graphics.Canvas; import android.graphics.Matrix; import android.view.View; class LeftView extends View { Bitmap leftbm = null; public int g_r = 0; private int scr_w = 0, scr_h = 0; public void refrashView(int gr) { g_r = gr; invalidate(); } public LeftView(Context context) { super(context); } void setBitmap(Bitmap bm) { leftbm = bm; scr_w = bm.getWidth() * 2; scr_h = bm.getHeight(); } @Override protected void onConfigurationChanged(Configuration newConfig) { // TODO Auto-generated method stub super.onConfigurationChanged(newConfig); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // TODO Auto-generated method stub super.onLayout(changed, left, top, right, bottom); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); float deg = (g_r * 1.0f / scr_w) * 180; if (deg >= 90) { canvas.drawBitmap(leftbm, rotateY((int) deg), null); } else if (deg > 0) { canvas.drawBitmap(leftbm, rotateY((int) deg), null); // lv.bringToFront(); } else { canvas.drawBitmap(leftbm, rotateY((int) 0), null); } } private int centerX = 0, centerY = 0; // 转动的总距离,跟度数比例1:1 private int deltaX = 0, deltaY = 0; // 图片宽度高度 private int bWidth, bHeight; // 摄像机 private Camera mCamera = new Camera(); private Matrix mMatrix = new Matrix(); Matrix rotateXY(int degreeX, int degreeY) { deltaX = degreeX; deltaY = degreeY; centerX = scr_w / 2; centerY = scr_h / 2; mCamera.save(); mCamera.rotateY(deltaY); mCamera.rotateX(-deltaX); mCamera.translate(0, 0, 0); mCamera.getMatrix(mMatrix); mCamera.restore(); // 以图片的中心点为旋转中心,如果不加这两句,就是以(0,0)点为旋转中心 mMatrix.preTranslate(-centerX, -centerY); mMatrix.postTranslate(centerX, centerY); // mCamera.save(); // postInvalidate(); return mMatrix; } Matrix rotateY(int degreeY) { return rotateXY(0, degreeY); } }
右View的
代码RightView.java:
package com.magcomm.lockscrenn; import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Camera; import android.graphics.Canvas; import android.graphics.Matrix; import android.view.View; class RightView extends View { Bitmap rightbm = null; public int g_r = 0; private int scr_w = 0, scr_h = 0; public void refrashView(int gr) { g_r = gr; invalidate(); } public RightView(Context context) { super(context); } void setBitmap(Bitmap bm) { rightbm = bm; scr_w = bm.getWidth() * 2; scr_h = bm.getHeight(); } @Override protected void onConfigurationChanged(Configuration newConfig) { // TODO Auto-generated method stub super.onConfigurationChanged(newConfig); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // TODO Auto-generated method stub super.onLayout(changed, left, top, right, bottom); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); float deg = (g_r * 1.0f / scr_w) * 180; if (deg <= -90) { canvas.drawBitmap(rightbm, rotateY2((int) deg), null); } else if (deg < 0) { canvas.drawBitmap(rightbm, rotateY2((int) deg), null); // rv.bringToFront(); } else { canvas.drawBitmap(rightbm, rotateY2(0), null); } } // 图片的中心点坐标 private int centerX = 0, centerY = 0; // 转动的总距离,跟度数比例1:1 private int deltaX = 0, deltaY = 0; // 图片宽度高度 private int bWidth, bHeight; // 摄像机 private Camera mCamera = new Camera(); private Matrix mMatrix = new Matrix(); Matrix rotateXY2(int degreeX, int degreeY) { deltaX = degreeX; deltaY = degreeY; centerX = scr_w / 2; centerY = scr_h / 2; mCamera.save(); mCamera.rotateY(deltaY); mCamera.rotateX(-deltaX); mCamera.translate(scr_w / 2, 0, 0); mCamera.getMatrix(mMatrix); mCamera.restore(); // 以图片的中心点为旋转中心,如果不加这两句,就是以(0,0)点为旋转中心 mMatrix.preTranslate(-centerX, -centerY); mMatrix.postTranslate(centerX, centerY); // mCamera.save(); // postInvalidate(); return mMatrix; } Matrix rotateY2(int degreeY) { return rotateXY2(0, degreeY); } }
下面代码是两个view的动画效果,手指放开时出现缓慢的上下的有衰减的扇动效果,宛如蝴蝶颤抖的翅膀。本来自己写了一个弹簧振子的模型,通过滑动距离转化为弹簧的能量波动,进而转化为扇动的初速度,加上弹簧的阻尼运动的衰减系数,使其做带负加速度的圆周运动。但运行效果不太满意,大概需要使用JNI来实现才能满足吧,后来采取了系统自带的动画实现。
int ani_index = 0; private void applyRotation(float start, float end, final View v) { // 计算中心点 final float centerX = scr_w / 2.0f; final float centerY = scr_h / 2.0f; final Rotate3dAnimation rotation = new Rotate3dAnimation(start, end, 0, 0, 0, 0, 0, 0, centerX, centerY, true); rotation.setDuration(500); // rotation.setFillAfter(true); rotation.setRepeatCount(1); rotation.setRepeatMode(Animation.REVERSE); // rotation.setFillAfter(true); // rotation.setDetachWallpaper(true); rotation.setInterpolator(new AnticipateInterpolator()); // 设置监听 rotation.setAnimationListener(new Animation.AnimationListener() { public void onAnimationStart(Animation animation) { } // 动画结束 public void onAnimationEnd(Animation animation) { // tv.post(new SwapViews()); startAni(ani_index++); } public void onAnimationRepeat(Animation animation) { } }); v.startAnimation(rotation); } /** * * AccelerateDecelerateInterpolator 在动画开始与介绍的地方速率改变比较慢,在中间的时候加速 * * AccelerateInterpolator 在动画开始的地方速率改变比较慢,然后开始加速 * * AnticipateInterpolator 开始的时候向后然后向前甩 * * AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值 * * BounceInterpolator 动画结束的时候弹起 * * CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线 * * DecelerateInterpolator 在动画开始的地方快然后慢 * * LinearInterpolator 以常量速率改变 * * OvershootInterpolator 向前甩一定值后再回到原来位置 * * @param start * @param end * @param v * @param time */ private void applyRotation2(float start, float end, final View v, long time) { // 计算中心点 final float centerX = scr_w / 2.0f; final float centerY = scr_h / 2.0f; final Rotate3dAnimation rotation = new Rotate3dAnimation(start, end, 0, 0, 0, 0, 0, 0, centerX, centerY, true); rotation.setDuration(time); // rotation.setFillAfter(true); rotation.setRepeatCount(0); rotation.setRepeatMode(Animation.REVERSE); // rotation.setFillAfter(true); // rotation.setDetachWallpaper(true); rotation.setInterpolator(new LinearInterpolator()); // 设置监听 rotation.setAnimationListener(new Animation.AnimationListener() { public void onAnimationStart(Animation animation) { } // 动画结束 public void onAnimationEnd(Animation animation) { // tv.post(new SwapViews()); startAni(ani_index++); } public void onAnimationRepeat(Animation animation) { } }); v.startAnimation(rotation); } void startAni(int index) { // final int ani_deg1[][] = {{60, 0}, {40, 0}, {20, 0}}; // final int ani_deg1[][] = {{0, 60}, {0, 40}, {0, 20}}; final int ani_deg1[][] = { { 0, 80 }, { 80, 0 }, { 0, 60 }, { 60, 0 }, { 0, 40 }, { 40, 0 } }; // final int ani_deg2[][] = {{-60, 0}, {-40, 0}, {-20, 0}}; final int ani_deg2[][] = { { 0, -80 }, { -80, 0 }, { 0, -60 }, { -60, 0 }, { 0, -40 }, { -40, 0 } }; final int time[] = { 500, 500, 400, 400, 200, 200 }; if (index > 5) { ani_index = 0; } else { if (u_x <= (scr_w / 2)) { applyRotation2(ani_deg1[index][0], ani_deg1[index][1], lv, time[index]); } else { applyRotation2(ani_deg2[index][0], ani_deg2[index][1], rv, time[index]); } } }
在oppo的解锁效果中,view的背面加入了对窗口buffer的特殊处理。使我们能够看到一个打开新窗口的效果,如下图:
需要添加如下函数。这里调用了系统的隐藏函数Surface.screenshot,必须使用系统签名。加入系统app中才能使用。当然,也许你也可以使用反射实现。
publicBitmap getScreenBuffer()
{
DisplaymDisplay;
DisplayMetricsmDisplayMetrics;
MatrixmDisplayMatrix = new Matrix();
WindowManagermWindowManager = (WindowManager) mContext
.getSystemService(Context.WINDOW_SERVICE);
mDisplay= mWindowManager.getDefaultDisplay();
mDisplayMetrics= new DisplayMetrics();
mDisplay.getRealMetrics(mDisplayMetrics);
mDisplay.getRealMetrics(mDisplayMetrics);
float[]dims = { mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels};
mlock= Surface.screenshot((int) dims[0], (int) dims[1]);
returnmlock;
}