2.自定义控件之挖掘机工作装置姿态View

先看效果图


挖掘机工作装置姿态

具体功能:
1.动臂、斗杆、铲斗任意角度变换显示
2.触摸实现拖拉效果

实现步骤:
1.找图,切图(PS用的不熟,只能用美图秀秀傻瓜式切图),最后切得动臂,斗杆,铲斗和挖掘整车部分图


切图内容

2.利用美图秀秀确定各切图的大小和相应的连接点位置


图片尺寸和连接点位置

3.Android自定义View 主要就是onMeasure onLayout onDraw的过程。本文主要针对onDraw 过程,在初始化过程中需要计算屏幕尺寸对图片进行缩放。
图片的缩放,旋转,平移主要通过Matrix实现,对触摸的响应通过动臂、斗杆、铲斗的矩形区域确定,而且当三个区域重叠优先级是铲斗>斗杆>动臂 。如下图所示触摸铲斗红色区域,优先响应铲斗旋转。
触摸处理.png

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);


        //宽度最大化,高度比例缩放



    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容