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

一、概述

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

《Android手摸手实现一个画板功能(一)——View的拖拽》 效果图.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或者评论区讨论。

    原文作者:EnjoyAndroid
    原文地址: https://www.jianshu.com/p/7853f1a968e9
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞