一、概述
从之前项目中抽取出来的一个“画板”功能模块,就是可以在一个空白布局上,添加不同的元素,实现自由组合,暂时没想到啥好名字,姑且叫它“画板”吧。
主要实现了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() {
@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值。
由于本文的篇幅已经较长,为了能够让各位大佬获得更好的阅读体验(我要偷懒了_),笔者打算将其他几个知识点分到其他章节讲解,现提供完整工程,可以先睹为快,地址如下:
喜欢就star一下吧,fork也行,你开心就好,如果有啥问题欢迎在issue或者评论区讨论。