安卓权威编程指南-笔记 (第29章定制视图与触摸事件)

1.定制视图

  Android自带众多优秀的标准视图与组件,但有时为追求独特的应用视觉效果,我们仍需创建定制视图。

  定制视图分为两大类别:

  •   简单视图: 简单视图内部也可以很复杂,之所以归为简单类别,是因为简单视图不包括子视图,而且简单视图几乎总是会执行定制绘制。
  •   聚合视图:聚合视图由其他视图对象组成,聚合视图通常管理着子视图,但不负责执行定制绘制,图形绘制任务都委托给了各个子视图。

  

  创建定制视图的所需的三大步骤:

  •   选择超类。对于简单定制视图而言,View是个空白画布,因此它作为超类最常见,对于聚合定制视图,我们应选择合适的超类布局,比如FrameLayout.
  •   继承选定的超类,并至少覆盖一个超类构造方法。
  •   覆盖其他关键方法,以定制视图行为。

1.1 创建一个简单的视图类

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);
    }
}

  从布局文件中实例化的视图可收到一个AttributeSet实例,该实例包含了XML布局文件中指定的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"
/>

  在引用时必须使用自定义View的全路径名,这样布局inflater才能够找到它,布局文件inflater解析布局XML文件,并按视图定义创建View实例,如果元素名不是全路径名,布局inflater

会转而在android.view和android.widget包中寻找目标,如果目标视图放置在其他包中,布局inflater将无法找到目标并最终导致应用崩溃。

  因此,对于android.view和android.widger包以外的定制视图类,必须指定它们的全路径名。

1.2 处理触摸事件

监听触摸事件的一种方式是使用以下view方法,设置一个触摸事件监听器:

public void setOnTouchListener(View.OnTouchListener l)

不过我们的定制视图是View的子类,因此可走捷径直接覆盖以下View方法:

public boolean onTouchEvent(MotionEvent event)

该方法接收一个MotionEvent类实例,MotionEvent类可用来描述包括位置和动作的触摸事件。动作用于描述事件所处的阶段。

动作常量   动作描述
ACTION_DOWN 手指触摸到屏幕
ACTION_MOVE 手指在屏幕上移动
ACTION_UP    手指离开屏幕
ACTION_CANCEL   父视图拦截了触摸事件

在onTouchEvent()实现方法中,可使用以下MotionEvent方法查看动作值:

public final int getAction()

我们的目的就是在一根手指放下的时候记录下放下的位置,移动时随之变化,放开时固定该矩形框。并且之前画的矩形框数据需要记录下来。

建立一个实体类,用于表示一个矩形框的定义数据。用来保存原始坐标点(手指的初始位置)和当前坐标点(手指的当前位置):

public class Box {
    private PointF mOrigin;
    private PointF mCurrent;

    public Box(PointF origin) {
        mOrigin = origin;
        mCurrent = origin;
    }   //get、set略
}

然后重写onTouchEvent()方法并进行相应操作:

private Box mCurrentBox;
private List<Box> mBoxen = new ArrayList<>();

@Override
public boolean onTouchEvent(MotionEvent event) {
    // 每次有触摸事件都记录下现在的坐标
    PointF current = new PointF(event.getX(), event.getY());
    String action = "";

    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            action = "ACTION_DOWN";
            // 每次按下的时候在列表中中新增一个 Box
            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:
            // 抬起的时候不再指向最新的 Box
            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);z

    return true;
}

在取消触摸事件或用户手指离开屏幕时,应清空mCurrentBox以结束屏幕绘制,以完成的Box会安全的存储在数组中。

invalidate()方法会强制BoxDrawingView重新绘制自己,这样在拖拽时就能实时看到矩形框。

2 onDraw()方法内的图形绘制

  应用启动时,所有视图都处于无效状态(视图还没有绘制到屏幕上),为解决这个问题,Android调用了顶级View视图的draw()方法,这会引起自上而下的链式调用反映。

首先,视图完成自我绘制,然后是子视图的自我绘制,再然后是子视图的子视图的自我绘制,如此调用下去直至继承结构的末端。当继承结构中的所有视图都完成自我绘制后,最顶级

View 视图也就生效了。

  为加入这种绘制,可覆盖以下 View 方法:
    protected void onDraw(Canvas canvas);

  在onTouchEvent()方法中响应ACTION_MOVE动作时,我们调用invalidate()方法再次让BoxDrawingView处于失效状态,这迫使他重新完成自我绘制,并再次调用onDraw()方法。

  

  Canvas和Paint是Android系统的两大绘制类。

  •   Canvas类拥有我们需要的所有绘制操作,其方法可决定在哪里以及绘什么,比如线条、圆形、字词、矩形等。
  •   Paint类决定如何绘制,其方法可指定绘制图形的特征,例如是否填充图形、使用什么字体绘制、线条是什么颜色等

  

 public BoxDrawingView(Context context, AttributeSet attrs){ //AttributeSet实例包含了XML布局文件中指定的XML属性。
        super(context,attrs);

        mBoxPaint = new Paint();
        mBoxPaint.setColor(0x22ff0000);

        mBackgroundPaint = new Paint();
        mBackgroundPaint.setColor(0xfff8efe0);
    }

 @Override
    protected void onDraw(Canvas canvas){
        //先画出背景
        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(...) 方法,在屏幕上绘制红色矩形框。

  

  

时间: 2024-10-25 02:50:34

安卓权威编程指南-笔记 (第29章定制视图与触摸事件)的相关文章

安卓权威编程指南-笔记(第21章 XML drawable)

在Andorid的世界里,凡事要在屏幕上绘制的东西都可以叫drawable,比如抽象图形,Drawable的子类,位图图形等,我们之前用来封装图片的BitmapDrawable就是一种drawable. 本章我们还会看到更多的drawable:state list drawable.shape drawable和layer list drawable. 这三个drawable都定义在XML文件中,可以归为一类,统称为XML drawable. shape drawable 使用ShapeDraw

安卓权威编程指南-笔记(第26章 服务的作用)

1. IntentService IntentService也是一个context(Service是Context的子类),并能够响应intent. 一个最基本的IntentService实例如下: public class PollService extends IntentService { private static final String TAG = "PollService"; public static Intent newIntent(Context context)

安卓权威编程指南-笔记(第27章 broadcast intent)

本章需求:首先,让应用轮询新结果并在有所发现时及时通知用户,即使用户重启设备后还没有打开过应用.其次,保证用户在使用应用时不出现新结果通知. 1. 一般intent和broadcast intent 许多系统组件需要知道某些事件的发生(WIFI信号时有时无,电话的呼入等),为满足这样的需求,Andorid提供了broadcast intent 组件. broadcast intent的工作原理类似于之前学过的intent,但不同的是broadcast intent可以被多个叫做broadcast

安卓权威编程指南-笔记(第25章 搜索)

1. SearchView SearchView是个操作视图,所谓操作视图,就是可以内置在工具栏中的视图.SearchView可以让整个搜索界面完全内置在应用的工具栏中. 1.1 SearchView的配置 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ap

安卓权威编程指南-笔记(第24章 Looper Handler 和 HandlerThread)

AsyncTask是执行后台线程的最简单方式,但它不适用于那些重复且长时间运行的任务. 1. Looper Android中,线程拥有一个消息队列(message queue),使用消息队列的线程叫做消息循环(message loop).消息循环会循环检查队列上是否有新消息. 消息循环由线程和looper组成,Looper对象管理着线程的消息队列. 主线程就是个消息循环,因此也拥有Looper,主线程的所有工作都是由其looper完成的,looper不断的从消息队列中抓去消息,然后完成消息指定的

安卓权威编程指南 挑战练习 22章 应用图标

本章使用了 ResolveInfo.loadLabel(...) 方法,在启动器应用中显示了各个activity的名 称. ResolveInfo 类还提供了另一个名为 loadIcon() 的方法.可以使用该方法为每个应用加载 显示图标.你要接受的挑战就是,为NerdLauncher应用中显示的所有应用添加对应的图标. 首先增加一个RecyclerView的条目布局,代码如下: 1 <?xml version="1.0" encoding="utf-8"?&

安卓权威编程指南 挑战练习 24章 预加载以及缓存

24.7 挑战练习:预加载以及缓存 应用中并非所有任务都能即时完成,对此,大多用户表示理解.不过,即使是这样,开发者们也一直在努力做到最好.为了让应用反应更快,大多数现实应用都通过以下两种方式增强自己的代码:? 增加缓存层 ? 预加载图片 缓存指存储一定数目 Bitmap 对象的地方.这样,即使不再使用这些对象,它们也依然存储在那里. 缓存的存储空间有限,因此,在缓存空间用完的情况下,需要某种策略对保存的对象做一定的取舍.许多缓存机制使用一种叫作LRU(least recently used,最

安卓权威编程指南 挑战练习 25章 深度优化 PhotoGallery 应用

你可能已经注意到了,提交搜索时, RecyclerView 要等好一会才能刷新显示搜索结果.请接受挑战,让搜索过程更流畅一些.用户一提交搜索,就隐藏软键盘,收起 SearchView 视图(回到只显示搜索按钮的初始状态).再来个挑战.用户一提交搜索,就初始化 RecyclerView ,显示一个搜索结果加载状态界面(使用状态指示器).下载到JSON数据之后,就删除状态指示器.也就是说,一旦开始下载图片, 就不应显示加载状态了 1.提交搜索,隐藏软键盘,收起SearchView: 将SearchV

安卓权威编程指南 - 第五章学习笔记(两个Activity)

学习安卓编程权威指南第五章的时候自己写了个简单的Demo来加深理解两个Activity互相传递数据的问题,然后将自己的学习笔记贴上来,如有错误还请指正. IntentActivityDemo学习笔记 题目:ActivityA登录界面(用户名.密码.登陆按钮),ActivityB(Edit,返回按键:SubmitButton).A界面输入用户名和密码传到B中,B验证用户输入的用户名和密码,如果错误就返回A,并用Toast 显示用户名和密码错误:如果正确,就在第二个 activity中显示一个Edi