• 安卓权威编程指南-笔记 (第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(...) 方法,在屏幕上绘制红色矩形框。

       

      

  • 相关阅读:
    根据输入参数,判定时间范围CheckTimeSpan
    C#登出系统并清除Cookie
    MySQL中使用group_concat遇到的坑
    MySQL中group by 与 order by 一起使用排序问题
    使用VMware安装CentOS 7
    VMware安装Linux提示此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态
    Yii2处理密码加密及验证
    Yii2 的安装及简单使用
    git merge的使用
    PHP中上传文件打印错误,错误类型
  • 原文地址:https://www.cnblogs.com/chase1/p/7225877.html
Copyright © 2020-2023  润新知