创建DragAndDraw新项目,最低SDK版本选择API19,新建空activity并命名为DragAndDraw- Activity。
既然SingLeFragmentActivity可实例化仅包含单个fragment的布局,我们让DragAndDraw-Activity继承它。
在Android Studio中,复制之前项目的SingleFragmentActivity.java和activity fragment. xml文件到DragAndDraw项目的对应目录中。
在DragAndDrawActivity.java中,修改代码继承SingleFragmentActivity类,并创建返回一个DragAndDrawFragment对象(稍后会创建该类)。代码给该如下。
修改activity(DragAndDrawActivity.java)
public class DragAndDrawActivity extends AppCompatActivity SingLeFragmentActivity {
@0verride
public Fragment createFragment() {
return DragAndDrawFragment.newInstance()
}
为准备DragAndDrawFragment的布局,重命名activity_drag and_drawxml为fragment_drag and draw.xml。
继承android.support.v4.app.Fragment超类,新建DragAndDrawFragment类。然后覆盖 onCreateView(…)方法,并在其中实例化fragment drag and_draw.xml布局,如以下代码所示。
创建DragAndDrawFragment
public class DragAndDrawFragment extends Fragment
{
pubLic static DragAndDrawFragment newInstance(){
return new DragAndDrawF ragment();
}
@0verride
public View onCreateView(LayoutInfLater infLater,ViewGroup container,
Bundle savedInstanceState) {
View v= infLater.infLate(R.Layout.fragment_drag_and_draw,container,false);
return v;
}
}
运行DragAndDraw应用即可得到带默认布局的DragAndDraw应用。
创建定制视图所需三大步骤。
(1)选择超类。对于简单定制视图而言,View是个空白画布,因此它作为超类最常见。对于聚合定制视图,我们应选择合适的超类布局,比如FrameLayout。
(2)继承选定的超类,覆盖超类的构造方法。(3)覆盖其他关键方法,以定制视图行为。创建 BoxDrawingView 视图
BoxDrawingView是个简单视图,同时也是View的直接子类。
以View为超类,新建BoxDrawingView类。在BoxDrawingViewjava中,添加两个构造方法。如下代码所示
初始的BoxDrawingView视图类(BoxDrawingViewjava)
public class BoxDrawingView extends View {
// Used when creating the view in code
public BoxDrawingView(Context context){
this(context, null);
}
// Used when infLating the view from XML
pubLic BoxDrawingView(Context context,AttributeSet attrs){
super(context,attrs);
}
}
更新fragment _drag _and_draw.xml布局文件以使用它,如下代码清单 所示。
在布局中添加BoxDrawingView(fragment_drag_and_draw.xml)<com.bignerdranch.android.draganddraw.BoxDrawingView
xmLns:android=“http://schemas.android.com/apk/res/android”
android:Layout_width=“match_parent”
android: Layout_height=“match_parent” />
注意,应给出BoxDrawingView的全路径类名,这样布局inflater才能够找到它。
运行DragAndDraw应用,一切正常的话,屏幕上会出现一个空视图。
设置一个触摸事件监听器∶ pubLic void setOnTouchListener(View.0nTouchListener L)
其工作方式与setOnClickListener(View.0nCLickListener)相同。实现View.0nTouch- Listener接口,供触摸事件发生时调用。不过,由于定制视图是View的子类,也可走捷径直接覆盖以下View方法∶ public booleanonTouchEvent(MotionEvent event)该方法接收一个MotionEvent类实例。MotionEvent类可用来描述包括位置和动作的触摸事件。
动作用于描述事件所处的阶段。
动作描述动作常量
手指触摸到屏幕 ACTION_DOWN
手指在屏幕上移动 ACTION_MOVE
手指离开屏幕 ACTION_UP
父视图拦截了触摸事件 ACTION_CANCEL
在BoxDrawingView.java中,添加一个日志tag。如下代码所示。
实现BoxDrawingView视图类(BoxDrawingView.java)
public class BoxDrawingView extends View {
private static final String TAG = “BoxDrawingView”;
@0verride
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;
case MotionEvent.ACTION_MOVE:
action = “ACTION_MOVE”;
break;
case MotionEvent.ACTION_UP:
action = “ACTION_UP”;
break;
case MotionEvent.ACTION_CANCEL:
action = “ACTION_CANCEL”;
break;
}
Log.i(TAG,action +" at x="+ current.X +
",y="+ current.y);
return true;
}
}
运行DragAndDraw应用并打开LogCat视图窗口。触摸屏幕并移动手指,查看BoxDrawingView接收的触摸动作的X和Y坐标记录。
随后新建一个Box类,用于表示一个矩形框的定义数据,如下代码所示。
添加Box类(Box.java)
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 PointF getOrigin(){
return mOrigin;
}
}
DragAndDraw应用中的对象
回到BoxDrawingView类中,使用新Box对象跟踪绘制状态,如下代码所示。
添加拖曳生命周期方法(BoxDrawingViewjava)
public class BoxDrawingView extends View {
private static final String TAG =“BoxDrawingview”;
private Box mCurrentBox;
private List mBoxen = new ArrayList<>();
@0verride
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”;
// Reset drawing state
mCurrentBox = new Box(current);
mBoxen.add(mCurrentBox);
break;
case MotionEvent.ACTION_MOVE:
action = “ACTION_MOVE”;
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 t rue;
}
}
任何时候,只要接收到ACTION_DOWN动作事件,就以事件原始坐标新建Box对象并赋值给 mCurrentBox,然后再添加到矩形框数组中。
注意ACTION_MOVE事件发生时调用的invatidate()方法。该方法会强制BoxDrawingView重新绘制自己。这样,用户在屏幕上拖曳时就能实时看到矩形框。这同时也引出了接下来的任务∶在屏幕上绘出矩形框。
Android调用了顶级View视图的draw()方法。当继承树中的所有视图都完成自我绘制后,最顶级View视图也就生效了。
返回BoxDrawingView.java中,在BoxDrawingView的XML构造方法中创建两个Paint对象,如下代码所示。
创建Paint(BoxDrawingView.java)
public class BoxDrawingView extends View {
private static final String TAG = “BoxDrawingView”;
private Box mCurrentBox;
private List mBoxen = new ArrayList<>();
private Paint mBoxPaint;
private Paint mBackgroundPaint;
// Used when inflating the view from XML
public BoxDrawingView(Context context,AttributeSet attrs){
super(context,attrs);
// Paint the boxes a nice semitransparent red (ARGB) mBoxPaint = new Paint();
mBoxPaint.setColor(0x22ff0000);
// Paint the background off-white
mBackgroundPaint = new Paint();
mBackgroundPaint.setColor(0xfff8efe0);
}
}
有了Paint对象的支持,现在能在屏幕上绘制矩形框了,如下代码所示。
覆盖onDraw(Canvas)方法(BoxDrawingViewjava)
public BoxDrawingView(Context context,AttributeSet attrs){
}
@0verride
protected void onDraw(Canvas canvas){
// FiLl the background
canvas.drawPaint(mBackgroundPaint);
for (Box box : mBoxen){
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);
}
}
以上代码的第一部分简单直接∶使用米白背景paint,填充canvas以衬托矩形框。然后,针对矩形框数组中的每一个矩形框,据其两点坐标,确定矩形框上下左右的位置。绘制时,左端和顶端的值作为最小值,右端和底端的值作为最大值。完成位置坐标值计算后,调用Canvas.drawRect(…)方法,在屏幕上绘制红色矩形框。
运行代码即可。