先看效果图
具体功能:
1.动臂、斗杆、铲斗任意角度变换显示
2.触摸实现拖拉效果
实现步骤:
1.找图,切图(PS用的不熟,只能用美图秀秀傻瓜式切图),最后切得动臂,斗杆,铲斗和挖掘整车部分图
2.利用美图秀秀确定各切图的大小和相应的连接点位置
3.Android自定义View 主要就是onMeasure onLayout onDraw的过程。本文主要针对onDraw 过程,在初始化过程中需要计算屏幕尺寸对图片进行缩放。
图片的缩放,旋转,平移主要通过Matrix实现,对触摸的响应通过动臂、斗杆、铲斗的矩形区域确定,而且当三个区域重叠优先级是铲斗>斗杆>动臂 。如下图所示触摸铲斗红色区域,优先响应铲斗旋转。
4.触摸移动角度和逆时针顺时针手势的判断,通过acos计算角度通过与轴心交的前后两点之间的向量关系确定顺时针还是逆时针旋转。
5.具体代码
package com.cwd.displayer.ui.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.ConvertUtils;
import com.blankj.utilcode.util.ScreenUtils;
import com.cwd.displayer.R;
/**
* 挖掘机的动臂、斗杆、铲斗动态图
*/
public class ExcavatorView extends View {
private Paint mPaint;
private float scaleW;
private float scaleH;
private float scaleXY;
private Bitmap catBody;
private Bitmap catBoom;
private Bitmap catArm;
private Bitmap catBucket;
private int screenW;
private int screenH;
private float bodyH;
private float bodyW;
private float boomH;
private float boomW;
private float armH;
private float armW;
private float bucketH;
private float bucketW;
private Rect rectBody;
private Rect rectBoom;
private Rect rectArm;
private Rect rectBucket;
private RectF rectFBody;
private RectF rectFBoom;
private RectF rectFArm;
private RectF rectFBucket;
float boomCircleX;
float boomCircleY;
float armCircleX;
float armCircleY;
float bucketCircleX;
float bucketCircleY;
float bodyCenterX;
float bodyCenterY;
RectF rectFBoomEnd;
RectF rectFArmEnd;
RectF rectFBucketEnd;
public ExcavatorView(Context context) {
super(context);
initView();
}
public ExcavatorView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView();
}
public ExcavatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public ExcavatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView();
}
private void initView(){
mPaint = new Paint();
mPaint.setAntiAlias(false);
catBody = BitmapFactory.decodeResource(getResources(),R.drawable.cat_body);
catBoom = BitmapFactory.decodeResource(getResources(),R.drawable.cat_boom);
catArm = BitmapFactory.decodeResource(getResources(),R.drawable.cat_arm);
catBucket = BitmapFactory.decodeResource(getResources(),R.drawable.cat_bucket);
screenW = ScreenUtils.getScreenWidth();
screenH = ScreenUtils.getScreenHeight();
Log.i("xsl_init","screenW="+screenW+",screenH="+screenH);
scaleW = (float) (845.0/screenW);
scaleH = (float) (1347.0/screenH);
Log.i("xsl_measure","scaleW="+scaleW+",scaleH="+scaleH);
if (scaleW<1 && scaleH<1){
//如果宽高都够,那么不进行缩放,原图显示
scaleXY=1;
}else {
//只要宽高有一个大于1
//则进行缩放
if(scaleW>=scaleH){
scaleXY =(float) ((float) Math.round(scaleW*10)/10);
}else{
scaleXY =(float) ((float) Math.round(scaleH*10)/10);
}
}
Log.i("xsl_measure","scaleW="+scaleW+",scaleH="+scaleH+",scaleXY="+scaleXY);
bodyH = catBody.getHeight()/scaleXY;
bodyW = catBody.getWidth()/scaleXY;
Log.i("xsl_measure","bodyH="+bodyH+",bodyW="+bodyW);
boomH = catBoom.getHeight()/scaleXY;
boomW = catBoom.getWidth()/scaleXY;
armH = catArm.getHeight()/scaleXY;
armW = catArm.getWidth()/scaleXY;
bucketH = catBucket.getHeight()/scaleXY;
bucketW = catBucket.getWidth()/scaleXY;
rectBody = new Rect(0,0,catBody.getWidth(),catBody.getHeight());
rectBoom = new Rect(0,0,catBoom.getWidth(),catBoom.getHeight());
rectArm = new Rect(0,0,catArm.getWidth(),catArm.getHeight());
rectBucket = new Rect(0,0,catBucket.getWidth(),catBucket.getHeight());
rectFBody = new RectF(screenW-bodyW,(screenH-bodyH)/2,screenW,(screenH+bodyH)/2);
bodyCenterX = screenW-bodyW*3.5f/5;
bodyCenterY = screenH/2;
//rectFBoom = new RectF(bodyCenterX-boomW,bodyCenterY-boomH,bodyCenterX,bodyCenterY);
rectFBoom = new RectF(0,0,catBoom.getWidth(),catBoom.getHeight());
rectFArm = new RectF(0,0,catArm.getWidth(),catArm.getHeight());
rectFBucket = new RectF(0,0,catBucket.getWidth(),catBucket.getHeight());
boomCircleX=bodyCenterX;
boomCircleY=bodyCenterY-(catBoom.getHeight()-38)/scaleXY;
destBoomC = boomC.clone();
destArmC = armC1.clone();
armDestC2 = armC2.clone();
destBucketC = bucketC1.clone();
bucketDestC2 = bucketC2.clone();
rectFBoomEnd=new RectF();
rectFArmEnd=new RectF();
rectFBucketEnd=new RectF();
boomMatrix = new Matrix();
armMatrix = new Matrix();
bucketMatrix = new Matrix();
double x = getDegree(new Point(0,0),new Point(1,0),new Point(0,1));
double y = getDegree(new Point(0,0),new Point(0,1),new Point(1,0));
Log.i("xsl_measure","xDegree="+x+",yDegree="+y);
}
//动臂角度和中心点
int degreeBoom =0;
float[] boomC=new float[]{280.0f,38.0f};
float[] destBoomC;
//斗杆角度和中心点
int degreeArm =0;
float[] armC1=new float[]{15.0f,81.0f};//动臂——斗杆轴点 动臂上
float[] destArmC;
float[] armC2=new float[]{65.0f,15.0f};//动臂——斗杆轴点 斗杆上
float[] armDestC2;
//铲斗的角度和中心
int degreeBucket =0;
float[] bucketC1=new float[]{223.0f,68.0f};//斗杆-铲斗轴点 斗杆上
float[] destBucketC;
float[] bucketC2=new float[]{58.0f,58.0f};//斗杆-铲斗轴点 铲斗上
float[] bucketDestC2;
final int TypeBoom=1;
final int TypeArm=2;
final int TypeBucket=3;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(0xffff0000);
paint.setStrokeWidth(6f);
/***********动臂相关计算**********/
Matrix boomMatrix = new Matrix();
boomMatrix.preScale((float) 1.0/scaleXY,(float) 1.0/scaleXY);
boomMatrix.preRotate(degreeBoom,boomCircleX,boomCircleY);
boomMatrix.mapPoints(destBoomC,boomC);
Log.i("xsl","degreeBoom="+degreeBoom);
Log.i("xsl","[x,y]=["+destBoomC[0]+","+destBoomC[1]+"]");
Log.i("xsl","[boomCircleX,boomCircleY]=["+boomCircleX+","+boomCircleY+"]");
boomMatrix.postTranslate(boomCircleX-destBoomC[0],boomCircleY-destBoomC[1]);
boomMatrix.mapRect(rectFBoomEnd,rectFBoom);
boomMatrix.mapPoints(destArmC,armC1);//斗杆的轴点
/************斗杆相关计算**********/
Matrix armMatrix = new Matrix();
armMatrix.preScale((float) 1.0/scaleXY,(float) 1.0/scaleXY);
//以动臂上的斗杆连接点为轴心
armMatrix.preRotate(degreeBoom+degreeArm,destArmC[0],destArmC[1]);
//斗杆图片上的连接点
armMatrix.mapPoints(armDestC2,armC2);
armMatrix.postTranslate(destArmC[0]-armDestC2[0],destArmC[1]-armDestC2[1]);
armMatrix.mapRect(rectFArmEnd,rectFArm);
armMatrix.mapPoints(destBucketC,bucketC1);//铲斗的轴心
/*******铲斗相关计算********/
Matrix bucketMatrix = new Matrix();
bucketMatrix.preScale((float) 1.0/scaleXY,(float) 1.0/scaleXY);
//以斗杆上的铲斗连接点为轴心
bucketMatrix.preRotate(degreeBoom+degreeArm+degreeBucket,destBucketC[0],destBucketC[1]);
//铲斗图片上的连接点
bucketMatrix.mapPoints(bucketDestC2,bucketC2);
bucketMatrix.postTranslate(destBucketC[0]-bucketDestC2[0],destBucketC[1]-bucketDestC2[1]);
bucketMatrix.mapRect(rectFBucketEnd,rectFBucket);
//画工作装置的区域
paint.setColor(0xff00ff00);
canvas.drawRect(rectFBoomEnd,paint);
paint.setColor(0xff0000ff);
canvas.drawRect(rectFArmEnd,paint);
paint.setColor(0xffff0000);
canvas.drawRect(rectFBucketEnd,paint);
//画铲斗
canvas.drawBitmap(catBucket,bucketMatrix,mPaint);
//画斗杆
canvas.drawBitmap(catArm,armMatrix,mPaint);
//画动臂
canvas.drawBitmap(catBoom,boomMatrix,mPaint);
//画车辆
canvas.drawBitmap(catBody,
rectBody,
rectFBody,mPaint);
//动臂的轴点
paint.setColor(0xff000000);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
// canvas.drawPoint(boomCircleX,boomCircleY,paint);
// canvas.drawPoint(destArmC[0],destArmC[1],paint);
// canvas.drawPoint(destBucketC[0],destBucketC[1],paint);
canvas.drawCircle(boomCircleX,boomCircleY,1,paint);
canvas.drawCircle(destArmC[0],destArmC[1],1,paint);
canvas.drawCircle(destBucketC[0],destBucketC[1],1,paint);
// degreeBoom--;
// degreeArm--;
// degreeBucket--;
// invalidate();
}
private Matrix boomMatrix;
private Matrix armMatrix;
private Matrix bucketMatrix;
float posX=0f;
float posY=0f;
float posX_old=0f;
float posY_old=0f;
@Override
public boolean onTouchEvent(MotionEvent event) {
posX = (float) event.getX();
posY = (float) event.getY();
if (event.getAction()==MotionEvent.ACTION_DOWN){
posX_old = (float) event.getX();
posY_old = (float) event.getY();
}
if (event.getAction()==MotionEvent.ACTION_MOVE) {
posX = (float) event.getX();
posY = (float) event.getY();
if (rectFBucketEnd.contains(posX, posY)) {
//degreeBucket =
double degree = getDegree(new Point((int)destBucketC[0],(int)destBucketC[1]),
new Point((int)posX,(int)posY),
new Point((int)posX_old,(int)posY_old)
);
degreeBucket =(int) (degreeBucket+degree);
} else if (rectFArmEnd.contains(posX, posY)) {
double degree = getDegree(new Point((int)destArmC[0],(int)destArmC[1]),
new Point((int)posX,(int)posY),
new Point((int)posX_old,(int)posY_old)
);
degreeArm =(int) (degreeArm+degree);
} else if (rectFBoomEnd.contains(posX, posY)) {
double degree = getDegree(new Point((int)boomCircleX,(int)boomCircleY),
new Point((int)posX,(int)posY),
new Point((int)posX_old,(int)posY_old)
);
degreeBoom = (int) (degreeBoom+degree);
}
posX_old = posX;
posY_old = posY;
}
if (event.getAction()==MotionEvent.ACTION_UP){
posX=0f;
posY=0f;
posX_old=0f;
posY_old=0f;
}
invalidate();
return true;
}
private double getDegree(Point o,Point s,Point e){
double cosfi = 0;
double fi = 0;
double norm = 0;
double dsx = s.x - o.x;
double dsy = s.y - o.y;
double dex = e.x - o.x;
double dey = e.y - o.y;
cosfi = dsx * dex + dsy * dey;
norm = (dsx * dsx + dsy * dsy) * (dex * dex + dey * dey);
cosfi /= Math.sqrt(norm);
if (cosfi >= 1.0) return 0;
if (cosfi <= -1.0) return Math.PI;
fi = Math.acos(cosfi);
int temp = (s.x-o.x)*(e.y-o.y)-(s.y-o.y)*(e.x-o.x);
if (180 * fi / Math.PI < 180) {
if (temp<=0) {
return 180 * fi / Math.PI;
}else{
return 180 * fi*(-1) / Math.PI;
}
} else {
if (temp<=0) {
return 360 - 180 * fi / Math.PI;
}else{
return (360 - 180 * fi / Math.PI)*(-1);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//计算最小的长宽高 540*960
//屏幕小于那个尺寸的时候进行比例缩放
//body 256*169
//boom 280*91
//arm 228*74
//bucker 81*67
//动臂+斗杆+铲斗==589
//宽最大 589+256 = 845
//高最大 169+ 589*2=1347
//screenW = ScreenUtils.getScreenWidth();
//screenH = ScreenUtils.getScreenHeight();
// Log.i("xsl_measure","screenW="+screenW+",screenH="+screenH);
//
// scaleW = (float) (845.0/screenW);
// scaleH = (float) (1347.0/screenH);
//
// Log.i("xsl_measure","scaleW="+scaleW+",scaleH="+scaleH);
//宽度最大化,高度比例缩放
}
}