Android单点触控实现图片平移、缩放、旋转功能

相信大家使用多点对图片进行缩放,平移的操作很熟悉了,大部分大图的浏览都具有此功能,有些app还可以对图片进行旋转操作,QQ的大图浏览就可以对图片进行旋转操作,大家都知道对图片进行缩放,平移,旋转等操作可以使用Matrix来实现,Matrix就是一个3X3的矩阵,对图片的处理可分为四个基础变换操作,Translate(平移变换)、Rotate(旋转变换)、Scale (缩放变换)、Skew(错切变换),如果大家对Matrix不太了解的话可以看看这篇文章(点击查看),作者对每一种Matrix的变换写的很清楚,但是如果使用一个手指对图片进行缩放,平移,旋转等操作大家是否了解呢,其实单手指操作跟多手指操作差不多,当然也是使用Matrix来实现的,无非是在缩放比例和旋转角度的计算上面有些不一样,也许你会有疑问,多点操作图片缩放旋转是两个手指操作,平移的时候是一个手指操作,那么你单手在图片即平移,又缩放旋转难道不会有冲突吗?是的,这样子肯定是不行的,我们必须将平移和缩放旋转进行分开。如下图

图片外面的框是一个边框,如果我们手指触摸的是上面的蓝色小图标我们就对其进行缩放旋转操作,如果是触摸到其他的区域我们就对其进行平移操作,这样就避免了上面所说的冲突问题,这里对图片的平移操作并没有使用Matrix来实现,而是使用layout()方法来对其进行位置的变换。

计算缩放比例比较简单,使用手指移动的点到图片所在中心点的距离除以图片对角线的一半就是缩放比例了,接下来就计算旋转角度,如下图

preMove是手指移动前一个点,curMove就是当前手指所在的点,还有一个中心点center,知道三个点求旋转的夹角是不是很简单呢,就是线段a和线段c的一个夹角,假设夹角为o,  o的余弦值 cos o = (a * a + c * c - b * b) / (2 * a * c), 知道余弦值夹角就出来了,但是这里还有一个问题,我们在使用Matrix对图片进行旋转的时候需要区别顺时针旋转还是逆时针旋转,顺时针旋转角度为正,所以上面我们只求出了旋转的角度,并不知道是顺时针还是逆时针。

具体怎么求是顺时针角度还是逆时针角度呢?有些同学可能会根据curMove和ProMove的x ,y 的大小来判断,比如上面的图中,如果curMove.x > proMove.x则为顺时针,否则为逆时针,这当然是一种办法,可是你想过这种方法只适合在第二象限,在第一,第三,第四象限这样子判断就不行了,当然你可以判断当前的点在第几象限,然后在不同的象限采用不同的判断,这样子判断起来会很复杂。

有没有更加简单的方法来判断呢?答案是肯定的,我们可以使用数学中的向量叉乘来判断。假如向量A(x1, y1)和向量B(x2, y2),我们可以使用向量叉乘 |A X B| = x1*y2 - x2*y1 = |A|×|B|×sin(向量A到B的夹角), 所以这个值的正负也就是A到B旋转角sin值的正负, 顺时针旋转角度0~180,sin>0, 顺时针旋转角度180~360或者说逆时针旋转0~180,sin<0, 所以我们可以用个center到proMove的向量 叉乘 center到curMove的向量来判断是顺时针旋转还是逆时针旋转。

接下来我们就开始动手实现此功能,我们采用一个自定义的View来实现,这里就叫SingleTouchView,直接继承View, 从上面的图中我们可以定义出一些自定义的属性,比如用于缩放的图片,控制缩放旋转的小图标,图片边框的颜色等,我定义了如下的属性

<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>

接下来就是自定义SingleTouchView的代码,代码有点长,注释还是蛮详细的

package com.example.singletouchview; 

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; 

/**
 * 单手对图片进行缩放,旋转,平移操作,详情请查看
 *
 * @blog http://blog.csdn.net/xiaanming/article/details/42833893
 *
 * @author xiaanming
 *
 */
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.getWidth() / 2;
        int halfBitmapHeight = mBitmap.getHeight() /2 ; 

        //图片某个点到图片中心的距离
        float bitmapToCenterDistance = FloatMath.sqrt(halfBitmapWidth * halfBitmapWidth + halfBitmapHeight * halfBitmapHeight); 

        //移动的点到图片中心的距离
        float moveToCenterDistance = distance4PointF(mCenterPoint, mCurMovePointF); 

        //计算缩放比例
        scale = moveToCenterDistance / bitmapToCenterDistance; 

        //缩放比例的界限判断
        if (scale <= MIN_SCALE) {
          scale = MIN_SCALE;
        } else if (scale >= MAX_SCALE) {
          scale = MAX_SCALE;
        } 

        // 角度
        double a = distance4PointF(mCenterPoint, mPreMovePointF);
        double b = distance4PointF(mPreMovePointF, mCurMovePointF);
        double c = distance4PointF(mCenterPoint, mCurMovePointF); 

        double cosb = (a * a + c * c - b * b) / (2 * a * c); 

        if (cosb >= 1) {
          cosb = 1f;
        } 

        double radian = Math.acos(cosb);
        float newDegree = (float) radianToDegree(radian); 

        //center -> proMove的向量, 我们使用PointF来实现
        PointF centerToProMove = new PointF((mPreMovePointF.x - mCenterPoint.x), (mPreMovePointF.y - mCenterPoint.y)); 

        //center -> curMove 的向量
        PointF centerToCurMove = new PointF((mCurMovePointF.x - mCenterPoint.x), (mCurMovePointF.y - mCenterPoint.y)); 

        //向量叉乘结果, 如果结果为负数, 表示为逆时针, 结果为正数表示顺时针
        float result = centerToProMove.x * centerToCurMove.y - centerToProMove.y * centerToCurMove.x; 

        if (result < 0) {
          newDegree = -newDegree;
        }  

        mDegree = mDegree + newDegree;
        mScale = scale; 

        transformDraw();
      }
      else if (mStatus == STATUS_DRAG) {
        // 修改中心点
        mCenterPoint.x += mCurMovePointF.x - mPreMovePointF.x;
        mCenterPoint.y += mCurMovePointF.y - mPreMovePointF.y; 

        System.out.println(this + "move = " + mCenterPoint); 

        adjustLayout();
      } 

      mPreMovePointF.set(mCurMovePointF);
      break;
    }
    return true;
  } 

  /**
   * 获取四个点和View的大小
   * @param left
   * @param top
   * @param right
   * @param bottom
   * @param degree
   */
  private void computeRect(int left, int top, int right, int bottom, float degree){
    Point lt = new Point(left, top);
    Point rt = new Point(right, top);
    Point rb = new Point(right, bottom);
    Point lb = new Point(left, bottom);
    Point cp = new Point((left + right) / 2, (top + bottom) / 2);
    mLTPoint = obtainRoationPoint(cp, lt, degree);
    mRTPoint = obtainRoationPoint(cp, rt, degree);
    mRBPoint = obtainRoationPoint(cp, rb, degree);
    mLBPoint = obtainRoationPoint(cp, lb, degree); 

    //计算X坐标最大的值和最小的值
    int maxCoordinateX = getMaxValue(mLTPoint.x, mRTPoint.x, mRBPoint.x, mLBPoint.x);
    int minCoordinateX = getMinValue(mLTPoint.x, mRTPoint.x, mRBPoint.x, mLBPoint.x);; 

    mViewWidth = maxCoordinateX - minCoordinateX ; 

    //计算Y坐标最大的值和最小的值
    int maxCoordinateY = getMaxValue(mLTPoint.y, mRTPoint.y, mRBPoint.y, mLBPoint.y);
    int minCoordinateY = getMinValue(mLTPoint.y, mRTPoint.y, mRBPoint.y, mLBPoint.y); 

    mViewHeight = maxCoordinateY - minCoordinateY ; 

    //View中心点的坐标
    Point viewCenterPoint = new Point((maxCoordinateX + minCoordinateX) / 2, (maxCoordinateY + minCoordinateY) / 2); 

    offsetX = mViewWidth / 2 - viewCenterPoint.x;
    offsetY = mViewHeight / 2 - viewCenterPoint.y; 

    int halfDrawableWidth = mDrawableWidth / 2;
    int halfDrawableHeight = mDrawableHeight /2; 

    //将Bitmap的四个点的X的坐标移动offsetX + halfDrawableWidth
    mLTPoint.x += (offsetX + halfDrawableWidth);
    mRTPoint.x += (offsetX + halfDrawableWidth);
    mRBPoint.x += (offsetX + halfDrawableWidth);
    mLBPoint.x += (offsetX + halfDrawableWidth); 

    //将Bitmap的四个点的Y坐标移动offsetY + halfDrawableHeight
    mLTPoint.y += (offsetY + halfDrawableHeight);
    mRTPoint.y += (offsetY + halfDrawableHeight);
    mRBPoint.y += (offsetY + halfDrawableHeight);
    mLBPoint.y += (offsetY + halfDrawableHeight); 

    mControlPoint = LocationToPoint(controlLocation);
  } 

  /**
   * 根据位置判断控制图标处于那个点
   * @return
   */
  private Point LocationToPoint(int location){
    switch(location){
    case LEFT_TOP:
      return mLTPoint;
    case RIGHT_TOP:
      return mRTPoint;
    case RIGHT_BOTTOM:
      return mRBPoint;
    case LEFT_BOTTOM:
      return mLBPoint;
    }
    return mLTPoint;
  } 

  /**
   * 获取变长参数最大的值
   * @param array
   * @return
   */
  public int getMaxValue(Integer...array){
    List<Integer> list = Arrays.asList(array);
    Collections.sort(list);
    return list.get(list.size() -1);
  } 

  /**
   * 获取变长参数最大的值
   * @param array
   * @return
   */
  public int getMinValue(Integer...array){
    List<Integer> list = Arrays.asList(array);
    Collections.sort(list);
    return list.get(0);
  } 

  /**
   * 获取旋转某个角度之后的点
   * @param viewCenter
   * @param source
   * @param degree
   * @return
   */
  public static Point obtainRoationPoint(Point center, Point source, float degree) {
    //两者之间的距离
    Point disPoint = new Point();
    disPoint.x = source.x - center.x;
    disPoint.y = source.y - center.y; 

    //没旋转之前的弧度
    double originRadian = 0; 

    //没旋转之前的角度
    double originDegree = 0; 

    //旋转之后的角度
    double resultDegree = 0; 

    //旋转之后的弧度
    double resultRadian = 0; 

    //经过旋转之后点的坐标
    Point resultPoint = new Point(); 

    double distance = Math.sqrt(disPoint.x * disPoint.x + disPoint.y * disPoint.y);
    if (disPoint.x == 0 && disPoint.y == 0) {
      return center;
      // 第一象限
    } else if (disPoint.x >= 0 && disPoint.y >= 0) {
      // 计算与x正方向的夹角
      originRadian = Math.asin(disPoint.y / distance); 

      // 第二象限
    } else if (disPoint.x < 0 && disPoint.y >= 0) {
      // 计算与x正方向的夹角
      originRadian = Math.asin(Math.abs(disPoint.x) / distance);
      originRadian = originRadian + Math.PI / 2; 

      // 第三象限
    } else if (disPoint.x < 0 && disPoint.y < 0) {
      // 计算与x正方向的夹角
      originRadian = Math.asin(Math.abs(disPoint.y) / distance);
      originRadian = originRadian + Math.PI;
    } else if (disPoint.x >= 0 && disPoint.y < 0) {
      // 计算与x正方向的夹角
      originRadian = Math.asin(disPoint.x / distance);
      originRadian = originRadian + Math.PI * 3 / 2;
    } 

    // 弧度换算成角度
    originDegree = radianToDegree(originRadian);
    resultDegree = originDegree + degree; 

    // 角度转弧度
    resultRadian = degreeToRadian(resultDegree); 

    resultPoint.x = (int) Math.round(distance * Math.cos(resultRadian));
    resultPoint.y = (int) Math.round(distance * Math.sin(resultRadian));
    resultPoint.x += center.x;
    resultPoint.y += center.y; 

    return resultPoint;
  } 

  /**
   * 弧度换算成角度
   *
   * @return
   */
  public static double radianToDegree(double radian) {
    return radian * 180 / Math.PI;
  } 

  /**
   * 角度换算成弧度
   * @param degree
   * @return
   */
  public static double degreeToRadian(double degree) {
    return degree * Math.PI / 180;
  } 

  /**
   * 根据点击的位置判断是否点中控制旋转,缩放的图片, 初略的计算
   * @param x
   * @param y
   * @return
   */
  private int JudgeStatus(float x, float y){
    PointF touchPoint = new PointF(x, y);
    PointF controlPointF = new PointF(mControlPoint); 

    //点击的点到控制旋转,缩放点的距离
    float distanceToControl = distance4PointF(touchPoint, controlPointF); 

    //如果两者之间的距离小于 控制图标的宽度,高度的最小值,则认为点中了控制图标
    if(distanceToControl < Math.min(mDrawableWidth/2, mDrawableHeight/2)){
      return STATUS_ROTATE_ZOOM;
    } 

    return STATUS_DRAG; 

  } 

  public float getImageDegree() {
    return mDegree;
  } 

  /**
   * 设置图片旋转角度
   * @param degree
   */
  public void setImageDegree(float degree) {
    if(this.mDegree != degree){
      this.mDegree = degree;
      transformDraw();
    }
  } 

  public float getImageScale() {
    return mScale;
  } 

  /**
   * 设置图片缩放比例
   * @param scale
   */
  public void setImageScale(float scale) {
    if(this.mScale != scale){
      this.mScale = scale;
      transformDraw();
    };
  } 

  public Drawable getControlDrawable() {
    return controlDrawable;
  } 

  /**
   * 设置控制图标
   * @param drawable
   */
  public void setControlDrawable(Drawable drawable) {
    this.controlDrawable = drawable;
    mDrawableWidth = drawable.getIntrinsicWidth();
    mDrawableHeight = drawable.getIntrinsicHeight();
    transformDraw();
  } 

  public int getFramePadding() {
    return framePadding;
  } 

  public void setFramePadding(int framePadding) {
    if(this.framePadding == framePadding)
      return;
    this.framePadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, framePadding, metrics);
    transformDraw();
  } 

  public int getFrameColor() {
    return frameColor;
  } 

  public void setFrameColor(int frameColor) {
    if(this.frameColor == frameColor)
      return;
    this.frameColor = frameColor;
    mPaint.setColor(frameColor);
    invalidate();
  } 

  public int getFrameWidth() {
    return frameWidth;
  } 

  public void setFrameWidth(int frameWidth) {
    if(this.frameWidth == frameWidth)
      return;
    this.frameWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, frameWidth, metrics);
    mPaint.setStrokeWidth(frameWidth);
    invalidate();
  } 

  /**
   * 设置控制图标的位置, 设置的值只能选择LEFT_TOP ,RIGHT_TOP, RIGHT_BOTTOM,LEFT_BOTTOM
   * @param controlLocation
   */
  public void setControlLocation(int location) {
    if(this.controlLocation == location)
      return;
    this.controlLocation = location;
    transformDraw();
  } 

  public int getControlLocation() {
    return controlLocation;
  } 

  public PointF getCenterPoint() {
    return mCenterPoint;
  } 

  /**
   * 设置图片中心点位置,相对于父布局而言
   * @param mCenterPoint
   */
  public void setCenterPoint(PointF mCenterPoint) {
    this.mCenterPoint = mCenterPoint;
    adjustLayout();
  } 

  public boolean isEditable() {
    return isEditable;
  } 

  /**
   * 设置是否处于可缩放,平移,旋转状态
   * @param isEditable
   */
  public void setEditable(boolean isEditable) {
    this.isEditable = isEditable;
    invalidate();
  } 

  /**
   * 两个点之间的距离
   * @param x1
   * @param y1
   * @param x2
   * @param y2
   * @return
   */
  private float distance4PointF(PointF pf1, PointF pf2) {
    float disX = pf2.x - pf1.x;
    float disY = pf2.y - pf1.y;
    return FloatMath.sqrt(disX * disX + disY * disY);
  } 

}

为了让SingleTouchView居中,我们需要获取父布局的长和宽,我们在onMeasure()中来获取,当然如果我们不需要居中显示我们也可以调用setCenterPoint方法来设置其位置.

onTouchEvent()方法中,mPreMovePointF和mCurMovePointF点的坐标不是相对View来的,首先如果采用相对于View本身(getX(), getY())肯定是不行的,假如你往x轴方向移动一段距离,这个SingleTouchView也会移动一段距离,mPreMovePointF和mCurMovePointF点和SingleTouchView的中心点都是会变化的,所以在移动的时候会不停的闪烁,相对于屏幕左上角(getRawX(), getRawY())是可以的,但是由于mCenterPointF并不是相对于屏幕的坐标,而是相对于父类布局的,所以将需要将mPreMovePointF和mCurMovePointF的坐标换算成相对于父类布局。

这里面最重要的方法就是transformDraw()方法,它主要做的是调用computeRect()方法求出图片的四个角的坐标点mLTPoint,mRTPoint,mRBPoint,mLBPoint(这几点的坐标是相对于SingleTouchView本身)和SingleTouchView的宽度和高度,以及控制图标所在图标四个点中的哪个点。如下图

上面的图忽略了控制旋转,缩放图标,黑色的框是开始的View的大小,而经过旋转之后,VIew的大小变成最外层的虚线框了,所以我们需要调用adjustLayout()方法来重新设置View的位置和大小,接下来就是设置Matrix了

matrix.setScale(mScale, mScale);
matrix.postRotate(mDegree % 360, bitmapWidth/2, bitmapHeight/2);
matrix.postTranslate(offsetX + mDrawableWidth/2, offsetY + mDrawableHeight/2);

先设置缩放比例, 然后设置围绕图片的中心点旋转mDegree,postTranslate( float dx, float dy)方法是画该图片的起始点进行平移dx, dy个单位,而不是移动到dx,dy这个点。
接下来就来使用,定义一个xml布局文件

<merge xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"> 

    <com.example.singletouchview.SingleTouchView
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:id="@+id/SingleTouchView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:scale="1.2"
      app:src="@drawable/scale"
      app:frameColor="#0022ff"
      app:controlLocation="right_top"/> 

</merge>

在里面写了一些自定义的属性,写自定义属性之前需要声明xmlns:app="http://schemas.android.com/apk/res-auto",Activity只需要设置这个布局文件作为ContentView就行了,接下来运行程序看下效果。

怎么样?效果还是不错的吧,如果我们想去掉蓝色的边框和用于缩放旋转的小图标,直接调用setEditable(false)就可以了,设置了setEditable(false)该View的点击事件,长按事件是正常的。

以上就是本文的全部内容,希望对大家学习Android软件编程有所帮助。

(0)

相关推荐

  • Android编程实现图片的浏览、缩放、拖动和自动居中效果

    本文实例讲述了Android编程实现图片的浏览.缩放.拖动和自动居中效果的方法.分享给大家供大家参考,具体如下: Touch.java /** * 图片浏览.缩放.拖动.自动居中 */ public class Touch extends Activity implements OnTouchListener { Matrix matrix = new Matrix(); Matrix savedMatrix = new Matrix(); DisplayMetrics dm; ImageVie

  • Android多点触控实现对图片放大缩小平移,惯性滑动等功能

    文章将在原有基础之上做了一些扩展功能: 1.图片的惯性滑动 2.图片缩放小于正常比例时,松手会自动回弹成正常比例 3.图片缩放大于最大比例时,松手会自动回弹成最大比例 实现图片的缩放,平移,双击缩放等基本功能的代码如下,每一行代码我都做了详细的注释 public class ZoomImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener , V

  • Android实现动态向Gallery中添加图片及倒影与3D效果示例

    本文实例讲述了Android实现动态向Gallery中添加图片及倒影与3D效果的方法.分享给大家供大家参考,具体如下: 在Android中gallery可以提供一个很好的显示图片的方式,实现上面的效果以及动态添加数据库或者网络上下载下来的图片资源.我们首先实现一个自定义的Gallery类. MyGallery.java: package nate.android.Service; import android.content.Context; import android.graphics.Ca

  • Android 图片缩放与旋转的实现详解

    本文使用Matrix实现Android实现图片缩放与旋转.示例代码如下: 复制代码 代码如下: package com.android.matrix;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Matrix;import android.graphics.drawable.BitmapDrawable

  • Android实现对图片放大、平移和旋转的功能

    先来看看要实现的效果图 在讲解中,需要大家提前了解一些关于图片绘制的原理的相关知识. 关于实现的流程 1.自定义View 2.获得操作图片的Bitmap 3.复写View的onTouchEvent()方法中的ACTION_DOWN,ACTION_POINTER_DOWN,ACTION_MOVE,ACTION_POINTER_UP以及ACTION_UP事件. 4.定义相应图片变化的Matrix矩阵,通过手势操作的变化来设置相应的Matrix. 5.完成最终的Matrix设置时,通过invalida

  • Android开发实现图片平移、缩放、倒影及旋转功能的方法

    本文实例讲述了Android开发实现图片平移.缩放.倒影及旋转功能的方法.分享给大家供大家参考,具体如下: 解析: 1)根据原来的图片创建新的图片 Bitmap modBm = Bitmap.createBitmap(bm.getWidth()+20, bm.getHeight()+20, bm.getConfig()); 2)设置到画布 Canvas canvas = new Canvas(modBm); 3)使用矩阵进行平移- Matrix matrix = new Matrix(); ma

  • 基于Android 实现图片平移、缩放、旋转同时进行

    前言 之前因为项目需求,其中使用到了图片的单击显示取消,图片平移缩放功能,昨天突然想再加上图片的旋转功能,在网上看了很多相关的例子,可是没看到能同时实现我想要的功能的. 需求: (1)图片平移.缩放.旋转等一系列操作后,图片需要自动居中显示. (2)图片旋转后选自动水平显示或者垂直显示 (3)图片在放大缩小的同时都能旋转 Demo实现部分效果截图 Demo主要代码 Java MainActivity.java package com.practice.noyet.rotatezoomimagev

  • android 图片操作(缩放移动) 实例代码

    view_show.xml 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?><LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"  android:orientation="vertical"  android:layout_width="match_par

  • Android实现手势滑动多点触摸缩放平移图片效果(二)

    上一篇已经带大家实现了自由的放大缩小图片,简单介绍了下Matrix:具体请参考:Android实现手势滑动多点触摸缩放平移图片效果,本篇继续完善我们的ImageView. 首先加入放大后的移动. 1.自由的进行移动 我们在onTouchEvent里面,加上移动的代码,当然了,必须长或宽大于屏幕才可以移动~~~ @Override public boolean onTouch(View v, MotionEvent event) { mScaleGestureDetector.onTouchEve

  • Android实现手势滑动多点触摸缩放平移图片效果

    现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位. 一.概述 想要做到图片支持多点触控,自由的进行缩放.平移,需要了解几个知识点:Matrix , GestureDetector , ScaleGestureDetector 以及事件分发机制,ps:不会咋办,不会你懂的. 1.Matrix 矩阵,看深入了都是3维矩阵的乘啊什么的,怪麻烦的~~ 其实这么了解下就行了: Matrix 数据结构:3维矩阵

  • Android中利用matrix 控制图片的旋转、缩放、移动

    本文主要讲解利用android中Matrix控制图形的旋转缩放移动,具体参见一下代码: 复制代码 代码如下: /**  * 使用矩阵控制图片移动.缩放.旋转  */  public class CommonImgEffectView extends View { private Context context ;      private Bitmap mainBmp , controlBmp ;      private int mainBmpWidth , mainBmpHeight , c

随机推荐