Android View 无处不在的图片查看器~ 不服就看

尊重原创转载请注明:http://blog.csdn.net/bfbx5173


C.C.    镇楼


从标题来看也许是一个老掉牙的功能,但是既然你点进来的就一定不会后悔。废话不多说,先看效果:



大家看完这个效果,有没有感觉手感更好一些,并且从开始查看到退出查看有没有一种过渡自然的感觉。

如果你有兴趣继续阅读,那我们开始吧~

分析:

Android View 无处不在的图片查看器~ 不服就看_第1张图片

1、以上就是整体的过渡流程,于此同时背景附带一个慢慢变黑,慢慢还原(也不就个透明度渐变的问题而已)。

而原本的View的位置,可以通过view 的api 【getLocationOnScreen】得到,而预计到达的位置不就是屏幕的中间嘛~

2、对图片的操作: 其实还是让图片回应用户手指的各种抚摸,一个手指的时候要怎么样,两个手指的时候要怎么样。

当然在这里一个手指的时候实现拖拽,两个手指的时候实现缩放。


那么整体的流程都理清了,实现的策略心中也有数了。那么先从第二点开始:

Android View 无处不在的图片查看器~ 不服就看_第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);
	}
}

之后我们来看下PictureView是怎么处理这些抚摸事件的:

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 };
	}
}

从PictureLayout 跟进到  PictureView 调用的方法是  dealTouchEvent ,好我们来看看:

在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);
		}
	}

感觉注释很全了,就不再做解释。 这样 继承了BaseActivity的活动只要简单的这样调用即可。

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);
			}
		});

看到没有,就只用传入一个图片的url地址和被点击的view本身就可以了!!

对了还用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;
	}

这里呢,要注意 onBackPressed被加上了final 关键字修饰,子类就不能继承了。至于为什么要这样写, 是为了达到一种代码执行顺序(最先判断是否是 看图的状态,是先hidden,之后才是子类activity的onBackPressed。


最后我们再次欣赏下效果吧~



戳我查看PictureLayout源码


谢谢观看~



你可能感兴趣的:(android,UI,动画,view,自定义)