尊重原创转载请注明: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;
/**
* 缩放大小比率计算所需要的 参考值,以什么为标准进行缩放。
* 就是双指移动多少的时候,又应该缩放多少
*/
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 = "https://img-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源码
谢谢观看~