文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2022-06-06 08:59

关注

相信大家使用多点对图片进行缩放,平移的操作很熟悉了,大部分大图的浏览都具有此功能,有些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; 
 
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 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); 
    } 
  } 
   
  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: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

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯