项目要求模仿时光手杖APP做一个可平移、旋转、缩放的控件,于是就开始研究了起来,网上找的方案加上自己的思考,得出了以下完美版平移、缩放、旋转控件,双指缩放旋转,单指缩放旋转都支持。
我们做出来的控件效果会比上图略好,时光手帐不支持双指的旋转,我们这个控件是支持的。
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
if (mOnTouchListener != null) {
mOnTouchListener.onTouch(this);
}
setBorder(true);
oriLeft = getLeft();
oriRight = getRight();
oriTop = getTop();
oriBottom = getBottom();
lastTrueY = event.getRawY();
lastTrueX = event.getRawX();
oriX = (int) event.getRawX();
oriY = (int) event.getRawY();
lastSpacing = getSpacingRelateMidPoint(oriX,oriY);
textRotationDegree = getRotation();
scale = getScaleX();
if (initWidth == 0) {
initWidth = oriRight - oriLeft;
if (initWidth <= 0) {
initWidth = minWidth;
}
}
if (initHeight == 0) {
initHeight = oriBottom - oriTop;
if (initHeight <= 0) {
initHeight = minHeight;
}
}
LogUtil.d(TAG + " initWidth = " + initWidth + " ,initHeight = " + initHeight);
moveType = 1;
actionX = event.getRawX();
actionY = event.getRawY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
moveType = 2;
spacing = getSpacing(event);
degree = getDegree(event);
break;
case MotionEvent.ACTION_MOVE:
if (mOnTouchListener != null) {
mOnTouchListener.onMoveEvent(this);
}
updateLocation();//更新图标图片可见矩形
float tempRawX = event.getRawX();
float tempRawY = event.getRawY();
float dx = tempRawX - lastTrueX;
float dy = tempRawY - lastTrueY;
lastTrueX = tempRawX;
lastTrueY = tempRawY;
if (event.getPointerCount() == 1 && dragDirection == RIGHT_BOTTOM) {
Point cen = new Point(oriLeft+(oriRight-oriLeft)/2,oriTop+(oriBottom-oriTop)/2);
Point first = new Point(oriX,oriY);
Point second = new Point((int) tempRawX,(int) tempRawY);
//旋转
textRotationDegree += angle(cen,first,second);
setRotation(textRotationDegree);
//缩放
float tempScale = 1.0f;
lastSpacing = getSpacing(cen,first);
currentSpacing = getSpacing(cen,second);
tempScale = currentSpacing/lastSpacing;
//想增加拖动缩放的灵敏度,将0.02增大即可
if (tempScale < 1) {
tempScale -= 0.008;
} else if (tempScale > 1) {
tempScale += 0.008;
}
scale = scale * tempScale;
if(scale >= MAX_SCALE){
scale = MAX_SCALE;
}else if(scale <= MIN_SCALE){
scale = MIN_SCALE;
}
setScaleX(scale);
setScaleY(scale);
oriX = (int) tempRawX;
oriY = (int) tempRawY;
lastSpacing = currentSpacing;
resetCornerSize();
break;
}
if (moveType == 1) {
translationX = getTranslationX();
translationY = getTranslationY();
if (isTranstX(event.getRawX()))
translationX = translationX + event.getRawX() - actionX;
if (isTranstY(event.getRawY()))
translationY = translationY + event.getRawY() - actionY;
setTranslationX(translationX);
setTranslationY(translationY);
actionX = event.getRawX();
actionY = event.getRawY();
} else if (moveType == 2) {
scale = scale * getSpacing(event) / spacing;
setScaleX(scale);
setScaleY(scale);
rotation = rotation + getDegree(event) - degree;
if (rotation > 360) {
rotation = rotation - 360;
}
if (rotation < -360) {
rotation = rotation + 360;
}
setRotation(rotation);
resetCornerSize();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (mOnTouchListener != null) {
mOnTouchListener.onActionUp();
}
moveType = 0;
dragDirection = CENTER;
requestLayout();
}
public float angle(Point cen, Point first, Point second)
{
float dx1, dx2, dy1, dy2;
dx1 = first.x - cen.x;
dy1 = first.y - cen.y;
dx2 = second.x - cen.x;
dy2 = second.y - cen.y;
// 计算三边的平方
float ab2 = (second.x - first.x) * (second.x - first.x) + (second.y - first.y) * (second.y - first.y);
float oa2 = dx1*dx1 + dy1*dy1;
float ob2 = dx2 * dx2 + dy2 *dy2;
// 根据两向量的叉乘来判断顺逆时针
boolean isClockwise = ((first.x - cen.x) * (second.y - cen.y) - (first.y - cen.y) * (second.x - cen.x)) > 0;
// 根据余弦定理计算旋转角的余弦值
double cosDegree = (oa2 + ob2 - ab2) / (2 * Math.sqrt(oa2) * Math.sqrt(ob2));
// 异常处理,因为算出来会有误差绝对值可能会超过一,所以需要处理一下
if (cosDegree > 1) {
cosDegree = 1;
} else if (cosDegree < -1) {
cosDegree = -1;
}
// 计算弧度
double radian = Math.acos(cosDegree);
// 计算旋转过的角度,顺时针为正,逆时针为负
return (float) (isClockwise ? Math.toDegrees(radian) : -Math.toDegrees(radian));
}
private LayoutParams setLayoutParams(View view) {
LayoutParams ltparams = (LayoutParams) view.getLayoutParams();
currentCornerWidth = (int) (dp2px(19) / scale);
currentCornerHeight = (int) (dp2px(19) / scale);
ltparams.width = currentCornerWidth;
ltparams.height = currentCornerHeight;
return ltparams;
}
// 触碰两点间距离
private float getSpacing(MotionEvent event) {
//通过三角函数得到两点间的距离
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
// 触碰两点间距离
private float getSpacing(Point start,Point end) {
//通过三角函数得到两点间的距离
float x = end.x - start.x;
float y = end.y - start.y;
return (float) Math.sqrt(x * x + y * y);
}
// 获得某点相对于中点的距离
private float getSpacingRelateMidPoint(int pointX,int pointY) {
//通过三角函数得到两点间的距离
Point midPoint = new Point(oriLeft+(oriRight-oriLeft),oriTop+(oriBottom-oriTop));
float x = pointX - midPoint.x;
float y = pointY - midPoint.y;
return (float) Math.sqrt(x * x + y * y);
}
// 取旋转角度
private float getDegree(MotionEvent event) {
//得到两个手指间的旋转角度
double delta_x = event.getX(0) - event.getX(1);
double delta_y = event.getY(0) - event.getY(1);
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
/**
* 触摸点为中心->>移动
*/
private void center(int dx, int dy) {
// isTopLimit = false;
// isBottomLimit = false;
int left = getLeft() + dx;
int top = getTop() + dy;
int right = getRight() + dx;
int bottom = getBottom() + dy;
if (left < 0) {
left = 0;
right = left + getWidth();
}
if (right > screenWidth) {
right = screenWidth;
left = right - getWidth();
}
if (top < 0) {
top = 0;
bottom = top + getHeight();
// isTopLimit = true;
}
if (bottom > screenHeight) {
bottom = screenHeight;
top = bottom - getHeight();
// isBottomLimit = true;
}
oriLeft = left;
oriTop = top;
oriRight = right;
oriBottom = bottom;
}
/**
* 触摸点为上边缘
*/
private void top(int dy) {
// isTopLimit = false;
// isBottomLimit = false;
int lastTop = oriTop;
oriTop += dy;
int newTop = oriTop;
Log.e("新旧长度:", lastTop + "," + newTop);
if (isImage) {
oriLeft += dy / 2;
oriRight -= dy / 2;
if (oriLeft < 0) {
oriLeft = 0;
}
if (oriRight - oriLeft < minWidth) {
oriLeft = oriRight - minWidth;
}
if (oriRight > screenWidth) {
oriRight = screenWidth;
}
if (oriRight - oriLeft < minWidth) {
oriRight = oriLeft + minWidth;
}
}
if (oriTop < 0) {
oriTop = 0;
// isTopLimit = true;
}
if (oriBottom - oriTop < minHeight) {
oriTop = oriBottom - minHeight;
}
}
/**
* 触摸点为下边缘
*/
private void bottom(int dy) {
// isTopLimit = false;
// isBottomLimit = false;
oriBottom += dy;
if (isImage) {
oriLeft -= dy / 2;
oriRight += dy / 2;
if (oriLeft < 0) {
oriLeft = 0;
}
if (oriRight - oriLeft < minWidth) {
oriLeft = oriRight - minWidth;
}
if (oriRight > screenWidth) {
oriRight = screenWidth;
}
if (oriRight - oriLeft < minWidth) {
oriRight = oriLeft + minWidth;
}
}
if (oriBottom > screenHeight) {
oriBottom = screenHeight;
// isBottomLimit = true;
}
if (oriBottom - oriTop < minHeight) {
oriBottom = minHeight + oriTop;
}
}
/**
* 触摸点为右边缘
*/
private void right(int dx) {
// isTopLimit = false;
// isBottomLimit = false;
oriRight += dx;
if (isImage) {
oriTop -= dx / 2;
oriBottom += dx / 2;
if (oriTop < 0) {
oriTop = 0;
// isTopLimit = true;
}
if (oriBottom - oriTop < minHeight)
oriTop = oriBottom = minHeight;
if (oriBottom > screenHeight) {
oriBottom = screenHeight;
// isBottomLimit = true;
}
if (oriBottom - oriTop < minHeight) {
oriBottom = minHeight + oriTop;
}
}
if (oriRight > screenWidth) {
oriRight = screenWidth;
}
if (oriRight - oriLeft < minWidth) {
oriRight = oriLeft + minWidth;
}
}
/**
* 触摸点为左边缘
*/
private void left(int dx) {
// isTopLimit = false;
// isBottomLimit = false;
oriLeft += dx;
if (isImage) {
oriTop += dx / 2;
oriBottom -= dx / 2;
if (oriTop < 0) {
oriTop = 0;
// isTopLimit = true;
}
if (oriBottom - oriTop < minHeight)
oriTop = oriBottom = minHeight;
if (oriBottom > screenHeight) {
oriBottom = screenHeight;
// isBottomLimit = true;
}
if (oriBottom - oriTop < minHeight) {
oriBottom = minHeight + oriTop;
}
}
if (oriLeft < 0) {
oriLeft = 0;
}
if (oriRight - oriLeft < minWidth) {
oriLeft = oriRight - minWidth;
}
}
以上就是该控件的核心代码和功能介绍。实现平移很简单,实现旋转和缩放的话,重点在于角度和距离的计算,因此需要一定的数学知识。以上代码是从项目中截取的重点部分,可能无法直接套用或者缺少一些东西,但是实现思路很明确了,提供给大家做个参考或者移植到自己的项目中。希望能帮到跟我遇到同样需求的攻城狮。有疑问可在下方留言。
该类完整代码下载地址:https://download.csdn.net/download/yonghuming_jesse/11906385
另,附上博主自己的淘宝客项目下载码(可查淘宝商品优惠券,使用优惠券购物后可加微信客服返现,返现额度全网最高,客服微信android_jesse):
博主上传资源下载链接:
全屏播放视频不拉伸源码:
https://download.csdn.net/download/yonghuming_jesse/10646274
科大讯飞语音评测服务接入源码:
https://download.csdn.net/download/yonghuming_jesse/10616924
android饺子播放器使用源码:
https://download.csdn.net/download/yonghuming_jesse/10619119
视频播放前显示视频第一帧源码:
https://download.csdn.net/download/yonghuming_jesse/10646332