本文目录 ⌄
第三十一章 定制视图与触摸事件
有关定制视图
定制试图是为了实现独特的应用视觉效果。
分类
简单视图:不包括子视图,几乎总是用来处理定制绘制。由于View是一个空白画布,经常将简单视图的超类设置为View,例如:
public class BoxDrawingView extends View聚合试图:由其它视图对象组成。太长用于管理里子视图,但是不负责处理定制绘制。绘制任务一般托管给子视图。一般用FrameLayout作为超类。
创建完一个视图之后就可以在布局文件中添加这个视图了。
<com.shopkeeper.dragandgraw.BoxDrawingView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/box_drag_view" android:layout_width="match_parent" android:layout_height="match_parent"/>有关处理触摸事件
监听触摸方法可以使用View的触摸事件监听器:
public void setOnTouchListener(View.onTouchListener l)也可以直接覆盖view的onTouchEvent方法:
public boolean onTouchEvent(MotionEvent event)这个方法接受MotionEvent实例。MotionEvent类是用来描述包括位置和动作的触摸事件,其getAction()方法可以查看动作值:
@Override
public boolean onTouchEvent(MotionEvent event) {
PointF current=new PointF(event.getX(),event.getY());
String action="";
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_POINTER_DOWN:
...
break;
case MotionEvent.ACTION_POINTER_UP:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
case MotionEvent.ACTION_UP:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
}
...
return true;
}值得注意的是,getAction用来监控手指的触摸的行为,getAction = getActionIndex() + getActionMasked()。getAction 是16位数,高八位为getActionIndex,指第几个手指的操作;低八位为getActionMasked,表示手指的触摸行为。当只有一只手指时(如代码所示),getActionIndex==0,所以getAction == getActionMasked。参考资料
有关onDraw方法图形绘制
onDraw要用到两大绘制类:Canvas和Paint。Canvas进行绘制操作,如在何处绘制什么图形、整个画布的旋转缩放等等;Paint决定如何绘制(绘制的内容),由填充、颜色字体等等。
不过一般这两个类都在构造方法中就被实例化了,只是需要在onDraw中调用:
public BoxDrawingView(Context context, AttributeSet attributeSet){ super(context,attributeSet); mBoxPaint=new Paint(); mBoxPaint.setColor(0x22ff0000); mBackgroundPaint=new Paint(); mBackgroundPaint.setColor(0xfff8efe0);}在onDraw中画图:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPaint(mBackgroundPaint);
for (Box box:mBoxes)
{
float left=Math.min(box.getOrigin().x,box.getCurrent().x);
float right=Math.max(box.getOrigin().x,box.getCurrent().x);
float top=Math.min(box.getOrigin().y,box.getCurrent().y);
float bottom=Math.max(box.getOrigin().y,box.getCurrent().y);
canvas.drawRect(left,top,right,bottom,mBoxPaint);
}
}在onTouchEvent调用invalidate();可以使得布局无效而触发onDraw方法重新绘制界面。
有关挑战联系 设备旋转问题
首先说一下,这个题设里面给的简单方法害人不浅,我私底下认为是胡说的,按照它的说法只要用Bundle就可以了,但是我无论怎么试,就算达到了旋转设备时会保留所有矩形框的信息,但是按下历史任务栏键应用就会崩溃。所以还是要老老实实写Parceable,其实也不复杂,Android studio已经强大到会帮你自动实现Parceable接口了:
public class Box implements Parcelable { private PointF mOrigin; private PointF mCurrent; public Box(PointF origin) { mCurrent=origin; mOrigin=origin; } protected Box(Parcel in) { mOrigin = in.readParcelable(PointF.class.getClassLoader()); mCurrent = in.readParcelable(PointF.class.getClassLoader()); } public static final Creator<Box> CREATOR = new Creator<Box>() { @Override public Box createFromParcel(Parcel in) { return new Box(in); } @Override public Box[] newArray(int size) { return new Box[size]; } }; public PointF getCurrent() { return mCurrent; } public PointF getOrigin() { return mOrigin; } public void setCurrent(PointF current) { mCurrent=current; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeParcelable(mOrigin, i); parcel.writeParcelable(mCurrent, i); }}重写方法存储/取出状态:
@Nullable@Overrideprotected Parcelable onSaveInstanceState() { Bundle bundle= new Bundle(); bundle.putParcelable(SUPER_BUNDLE,super.onSaveInstanceState()); bundle.putParcelableArrayList(SAVE_BUNDLE, mBoxes); return (Parcelable)bundle;}@Overrideprotected void onRestoreInstanceState(Parcelable state) { Bundle bundle=(Bundle)state; Parcelable superData = bundle.getParcelable(SUPER_BUNDLE); mBoxes =bundle.getParcelableArrayList(SAVE_BUNDLE); super.onRestoreInstanceState(superData);}有关挑战练习 旋转矩形框
我一开始卡了好久,怎么弄都转不起来,直到看到书上要我看的第一个方法的提示之后我才明白,多个手指操作之后getAction得到的并不单纯是操作的动作值了,需要修改成getActionMasked:
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
.....其它的几个方法可以参照这个帖子来看。
我的完整方法如下:
@Overridepublic boolean onTouchEvent(MotionEvent event) { PointF current=new PointF(event.getX(),event.getY()); String action=""; switch (event.getActionMasked()){ case MotionEvent.ACTION_DOWN: action="ACTION_DOWN"; mCurrentBox=new Box(current); mBoxes.add(mCurrentBox); mFirstIndex=event.getActionIndex(); break; case MotionEvent.ACTION_POINTER_DOWN: shouldRotate=true; mSecondIndex=event.getActionIndex(); if (mCurrentBox!=null) { mBoxes.remove(mCurrentBox); } break; case MotionEvent.ACTION_POINTER_UP: shouldRotate=false; break; case MotionEvent.ACTION_MOVE: action="ACTION_MOVE"; if (shouldRotate) { mAngle=mAngle+getAngle(event,mFirstIndex,mSecondIndex); invalidate(); } else { if (mCurrentBox!=null) { mCurrentBox.setCurrent(current); invalidate(); } } break; case MotionEvent.ACTION_UP: action="ACTION_UP"; mCurrentBox=null; break; case MotionEvent.ACTION_CANCEL: action="ACTION_CANCEL"; mCurrentBox=null; break; } Log.i(TAG,action+" at x="+current.x+", y="+current.y); return true;}思路是每当有手指第一次按下时就将其Index记录为第一个的;如果有第二个的就将其Index记录为第二个,同时标记应该旋转且将第一个手指按下时创建的Box删除(因为单纯的旋转不需要画新的方框);第二个手指被抬起时就标记位不应该旋转;每当接收到滑动消息时根据是否需要旋转的标记来选择操作:如果不需要旋转就照常,如果需要旋转,就计算旋转后的角度(计算角度方法如下),并调用invalidate()方法重新绘制旋转后的图像。
计算角度时根据之前记载下的第一个和第二个手指的index,得到它们的id,再有它们的x、y坐标来计算角度:
private static double getAngle(MotionEvent event,int firstIndex,int secondIndex) { int firstId=event.getPointerId(firstIndex); int secondId=event.getPointerId(secondIndex); double deltaX = (event.getX(firstId) - event.getX(secondId));//获取两个手指触摸点的X坐标值的差值 double deltaY = (event.getY(firstId) - event.getY(secondId));//获取两个手指触摸点的Y坐标值的差值 return Math.atan2(deltaY, deltaX);}旋转在onDraw方法中实现:
@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); canvas.rotate((float) mAngle,getWidth()/2,getHeight()/2); canvas.drawPaint(mBackgroundPaint); for (Box box:mBoxes) { float left=Math.min(box.getOrigin().x,box.getCurrent().x); float right=Math.max(box.getOrigin().x,box.getCurrent().x); float top=Math.min(box.getOrigin().y,box.getCurrent().y); float bottom=Math.max(box.getOrigin().y,box.getCurrent().y); canvas.drawRect(left,top,right,bottom,mBoxPaint); } canvas.restore();}canvas.rotate方法的第一个参数是旋转角度,后面两个参数是旋转的轴点。
我还写了canvas的save和restore方法,参考资料,这两个方法可以理解是一个是保存画布的状态,一个是还原画布的状态,然而改变或还原画布是不会对画纸(画面的内容)造成影响的。不过这两个方法在我的代码里面没什么作用,因为所有方块都是一股脑全都画完的。