一、概述

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

二、解析

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() {@Overridepublic 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() {@Overridepublic 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或者评论区讨论。

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

  1. 手摸手产品研究院 | 玲珑沙龙-一个可以“撕逼”的女性文化社区

    手摸手产品研究院是由PMCAFF发起的深度研究产品的产品经理精华小分队,旨在每天一起研究一款产品,并且由阿德老师手摸手指导写分析报告. 1-玲珑沙龙? 什么是玲珑沙龙,那些人在玩玲珑沙龙,为什么会玩? ...

  2. 每天研究一个产品,阿德老师“手摸手”带你写产品分析报告 |

    作为一个产品经理,要高频地去把玩各种最新产品,所以我们想把那些对世界充满好奇心.勇于探索新鲜事物的产品经理都聚在一起.一起深入研究国内外最新/奇产品,一起发现有趣的事情,并把研究心得都整理成文章沉淀下 ...

  3. android videoview 拉伸,手摸手带你用 VideoView 实现英语流利说炫酷引导页

    效果图: 一直听说英语流利说是个做的非常不错的app,于是乎抱着崇拜的心态下了一个瞅瞅,在打开app后就被引导页吸引了,继续抱着崇拜的心态去思考这是如何实现的. 刚开始的思路属性动画?(可以实现,但是 ...

  4. CSS —— 手摸手实现一个文字霓虹灯闪烁特效

    CSS -- 手摸手实现一个文字霓虹灯闪烁特效 一.了解 text-shadow 属性 text-shadow 属性应用于阴影文本,属于 CSS3 的属性,默认值为 none. text-shadow ...

  5. IN-我的生活in记 | 手摸手产品研究院

    手摸手产品研究院是由PMCAFF发起的深度研究产品的产品经理精华小分队,旨在每天一起研究一款产品,并且由阿德老师手摸手指导写分析报告. 引言 IN是一款基于女性和品牌的时尚品位分享移动端社区,以图片社 ...

  6. 手摸手教你做动态壁纸

    手摸手教你做动态壁纸 Android · jeasonwong · 于 5 天前发布 · 最后由 xingstarx 于 2 天前回复 · 440 次阅读 项目地址:https://github.co ...

  7. 快应用之手摸手,跟我走(1)

    快应用发布快两周啦.这两天有空,就捣鼓了一个快应用.整体感觉来说,交互很流畅,基本功能和组件都有.上手也很快.希望官网推广能做好.好了,话不多说,先上 gitHub (传送门) gankQuick-快 ...

  8. 招聘行业颠覆者【伯小乐】| 手摸手产品研究院

    手摸手产品研究院是由PMCAFF发起的深度研究产品的产品经理精华小分队,旨在每天一起研究一款产品,并且由阿德老师手摸手指导写分析报告. 作者微信:weihe2416 "伯小乐" 是 ...

  9. 短视频Gif快手-有点意思 | 手摸手产品研究院

    手摸手产品研究院是由PMCAFF发起的深度研究产品的产品经理精华小分队,旨在每天一起研究一款产品,并且由阿德老师手摸手指导写分析报告.                                 ...

最新文章

  1. .net下的富文本编辑器FCKeditor的配置方法(图)原创
  2. visualSVN-server的安装图解
  3. VB6.0 怎样启用控件comdlg32.ocx
  4. 手撕 MySQL 事务,发生了什么?
  5. php坐标轴取整,PHP取整函数:ceil,floor,round,intval的区别详细解析
  6. Linux就该这么学---第七章(LVM逻辑卷管理器)
  7. hdu1686:KMP板子
  8. 【教程】条形码组件Spire.Barcode 教程:如何在C#中创建DataMatrix条码
  9. Oracle 11g R2 ADG 运维
  10. android 同步list数据,android SharedPreferences保存list数据
  11. Java包装类介绍与类型之间相互转换
  12. php 判断 小米 手机浏览器,小米2下的chrome调试
  13. Spring Cloud 基础教程 - 程序猿DD
  14. 最新最全的阿里云产品手册出炉
  15. 星星泡饭伴奏_星星泡饭 - Ayo_Lvlv - 5SING中国原创音乐基地
  16. 8脚51单片机DIY时间显示+闹钟技术分享(一)
  17. 对高尔顿数据集实现线性回归分析
  18. java linest_java基础:学生管理系统
  19. Pikachu靶场之越权漏洞详解
  20. 叶新伟 php,基于php+mysql技术bbs论坛设计的开发与实现最终版(样例3)

热门文章

  1. 阿兰•图灵与人工智能
  2. 拓嘉恒业:拼多多开店条件分享
  3. Python我的世界小游戏源代码
  4. 高防CDN和BGP高防有哪些区别和优势
  5. 什么是大数据?带你深度了解大数据
  6. serial.serialutil.SerialException: could not open port 'COM1': PermissionError(13, '拒绝访问。', None, 5)
  7. java 函数 作为参数_如何在Java中将函数作为参数传递?
  8. js的高亮关键写法,简单粗暴,行之有效
  9. 迄今最全的国内引进的凯迪克获奖绘本书单172本(1938年-2016年)
  10. react-Mobx基本使用