Android手摸手实现一个画板功能(一)——View的拖拽

一、概述

从之前项目中抽取出来的一个“画板”功能模块,就是可以在一个空白布局上,添加不同的元素,实现自由组合,暂时没想到啥好名字,姑且叫它“画板”吧。
  主要实现了View的拖拽、缩放、旋转、复制、View导出图片、文本编辑、磁力连接线、上一步和下一步状态备忘等功能。该项目主要涉及的知识点:View的事件分发、手势多点触控、View坐标系、备忘录设计模式等。
  由于该项目是为特定pad机型定制项目,未做其他机型兼容性处理,但是这并不影响本文对其原理的讲解,建议使用1200 x 1920平板模拟器或真机运行工程以获得最佳体验。
  无图言屌?上图:

效果图.gif

二、解析

2.1 侧边栏长按拖拽到画布

思路大概是酱紫:
  第一步,为侧边栏的每个Imageview设置OnLongClickListener、OnTouchListener;
  第二步,长按时生成一个新的Imageview对象,根据当前长按的Imageview的id,设置相应的ImageResource,并添加到画布中;
  第三步,为刚刚生成的Imageview对象设置OnTouchListener,在onTouch方法中,不断的更新ImageView的xy坐标,从而实现view的拖拽。
  看代码:

2.1.1 setOnLongClickListener()、setOnTouchListener()

        ImageView allImageView = (ImageView) findViewById(R.id.allIcon);
        allImageView.setOnTouchListener(mTouchListener);
        allImageView.setOnLongClickListener(mLongClickListener);

        ImageView smileImageView = (ImageView) findViewById(R.id.smileIcon);
        smileImageView.setOnTouchListener(mTouchListener);
        smileImageView.setOnLongClickListener(mLongClickListener);

        ImageView jewelryImageView = (ImageView) findViewById(R.id.jewelryIcon);
        jewelryImageView.setOnTouchListener(mTouchListener);
        jewelryImageView.setOnLongClickListener(mLongClickListener);

        ImageView hotImageView = (ImageView) findViewById(R.id.hotIcon);
        hotImageView.setOnTouchListener(mTouchListener);
        hotImageView.setOnLongClickListener(mLongClickListener);

        ImageView lineImageView = (ImageView) findViewById(R.id.lineIcon);
        lineImageView.setOnTouchListener(mTouchListener);
        lineImageView.setOnLongClickListener(mLongClickListener);

        ImageView rect = (ImageView) findViewById(R.id.rectIcon);
        rect.setOnTouchListener(mTouchListener);
        rect.setOnLongClickListener(mLongClickListener);

2.1.2 长按事件处理:

 private View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            final ImageView imageView = new ImageView(MainActivity.this);
            mCurrentImageView = imageView;
            ViewInfo viewInfo = new ViewInfo(v.getId(), 0);
            viewInfo.type = ViewInfo.TYPE_IMAGEVIEW;
            viewInfo.color = mCurrentColor;
            viewInfo.realId = ++mRealInfoId;
            imageView.setTag(viewInfo);
            imageView.setScaleType(ImageView.ScaleType.FIT_XY);
            setImageResource(imageView, true);
            int[] location = new int[2];
            v.getLocationOnScreen(location);
            locationX = location[0];
            locationY = location[1];
            imageView.setX(locationX + 5);
            imageView.setY(locationY + 5);
            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(v.getWidth(), v.getHeight());
            mRootView.addView(imageView, params);
            mViewList.add(imageView);
            imageView.setOnTouchListener(new MyTouchListener(imageView));
            return true;
        }
    };


//根据不同的id设置不同的图片资源
    public void setImageResource(ImageView v, boolean focus) {
        ViewInfo viewInfo = (ViewInfo) v.getTag();
        switch (viewInfo.id) {
            case R.id.allIcon:
                realSetImageResource(v, viewInfo, focus, R.drawable.all_selected, R.drawable.ic_all_black, R.drawable.ic_all_green, R.drawable.ic_all_red);
                break;
            case R.id.smileIcon:
                realSetImageResource(v, viewInfo, focus, R.drawable.smile_selected, R.drawable.ic_smile_black, R.drawable.ic_smile_green, R.drawable.ic_smile_red);
                break;
            case R.id.jewelryIcon:
                realSetImageResource(v, viewInfo, focus, R.drawable.jewelry_selected, R.drawable.ic_jewelry_black, R.drawable.ic_jewelry_green, R.drawable.ic_jewelry_red);
                break;
            case R.id.hotIcon:
                realSetImageResource(v, viewInfo, focus, R.drawable.hot_selected, R.drawable.ic_hot_black, R.drawable.ic_hot_green, R.drawable.ic_hot_red);
                break;

            case R.id.lineIcon:

                if (mLineBitmap == null) {

                    mLineBitmapBlack = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
                    mLineBitmapGreen = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
                    mLineBitmapRed = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);

                    Paint paint = new Paint();
                    paint.setColor(Color.BLACK);
                    paint.setStrokeWidth(STROKE_WIDTH);

                    Canvas canvas = new Canvas(mLineBitmapBlack);
                    canvas.drawLine(0, 50, 100, 50, paint);

                    paint.setColor(Color.RED);
                    canvas = new Canvas(mLineBitmapRed);
                    canvas.drawLine(0, 50, 100, 50, paint);

                    paint.setColor(Color.GREEN);
                    canvas = new Canvas(mLineBitmapGreen);
                    canvas.drawLine(0, 50, 100, 50, paint);

                    mLineBitmap = mLineBitmapBlack;

                    if (viewInfo.color == 2) {
                        mLineBitmap = mLineBitmapRed;
                    } else if (mCurrentColor == 1) {
                        mLineBitmap = mLineBitmapGreen;
                    }
                }
                if (focus) {
                    v.setImageResource(R.drawable.line_selected);
                } else {
                    v.setImageBitmap(mLineBitmap);
                }
                break;

            case R.id.rectIcon:
                if (focus) {
                    v.setBackgroundResource(R.drawable.border_shape_focus);
                } else {
                    v.setBackgroundResource(R.drawable.border_shape);
                }
                break;
            default:
                break;
        }
    }

2.1.3 处理View的拖拽

    private View.OnTouchListener mTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, final MotionEvent event) {
            int action = event.getAction();
            if (mCurrentImageView == null && MotionEvent.ACTION_DOWN != action) {
                return false;
            }

            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    fdownX = event.getX();
                    fdownY = event.getY();
                    LogUtils.d("fdownX: " + fdownX + " ###fdownY: " + fdownY);
                    getLineCoordinate();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float disX = event.getX() - fdownX - OFFSET;
                    float disY = event.getY() - fdownY - OFFSET;
                    LogUtils.d("disX: " + disX + " ###  disY: " + disY + " ###  getX: " + event.getX() + " ###  getY: " + event.getY());
                    mCurrentImageView.setX(mCurrentImageView.getX() + disX);
                    mCurrentImageView.setY(mCurrentImageView.getY() + disY);
                    LogUtils.i("mCurrentImageView.getX(): " + mCurrentImageView.getX() + " ###  mCurrentImageView.getY(): " + mCurrentImageView.getY());
                    fdownX = event.getX() - OFFSET;
                    fdownY = event.getY() - OFFSET;
                    LogUtils.v("fdownX: " + fdownX + " ###  fdownY: " + fdownY);

                    imageW = mCurrentImageView.getWidth();
                    imageH = mCurrentImageView.getHeight();

                    mCurrentImageView.setBackgroundResource(android.R.color.transparent);
                    setImageResource(mCurrentImageView, true);
                    return true;
                case MotionEvent.ACTION_UP:
                    float x = mCurrentImageView.getX();
                    float y = mCurrentImageView.getY();
                    if (x <= 212) {
                        cancelMoveView(x, y);
                        return true;
                    } else {
                        if (x > 212 && x < 312) {
                            x = 312;
                        } else if (x > (mDisplayMetrics.widthPixels - 100)) {
                            x = mDisplayMetrics.widthPixels - 100;
                        }

                        if (y <= 106) {
                            y = 106;
                        } else if (y > mDisplayMetrics.heightPixels - 100 - mStatusBarHeight) {
                            y = mDisplayMetrics.heightPixels - 100 - mStatusBarHeight;
                        }
                        mCurrentImageView.setX(x - 312);
                        mCurrentImageView.setY(y - 107);
                        setImageResource(mCurrentImageView, false);
                        mRootView.removeView(mCurrentImageView);
                        mContent.addView(mCurrentImageView);

                        createMemento(mCurrentImageView, false, true);

                        if (((ViewInfo) mCurrentImageView.getTag()).id == R.id.rectIcon) {
                            mCurrentImageView.setBackgroundResource(R.drawable.border_shape);
                        } else {
                            mCurrentImageView.setBackgroundResource(android.R.color.transparent);
                        }
                    }
                    mCurrentImageView = null;
                    break;
                case MotionEvent.ACTION_CANCEL:
                    float x1 = mCurrentImageView.getX();
                    float y1 = mCurrentImageView.getY();
                    cancelMoveView(x1, y1);
                    break;
            }
            return false;
        }
    };

处理拖拽的难点在于View新的x、y坐标计算,如果能够准确计算出View新的坐标,那么拖拽问题就可迎刃而解!
  首先拿到ACTION_DOWN事件按下的(x,y),对应fdownX、fdownY,其次在ACTION_MOVE时获取新的(x,y),通过新的(x,y)-旧的(x,y),就可以得到移动距离disX、disY,再将View的(x,y)坐标设置成:原来的坐标+移动距离,就可以实现View移动,从而实现拖拽;最后,别忘了,ACTION_MOVE事件是会持续触发的,所以每一个新的坐标相对于下一次移动坐标,都会变成旧的坐标,因此拖拽完View之后,还需要对手指的按下位置重新赋值。
  核心代码如下:

//OFFSET:由于体验问题,手指按在View上会遮挡住当前View,所以设置了一个偏移量来错开一定距离,该值可以不设置
       case MotionEvent.ACTION_DOWN:
                    fdownX = event.getX();
                    fdownY = event.getY();
                    LogUtils.d("fdownX: " + fdownX + " ###fdownY: " + fdownY);
                    getLineCoordinate();
                    break;
       case MotionEvent.ACTION_MOVE:
                    float disX = event.getX() - fdownX - OFFSET;
                    float disY = event.getY() - fdownY - OFFSET;
                    LogUtils.d("disX: " + disX + " ###  disY: " + disY + " ###  getX: " + event.getX() + " ###  getY: " + event.getY());
                    mCurrentImageView.setX(mCurrentImageView.getX() + disX);
                    mCurrentImageView.setY(mCurrentImageView.getY() + disY);
                    LogUtils.i("mCurrentImageView.getX(): " + mCurrentImageView.getX() + " ###  mCurrentImageView.getY(): " + mCurrentImageView.getY());
                    fdownX = event.getX() - OFFSET;
                    fdownY = event.getY() - OFFSET;
                    LogUtils.v("fdownX: " + fdownX + " ###  fdownY: " + fdownY);

                    imageW = mCurrentImageView.getWidth();
                    imageH = mCurrentImageView.getHeight();

                    mCurrentImageView.setBackgroundResource(android.R.color.transparent);
                    setImageResource(mCurrentImageView, true);
                    return true;

三、一句话总结

View的移动本质上就是x、y坐标值的变换,拖拽就是在ontouch()事件中,改变View的x、y值。
  由于本文的篇幅已经较长,为了能够让各位大佬获得更好的阅读体验(我要偷懒了_),笔者打算将其他几个知识点分到其他章节讲解,现提供完整工程,可以先睹为快,地址如下:

DrawLayoutSample

喜欢就star一下吧,fork也行,你开心就好,如果有啥问题欢迎在issue或者评论区讨论。

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

推荐阅读更多精彩内容