最近公司要做一个戒指试戴的功能,就是把戒指通过手势移动到你指定的手指处,并且保存状态,方便下次进入时显示,可以参考APP“钻石快线”的试戴功能,
图片网上有很多的教程教你怎么把图片旋转,移动,缩放,等等,却没有教你保存状态,而且网上的教程都乱七八糟,都是复制粘贴,都不是自己想要的,所以有时间自己写一个,现在把实现步骤,代码,源码贴出来,共同学习!
* 图片的变化主要是matrix的变化,对matrix不懂的可以先了解下matrxi.
*
* 产品试戴的地方,需要保存戒指的位置,之前用TouchImageView(可以去网上找找,我项目里面也自带了)来处理,因为是别人写好的,要理解里面意思很难,想了很多种方法,比如保存当前的戒指图片位置,获取图片的四个点坐标, 画出正方形,在用图片来填充正方形,后来只能去记录移动,旋转,缩放的每一次事件,还是没 有解决,到了第四天用google在搜了一下类似的,总于找到一个,而且是我想要的已对角线的中 点为坐标,就很方便,也不会卡,现在说下思路
*
* 1.不要在ondraw()里面创建对象,做任何复杂的计算和操作。因为ondraw()被调用执行的次数 量大的惊人。所以在外面创建对象 new Matrix();new Paint ();
* 2.ontouchEvent的状态判断,event有很多种类型,要熟知。
* 3.已bitmap中心点坐标为中心
* 4.postTranslate///偏移量,只要相加就好了,postRotate//需要计算角度,,mScale大于1就是放大,少于1就是缩小
* 5.我这里把偏移量等数据放在本地,ShareFileUtils
* 6.在重新给戒指确定位置的时候,排序有要求按postTranslate,postRotate,postScale去做, 不然后果就很清楚了
* 7.// 旋轉,不能以当时旋转的对角线中心点,要以最后的对角线中心位置(通过偏移量计算),因为移动过程中可能没有去旋转,
* //以对角线为中心,在哪里都是可以的,所以:::: 当前的坐标=偏移量+最开始的对角线的坐标
布局文件和实例化控件的activity就不写了,主要就是2个自定义的view,一个做手势,一个显示。
FunnyView
public class FunnyView extends View {
private static final String TAG = "FUNNYVIEW";
/*
* 手指按下时可能是移动 也可能是拖动
*/
private static final int ZOOM = -1;
private static final int DRAG = 1;
private int mode = 0;
// 第一个触控点
private PointF startPointF = new PointF();
// 第二个触控点
private PointF mCurMovePointF = new PointF();
private float mDegree;
private float mScale;
private Bitmap mBitmap = null;
private Matrix mMatrix = null;
private Paint mPaint = null;
// bitmap的中心点
private PointF mCenterPoint = new PointF();
float scale_sx = 1;
float scale_sy = 1;
float rotate_degrees = 0;
float translate_dx = 0;
float translate_dy = 0;
boolean isload=false;//只让他在第二遍执行 ondraw()
public FunnyView(Context context) {
this(context, null, 0);
}
public FunnyView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
Log.i(TAG, "context, attrs");
}
public FunnyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public void getPicBitmap(Bitmap bitmap) {
// TODO Auto-generated method stub
mBitmap=bitmap;
Log.i(TAG, "getPicBitmap");
isload=true;
invalidate();
}
private void init(Context context) {
Log.i(TAG, "init");
ShareFileUtils.setContext(context);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.diamond);
mMatrix = new Matrix();
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setStrokeWidth(10);
mPaint.setAntiAlias(true);
// 计算bitmap中心点坐标
mCenterPoint.set(mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
ShareFileUtils.setFloat("getTryDiamandWidth", mBitmap.getWidth() / 2);// 将试戴的图片的宽高保存在数组里面
ShareFileUtils.setFloat("getTryDiamandHeight", mBitmap.getHeight() / 2);
}
@Override
protected void onDraw(Canvas canvas) {
if (isload) {
canvas.drawBitmap(mBitmap, mMatrix, mPaint);
}
super.onDraw(canvas);
}
/*
* 计算角度和缩放
*
* 这里使用余弦定理计算 容易理解 如果对android矩阵matrix底层原理清楚的同学 可以使用matrix计算出旋转角度和缩放量
*/
private void getRotationScale() {
// 角度
double a = distance4PointF(mCenterPoint, startPointF);
double b = distance4PointF(startPointF, 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);
PointF centerToStartMove = new PointF((startPointF.x - mCenterPoint.x),
(startPointF.y - mCenterPoint.y));
PointF centerToCurMove = new PointF(
(mCurMovePointF.x - mCenterPoint.x),
(mCurMovePointF.y - mCenterPoint.y));
// 向量叉乘结果, 如果结果为负数, 表示为逆时针, 结果为正数表示顺时针
float result = centerToStartMove.x * centerToCurMove.y
- centerToStartMove.y * centerToCurMove.x;
if (result < 0) {
newDegree = -newDegree;
}
mDegree = newDegree;
mScale = (float) (c / a);
}
/**
* 弧度换算成角度
*
* @return
*/
public static double radianToDegree(double radian) {
return radian * 180 / Math.PI;
}
/**
* 两个点之间的距离
*/
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);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// 注意getX()和getRawX()的区别
startPointF.set(event.getX(), event.getY());
mode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
float x = event.getX() - startPointF.x;
float y = event.getY() - startPointF.y;
mCenterPoint.x = mCenterPoint.x + x;
mCenterPoint.y = mCenterPoint.y + y;
startPointF.set(event.getX(), event.getY());
mMatrix.postTranslate(x, y);// 偏移量,只要相加就好了
translate_dx = translate_dx + (x);
translate_dy = translate_dy + (y);
// Log.i(TAG, "x="+x+"y="+y);
ShareFileUtils.setFloat("translate_dx", translate_dx);// 将Translate的位置保存在数组里面
ShareFileUtils.setFloat("translate_dy", translate_dy);
} else if (mode == ZOOM) {
mCurMovePointF.set(event.getX(), event.getY());
getRotationScale();
/*
* 旋转和缩放的中心点都是bitmap的中心点,根据需求可以更改。
*/
// 旋转
mMatrix.postRotate(mDegree, mCenterPoint.x, mCenterPoint.y);// 旋转的角度,X,Y的坐标
// 缩放
mMatrix.postScale(mScale, mScale, mCenterPoint.x,mCenterPoint.y);
// mScale大于1就是放大,少于1就是缩小
scale_sx = scale_sx *mScale;
scale_sy = scale_sy*mScale;
// Log.i(TAG, "mScale=" + mScale + " mCenterPoint.x="+ mCenterPoint.x + "mCenterPoint.y=" + mCenterPoint.y);
// Log.i(TAG, "mDegree=" + mDegree);
ShareFileUtils.setFloat("scale_sx", scale_sx);// 将缩放的位置保存在数组里面,因为是正方形,所以x,y是一样的
ShareFileUtils.setFloat("scale_sy", scale_sy);
rotate_degrees = rotate_degrees + mDegree;//360度为一圈
ShareFileUtils.setFloat("rotate_degrees", rotate_degrees);// 将Rotate,旋转,的位置保存在数组里面
// 重新赋值起始点
startPointF.set(mCurMovePointF);
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = 0;
break;
case MotionEvent.ACTION_CANCEL:
mode = 0;
break;
}
invalidate();
// 返回true 事件不再往下分发
return true;
}
public void bitmapRecycle()
{
if (mBitmap!=null&& !mBitmap.isRecycled()) {
Log.i(TAG, "mBitmap.recycle()");
mBitmap.recycle();
}
}
}
public class SelectedDiamondPosition extends View {
private static final String TAG = "SELECTEDDIAMONDPOSITION";
private float[] position;
private Context context;
int widthScreen;
int heightScreen;
Matrix matrix;
Bitmap mFieldBitmap = null;
boolean isload=false;//只让他在第二遍执�? ondraw()
float topHight=0;//标题栏的高度
public SelectedDiamondPosition(Context context) {
super(context);
Log.i(TAG, "context");
}
public SelectedDiamondPosition(Context context, AttributeSet attrs,int defStyle) {
super(context, attrs, defStyle);
Log.i(TAG, "context, attrs, defStyle)");
}
public SelectedDiamondPosition(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i(TAG, "context, attrs");
init(context);
}
public void getPicBitmap(Bitmap bitmap) {
// TODO Auto-generated method stub
this.mFieldBitmap = bitmap;
Log.i(TAG, "getPicBitmap");
isload=true;
invalidate();//注意
//Invalidate()之后:
//...OnPaint()->OnPrepareDC()->OnDraw()
//所以只是刷新在OnPaint()和OnDraw()函数中的绘图语句。其它地方没有影响。
}
public void init(Context context) {
Log.i(TAG, "init");
ShareFileUtils.setContext(context);
DisplayMetrics dm = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(dm);
widthScreen = dm.widthPixels;
heightScreen = dm.heightPixels;
// mFieldBitmap = BitmapFactory.decodeResource(this.getResources(),
// R.drawable.diamond);
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
try {
super.onDraw(canvas);
Log.i(TAG, "onDraw");
if (isload) {
matrix = new Matrix();// 矩阵; 模型;
// 去除锯齿毛边
canvas.setDrawFilter(new PaintFlagsDrawFilter(0,
Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
canvas.save();
Log.i(TAG,"translate_dx_ok="+ ShareFileUtils.getFloat("translate_dx", 0));
Log.i(TAG,"translate_dy_ok="+ ShareFileUtils.getFloat("translate_dy", 0));
float scale_sx = ShareFileUtils.getFloat("scale_sx", 1);
float scale_sy = ShareFileUtils.getFloat("scale_sy", 1);
float rotate_degrees = ShareFileUtils.getFloat("rotate_degrees", 0);
float translate_dx = ShareFileUtils.getFloat("translate_dx", 0);
float translate_dy = ShareFileUtils.getFloat("translate_dy", 0);
float getTryDiamandWidth = ShareFileUtils.getFloat("getTryDiamandWidth", 1);
float getTryDiamandHeight = ShareFileUtils.getFloat("getTryDiamandHeight", 1);
Log.i(TAG, "getTryDiamandWidth=" + getTryDiamandWidth+ "getTryDiamandHeight=" + getTryDiamandHeight);
Log.i(TAG, "translate_dx=" + translate_dx + "translate_dy"+ translate_dy);
Log.i(TAG, "rotate_degrees=" + rotate_degrees);
Log.i(TAG, "scale_sx=" + scale_sx + "scale_sy=" + scale_sy);
Log.i(TAG, "topHight="+topHight);
matrix.postTranslate(translate_dx, translate_dy);// 平移
matrix.postRotate(rotate_degrees,translate_dx + getTryDiamandWidth, translate_dy+ getTryDiamandHeight);
// 旋轉,不能以当时旋转的对角线中心点,要以最后的对角线中心位置(通过偏移量计算),因为移动过程中可能没有去旋转,
// 以对角线为中心,在哪里都是可以的,所以:::�? 当前的坐�?=偏移�?+�?�?始的对角线的坐标+标题�?
matrix.postScale(scale_sx, scale_sy, translate_dx+ getTryDiamandWidth, translate_dy + getTryDiamandHeight);// 縮放,同�?
//
// matrix.setScale(1.75f, 1.75f);//三种不同的情�?
// matrix.resRotate(15);
// matrix.postTranslate(0, 0);
canvas.drawBitmap(mFieldBitmap, matrix, null);
canvas.restore();
}
} catch (Exception e) {
System.out.println("MyImageView -> onDraw() Canvas: trying to use a recycled bitmap");
}
// isload=false;
}
public void bitmapRecycle() {
if (mFieldBitmap != null && !mFieldBitmap.isRecycled()) {
Log.i(TAG, "mFieldBitmap.recycle()");
mFieldBitmap.recycle();
}
}
}
免费项目的源码:下载导入eclipse,直接运行,studio也很方便,就把代码拷进studio的项目就好了,之所以不用studio是因为本人在弄unity3D,unity3D项目只能导入eclipse中.
源码地址:http://download.csdn.net/detail/tangpengtp/9370559
亲们,给我点个赞或者评论算是对我的支持,谢谢