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

尊重原创转载请注明: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);
	}
}

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

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

从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 = "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);
			}
		});

看到没有,就只用传入一个图片的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,View,UI)