本文转载自Android 实现可以自由移动缩放的图片控件
那位大佬本身已经把此控件说的很明白了,有什么不懂的可以去原帖看看,我这里就不说明所有代码的意思了,就简单的贴一下全部的代码吧
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
/**
* @author dj
* @version 1.0
* @description 可缩放的图片控件
* @created on 2017/4/19.
*/
public class ScaleView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,
ScaleGestureDetector.OnScaleGestureListener
,View.OnTouchListener{
//** 表示是否只有一次加载 */
private boolean isOnce = false;
/** 初始时的缩放值 */
private float mInitScale;
/** 双击时 的缩放值 */
private float mClickScale;
/** 最大的缩放值 */
private float mMaxScale;
/** 图片缩放矩阵 */
private Matrix mMatrix;
/** 图片缩放手势 */
private ScaleGestureDetector mScaleGesture;
// ----------------------------自由移动--------------------------------
/** 可移动最短距离限制,大于这个值时就可移动 */
private int mTouchSlop;
/** 是否可以拖动 */
private boolean isCanDrag;
// ----------------------------双击放大--------------------------------
private GestureDetector mGesture;
// 是否自动缩放
private boolean isAutoScale;
public ScaleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScaleView(Context context) {
this(context, null);
}
public ScaleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 必须设置才能触发
this.setOnTouchListener(this);
mMatrix = new Matrix();
// 设置缩放模式
super.setScaleType(ScaleType.MATRIX);
mScaleGesture = new ScaleGestureDetector(context, this);
mGesture = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
// 如果正在缩放时,不能放大
if (isAutoScale) {
return true;
}
float px = e.getX();
float py = e.getY();
// 只有小于最大缩放比例才能放大
float scale = getScale();
if (scale < mClickScale) {
// mMatrix.postScale(mClickScale/scale, mClickScale/scale,
// px, py);
postDelayed(new ScaleRunnale(px, py, mClickScale), 16);
isAutoScale = true;
} else {
// mMatrix.postScale(mInitScale/scale, mInitScale/scale, px,
// py);
postDelayed(new ScaleRunnale(px, py, mInitScale), 16);
isAutoScale = true;
}
// setImageMatrix(mMatrix);
return true;
}
});
/**
* 是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,如viewpager
* 就是用这个距离来判断用户是否翻页。
*/
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
private class ScaleRunnale implements Runnable {
// 放大值
private static final float BIGGER = 1.08f;
// 缩小值
private static final float SMALLER = 0.96f;
private float x;
private float y;
private float mTargetScale;
private float mTempScale;
public ScaleRunnale(float x, float y, float mTargetScale) {
super();
this.x = x;
this.y = y;
this.mTargetScale = mTargetScale;
if (getScale() < mTargetScale) {
mTempScale = BIGGER;
} else if (getScale() > mTargetScale) {
mTempScale = SMALLER;
}
}
@Override
public void run() {
// 先进行缩放
mMatrix.postScale(mTempScale, mTempScale, x, y);
checkSideAndCenterWhenScale();
setImageMatrix(mMatrix);
float currentScale = getScale();
// 如果想放大,并且当前的缩放值小于目标值
if ((mTempScale > 1.0f && currentScale < mTargetScale)
|| (mTempScale < 1.0f && currentScale > mTargetScale)) {
// 递归执行run方法
postDelayed(this, 16);
} else {
float scale = mTargetScale / currentScale;
mMatrix.postScale(scale, scale, x, y);
checkSideAndCenterWhenScale();
setImageMatrix(mMatrix);
isAutoScale = false;
}
}
}
@Override
public void onGlobalLayout() {
// 如果还没有加载图片
if (!isOnce) {
// 获得控件的宽高
int width = getWidth();
int height = getHeight();
Drawable drawable = getDrawable();
if (drawable == null) {
return;
}
// 获得图片的宽高
int bitmapWidth = drawable.getIntrinsicWidth();
int bitmapHeight = drawable.getIntrinsicHeight();
// 设定比例值
float scale = 0.0f;
// 如果图片的宽度>控件的宽度,缩小
if (bitmapWidth > width && bitmapHeight < height) {
scale = width * 1.0f / bitmapWidth;
}
// 如果图片的高度>控件的高度,缩小
if (bitmapHeight > height && bitmapWidth < width) {
scale = height * 1.0f / bitmapHeight;
}
// 如果图片的宽高度>控件的宽高度,缩小 或者 如果图片的宽高度<控件的宽高度,放大
if ((bitmapWidth > width && bitmapHeight > height) || (bitmapWidth < width && bitmapHeight < height)) {
float f1 = width * 1.0f / bitmapWidth;
float f2 = height * 1.0f / bitmapHeight;
scale = Math.min(f1, f2);
}
// 初始化缩放值
mInitScale = scale;
mClickScale = mInitScale * 2;
mMaxScale = mInitScale * 4;
// 得到移动的距离
int dx = width / 2 - bitmapWidth / 2;
int dy = height / 2 - bitmapHeight / 2;
// 平移
mMatrix.postTranslate(dx, dy);
// 在控件的中心缩放
mMatrix.postScale(scale, scale, width / 2, height / 2);
// 设置矩阵
setImageMatrix(mMatrix);
// 关于matrix,就是个3*3的矩阵
/**
* xscale xskew xtrans yskew yscale ytrans 0 0 0
*/
isOnce = true;
}
}
/**
* 注册全局事件
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
/**
* 移除全局事件
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
/**
* 获得缩放值
*
* @return
*/
public float getScale() {
/**
* xscale xskew xtrans yskew yscale ytrans 0 0 0
*/
float[] values = new float[9];
mMatrix.getValues(values);
return values[Matrix.MSCALE_X];
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
// 如果没有图片,返回
if (getDrawable() == null) {
return true;
}
// 缩放因子,>0表示正在放大,<0表示正在缩小
float intentScale = detector.getScaleFactor();
float scale = getScale();
// 进行缩放范围的控制
// 判断,如果<最大缩放值,表示可以放大,如果》最小缩放,说明可以缩小
if ((scale < mMaxScale && intentScale > 1.0f) || (scale > mInitScale && intentScale < 1.0f)) {
// scale 变小时, intentScale变小
if (scale * intentScale < mInitScale) {
// intentScale * scale = mInitScale ;
intentScale = mInitScale / scale;
}
// scale 变大时, intentScale变大
if (scale * intentScale > mMaxScale) {
// intentScale * scale = mMaxScale ;
intentScale = mMaxScale / scale;
}
// 以控件为中心缩放
// mMatrix.postScale(intentScale, intentScale, getWidth()/2,
// getHeight()/2);
// 以手势为中心缩放
mMatrix.postScale(intentScale, intentScale, detector.getFocusX(), detector.getFocusY());
// 检测边界与中心点
checkSideAndCenterWhenScale();
setImageMatrix(mMatrix);
}
return true;
}
/**
* 获得图片缩放后的矩阵
*
* @return
*/
public RectF getMatrixRectF() {
Matrix matrix = mMatrix;
RectF rectF = new RectF();
Drawable drawable = getDrawable();
if (drawable != null) {
// 初始化矩阵
rectF.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
// 移动s
matrix.mapRect(rectF);
}
return rectF;
}
private void checkSideAndCenterWhenScale() {
RectF rectF = getMatrixRectF();
float deltaX = 0f;
float deltaY = 0f;
int width = getWidth();
int height = getHeight();
// 情况1, 如果图片的宽度大于控件的宽度
if (rectF.width() >= width) {
if (rectF.left > 0) {
deltaX = -rectF.left;// 如果图片没有左边对齐,就往左边移动
}
if (rectF.right < width) {
deltaX = width - rectF.right;// 如果图片没有右边对齐,就往右边移动
}
}
// 情况2, 如果图片的宽度大于控件的宽度
if (rectF.height() >= height) {
if (rectF.top > 0) {
deltaY = -rectF.top;//
}
if (rectF.bottom < height) {
deltaY = height - rectF.bottom;// 往底部移动
}
}
// 情况3,如图图片在控件内,则让其居中
if (rectF.width() < width) {
// deltaX = width/2-rectF.left - rectF.width()/2;
// 或
deltaX = width / 2f - rectF.right + rectF.width() / 2f;
}
if (rectF.height() < height) {
deltaY = height / 2f - rectF.bottom + rectF.height() / 2f;
}
mMatrix.postTranslate(deltaX, deltaY);
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
}
private float mLastX;
private float mLastY;
/** 上次手指的数量 */
private int mLastPointerCount;
/** 判断是否检测了x,y轴 */
private boolean isCheckX;
private boolean isCheckY;
@Override
public boolean onTouch(View v, MotionEvent event) {
// 把事件传递给双击手势
if (mGesture.onTouchEvent(event)) {
return true;
}
// 把事件传递给缩放手势
mScaleGesture.onTouchEvent(event);
float x = event.getX();
float y = event.getY();
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
x += event.getX(i);
y += event.getY(i);
}
x /= pointerCount;
y /= pointerCount;
// 说明手指改变
if (mLastPointerCount != pointerCount) {
isCanDrag = false;
mLastX = x;
mLastY = y;
}
mLastPointerCount = pointerCount;
RectF rectF = getMatrixRectF();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (rectF.width() > getWidth()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_MOVE:
if (rectF.width() > getWidth()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
float dx = x - mLastX;
float dy = y - mLastY;
if (!isCanDrag) {
isCanDrag = isMoveAction(dx, dy);
}
/**
* 如果能移动
*/
if (isCanDrag) {
//RectF rectF = getMatrixRectF();
if (getDrawable() == null) {
return true;
}
isCheckX = isCheckY = true;
// 如果图片在控件内,不允许移动
if (rectF.width() < getWidth()) {
isCheckX = false;
dx = 0f;
}
if (rectF.height() < getHeight()) {
isCheckY = false;
dy = 0f;
}
mMatrix.postTranslate(dx, dy);
// 移动事检测边界
checkSideAndCenterWhenTransate();
setImageMatrix(mMatrix);
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 清楚手指
mLastPointerCount = 0;
break;
}
return true;
}
private void checkSideAndCenterWhenTransate() {
RectF rectF = getMatrixRectF();
float deltaX = 0f;
float deltaY = 0f;
int width = getWidth();
int height = getHeight();
if (rectF.top > 0 && isCheckY) {
deltaY = -rectF.top;// 往上边移动
}
if (rectF.bottom < height && isCheckY) {
deltaY = height - rectF.bottom;// 往底部移动
}
if (rectF.left > 0 && isCheckX) {
deltaX = -rectF.left;// 往左边移动
}
if (rectF.right < width && isCheckX) {
deltaX = width - rectF.right;// 往右边移动
}
// 移动
mMatrix.postTranslate(deltaX, deltaY);
}
private boolean isMoveAction(float dx, float dy) {
// 求得两点的距离
return Math.sqrt(dx * dx + dy * dy) > mTouchSlop;
}
}
import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.bumptech.glide.DrawableRequestBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.ListPreloader;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.example.user.mypractice.R;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.List;
/**
* @author dj
* @version 1.0
* @description 缩放图片
* @created on 2017/4/19.
*/
public class ScaleViewActivity extends AppCompatActivity {
private List list;
private RelativeLayout rlBack;
private TextView tvCurrent;
private TextView tvTotal;
private ViewPager vpImgCheck;
private ImageVpAdapter adapter;
//图片所在的位置
private int position;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scale_view);
initView();
list = new ArrayList<>();
adapter=new ImageVpAdapter();
position = 0;
vpImgCheck.setAdapter(adapter);
vpImgCheck.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {
}
@Override
public void onPageSelected(int i) {
int current=i+1;
tvCurrent.setText(current+"");
}
@Override
public void onPageScrollStateChanged(int i) {
}
});
//加载数据
loadData();
}
/**
* 加载数据
*/
private void loadData() {
list.clear();
list.add("http://scimg.jb51.net/allimg/151228/14-15122Q60431W4.jpg");
list.add("http://img1.3lian.com/2015/a1/137/d/37.jpg");
list.add("http://pic.qiantucdn.com/58pic/18/37/96/18n58PICPb7_1024.jpg");
list.add("http://pic.qiantucdn.com/58pic/12/81/76/44n58PICAT2.jpg");
list.add("http://pic.qiantucdn.com/58pic/14/44/24/94b58PICCxn_1024.jpg");
tvTotal.setText(list.size()+"");
vpImgCheck.setCurrentItem(position);
adapter.notifyDataSetChanged();
}
private void initView() {
rlBack = (RelativeLayout) findViewById(R.id.rl_back);
tvCurrent = (TextView) findViewById(R.id.tv_current);
tvTotal = (TextView) findViewById(R.id.tv_total);
vpImgCheck = (ViewPager) findViewById(R.id.vp_img_check);
}
/**
* 适配器
*/
private class ImageVpAdapter extends PagerAdapter {
@Override
public int getCount() {
return list.size();
}
@Override
public boolean isViewFromObject(View view, Object o) {
return view == o;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
final ScaleView scaleView = new ScaleView(ScaleViewActivity.this);
scaleView.setScaleType(ImageView.ScaleType.MATRIX);
Picasso.with(ScaleViewActivity.this).load(list.get(position)).placeholder(R.mipmap.load).error(R.mipmap.error)
.resize(500,500).centerCrop().into(scaleView);
container.addView(scaleView);
return scaleView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (object instanceof ScaleView) {
ScaleView view = (ScaleView) object;
container.removeView(view);
}
}
}
咦?这TM是什么和我想象的不一样啊?
明明原图是这么的山清水秀,怎么变成那样的呢?
这是因为picasso图片没有加载完整然后就将图片放置到控件里了,所以没有显示完全,可以随意拖动,但是看起来就好像放大了数倍一样。
那么有没有什么方法可以让图片加载完成后再把图片放置上去呢?
我试着查了一下,发现picasso并没有这个方法,不过在glide这个图片加载框架有相应的方法
new SimpleTarget() {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation super GlideDrawable> glideAnimation) {
}
}
Glide.with(ScaleViewActivity.this).load(list.get(position)).placeholder(R.mipmap.load).error(R.mipmap.error)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(new SimpleTarget() {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation super GlideDrawable> glideAnimation) {
scaleView.setImageDrawable(resource);
}
});
这样就达到效果了,破费。
注意:使用时如果这个控件在布局文件里写的要加上
android:scaleType="matrix"