Android自定义控件篇 图片进行平移,缩放,旋转

Posted 彭老希

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义控件篇 图片进行平移,缩放,旋转相关的知识,希望对你有一定的参考价值。

一、自定义属性

    <declare-styleable name="SingleTouchView">
        <attr name="src" format="reference" />            <!-- 用于缩放旋转的图标 -->
        <attr name="editable" format="boolean"/>          <!-- 是否处于可编辑状态 -->
        <attr name="frameColor" format="color" />         <!-- 边框颜色 -->
        <attr name="frameWidth" format="dimension" />     <!-- 边框线宽度 -->
        <attr name="framePadding" format="dimension" />   <!-- 边框与图片的间距 -->
        <attr name="degree" format="float" />             <!-- 旋转角度 -->
        <attr name="scale" format="float" />              <!-- 缩放比例 -->
        <attr name="controlDrawable" format="reference"/> <!-- 控制图标 -->
        <attr name="controlLocation">                     <!-- 控制图标的位置 -->
            <enum name="left_top" value="0" />
            <enum name="right_top" value="1" />
            <enum name="right_bottom" value="2" />
            <enum name="left_bottom" value="3" />
        </attr>
    </declare-styleable>

二、自定义控件代码

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.FloatMath;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
 
/**
 * 单手对图片进行缩放,旋转,平移操作,详情请查看
 */
public class SingleTouchView extends View {
	/**
	 * 图片的最大缩放比例
	 */
	public static final float MAX_SCALE = 4.0f;
	
	/**
	 * 图片的最小缩放比例
	 */
	public static final float MIN_SCALE = 0.3f;
	
	/**
	 * 控制缩放,旋转图标所在四个点得位置
	 */
	public static final int LEFT_TOP = 0;
	public static final int RIGHT_TOP = 1;
	public static final int RIGHT_BOTTOM = 2;
	public static final int LEFT_BOTTOM = 3;
	
	/**
	 * 一些默认的常量
	 */
	public static final int DEFAULT_FRAME_PADDING = 8;
	public static final int DEFAULT_FRAME_WIDTH = 2;
	public static final int DEFAULT_FRAME_COLOR = Color.WHITE;
	public static final float DEFAULT_SCALE = 1.0f;
	public static final float DEFAULT_DEGREE = 0;
	public static final int DEFAULT_CONTROL_LOCATION = RIGHT_TOP;
	public static final boolean DEFAULT_EDITABLE = true;
	public static final int DEFAULT_OTHER_DRAWABLE_WIDTH = 50;
	public static final int DEFAULT_OTHER_DRAWABLE_HEIGHT = 50;
	
	
	
	/**
	 * 用于旋转缩放的Bitmap
	 */
	private Bitmap mBitmap;
	
	/**
	 * SingleTouchView的中心点坐标,相对于其父类布局而言的
	 */
	private PointF mCenterPoint = new PointF();
	
	/**
	 * View的宽度和高度,随着图片的旋转而变化(不包括控制旋转,缩放图片的宽高)
	 */
	private int mViewWidth, mViewHeight;
	
	/**
	 * 图片的旋转角度
	 */
	private float mDegree = DEFAULT_DEGREE;
	
	/**
	 * 图片的缩放比例
	 */
	private float mScale = DEFAULT_SCALE;
	
	/**
	 * 用于缩放,旋转,平移的矩阵
	 */
	private Matrix matrix = new Matrix();
	
	/**
	 * SingleTouchView距离父类布局的左间距
	 */
	private int mViewPaddingLeft;
	
	/**
	 * SingleTouchView距离父类布局的上间距
	 */
	private int mViewPaddingTop;
	
	/**
	 * 图片四个点坐标
	 */
	private Point mLTPoint;
	private Point mRTPoint;
	private Point mRBPoint;
	private Point mLBPoint;
	
	/**
	 * 用于缩放,旋转的控制点的坐标
	 */
	private Point mControlPoint = new Point();
	
	/**
	 * 用于缩放,旋转的图标
	 */
	private Drawable controlDrawable;
	
	/**
	 * 缩放,旋转图标的宽和高
	 */
	private int mDrawableWidth, mDrawableHeight;
	
	/**
	 * 画外围框的Path
	 */
	private Path mPath = new Path();
	
	/**
	 * 画外围框的画笔
	 */
	private Paint mPaint ;
	
	/**
	 * 初始状态
	 */
	public static final int STATUS_INIT = 0;
	
	/**
	 * 拖动状态
	 */
	public static final int STATUS_DRAG = 1;
	
	/**
	 * 旋转或者放大状态
	 */
	public static final int STATUS_ROTATE_ZOOM = 2; 
	
	/**
	 * 当前所处的状态
	 */
	private int mStatus = STATUS_INIT;
	
	/**
	 * 外边框与图片之间的间距, 单位是dip
	 */
	private int framePadding = DEFAULT_FRAME_PADDING;
	
	/**
	 * 外边框颜色
	 */
	private int frameColor = DEFAULT_FRAME_COLOR;
	
	/**
	 * 外边框线条粗细, 单位是 dip
	 */
	private int frameWidth = DEFAULT_FRAME_WIDTH;
	
	/**
	 * 是否处于可以缩放,平移,旋转状态
	 */
	private boolean isEditable = DEFAULT_EDITABLE;
	
	private DisplayMetrics metrics;
	
	
	private PointF mPreMovePointF = new PointF();
	private PointF mCurMovePointF = new PointF();
	
	/**
	 * 图片在旋转时x方向的偏移量
	 */
	private int offsetX;
	/**
	 * 图片在旋转时y方向的偏移量
	 */
	private int offsetY;
	
	/**
	 * 控制图标所在的位置(比如左上,右上,左下,右下)
	 */
	private int controlLocation = DEFAULT_CONTROL_LOCATION;
 
	
	public SingleTouchView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}
 
	public SingleTouchView(Context context) {
		this(context, null);
	}
 
	public SingleTouchView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		obtainStyledAttributes(attrs);
		init();
	}
	
	/**
	 * 获取自定义属性
	 * @param attrs
	 */
	private void obtainStyledAttributes(AttributeSet attrs){
		metrics = getContext().getResources().getDisplayMetrics();
		framePadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_PADDING, metrics);
		frameWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_WIDTH, metrics);
		
		TypedArray mTypedArray = getContext().obtainStyledAttributes(attrs,
				R.styleable.SingleTouchView);
		
		Drawable srcDrawble = mTypedArray.getDrawable(R.styleable.SingleTouchView_src);
		mBitmap = drawable2Bitmap(srcDrawble);
		
		framePadding = mTypedArray.getDimensionPixelSize(R.styleable.SingleTouchView_framePadding, framePadding);
		frameWidth = mTypedArray.getDimensionPixelSize(R.styleable.SingleTouchView_frameWidth, frameWidth);
		frameColor = mTypedArray.getColor(R.styleable.SingleTouchView_frameColor, DEFAULT_FRAME_COLOR);
		mScale = mTypedArray.getFloat(R.styleable.SingleTouchView_scale, DEFAULT_SCALE);
		mDegree = mTypedArray.getFloat(R.styleable.SingleTouchView_degree, DEFAULT_DEGREE);
		controlDrawable = mTypedArray.getDrawable(R.styleable.SingleTouchView_controlDrawable);
		controlLocation = mTypedArray.getInt(R.styleable.SingleTouchView_controlLocation, DEFAULT_CONTROL_LOCATION);
		isEditable = mTypedArray.getBoolean(R.styleable.SingleTouchView_editable, DEFAULT_EDITABLE);
		
		mTypedArray.recycle();
		
	}
	
	
	private void init(){
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setColor(frameColor);
		mPaint.setStrokeWidth(frameWidth);
		mPaint.setStyle(Style.STROKE);
		
		if(controlDrawable == null){
			controlDrawable = getContext().getResources().getDrawable(R.drawable.st_rotate_icon);
		}
		
		mDrawableWidth = controlDrawable.getIntrinsicWidth();
		mDrawableHeight = controlDrawable.getIntrinsicHeight();
		
		transformDraw(); 
	}
	
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		
		//获取SingleTouchView所在父布局的中心点
		ViewGroup mViewGroup = (ViewGroup) getParent();
		if(null != mViewGroup){
			int parentWidth = mViewGroup.getWidth();
			int parentHeight = mViewGroup.getHeight();
			mCenterPoint.set(parentWidth/2, parentHeight/2);
		}
	}
	
	
	/**
	 * 调整View的大小,位置
	 */
	private void adjustLayout(){
		int actualWidth = mViewWidth + mDrawableWidth;
		int actualHeight = mViewHeight + mDrawableHeight;
		
		int newPaddingLeft = (int) (mCenterPoint.x - actualWidth /2);
		int newPaddingTop = (int) (mCenterPoint.y - actualHeight/2);
		
		if(mViewPaddingLeft != newPaddingLeft || mViewPaddingTop != newPaddingTop){
			mViewPaddingLeft = newPaddingLeft;
			mViewPaddingTop = newPaddingTop;
			
//			layout(newPaddingLeft, newPaddingTop, newPaddingLeft + actualWidth, newPaddingTop + actualHeight);
		}
		
		layout(newPaddingLeft, newPaddingTop, newPaddingLeft + actualWidth, newPaddingTop + actualHeight);
	}
	
	
	/**
	 * 设置旋转图
	 * @param bitmap
	 */
	public void setImageBitamp(Bitmap bitmap){
		this.mBitmap = bitmap;
		transformDraw();
	}
	
	
	/**
	 * 设置旋转图
	 * @param drawable
	 */
	public void setImageDrawable(Drawable drawable){
		this.mBitmap = drawable2Bitmap(drawable);
		transformDraw();
	}
	
	/**
	 * 从Drawable中获取Bitmap对象
	 * @param drawable
	 * @return
	 */
	private Bitmap drawable2Bitmap(Drawable drawable) {
		try {
			if (drawable == null) {
				return null;
			}
 
			if (drawable instanceof BitmapDrawable) {
				return ((BitmapDrawable) drawable).getBitmap();
			}
 
			int intrinsicWidth = drawable.getIntrinsicWidth();
			int intrinsicHeight = drawable.getIntrinsicHeight();
			Bitmap bitmap = Bitmap.createBitmap(
					intrinsicWidth <= 0 ? DEFAULT_OTHER_DRAWABLE_WIDTH
							: intrinsicWidth,
					intrinsicHeight <= 0 ? DEFAULT_OTHER_DRAWABLE_HEIGHT
							: intrinsicHeight, Config.ARGB_8888);
 
			Canvas canvas = new Canvas(bitmap);
			drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
			drawable.draw(canvas);
			return bitmap;
		} catch (OutOfMemoryError e) {
			return null;
		}
 
	}
	
	/**
	 * 根据id设置旋转图
	 * @param resId
	 */
	public void setImageResource(int resId){
		Drawable drawable = getContext().getResources().getDrawable(resId);
		setImageDrawable(drawable);
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		//每次draw之前调整View的位置和大小
		super.onDraw(canvas);
		
		if(mBitmap == null) return;
		canvas.drawBitmap(mBitmap, matrix, mPaint);
		
		
		//处于可编辑状态才画边框和控制图标
		if(isEditable){
			mPath.reset();
			mPath.moveTo(mLTPoint.x, mLTPoint.y);
			mPath.lineTo(mRTPoint.x, mRTPoint.y);
			mPath.lineTo(mRBPoint.x, mRBPoint.y);
			mPath.lineTo(mLBPoint.x, mLBPoint.y);
			mPath.lineTo(mLTPoint.x, mLTPoint.y);
			mPath.lineTo(mRTPoint.x, mRTPoint.y);
			canvas.drawPath(mPath, mPaint);
			//画旋转, 缩放图标
			
			controlDrawable.setBounds(mControlPoint.x - mDrawableWidth / 2,
					mControlPoint.y - mDrawableHeight / 2, mControlPoint.x + mDrawableWidth
							/ 2, mControlPoint.y + mDrawableHeight / 2);
			controlDrawable.draw(canvas);
		}
		
		adjustLayout();
		
		
	}
	
	
	
	/**
	 * 设置Matrix, 强制刷新
	 */
	private void transformDraw(){
		if(mBitmap == null) return;
		int bitmapWidth = (int)(mBitmap.getWidth() * mScale);
		int bitmapHeight = (int)(mBitmap.getHeight()* mScale);
		computeRect(-framePadding, -framePadding, bitmapWidth + framePadding, bitmapHeight + framePadding, mDegree);
		
		//设置缩放比例
		matrix.setScale(mScale, mScale);
		//绕着图片中心进行旋转
		matrix.postRotate(mDegree % 360, bitmapWidth/2, bitmapHeight/2);
		//设置画该图片的起始点
		matrix.postTranslate(offsetX + mDrawableWidth/2, offsetY + mDrawableHeight/2);
		
		adjustLayout();
	}
	
	
	public boolean onTouchEvent(MotionEvent event) {
		if(!isEditable){
			return super.onTouchEvent(event);
		}
		switch (event.getAction() ) {
		case MotionEvent.ACTION_DOWN:
			mPreMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
			
			mStatus = JudgeStatus(event.getX(), event.getY());
 
			break;
		case MotionEvent.ACTION_UP:
			mStatus = STATUS_INIT;
			break;
		case MotionEvent.ACTION_MOVE:
			mCurMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
			if (mStatus == STATUS_ROTATE_ZOOM) {
				float scale = 1f;
				
				int halfBitmapWidth = mBitmap.以上是关于Android自定义控件篇 图片进行平移,缩放,旋转的主要内容,如果未能解决你的问题,请参考以下文章

自定义 Winforms 设计器控件同时缩放和平移控件

Android Matrix手势缩放自定义view 不止于Imageview

Android自定义View之上拉下拉列表 头部元素跟随 缩放平移效果的实现

Android-系统绘图真相

Android实现图片的缩放和拖动

android如何对viewpager里面的图片进行缩放