我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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="class="lazy" data-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; 
 
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; 
   
  private Bitmap mBitmap; 
   
  private PointF mCenterPoint = new PointF(); 
   
  private int mViewWidth, mViewHeight; 
   
  private float mDegree = DEFAULT_DEGREE; 
   
  private float mScale = DEFAULT_SCALE; 
   
  private Matrix matrix = new Matrix(); 
   
  private int mViewPaddingLeft; 
   
  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; 
   
  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; 
   
  private int framePadding = DEFAULT_FRAME_PADDING; 
   
  private int frameColor = DEFAULT_FRAME_COLOR; 
   
  private int frameWidth = DEFAULT_FRAME_WIDTH; 
   
  private boolean isEditable = DEFAULT_EDITABLE; 
  private DisplayMetrics metrics; 
  private PointF mPreMovePointF = new PointF(); 
  private PointF mCurMovePointF = new PointF(); 
   
  private int offsetX; 
   
  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(); 
  } 
   
  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 class="lazy" data-srcDrawble = mTypedArray.getDrawable(R.styleable.SingleTouchView_class="lazy" data-src); 
    mBitmap = drawable2Bitmap(class="lazy" data-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); 
    } 
  } 
   
  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); 
  } 
   
  public void setImageBitamp(Bitmap bitmap){ 
    this.mBitmap = bitmap; 
    transformDraw(); 
  } 
   
  public void setImageDrawable(Drawable drawable){ 
    this.mBitmap = drawable2Bitmap(drawable); 
    transformDraw(); 
  } 
   
  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; 
    } 
  } 
   
  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(); 
  } 
   
  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; 
  } 
   
  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); 
  } 
   
  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; 
  } 
   
  public int getMaxValue(Integer...array){ 
    List<Integer> list = Arrays.asList(array); 
    Collections.sort(list); 
    return list.get(list.size() -1); 
  } 
   
  public int getMinValue(Integer...array){ 
    List<Integer> list = Arrays.asList(array); 
    Collections.sort(list); 
    return list.get(0); 
  } 
   
  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; 
  } 
   
  public static double radianToDegree(double radian) { 
    return radian * 180 / Math.PI; 
  } 
   
  public static double degreeToRadian(double degree) { 
    return degree * Math.PI / 180; 
  } 
   
  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; 
  } 
   
  public void setImageDegree(float degree) { 
    if(this.mDegree != degree){ 
      this.mDegree = degree; 
      transformDraw(); 
    } 
  } 
  public float getImageScale() { 
    return mScale; 
  } 
   
  public void setImageScale(float scale) { 
    if(this.mScale != scale){ 
      this.mScale = scale; 
      transformDraw(); 
    }; 
  } 
  public Drawable getControlDrawable() { 
    return controlDrawable; 
  } 
   
  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(); 
  } 
   
  public void setControlLocation(int location) { 
    if(this.controlLocation == location) 
      return; 
    this.controlLocation = location; 
    transformDraw(); 
  } 
  public int getControlLocation() { 
    return controlLocation; 
  } 
  public PointF getCenterPoint() { 
    return mCenterPoint; 
  } 
   
  public void setCenterPoint(PointF mCenterPoint) { 
    this.mCenterPoint = mCenterPoint; 
    adjustLayout(); 
  } 
  public boolean isEditable() { 
    return isEditable; 
  } 
   
  public void setEditable(boolean isEditable) { 
    this.isEditable = isEditable; 
    invalidate(); 
  } 
   
  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:class="lazy" data-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通过多点触控的方式对图片进行缩放的实例代码Android实现手指触控图片缩放功能cocos creator Touch事件应用(触控选择多个子节点的实例)微信小程序 触控事件详细介绍Android开发实例之多点触控程序jQuery和hwSlider实现内容响应式可触控滑动切换效果附源码下载(二)Android多点触控实现对图片放大缩小平移,惯性滑动等功能unity实现多点触控代码jquery mobile的触控点击事件会多次触发问题的解决方法CDC与BG-CDC的含义电容触控学习整理


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

相信大家使用多点对图片进行缩放,平移的操作很熟悉了,大部分大图的浏览都具有此功能,有些app还可以对图片进行旋转操作,QQ的大图浏览就可以对图片进行旋转操作,大家都知道对图片进行缩放,平移,旋转等操作可以使用Matrix来实现,Matrix
2022-06-06

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

先来看看要实现的效果图在讲解中,需要大家提前了解一些关于图片绘制的原理的相关知识。 关于实现的流程 1、自定义View 2、获得操作图片的Bitmap 3、复写View的onTouchEvent()方法中的ACTIO
2022-06-06

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

文章将在原有基础之上做了一些扩展功能: 1.图片的惯性滑动 2.图片缩放小于正常比例时,松手会自动回弹成正常比例 3.图片缩放大于最大比例时,松手会自动回弹成最大比例实现图片的缩放,平移,双击缩放等基本功能的代码如下,每一行代码我都做了
2022-06-06

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

本文实例讲述了Android开发实现图片平移、缩放、倒影及旋转功能的方法。分享给大家供大家参考,具体如下:解析:1)根据原来的图片创建新的图片Bitmap modBm = Bitmap.createBitmap(bm.getWidth()+
2023-05-30

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

前言 之前因为项目需求,其中使用到了图片的单击显示取消,图片平移缩放功能,昨天突然想再加上图片的旋转功能,在网上看了很多相关的例子,可是没看到能同时实现我想要的功能的。 需求:(1)图片平移、缩放、旋转等一系列操作后,图片需要自动居中显示。
2022-06-06

Android多点触控实现图片自由缩放

Android多点触控涉及到的知识点 1、ScaleGestureDetector 2、OnScaleGestureListener 3、Matrix 4、OnTouchListener 四个知识点需要了解一下,需要注意的是Matri
2022-06-06

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

现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位。 一、概述 想要做到图片支持多点触控,自由的进行缩放、平移,需要了解几个知识点:Matrix
2022-06-06

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

上一篇已经带大家实现了自由的放大缩小图片,简单介绍了下Matrix;具体请参考:Android实现手势滑动多点触摸缩放平移图片效果,本篇继续完善我们的ImageView。 首先加入放大后的移动。 1、自由的进行移动 我们在onTouchEv
2022-06-06

Android实现多点触控,自由缩放图片的实例代码

Android多点触控涉及到的知识点 1、ScaleGestureDetector 2、OnScaleGestureListener 3、Matrix 4、OnTouchListener 四个知识点需要了解一下,需要注意的是Matrix在内
2022-06-06

Android多点触控技术实战 针对图片自由缩放和移动

在上一篇文章中我带着大家一起实现了Android瀑布流照片墙的效果,虽然这种效果很炫很酷,但其实还只能算是一个半成品,因为照片墙中所有的图片都是只能看不能点的。因此本篇文章中,我们就来对这一功能进行完善,加入点击图片就能浏览大图的功能,并且
2022-06-06

基于RxPaparazzo实现图片裁剪、图片旋转、比例放大缩小功能

前言:基于RxPaparazzo的图片裁剪,图片旋转、比例放大|缩小。效果: 开发环境:AndroidStudio2.2.1+gradle-2.14.1 涉及知识:1.Material Design(CardView+Coordinator
2023-05-31

Android编程实现图片放大缩小功能ZoomControls控件用法实例

本文实例讲述了Android编程实现图片放大缩小功能ZoomControls控件用法。分享给大家供大家参考,具体如下:MainActivity代码:package example.com.myapplication;import andro
2023-05-30

Vue使用v-viewer插件实现图片预览和缩放和旋转等功能(推荐)

v-viewer是一个基于viewerjs封装的vue图片预览组件,有预览缩放拉伸旋转切换拖拽等功能,支持配置化,这篇文章主要介绍了Vue使用v-viewer插件实现图片预览和缩放和旋转等功能,需要的朋友可以参考下
2023-02-09

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录