Imageview自适应显示图片(不管任何图片任何尺寸都自适应View大小显示)
主要使用了一下知识点:
Matrix:矩阵变换(缩放,平移)
ScaleGestureDetector:缩放手势检测
GestureDetector:手势检测
- ImageView 缩放功能
缩放实现重写:
//如果返回true,则会重置detector对放大比例的计算。默认为1.0
//如果返回false,则持续计算放大比例
public boolean onScale(ScaleGestureDetector detector);
实现步骤:
- 获取Image本身的缩放比例
//因为是等比缩放,所以X轴的缩放等于y轴缩放
private float getScale(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MSCALE_X];
}
- 获取当前用户操作的缩放比例的增量
float scaleFactor = detector.getScaleFactor();
scaleFactor = scaleFactor - 1.0f;
- 使用Matrix进行缩放
float now_scale = getScale();
//当前放大倍数+当次放大的比例,放大倍数不能为负数,负数的话则图片上下颠倒了
now_scale = now_scale + (scaleFactor - 1.0f);
now_scale = Math.abs(now_scale);
Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);
- ImageView 平移功能
平移重写:
public boolean onTouch(View v, MotionEvent event);
实现步骤:
- OnTouch 处理ACTION_MOVE事件,计算Move的偏移量
float dx = event.getX() - mLastX;
float dy = event.getY() - mLastY;
//计算完之后更新最后的X,Y位置
mLastX = event.getX();
mLastY = event.getY();
- 获取Img已经发生的平移量
private float getTranclateX(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MTRANS_X];
}
private float getTranclateY(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MTRANS_Y];
}
- 使用Matrix进行平移
Matrix matrix = new Matrix();
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
- 平移+缩放综合处理
上述只是单独的实现了缩放或者平移某一功能,setImageMatrix的Matrix是New的新对象会重置Scale或Translate因子。但是图片缩放和平移可能是同步进行的,所以我们需要同时处理Scale和Translate。
例如:图片目前平移量是(-150,-200),放大比例是(2.0,2.0);如果我们将图片平移到(-300,-400),那么图片的放大比例依然是(2.0,2.0)。如果我们将图片放大到(3.0,3.0),因为图片放大了,所以尺寸发生变化导致平移量也将发生变化,不在保持为(-300,-400)。具体偏移量的更新算法,可以参考Matrix 变换原理。
所以平移的时候我们需要保持缩放因子
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
缩放的时候需要更新平移量为缩放之后的平移量
//先进行缩放
Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);
//缩放成功之后,获取最新平移量,更新平移量
matrix = new Matrix();
matrix.postScale(getScale(),getScale());
matrix.postTranslate(getTranclateX(),getTranclateY());
setImageMatrix(matrix);
此时就可以对图片进行正常的缩放和平移了。
为什么我们不使用getImageMatrix来获取image的Matrix,然后在通过Matrix来进行变换?
因为矩阵变换的算法很复杂,如果操作不当,那么结果就和我们预期的有差别了。所以为了简单计算,控制变量因子,每次都重置Matrix,保证Matrix是我们想象中的模样。
- 实现图片的Fling功能
基本监听GestureDetector的onFling中的垂直方向投递速度,然后在定时器里面进行变速移动
当前实现最基本的算法。
1.投掷之后再1s中内完成垂直平移。
2.因为投掷之后是变速的先快后慢,所以打算在1s中完成50次的变速移动
3.变速算法使用垂直速度(velocityY)/5000获得单步移动量。然后50次的移动中,第一次移动50个单位,第二次移动49个单位,逐次递减,直至最后一次移动一个单位,完成Fling过程。
4.如果在移动的过程中用户点击了屏幕,此时应该停止移动。
(以上算法仅仅为最基本的模拟算法,用户可以实现自己的更完善的算法)
mGestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, final float velocityY) {
Log.i(TAG, "onFling: y0:"+e1.getRawY()+",y1:"+e2.getRawY()+",velocityY:"+velocityY);
//投掷距离过短或者速度很慢不作为Fling处理
if( Math.abs(e1.getY() - e2.getY()) < 30 || Math.abs(velocityY) < 5000)
return false;
//启动定时器20ms移动一次
try {
mTimer = new Timer();
mTimerCount = 0;
mTimer.schedule(new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
message.arg1 = (int) velocityY;
mHandler.sendMessage(message);
}
},0,20);
}
catch (Exception e){
e.printStackTrace();
}
return super.onFling(e1, e2, velocityX, velocityY);
}
});
//处理定时器
mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1: {
Log.i(TAG, "handleMessage: arg1:"+msg.arg1);
float fStep = Math.max(1,Math.abs(msg.arg1)/5000.0f);
float fOffset = fStep * (MAX_COUNT - mTimerCount);
if(msg.arg1 < 0)
fOffset = -fOffset;
++mTimerCount;
//如果滑动结束
if(scrollByY(fOffset) ||mTimerCount >= MAX_COUNT){
mTimer.cancel();
mTimer = null;
}
break;
}
}
super.handleMessage(msg);
}
};
private boolean scrollByY(float dOffset){
float dy = getTranclateY();
dy += dOffset;
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(getTranclateX(), dy);
setImageMatrix(matrix);
return false;
}
- 优化
1.图片如果已经可以完全展示在视图内,则应该不允许在缩小,而且上下居中显示
Drawable d = getDrawable();
if (null == d) return;
//计算最小的缩放比例
mMinScale = getWidth()/d.getIntrinsicWidth();
ow_scale = now_scale + (scaleFactor - 1.0f);
now_scale = Math.abs(now_scale);
now_scale = Math.max(mMinScale,now_scale);
if(now_scale < SCALE_MAX ){
Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);
//缩放之后TranslX和TranslateY会发生变化,如果缩放之后图片小于视图则居中显示
RectF rtMatrixf = getMatrixRectF();
//检查上下左右边界
float dx =Math.min(0, getTranclateX()),dy = Math.min(0,getTranclateY());
//如果图片小于视图则左右居中显示
if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
}
//制图片右边界显示在视图内,导致白边
else if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
}
//如果图片小于视图则垂直居中显示
if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
}
//控制图片下边界显示在视图内,导致白边
else if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
}
matrix = new Matrix();
matrix.postScale(getScale(),getScale());
matrix.postTranslate(dx,dy);
setImageMatrix(matrix);
}
2.图片移动的过程中,上下左右边框不应该出现在视图范围之内,造成白边
case MotionEvent.ACTION_MOVE:{
Drawable drawable = getDrawable();
if(drawable == null)
return true;
RectF rtMatrixf = getMatrixRectF();
//检查左右边界
float dx = 0;
//如果图片小于视图则左右居中显示
if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
}
else if(rtMatrixf.right - rtMatrixf.left > getWidth()){
//控制图片左边界显示在视图内,导致白边,dx只允许 < 0
dx = getTranclateX() + (event.getX() - mLastX);
dx = Math.min(0,dx);
//制图片右边界显示在视图内,导致白边
if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
}
}
//检查上下边界
float dy = 0;
//如果图片小于视图则垂直居中显示
if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
}
else if(rtMatrixf.bottom - rtMatrixf.top > getHeight()){
//控制图片上边界显示在视图内,导致白边,dy只允许 < 0
dy = getTranclateY() + (event.getY() - mLastY);
dy = Math.min(0,dy);
//控制图片下边界显示在视图内,导致白边
if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
}
}
mLastX = event.getX();
mLastY = event.getY();
Log.i(TAG, "ACTION_MOVE,dx: "+dx + ",dy:"+dy);
//计算边界,
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
break;
}
- Fling的时候,如果已经到底或者到顶部则应该结束
private boolean scrollByY(float dOffset){
boolean bEnd = false;
RectF rtMatrixf = getMatrixRectF();
//检查上下边界
float dy = getTranclateY();
dy += dOffset;
if(dy > 0) {
dy = 0;
bEnd = true;
}
if(rtMatrixf.bottom - rtMatrixf.top + dy < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
bEnd = true;
}
//计算边界,
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(getTranclateX(), dy);
setImageMatrix(matrix);
return bEnd;
}
Fling:
缩放和拖拽
最终源码:
public class ZoomImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener,
View.OnTouchListener {
private static final String TAG = ZoomImageView.class.getName();
private ScaleGestureDetector mScaleGestureDetector;
private GestureDetector mGestureDetector;
private static final float SCALE_MAX = 4.0f;
private final float[] matrixValues = new float[9];
//最终点击的位置
private float mLastX = 0,mLastY = 0;
//定时器滑动,滑动1s中,即50次
private Timer mTimer = null;
private final int MAX_COUNT = 50;
int mTimerCount = 0;
private Handler mHandler;
public ZoomImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
super.setScaleType(ScaleType.MATRIX);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mGestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, final float velocityY) {
Log.i(TAG, "onFling: y0:"+e1.getRawY()+",y1:"+e2.getRawY()+",velocityY:"+velocityY);
//投掷距离过短或者速度很慢不作为Fling处理
if( Math.abs(e1.getY() - e2.getY()) < 30 || Math.abs(velocityY) < 5000)
return false;
//只有当前显示不下才可以进行Fling
RectF rtF = getMatrixRectF();
if(rtF.bottom - rtF.top > getHeight())
{
try {
mTimer = new Timer();
mTimerCount = 0;
mTimer.schedule(new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
message.arg1 = (int) velocityY;
mHandler.sendMessage(message);
}
},0,20);
}
catch (Exception e){
e.printStackTrace();
}
}
return super.onFling(e1, e2, velocityX, velocityY);
}
});
this.setOnTouchListener(this);
mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1: {
Log.i(TAG, "handleMessage: arg1:"+msg.arg1);
float fStep = Math.max(1,Math.abs(msg.arg1)/5000.0f);
float fOffset = fStep * (MAX_COUNT - mTimerCount);
if(msg.arg1 < 0)
fOffset = -fOffset;
++mTimerCount;
//如果滑动结束
if(scrollByY(fOffset) ||mTimerCount >= MAX_COUNT){
mTimer.cancel();
mTimer = null;
}
break;
}
}
super.handleMessage(msg);
}
};
}
//返回true会重新计算getScale的返回值,默认为1
public boolean onScale(ScaleGestureDetector detector){
float now_scale = getScale();
float scaleFactor = detector.getScaleFactor();
Drawable d = getDrawable();
if( d == null)
return true;
Log.i(TAG, "Scale: "+scaleFactor + ",Scale:"+now_scale + ",TranslateX:"+getTranclateX()+",TranslateY:"+getTranclateY()+
",width:"+getWidth()+",height:"+getHeight());
//当前放大倍数+当次放大的比例,放大倍数不能为负数,负数的话则图片上下颠倒了
float fMinScale = (getWidth()*1.0f)/d.getIntrinsicWidth();
now_scale = now_scale + (scaleFactor - 1.0f);
now_scale = Math.abs(now_scale);
now_scale = Math.max(fMinScale,now_scale);
if(now_scale < SCALE_MAX ){
Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);
//缩放之后TranslX和TranslateY会发生变化,如果缩放之后图片小于视图则居中显示
RectF rtMatrixf = getMatrixRectF();
//检查上下左右边界
float dx =Math.min(0, getTranclateX()),dy = Math.min(0,getTranclateY());
//如果图片小于视图则左右居中显示
if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
}
//制图片右边界显示在视图内,导致白边
else if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
}
//如果图片小于视图则垂直居中显示
if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
}
//控制图片下边界显示在视图内,导致白边
else if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
}
matrix = new Matrix();
matrix.postScale(getScale(),getScale());
matrix.postTranslate(dx,dy);
setImageMatrix(matrix);
}
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector){
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector){
}
@Override
public boolean onTouch(View v, MotionEvent event)
{
mGestureDetector.onTouchEvent(event);
mScaleGestureDetector.onTouchEvent(event);
int nTouchCount = event.getPointerCount();
if(nTouchCount > 1)
return true;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:{
mLastX = event.getX();
mLastY = event.getY();
if(mTimer != null)
mTimer.cancel();
mTimer = null;
break;
}
case MotionEvent.ACTION_MOVE:{
Drawable drawable = getDrawable();
if(drawable == null)
return true;
RectF rtMatrixf = getMatrixRectF();
//检查左右边界
float dx = 0;
//如果图片小于视图则左右居中显示
if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
}
else if(rtMatrixf.right - rtMatrixf.left > getWidth()){
//控制图片左边界显示在视图内,导致白边,dx只允许 < 0
dx = getTranclateX() + (event.getX() - mLastX);
dx = Math.min(0,dx);
//制图片右边界显示在视图内,导致白边
if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
}
}
//检查上下边界
float dy = 0;
//如果图片小于视图则垂直居中显示
if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
}
else if(rtMatrixf.bottom - rtMatrixf.top > getHeight()){
//控制图片上边界显示在视图内,导致白边,dy只允许 < 0
dy = getTranclateY() + (event.getY() - mLastY);
dy = Math.min(0,dy);
//控制图片下边界显示在视图内,导致白边
if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
}
}
mLastX = event.getX();
mLastY = event.getY();
Log.i(TAG, "ACTION_MOVE,dx: "+dx + ",dy:"+dy);
//计算边界,
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
break;
}
case MotionEvent.ACTION_UP:{
mLastX = event.getX();
mLastY = event.getY();
}
}
return true;
}
private float getScale(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MSCALE_X];
}
private float getTranclateX(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MTRANS_X];
}
private float getTranclateY(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MTRANS_Y];
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(mTimer != null)
mTimer.cancel();
mTimer = null;
}
//获取变换之后的图片尺寸
private RectF getMatrixRectF()
{
Matrix matrix = getImageMatrix();
RectF rect = new RectF();
Drawable d = getDrawable();
if (null != d)
{
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rect);
}
return rect;
}
private boolean scrollByY(float dOffset){
boolean bEnd = false;
RectF rtMatrixf = getMatrixRectF();
//检查上下边界
float dy = getTranclateY();
dy += dOffset;
if(dy > 0) {
dy = 0;
bEnd = true;
}
if(rtMatrixf.bottom - rtMatrixf.top + dy < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
bEnd = true;
}
//计算边界,
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(getTranclateX(), dy);
setImageMatrix(matrix);
return bEnd;
}
}