尊重原创转载请注明:http://blog.csdn.net/bfbx5173
C.C. 镇楼
从标题来看也许是一个老掉牙的功能,但是既然你点进来的就一定不会后悔。废话不多说,先看效果:
大家看完这个效果,有没有感觉手感更好一些,并且从开始查看到退出查看有没有一种过渡自然的感觉。
如果你有兴趣继续阅读,那我们开始吧~
分析:
1、以上就是整体的过渡流程,于此同时背景附带一个慢慢变黑,慢慢还原(也不就个透明度渐变的问题而已)。
而原本的View的位置,可以通过view 的api 【getLocationOnScreen】得到,而预计到达的位置不就是屏幕的中间嘛~
2、对图片的操作: 其实还是让图片回应用户手指的各种抚摸,一个手指的时候要怎么样,两个手指的时候要怎么样。
当然在这里一个手指的时候实现拖拽,两个手指的时候实现缩放。
那么整体的流程都理清了,实现的策略心中也有数了。那么先从第二点开始:
如图所示,仍然使用老策略。 如果你问我什么是老策略 请看 Android View 给触摸操作提供视觉提示 细致才是王道
在这篇触摸视觉提示的文章中, 使用到了 ShinyLayout 和 ShinyView 。 这里是 PictureLayout 和 PictureView。
用 PictureLayout 接收各种触摸事件, 把事件传递给PictureView处理。 So~
public class PictureLayout extends FrameLayout { private PictureView viewPicture; public PictureLayout(Context context) { this(context, null); } public PictureLayout(Context context, AttributeSet attrs) { super(context, attrs); setBackgroundColor(0xff000000); viewPicture = new PictureView(context); viewPicture.setScaleType(ScaleType.CENTER); addView(viewPicture); } /** 为了接受各种抚摸 */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return true; } return super.onTouchEvent(event); } /** 将接受到的抚摸交给 viewPicture处理 */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { viewPicture.dealTouchEvent(ev); return super.dispatchTouchEvent(ev); } }
public class PictureView extends ImageView { /** 用于记录位图的大小信息 */ private int imageWidth, imageHeight; /** */ private int screenWidth, screenHeight; /** * 缩放大小比率计算所需要的 参考值,以什么为标准进行缩放。<br> * 就是双指移动多少的时候,又应该缩放多少 */ private int scaleRatioReferWidth; /** 缩放的极限最小值, 当手指抬起将还原到正常最小值,表达一种弹性 */ private static final float MIN_MIN = 0.5f; /** 缩放的正常最小值*/ private static final float MIN_NORMAL = 1f; /** 缩放的极限最大值, 当手指抬起将还原到正常最大值,表达一种弹性 */ private static final float MAX_MAX = 7f; /** 缩放的正常最大值*/ private static final float MAX_NORMAL = 6f; private float downX, downY; /** 双指落下时候的距离 */ private double downDist; /** 在下次抚摸之前,记录那个时候的前一刻的状态 */ private float oldScaleX, oldScaleY; /** 同上, 所以你会在ACTION_DOWN中看到赋值等相关操作*/ private float oldTranslationX, oldTranslationY; /** 两指同时按下时候的中心点, 由于缩放同时的时候进行平移的细致操作~*/ private float fingersCenterX, fingersCenterY; /** 是否为缩放模式, 进入缩放模式后将屏蔽单指操作 */ private boolean scaleMode; /** 动画进行中的标示 */ private boolean anim; public PictureView(Context context) { this(context, null); } public PictureView(Context context, AttributeSet attrs) { super(context, attrs); screenWidth = BaseUtil.getScreenWidth(); screenHeight = BaseUtil.getScreenHeight(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); LogUtil.e("PictureView onSizeChanged width:" + w + ",height:" + h); scaleRatioReferWidth = w / 10; } @Override public void setImageBitmap(Bitmap bm) { super.setImageBitmap(bm); LogUtil.e("PictureView setImageBitmap width:" + bm.getWidth() + ",height:" + bm.getHeight()); imageWidth = bm.getWidth(); imageHeight = bm.getHeight(); } /** 处理PictureLayout 传递过来的 各种抚摸 */ public void dealTouchEvent(MotionEvent event) { if (!anim) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 记录各种状态 downX = event.getRawX(); downY = event.getRawY(); oldTranslationX = getTranslationX(); oldTranslationY = getTranslationY(); oldScaleX = getScaleX(); oldScaleY = getScaleY(); case MotionEvent.ACTION_MOVE: // 双指操作 if (event.getPointerCount() == 2) { scaleMode = true; //因为模式的转化需要重新记录各种状态 if (downDist == 0) { downDist = calcDistanceBetweenFingers(event); downX = event.getRawX(); downY = event.getRawY(); oldTranslationX = getTranslationX(); oldTranslationY = getTranslationY(); oldScaleX = getScaleX(); oldScaleY = getScaleY(); } //梅西的两只腿动了,动了,开始动了。 球进了~ double moveDist = calcDistanceBetweenFingers(event); if (moveDist != downDist) { LogUtil.e("moveDist:" +moveDist +"downDist:" + downDist); // 两指的距离发生了变化, 得到应该缩放的比率 float changedScaleSize = (float) ((moveDist - downDist) / scaleRatioReferWidth); scalePicture(changedScaleSize); } // 双指按下时候中心点 if (fingersCenterX == 0 && fingersCenterX == 0) { float[] downResult = calcCenterPointBetweenFingers(event); fingersCenterX = downResult[0]; fingersCenterY = downResult[1]; } // [双指的中心的移动作为]单指平移,不扯 float[] moveResult = calcCenterPointBetweenFingers(event); if (fingersCenterX != moveResult[0] && fingersCenterY != moveResult[1]) { final float moveX = moveResult[0] - fingersCenterX; final float moveY = moveResult[1] - fingersCenterY; translatePicture(moveX, moveY); } } else if (event.getPointerCount() == 1 && !scaleMode) { // 单指平移 Log.e("Picture", "mvoeX:" + event.getRawX() + ",mvoeX:" + event.getRawY()); final float moveX = event.getRawX() - downX; final float moveY = event.getRawY() - downY; translatePicture(moveX, moveY); } break; case MotionEvent.ACTION_POINTER_UP: break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // 手指抬起后,对于极限值的还原~ 表达一种弹性 recoverPicture(); break; } } } /** 在手指按压后,根据原先的状态 进行一种平移变化 */ private void translatePicture(float x, float y) { setTranslationX(oldTranslationX + x / 2); setTranslationY(oldTranslationY + y / 2); } /** 在手指按压后,根据原先的状态 进行一种缩放变化 */ private void scalePicture(float changedScaleSize) { if (oldScaleX + changedScaleSize > MIN_MIN && oldScaleX + changedScaleSize < MAX_MAX) { setScaleX(oldScaleX + changedScaleSize); } if (oldScaleY + changedScaleSize > MIN_MIN && oldScaleY + changedScaleSize < MAX_MAX) { setScaleY(oldScaleY + changedScaleSize); } } /** 还原各种状态 */ private void recoverPicture() { downX = 0; downY = 0; downDist = 0; scaleMode = false; fingersCenterX = 0; fingersCenterY = 0; // 弹性表达的开始, 图片状态“从极限还原到正常” // 比如将图片拉倒太左边, 将它稍稍向右移动,放大的太大,将它稍稍缩小。 // 美观的同时,将各种BUG防患于未然,比如OOM~ final float scaleX = getScaleX(); final float scaleY = getScaleY(); final float translationX = getTranslationX(); final float translationY = getTranslationY(); final float transOffsetX = (imageWidth * scaleX - screenWidth) / 2; // X轴的处理 ObjectAnimator anim1 = null; if (transOffsetX < 0) { anim1 = ObjectAnimator.ofFloat(this, "translationX", translationX, 0); } else if (translationX > transOffsetX) { anim1 = ObjectAnimator.ofFloat(this, "translationX", translationX, transOffsetX); } else if (translationX < -transOffsetX) { anim1 = ObjectAnimator.ofFloat(this, "translationX", translationX, -transOffsetX); } // Y轴的处理 final float transOffsetY = (imageHeight * scaleY - screenHeight) / 2; ObjectAnimator anim2 = null; if (transOffsetY < 0) { anim2 = ObjectAnimator.ofFloat(this, "translationY", translationY, 0); } else if (translationY > transOffsetY) { anim2 = ObjectAnimator.ofFloat(this, "translationY", translationY, transOffsetY); } else if (translationY < -transOffsetY) { anim2 = ObjectAnimator.ofFloat(this, "translationY", translationY, -transOffsetY); } // 缩放的处理 过大 ObjectAnimator anim3 = null; if (scaleX < MIN_NORMAL) { anim3 = ObjectAnimator.ofFloat(this, "scaleX", scaleX, MIN_NORMAL); } else if (scaleX > MAX_NORMAL) { anim3 = ObjectAnimator.ofFloat(this, "scaleX", scaleX, MAX_NORMAL); } // 缩放的处理 过小 ObjectAnimator anim4 = null; if (scaleY < MIN_NORMAL) { anim4 = ObjectAnimator.ofFloat(this, "scaleY", scaleY, MIN_NORMAL); } else if (scaleY > MAX_NORMAL) { anim4 = ObjectAnimator.ofFloat(this, "scaleY", scaleY, MAX_NORMAL); } AnimatorSet set = new AnimatorSet(); AnimatorSet.Builder b = set.play(ObjectAnimator.ofInt(0)); if (anim1 != null) b.with(anim1); if (anim2 != null) b.with(anim2); if (anim3 != null) b.with(anim3); if (anim4 != null) b.with(anim4); set.setInterpolator(new DecelerateInterpolator()); set.setDuration(400); set.start(); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); anim = true; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); anim = false; } }); } /** 两指之间距离计算 */ private double calcDistanceBetweenFingers(MotionEvent event) { float disX = Math.abs(event.getX(0) - event.getX(1)); float disY = Math.abs(event.getY(0) - event.getY(1)); return Math.sqrt(disX * disX + disY * disY); } /** 两指之间中心点坐标计算*/ private float[] calcCenterPointBetweenFingers(MotionEvent event) { float xPoint0 = event.getX(0); float yPoint0 = event.getY(0); float xPoint1 = event.getX(1); float yPoint1 = event.getY(1); return new float[] { (xPoint0 + xPoint1) / 2, (yPoint0 + yPoint1) / 2 }; } }
在ACTION_DOWN中记录的初始的状态。在ACTION_MOVE中根据 event.getPointerCount() 来判断是单指操作还是双指操作,重重逻辑之后都会调用两个方法,分别是 translatePicture 和 scalePicture。 在这两个方法之中会根据移动是计算出来的一些数值调用View的原本API 改变视图的属性而达到期望的显示效果。最后在ACTION_UP中,直接调用recoverPicture来还原一些东西来进行善后操作。
那么之后再把过渡的任务给完成了,在PictureLayout中:
public class PictureLayout extends FrameLayout { // 省略若干代码 private PictureView viewPicture; private int originalX, originalY, centerX, centerY; private boolean finishAnim; /** 显示图片 */ public void showPicture(String url, View original) { if (TextUtils.isEmpty(url)) throw new IllegalArgumentException("empty img url"); // 得到原本视图的位置 int[] location = new int[2]; original.getLocationOnScreen(location); originalX = location[0]; originalY = location[1] - BaseUtil.getStatusBarHeight(); setVisibility(View.VISIBLE); // 加载图片 App.i() .getImageLoader() .get(url, ImageLoader.getImageListener(viewPicture, R.drawable.test_img_default, R.drawable.test_img_loading, R.drawable.test_img_fail)); centerX = (BaseUtil.getScreenWidth()) / 2; centerY = (BaseUtil.getScreenHeight()) / 2; LogUtil.d("centerX:" + centerX + ",centerY:" + centerY); // 将PictureView 在 原本的位置显示做好过渡到中间的准备 if (originalX != 0 || originalY != 0) { viewPicture.setTranslationX(originalX - centerX); viewPicture.setTranslationY(originalY - centerY); } // 执行各种转化动画, 有缩放, 旋转, 平移~ 将PictureView过渡到中间 ObjectAnimator anim1 = ObjectAnimator.ofFloat(viewPicture, "translationX", originalX - centerX, 0); ObjectAnimator anim2 = ObjectAnimator.ofFloat(viewPicture, "translationY", originalY - centerY, 0); ObjectAnimator anim3 = ObjectAnimator.ofFloat(viewPicture, "scaleX", 1, 2); ObjectAnimator anim4 = ObjectAnimator.ofFloat(viewPicture, "scaleY", 1, 2); ObjectAnimator anim5 = ObjectAnimator.ofFloat(viewPicture, "rotation", 0, 360); ObjectAnimator anim6 = ObjectAnimator.ofFloat(this, "alpha", 0, 1); AnimatorSet set = new AnimatorSet(); set.play(anim1).with(anim2).with(anim3).with(anim4).with(anim5).with(anim6); set.setInterpolator(new DecelerateInterpolator()); set.setDuration(600); set.start(); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); } }); } /** 隐藏图片 */ public void hiddenPictrue() { if (finishAnim) return; finishAnim = true; // 将图片过渡回去的动画 ObjectAnimator anim1 = ObjectAnimator.ofFloat(viewPicture, "translationX", 0, originalX - centerX); ObjectAnimator anim2 = ObjectAnimator.ofFloat(viewPicture, "translationY", 0, originalY - centerY); ObjectAnimator anim3 = ObjectAnimator.ofFloat(viewPicture, "scaleX", viewPicture.getScaleX(), 1); ObjectAnimator anim4 = ObjectAnimator.ofFloat(viewPicture, "scaleY", viewPicture.getScaleY(), 1); ObjectAnimator anim5 = ObjectAnimator.ofFloat(viewPicture, "rotation", 360, 0); ObjectAnimator anim6 = ObjectAnimator.ofFloat(this, "alpha", 1, 0); anim5.setInterpolator(new DecelerateInterpolator()); AnimatorSet set = new AnimatorSet(); set.play(anim1).with(anim2).with(anim3).with(anim4).with(anim5).with(anim6); set.setDuration(600); set.start(); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); setVisibility(View.GONE); finishAnim = false; } }); } }
就只有两个公开的方法showPicture 和 hiddenPicture 而已,相信看方法名大家就知道是什么意思啦。
而他们的内容就是根据最开始分析的, 得到两个点后(原视图所在起点,展示图片的所在中心点) 来上一段动画过渡。
接下来 我们就要让它如标题一样无处不在了。 还是那句话,上一篇我们已经让轨迹点无处不在了,所以
在BaseActivity中
@Override public void setContentView(View view, ViewGroup.LayoutParams params) { layContentWrapper = new ShinyLayout(this); layContentWrapper.setDispatchTouchEventListener(new ShinyLayout.DispatchTouchEventListener() { @Override public void onDispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_UP: hideSoftInputFromWindow(); break; } } }); // layContentWrapper 是包裹层~ // 这样把 PictureLayout 放入包裹层中。然后将各个页面压在身下~嘿嘿。之后隐藏自己即可 layPicture = new PictureLayout(this); layPicture.setVisibility(View.GONE); layContentWrapper.addView(layPicture, 0); layContentWrapper.addView(view, 0); if (params != null) { super.setContentView(layContentWrapper, params); } else { super.setContentView(layContentWrapper); } }
final String testImageUrl2 = "http://my.csdn.net/uploads/avatar/5/1/A/1_bfbx5173.jpg"; final ImageView imgTest2 = (ImageView) findViewById(R.id.img_test2); imgTest2.setType(ImageView.TYPE_OVAL); imgTest2.loadImage(testImageUrl2); imgTest2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { layPicture.showPicture(testImageUrl2, v); } });
对了还用hiddenPicture方法,为了支持返回键关闭,我们继续重新BsseActivity的方法
@Override public final void onBackPressed() { if (View.VISIBLE == layPicture.getVisibility()) { layPicture.hiddenPictrue(); } else if (!onChildBackPressed()) { super.onBackPressed(); } } protected boolean onChildBackPressed() { // child impl return false; }
最后我们再次欣赏下效果吧~
戳我查看PictureLayout源码
谢谢观看~