继续坚持写博客,懒毛病尽快改掉。今天写了一个图片查看控件。初步功能具有移动,缩放效果。并且限制了移动的范围,使它不能完全移出边界。放大倍数限制默认给了4,有特别需要的同学可以自己修改,给个自定义参数来设置。双击图片能还原回初始大小和中间对齐的状态。效果图如下:
上面是初始中间对齐状态。
这个是移动状态
这个是缩放状态。
好了,下面贴上代码:
package com.example.huangyi.test.widge;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.view.GestureDetectorCompat;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import com.example.huangyi.test.R;
/**
* Created by huangyi on 17-5-25.
*/
public class ZoomImageView extends View {
private static final int DEF_SIZE = 200;
private static final float MAX_MULTIPLE = 4; //允许放大的最大倍数
private static final float SIZDE_PADDING = 50; //最大剩余的值,不让全移除边界
private Bitmap mBitmap;
private Paint mPaint;
private Matrix mMatrix; //用于对图片进行变换的矩阵
private RectF originalRect; //图片初始时边框所对应的矩形
private RectF translateRect; //变换后图片边框所对应的矩形
private int pointer = 0; //触摸点个数
private PointF lastP = new PointF(); //在移动图片中,记录上一次的触摸点
private int lastPId = -1; //在移动图片中,上次触摸点的id,避免多根手指触摸,然后第一根抬起后造成图片跳动
private float lastDistance = -1; //缩放图片时,上一次两个手指的距离.
private GestureDetectorCompat gestureDetector;
public ZoomImageView(Context context) {
super(context);
init(context,null);
}
public ZoomImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ZoomImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context,attrs);
}
private void init(Context context, AttributeSet attrs){
if(attrs != null){
TypedArray ta = context.getResources().obtainAttributes(attrs, R.styleable.ZoomImageView);
Drawable image = ta.getDrawable(R.styleable.ZoomImageView_ximage);
mBitmap = drawable2Bitmap(image);
ta.recycle();
}
mPaint = new Paint();
mPaint.setAntiAlias(true);
mMatrix = new Matrix();
translateRect = new RectF(0,0,mBitmap.getWidth(),mBitmap.getHeight());
originalRect = new RectF(translateRect);
ZoomGestureListener zoomGestureListener = new ZoomGestureListener();
gestureDetector = new GestureDetectorCompat(getContext(),zoomGestureListener);
gestureDetector.setOnDoubleTapListener(zoomGestureListener);
}
//双击就使图片恢复初始中心对齐状态
class ZoomGestureListener extends GestureDetector.SimpleOnGestureListener{
@Override
public boolean onDoubleTap(MotionEvent e) {
mMatrix.reset(); //让图片初始的时候与空间中心对齐
mMatrix.postTranslate(getWidth()/2-mBitmap.getWidth()/2,getHeight()/2-mBitmap.getHeight()/2);
mMatrix.mapRect(translateRect);
postInvalidate();
return true;
}
}
/**
* 把drawable转化为bitmap
* @param d
* @return
*/
private Bitmap drawable2Bitmap(Drawable d){
if(d instanceof BitmapDrawable){
return ((BitmapDrawable) d).getBitmap();
}else{
Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(),d.getIntrinsicHeight(),Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
d.draw(c);
return b;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
//在wrap_content的时候就给一个默认的尺寸
if(wMode == MeasureSpec.AT_MOST){
wSize = Math.min(wSize,DEF_SIZE);
}
if(hMode == MeasureSpec.AT_MOST){
hSize = Math.min(hSize,DEF_SIZE);
}
setMeasuredDimension(MeasureSpec.makeMeasureSpec(wSize,wMode),
MeasureSpec.makeMeasureSpec(hSize,hMode));
mMatrix.reset(); //让图片初始的时候与空间中心对齐
mMatrix.postTranslate(wSize/2-mBitmap.getWidth()/2,hSize/2-mBitmap.getHeight()/2);
mMatrix.mapRect(translateRect);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap,mMatrix,mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
gestureDetector.onTouchEvent(event);
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
if(isTouchOnBitmap(event,translateRect)){ //第一个触摸点在图片上才能进行操作
pointer = 1;
lastP.set(event.getX(),event.getY());
lastPId = event.getPointerId(0);
return true;
}else {
return false;
}
case MotionEvent.ACTION_POINTER_DOWN:
pointer +=1;
lastDistance = (float) getDistance(event.getX(0),event.getY(0),event.getX(1),event.getY(1));
break;
case MotionEvent.ACTION_MOVE:
if(pointer == 1 && event.getPointerId(0) == lastPId){ //只有一根手指时就是移动
moveBitmap(event);
}else if(pointer >= 2){ //两根手指就是放大缩小
scaleBitmap(event);
}
lastP.set(event.getX(),event.getY());
break;
case MotionEvent.ACTION_POINTER_UP:
pointer -=1;
if(event.getPointerId(0) != lastPId){ //松开了最先按下的那个手指,就要更新第一个点的信息,避免跳动
lastP.set(event.getX(),event.getY());
lastPId = event.getPointerId(0);
}
break;
case MotionEvent.ACTION_UP:
pointer = 0;
lastDistance = -1;
break;
}
return super.onTouchEvent(event);
}
/**
* 触摸点是否在图片上,只有触摸点在图片上才能移动图片
* @param event
* @param rectF
* @return
*/
private boolean isTouchOnBitmap(MotionEvent event,RectF rectF){
return rectF.contains(event.getX(),event.getY());
}
/**
* 单根手指时移动图片
* @param event
*/
private void moveBitmap(MotionEvent event){
float disX = event.getX()-lastP.x;
float disY = event.getY()-lastP.y;
if(translateRect.top + disY > getHeight() - SIZDE_PADDING || //边界检测,不让全移出边界外
translateRect.bottom + disY < SIZDE_PADDING ||
translateRect.left + disX > getWidth() - SIZDE_PADDING ||
translateRect.right +disX < SIZDE_PADDING){
return;
}
mMatrix.postTranslate(disX,disY);
mMatrix.mapRect(translateRect,originalRect); //更新translateRect,代表了变换后的图片的位置
postInvalidate();
}
/**
* 两根手指时放大缩小图片
* @param event
*/
private void scaleBitmap(MotionEvent event){
float curDis = (float) getDistance(event.getX(0),event.getY(0),event.getX(1),event.getY(1));
if(lastDistance == -1){
lastDistance = curDis;
}
float scale = curDis/lastDistance;
if(translateRect.width()/originalRect.width()*scale > MAX_MULTIPLE){ //倍数不能大于MAX_MULTIPLE
return;
}
lastDistance = curDis;
PointF centerP = getCenter(event.getX(0),event.getY(0),event.getX(1),event.getY(1));
mMatrix.postScale(scale,scale,centerP.x,centerP.y);
mMatrix.mapRect(translateRect,originalRect); //更新translateRect,代表了变换后的图片的位置
postInvalidate();
}
/**
* 获得两点的距离
* @param x1
* @param y1
* @param x2
* @param y2
* @return
*/
private double getDistance(float x1,float y1,float x2,float y2){
return Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2)) ;
}
/**
* 获得两点的中心点
* @param x1
* @param y1
* @param x2
* @param y2
* @return
*/
private PointF getCenter(float x1,float y1,float x2,float y2){
return new PointF((x1+x2)/2.0f,(y1+y2)/2.0f);
}
}
下面是自定义参数,只写了一个。同学们可以根据需要自己写更多的定制项。
<declare-styleable name="ZoomImageView">
<attr name="ximage" format="reference"></attr>
</declare-styleable>
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.huangyi.test.MainActivity">
<com.example.huangyi.test.widge.ZoomImageView
android:id="@+id/zoom"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
app:ximage="@drawable/carton"/>
</FrameLayout>
整个代码主要就是围绕对Metrix的操作变换和多点触摸的编写。