这一章是用来了解定制视图和触摸事件
这边会有一个项目来响应用户的触摸和拖动,在项目上绘制出矩形框
(1)创建一个activity类去继承SingleFragmentActivity,由此来实例化仅仅单个fragment的布局,修改代码并创建和返回一个DragAndDrawFragment对象
(2)为了准备DragAndDrawFragment的布局,重新命名activity_drag_and_draw.xml为fragment——draw_and_draw.xml
然后创建DragAndDrawFragment类然后覆盖onCreateView()方法,用来实例化fragment_drag_and_draw.xml
(3)定制视图
对于定制视图有两种,一种是简单视图,简单视图不包含子视图,它永远是用来处理定制绘制一种是聚合视图,聚合视图是由其他视图对象来组成的,聚合视图通常用来管理子视图,而不进行图形绘制
下面是创建定制视图所需的三大步骤
(1)选择超类,对于简单视图,我们可以选择用View来作为超类,它是一个空白的画布,所以用它来作为超类最常见,对于聚合视图,我们应该选择适合的超类布局,如FrameLayout
(2)继承选定的超类,覆盖超类的构造方法
(3)覆盖其他关键的方法,用来定制视图行为
这边BoxDrawingView是一个简单的视图,所以可以直接继承View,然后去添加两个构造方法
public class BoxDrawingView extends View {
public BoxDrawingView(Context context) {
this(context,null);
}
public BoxDrawingView(Context context,AttributeSet attrs) {
super(context,attrs);
}
}
这边之所以添加两个构造方法,是因为代码可以从代码或者布局文件中实例化,从布局文件中实例化的视图会接收到一个AttributeSet实例,该实例包含了XML布局文件中指定的XML属性,即使不打算使用构造方法,按习惯,也要添加这两个构造方法
有了定制视图类,我们就需要在布局中去更新布局文件来使用它
在fragment_drag_and_draw.xml添加
<com.bignerdranch.android.draganddraw.BoxDrawingView
xmln:android="http://schemas.android.com/apk/res/android"
android:layout_width = "match_parent"
android:layout_height = "match_parent"/>
注意,应该给出BoxDrawingView的全路径类名,这样布局inflater才能够找到它,布局inflater解析布局xml文件,并按照视图定义创建View实例,如果不给全路径,那么inflater会转向在android.view和android.widget包中寻找同类名,找不到就会崩溃
此时运行就会出现一个空的视图
(4)处理触摸事件
监听触摸事件是使用下面的View方法,设置一个触摸监听器:
setOnTouchListener(View.OnTouchListener l)
上面实现的接口,仅仅提供给触摸事件发送时调用
但是这边由于是View的子类,所以我们直接覆盖下面的方法就可以
public boolean onTouchEvent(MotionEvent event) {
}
该方法接收一个MotionEvent的实例,这个类可以用来描述位置和动作的触摸事件,动作用于描述事件所处的阶段
ACTION_CANCEL :父视图拦截了触摸事件
在MotionEvent方法中查看动作值可以使用 getAction()方法
public boolean onTouchEvent(MotionEvent event) {
PointF current = new PointF(event.getX(),event.getY());
String action = "";
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
action = "ACTION_DOWN";
break;
}
return true;
}
这里的x与y坐标已经封装到PointF对象里面了,我们需要同时传递两个坐标值,这个容器类刚好适合
(5)跟踪运动事件
除了记录坐标,BoxDrawingView主要用于在屏幕上绘制矩形框,要实现这个目标需要知道定义矩形框的(1)原始坐标点,当前坐标点(2)定义一个矩形框,我们还需要追踪记录来自多个MotionEvent的数据,这些数据会保存在Box对象中
public class Box {
private PointF mOrigin;
private PointF mCurrent;
public Box(PointF origin) {
mOrigin = origin;
mCurrent = origin;
}
public PointF getCurrent(){
return mCurrent;
}
public void setCurrent(PointF current){
mCurrent = current;
}
public PonintF getOrigin(){
return mOrigin;
}
}
这样当用户触摸视图界面的时候,新的Box对象就会加到现有的矩形中去
现在回到BoxDrawingView中,使用新的Box对象来跟踪绘制状态
private Box mCurrentBox;
private List<Box> mBoxen = new ArrayList<>();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
action = "ACTION_DOWN";
mCurrentBox = new Box(current);
mBoxen.add(mCurrentBox);
break;
case MotionEvent.ACTION_MOVE:
action = "dd";
if(mCurrentBox !=null) {
mCurrentBox.setCurrent(current);
invalidate();//这会让BoxDrawingView失效,这会使得它重新自我绘制,并再次调用onDraw(Canvas)方法
break;
}
case:MotionEvent.ACTION_UP:
action= "";
mCurrentBox = null;
break;
}
case MotionEvent.ACTION_CANCEL:
action = ""
mCurrentBox = null;
return true;
}
现在只要接收到ACTION_DOWN的动作坐标,就以事件原始坐标新建立一个Box对象并赋值给mCurrentBox中,然后再添加到矩形数组中去
(6)onDraw()方法中去进行图形的绘制
应用启动后,所有的视图都处于无效的状态,也就是说视图还没有绘制到屏幕上去,为了解决这个问题,Android调用了顶级的View视图的draw()方法,这会引起从上到下的链式调用的反应,首先视图完成自我绘制,然后是子视图的绘制,再然后就是子视图的子视图的自我绘制,由此调用到继承结构的末端,当所有的视图都自我绘制后,最顶级的View视图也已经生效了
为加入这种绘制,可以覆盖下面的方法
protected void onDraw(Canvas canvas) {
}
在Android中有两大绘制类
Canvas类拥有我们需要的所有绘制的操作,其方法可以决定绘制在哪里,以及绘制什么,比如圆形
Paint类决定如何绘制,其方法可以指定绘制图形的特征,例如是不是填补图形,使用什么字体绘制
(7)创建Paint
private Paint mBoxPaint;
private Paint mBackgroundPaint;
在BoxDrawingView(Context AttributeSet)方法中去实例化
public BoxDrawingView(Context context,AttributeSet attrs) {
super(context,attrs);
mBoxPaint = new Paint();
mBoxPaint.setColor();
mBackgroundPaint.setColor();
}
有了Paint对象,那么就能在屏幕上绘制矩形框了
(8)覆盖onDraw(Canvas)方法
protected void onDraw(Canvas canvas) {
canvas.drawPaint(mBackgroundPaint);
for(Box box:mBoxen) {
float left = Math.min(box.getOrigin().x,boxgetCurrent().x);
float right = Math.max(box.getOrigin().x,boxgetCurrent().x);
float top = Math.min(box.getOrigin().y,boxgetCurrent().y);
float bottom = Math.max(box.getOrigin().y,boxgetCurrent().y);
canvas.drawRect(left,top,right,bottom,mBoxPaint);
}
}
现在就可以绘制一些矩形了