一、Matrix详解
在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类。Android中的Matrix是一个3 x 3的矩阵,其内容如下:
Matrix的对图像的处理可分为四类基本变换:
Translate 平移变换
Rotate 旋转变换
Scale 缩放变换
Skew 错切变换
从字面上理解,矩阵中的MSCALE用于处理缩放变换,MSKEW用于处理错切变换,MTRANS用于处理平移变换,MPERSP用于处理透视变换。实际中当然不能完全按照字面上的说法去理解Matrix。同时,在Android的文档中,未见到用Matrix进行透视变换的相关说明,所以本文也不讨论这方面的问题。
针对每种变换,Android提供了pre、set和post三种操作方式。其中
set用于设置Matrix中的值。
pre是先乘,因为矩阵的乘法不满足交换律,因此先乘、后乘必须要严格区分。先乘相当于矩阵运算中的右乘。
post是后乘,因为矩阵的乘法不满足交换律,因此先乘、后乘必须要严格区分。后乘相当于矩阵运算中的左乘。
除平移变换(Translate)外,旋转变换(Rotate)、缩放变换(Scale)和错切变换(Skew)都可以围绕一个中心点来进行,如果不指定,在默认情况下是围绕(0, 0)来进行相应的变换的。
二、如何获取ImageView中图片的四个坐标
要获取图片的四角坐标,我们需要结合image的Matrix和边界Rect来处理
// 获取image边界矩形
Rect rect = imageView.getDrawable().getBounds();
// 获取图片的Matrix
mMatrix.set(imageView.getImageMatrix());
// 将Matrix中9个参数赋值到values
float[] values = new float[9];
mMatrix.getValues(values);
之后我们可以根据上述信息来获取四角坐标
//计算图片四角坐标
float leftTopX = values[Matrix.MTRANS_X];
float leftTopY = values[Matrix.MTRANS_Y];
float leftBottomX = values[Matrix.MTRANS_X];
float leftBottomY = values[Matrix.MTRANS_Y] + rect.height() * values[Matrix.MSCALE_Y];
float rightTopX = values[Matrix.MTRANS_X] + rect.width() * values[Matrix.MSCALE_X];
float rightTopY = values[Matrix.MTRANS_Y];
float rightBottomX = values[Matrix.MTRANS_X] + rect.width() * values[Matrix.MSCALE_X];
float rightBottomY = values[Matrix.MTRANS_Y] + rect.height() * values[Matrix.MSCALE_Y];
三、详细实现代码
1 布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:orientation="vertical">
<com.jyy.lzn.supportdesigntest.widget.JImageView
android:id="@+id/news_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:scaleType="center"/>
</RelativeLayout>
2 图片显示组件
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.NavUtils;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.jyy.lzn.supportdesigntest.R;
import com.jyy.lzn.supportdesigntest.news.bean.Node;
import com.jyy.lzn.supportdesigntest.util.DisplayUtil;
import com.squareup.picasso.Picasso;
/**
* Created by HP on 2016/9/22.
*/
public class NewsImageViewerFragment extends Fragment {
public static final String TAG = "NewsImageViewerFragment";
private ViewPager mViewPager;
private Node mNode;
private ImageView imageView;
// 两手指间距离
private float mFingerDistance = 0;
// 当前触摸模式
private int mOperateMode = -1;
private static final int MOVE = 0;
private static final int ZOOM = 1;
// 当前位置
private PointF mCurrentPointF = new PointF();
// 两手指间中间坐标点
private PointF mMidPointF = new PointF();
private Matrix mMatrix = new Matrix();
private Matrix mCurrentMatrix = new Matrix();
// 最大放大倍数
private static final float MAX_SCALE = 3f;
private float MIN_SCALE = 1f;
private boolean mIsReachMinScale = true;
private boolean mIsReachMaxScale = false;
// 手指间最小间隔距离
private static final float MIN_FINGER_DISTANCE = 10f;
// 图片默认宽高
private int requestedWidth;
private int requestedHeight;
private GestureDetector mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener());
public static Fragment newInstance(Node node) {
NewsImageViewerFragment newsImageViewerFragment = new NewsImageViewerFragment();
Bundle bundle = new Bundle();
bundle.putSerializable(TAG, node);
newsImageViewerFragment.setArguments(bundle);
return newsImageViewerFragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNode = (Node) getArguments().getSerializable(TAG);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_news_image_viewer, container, false);
mViewPager = ((ViewPager) getActivity().findViewById(R.id.news_image_view_pager));
mGestureDetector.setOnDoubleTapListener(mOnDoubleTapListener);
// 加载图片
imageView = (ImageView) view.findViewById(R.id.news_image_view);
imageView.setOnTouchListener(onTouchListener);
// 重置ImageView尺寸
requestedWidth = DisplayUtil.windowWidth(getContext());
requestedHeight = (int) (requestedWidth / mNode.getAspectRatio());
Picasso.with(getActivity()).load(mNode.getValue()).config(Bitmap.Config.ARGB_8888).resize(requestedWidth, requestedHeight).placeholder(R.drawable.imageview_default_bg).tag(TAG).into(imageView);
return view;
}
private View.OnTouchListener onTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
imageView.setScaleType(ImageView.ScaleType.MATRIX);
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
switch (event.getAction() & MotionEvent.ACTION_MASK) {
// 单点触摸动作
case MotionEvent.ACTION_DOWN:
mOperateMode = MOVE;
// 记录当前位置坐标
mCurrentPointF.set(event.getX(), event.getY());
mCurrentMatrix.set(imageView.getImageMatrix());
break;
// 触摸点移动动作
case MotionEvent.ACTION_MOVE:
mViewPager.requestDisallowInterceptTouchEvent(true);
if (mOperateMode == MOVE) {
drag(event);
} else if (mOperateMode == ZOOM) {
float endFingerDistance = fingerDistance(event);
if (endFingerDistance > MIN_FINGER_DISTANCE) {
float scale = endFingerDistance / mFingerDistance;
zoom(scale);
}
}
break;
// 多点触摸动作
case MotionEvent.ACTION_POINTER_DOWN:
mOperateMode = ZOOM;
// 记录当前位置坐标
mFingerDistance = fingerDistance(event);
if (mFingerDistance > MIN_FINGER_DISTANCE) {
mCurrentPointF.set(event.getX(), event.getY());
mCurrentMatrix.set(imageView.getImageMatrix());
midFingerDistance(event);
} else {
mOperateMode = MOVE;
}
break;
// 单点触摸离开动作
case MotionEvent.ACTION_UP:
mOperateMode = -1;
break;
// 多点离开动作
case MotionEvent.ACTION_POINTER_UP:
mOperateMode = -1;
adjustPosition();
break;
}
return true;
}
};
/**
* 调整图片位置
*/
private void adjustPosition() {
if (mIsReachMinScale) {
imageView.setScaleType(ImageView.ScaleType.CENTER);
}
int screenWidth = DisplayUtil.windowWidth(getContext());
Rect rect = imageView.getDrawable().getBounds();
mMatrix.set(imageView.getImageMatrix());
float[] values = new float[9];
mMatrix.getValues(values);
float leftTopX = values[Matrix.MTRANS_X];
float rightTopX = values[Matrix.MTRANS_X] + rect.width() * values[Matrix.MSCALE_X];
if (leftTopX > 0) {
values[Matrix.MTRANS_X] = 0f;
} else if (rightTopX < screenWidth) {
values[Matrix.MTRANS_X] = screenWidth - (rect.width() * values[Matrix.MSCALE_X]);
}
// 调整上下边界
float dy = DisplayUtil.windowHeight(getContext()) - DisplayUtil.getSystemStatueBarHeight(getContext()) - rect.height() * values[Matrix.MSCALE_Y];
values[Matrix.MTRANS_Y] = dy / 2;
// 更新图片矩阵
mMatrix.setValues(values);
imageView.setImageMatrix(mMatrix);
}
/**
* 处理图片拖动
*
* @param event
*/
private void drag(MotionEvent event) {
if (mIsReachMinScale) {
mViewPager.requestDisallowInterceptTouchEvent(false);
return;
}
boolean isFullScreen;
int screenWidth = DisplayUtil.windowWidth(getContext());
int screenHeight = DisplayUtil.windowHeight(getContext());
float transX = event.getX() - mCurrentPointF.x;
float transY = event.getY() - mCurrentPointF.y;
Rect rect = imageView.getDrawable().getBounds();
float[] intiValues = new float[9];
mCurrentMatrix.getValues(intiValues);
float height = rect.height() * intiValues[Matrix.MSCALE_Y];
// 图片没有充满屏幕 所以y轴禁止拖动
if (height < screenHeight) {
isFullScreen = false;
transY = 0;
} else {
isFullScreen = true;
}
mMatrix.set(mCurrentMatrix);
mMatrix.postTranslate(transX, transY);
float[] values = new float[9];
mMatrix.getValues(values);
//计算图片四角坐标
float leftTopX = values[Matrix.MTRANS_X];
float leftTopY = values[Matrix.MTRANS_Y];
float leftBottomX = values[Matrix.MTRANS_X];
float leftBottomY = values[Matrix.MTRANS_Y] + rect.height() * values[Matrix.MSCALE_Y];
float rightTopX = values[Matrix.MTRANS_X] + rect.width() * values[Matrix.MSCALE_X];
float rightTopY = values[Matrix.MTRANS_Y];
float rightBottomX = values[Matrix.MTRANS_X] + rect.width() * values[Matrix.MSCALE_X];
float rightBottomY = values[Matrix.MTRANS_Y] + rect.height() * values[Matrix.MSCALE_Y];
if (isFullScreen) { // 图片充满屏幕处理
// 左上角越界处理
if (leftTopX > 0 && leftTopY > 0) {
values[Matrix.MTRANS_X] = 0f;
values[Matrix.MTRANS_Y] = 0f;
mViewPager.requestDisallowInterceptTouchEvent(false);
} else if (leftTopX > 0 && leftTopY < 0) {
values[Matrix.MTRANS_X] = 0f;
mViewPager.requestDisallowInterceptTouchEvent(false);
} else if (leftTopX < 0 && leftTopY > 0) {
values[Matrix.MTRANS_Y] = 0f;
}
// 左下角越界处理
if (leftBottomX > 0 && leftBottomY < screenHeight) {
values[Matrix.MTRANS_X] = 0f;
values[Matrix.MTRANS_Y] = screenHeight - (rect.height() * values[Matrix.MSCALE_Y]);
mViewPager.requestDisallowInterceptTouchEvent(false);
} else if (leftBottomX > 0 && leftBottomY > screenHeight) {
values[Matrix.MTRANS_X] = 0f;
mViewPager.requestDisallowInterceptTouchEvent(false);
} else if (leftBottomX < 0 && leftBottomX > screenHeight) {
values[Matrix.MTRANS_Y] = screenHeight - (rect.height() * values[Matrix.MSCALE_Y]);
}
// 右上角越界处理
if (rightTopX < screenWidth && rightTopY > 0) {
values[Matrix.MTRANS_X] = screenWidth - (rect.width() * values[Matrix.MSCALE_X]);
values[Matrix.MTRANS_Y] = 0;
mViewPager.requestDisallowInterceptTouchEvent(false);
} else if (rightTopX < screenWidth && rightTopY < 0) {
values[Matrix.MTRANS_X] = screenWidth - (rect.width() * values[Matrix.MSCALE_X]);
mViewPager.requestDisallowInterceptTouchEvent(false);
} else if (rightTopX > screenWidth && rightTopY > 0) {
values[Matrix.MTRANS_Y] = 0;
}
// 右下角越界处理
if (rightBottomX < screenWidth && rightBottomY < screenHeight) {
values[Matrix.MTRANS_X] = screenWidth - (rect.width() * values[Matrix.MSCALE_X]);
values[Matrix.MTRANS_Y] = screenHeight - (rect.height() * values[Matrix.MSCALE_Y]);
mViewPager.requestDisallowInterceptTouchEvent(false);
} else if (rightBottomX < screenWidth && rightBottomY > screenHeight) {
values[Matrix.MTRANS_X] = screenWidth - (rect.width() * values[Matrix.MSCALE_X]);
mViewPager.requestDisallowInterceptTouchEvent(false);
} else if (rightBottomX > screenWidth && rightBottomY < screenHeight) {
values[Matrix.MTRANS_Y] = screenHeight - (rect.height() * values[Matrix.MSCALE_Y]);
}
} else { // 图片没有充满屏幕处理
if (leftTopX > 0) {
values[Matrix.MTRANS_X] = 0f;
mViewPager.requestDisallowInterceptTouchEvent(false);
} else if (rightBottomX < screenWidth) {
values[Matrix.MTRANS_X] = screenWidth - (rect.width() * values[Matrix.MSCALE_X]);
mViewPager.requestDisallowInterceptTouchEvent(false);
}
}
mMatrix.setValues(values);
imageView.setImageMatrix(mMatrix);
}
/**
* 处理图片缩放
*
* @param scale
*/
private void zoom(float scale) {
if (mIsReachMaxScale && scale > 1 || mIsReachMinScale && scale < 1) {
return;
}
mMatrix.set(mCurrentMatrix);
mMatrix.postScale(scale, scale, DisplayUtil.windowWidth(getContext()) / 2, DisplayUtil.windowHeight(getContext()) / 2 - DisplayUtil.getSystemStatueBarHeight(getContext()) / 2);
float[] values = new float[9];
mMatrix.getValues(values);
if (values[Matrix.MSCALE_X] >= MAX_SCALE) {
mIsReachMinScale = false;
mIsReachMaxScale = true;
values[Matrix.MSCALE_X] = MAX_SCALE;
values[Matrix.MSCALE_Y] = MAX_SCALE;
} else if (values[Matrix.MSCALE_X] <= MIN_SCALE) {
mIsReachMinScale = true;
mIsReachMaxScale = false;
values[Matrix.MSCALE_X] = MIN_SCALE;
values[Matrix.MSCALE_Y] = MIN_SCALE;
} else {
mIsReachMinScale = false;
mIsReachMaxScale = false;
}
mMatrix.setValues(values);
imageView.setImageMatrix(mMatrix);
}
/**
* 计算两指之间距离
*
* @param event
* @return
*/
private float fingerDistance(MotionEvent event) {
try {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getX(0);
return (float) Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return 0;
}
/**
* 计算两指间中间点
*
* @param event
*/
private void midFingerDistance(MotionEvent event) {
try {
float dx = event.getX(1) + event.getX(0);
float dy = event.getY(1) + event.getX(0);
mMidPointF.set(dx / 2, dy / 2);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
private GestureDetector.OnDoubleTapListener mOnDoubleTapListener = new GestureDetector.OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
if (NavUtils.getParentActivityName(getActivity()) != null) {
NavUtils.navigateUpFromSameTask(getActivity());
}
return true;
}
@Override
public boolean onDoubleTap(MotionEvent motionEvent) {
mMatrix.set(imageView.getImageMatrix());
if (mIsReachMinScale) {
mMatrix.postScale(MAX_SCALE, MAX_SCALE, DisplayUtil.windowWidth(getContext()) / 2, DisplayUtil.windowHeight(getContext()) / 2 - DisplayUtil.getSystemStatueBarHeight(getContext()) / 2);
imageView.setImageMatrix(mMatrix);
mIsReachMinScale = false;
mIsReachMaxScale = true;
float[] values = new float[9];
mMatrix.getValues(values);
Log.d(TAG, "y: " + values[Matrix.MTRANS_Y]);
} else {
mMatrix.postScale(MIN_SCALE, MIN_SCALE, DisplayUtil.windowWidth(getContext()) / 2, DisplayUtil.windowHeight(getContext()) / 2);
imageView.setImageMatrix(mMatrix);
imageView.setScaleType(ImageView.ScaleType.CENTER);
mIsReachMinScale = true;
mIsReachMaxScale = false;
}
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent motionEvent) {
return true;
}
};
}
注意点:由于ImageView设置的宽高是match_parent,所以在处理出示图片居中问题时,可以先将scaleType设置为center,之后再进行图片处理的时候将scaleType动态的设置为matrix
四、参考资料
1 http://blog.csdn.net/flash129/article/details/8234599
2 http://www.cnblogs.com/linjzong/p/4211661.html