Android_View详解

Android_View详解


本文由 Luzhuo 编写,转发请保留该信息.
原文: http://blog.csdn.net/Rozol/article/details/72848723


View的绘制
View的事件分发机制
View的时间冲突处理

Activity控件架构

  • 可以使用SDK下Tools目录里的 hierarchyviewer.bat 进行布局分析
  • 当我们在Activity中执行setContentView(R.layout.activity_main)时,是给ContentView设置布局
  • 每个控件都会在界面上占一块矩形区域
    -控件分为:
    • ViewGroup容器控件:
      • 可以包含多个View和ViewGroup
      • ViewGroup以树的结构管理View和ViewGroup, 主要负责下层控件的测量,绘制,事件传递.
    • View控件

View的绘制

  • 关键词: measure [ˈmɛʒɚ] / layout [ˈleˌaʊt] / draw [drɔ]
  • 绘制流程: measure -> layout -> draw

measure

  • 测量View的宽高
  • 代码的重写

    public class MyView extends View {
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
    • 我们看看 super.onMeasure(widthMeasureSpec, heightMeasureSpec) 做了什么?

      public class View implements Drawable.Callback, KeyEvent.Callback,
              AccessibilityEventSource {
      
          protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
              // ↓↓↓
              setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                      getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
          }
      
          // ↓↓↓
          public static int getDefaultSize(int size, int measureSpec) {
              int result = size;
              int specMode = MeasureSpec.getMode(measureSpec);
              int specSize = MeasureSpec.getSize(measureSpec);
      
              switch (specMode) {
              case MeasureSpec.UNSPECIFIED:
                  result = size;
                  break;
              case MeasureSpec.AT_MOST:
              case MeasureSpec.EXACTLY:
                  result = specSize;
                  break;
              }
              return result;
          }
      }
      • 看来源码中是用 MeasureSpec 这个类来计算大小的, 我们先来了解下这个类
      • MeasureSpec 这个类是用来测量View的

        • 测量模式:
          • UNSPECIFIED: 不指定测量模式, 你想多大就多大
          • EXACTLY: 精确测量模式, layout_width=”100dp”/”match-parent”都是使用该模式
          • AT_MOST: 最大值模式, layout_width=”wrap_content”时使用该模式
        • 我们来测试下三种模式的区别,在测试之前先修改下 public static int getDefaultSize(int size, int measureSpec) 方法的代码

          • 我们在 case MeasureSpec.AT_MOST: 下添加两行代码,让他在 layout_width=”wrap_content” 时使用默认值和测量值的最小值, 其他代码不变

            case MeasureSpec.AT_MOST:
                result = Math.min(size, specSize);
                break;
          • 效果:
      • Measure 是对ViewGroup树进行自上而下遍历而确定View最大可用大小的
      • 最后需要调用 setMeasuredDimension(width, height); 设置View的宽高值

layout

  • 确定View在父容器的位置
  • 代码的重写

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }
  • Layout的位置是对ViewGroup树进行自上而下遍历而确定的

  • 参数说明:
    • changed: View大小是否发生改变
    • left, top, right, bottom: 左上右下的坐标值
  • 实际开发中,ViewGroup会需要重写onLayout来确定子View的位置,View没啥位置可确定的

draw

  • 绘制View
  • 代码的重写

    @Override
    protected void onDraw(Canvas canvas) { }
  • 仅需调用canvas进行绘制即可

  • Canvas就像画板,Paint就像画笔,使用Paint在Canvas上作画,作画的水平决定这个View控件是否优美.

案例代码

package me.luzhuo.viewdemo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Printer;
import android.view.MotionEvent;
import android.view.View;

/**
 * =================================================
 * <p>
 * Author: Luzhuo
 * <p>
 * Version: 1.0
 * <p>
 * Creation Date: 2017/6/1 15:55
 * <p>
 * Description: View的绘制机制
 * <p>
 * Revision History:
 * <p>
 * Copyright: Copyright 2017 Luzhuo. All rights reserved.
 * <p>
 * =================================================
 **/
public class ViewDemo extends View {
    private Paint paint;
    private Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); // 测试图片

    public ViewDemo(Context context) {
        super(context);
        init();
    }

    public ViewDemo(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ViewDemo(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        paint = new Paint();
    }

    /**
     * 1. 测量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(bitmap.getWidth() * 3, widthMeasureSpec), getDefaultSize(bitmap.getHeight() * 3, heightMeasureSpec));
    }

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = View.MeasureSpec.getMode(measureSpec);
        int specSize = View.MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case View.MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case View.MeasureSpec.AT_MOST:
                result = Math.min(size, specSize);
                break;
            case View.MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(bitmap, x - bitmap.getWidth() / 2.0f, y - bitmap.getHeight() / 2.0f, paint);
    }

    private float x, y;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        x = event.getX(); // 控件x坐标
        y = event.getY();

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        invalidate();
        return true;
    }
}

其他重要方法补充

  • 回调方法:
    • protected void onFinishInflate() { } // XML控件加载完成后回调
    • protected void onSizeChanged(int w, int h, int oldw, int oldh) { } // 控件的大小改变时回调
  • 更新方法:
    • invalidate(); // 重绘(会调用onMeasure()), 如果onMeasure()测量的大小没有发生变化,就不会调用onLayout(), 但是会调用onDraw()
    • requestLayout(); // 重新测量View的大小, 会调用onMeasure()和onLayout(),但是不会调用onDraw()

View的事件分发机制

  • Android上的View可能是重叠在一起的,当我们在手机屏幕上按下时,哪个View该响应呢?事件分发机制就是为解决这个问题的.
  • 触摸事件: 就是触摸屏幕的事件; 触摸事件分为:按下 / 滑动 / 抬起, Android将触摸事件封装为MotionEvent类

    • MotionEvent的几个重要方法:

      • ev.getX(); // 相对于父容器的x坐标
      • ev.getRawX(); // 屏幕x坐标
      • MotionEvent.ACTION_DOWN; // 按下
      • MotionEvent.ACTION_MOVE; // 滑动
      • MotionEvent.ACTION_UP; // 抬起
    • 事件的传递流程

    • 当我们点击最上面的View时, 完成的事件传递是这样的:
      • Activity -> PhoneWindow -> DecorView -> ContentView -> ViewGroup -> ViewGroup2 -> View -> ViewGroup2 -> ViewGroup -> ContentView -> DecorView -> PhoneWindow -> Activity
      • 可见如果事件在传递的过程中都没有被消费, 那么事件将被回传
    • 以下我们将简化传递流程, 并演示 dispatchTouchEvent(事件分发) / onInterceptTouchEvent(事件拦截) / onTouchEvent(触摸事件)
      • 默认
      • ViewGroup2 的 dispatchTouchEvent 返回 true
        • 这里写图片描述
      • ViewGroup2 的 onInterceptTouchEvent 返回 true
      • ViewGroup2 的 onTouchEvent 返回 true
    • 总结
      • dispatchTouchEvent: 分发事件(return true:拦截(事件丢弃), false,不拦截(默认)) (注:返回true,事件不会交由onTouchEvent处理)
      • onInterceptTouchEvent: 拦截事件 (View没有该回调接口) (return: true:拦截事件,交由onTouchEvent处理, false:传递给子View,由子View的dispatchTouchEvent接收(默认))
      • onTouchEvent: 触摸事件(return true:消费了, false:向上级传递(默认))

View的事件冲突处理

  • 事件冲突实际开发中主要为滑动冲突:

    • 解决方案分为:

      • 第一种: 写一个新的ViewGroup继承父ViewGroup, 并重写onInterceptTouchEvent()
      • 第二种: 子View(或ViewGroup)使用getParent().requestDisallowInterceptTouchEvent(true);请求父类不要拦截Touch事件 (父ViewGroup将不会执行onInterceptTouchEvent()回调)
    • 第一种讲解: 较简单,没什么好讲的,贴个代码做参考吧 (这是限制ViewPager是否可左右滑动的案例代码)

      public class CustomViewPager extends ViewPager {
          private boolean setTouchModel = false;
      
          public CustomViewPager(Context context, AttributeSet attrs) {
              super(context, attrs);
          }
      
          @Override
          public boolean onInterceptTouchEvent(MotionEvent ev) {
              if(setTouchModel){
                  return super.onInterceptTouchEvent(ev);
              }else{
                  return false;
              }
          }
      
          @Override
          public boolean onTouchEvent(MotionEvent ev) {
              if(setTouchModel){
                  return super.onTouchEvent(ev);
              }else{
                  return false;
              }
          }
      
      }
    • 第二种讲解:

      • 先看容器ViewGroup2的代码, 拦截滑动事件,并进行处理

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            Log.e("ViewGroupEvent2", "onInterceptTouchEvent");
        
            switch (ev.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    return true;
            }
            return super.onInterceptTouchEvent(ev);
        }
        
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            Log.e("ViewGroupEvent2", "onTouchEvent");
            return true;
        }
      • 然后触摸view执行的结果, 可见滑动事件都被ViewGroup2消费掉了,view都没有拿到

      • 然后我们在子view中加入getParent().requestDisallowInterceptTouchEvent(true)

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            Log.e("ViewEvent", "dispatchTouchEvent");
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    System.out.println("requestDisallowInterceptTouchEvent(true)");
                    getParent().requestDisallowInterceptTouchEvent(true);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    System.out.println("requestDisallowInterceptTouchEvent(false)");
                    getParent().requestDisallowInterceptTouchEvent(false);
                    break;
            }
            return super.dispatchTouchEvent(ev);
        }
        
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            Log.e("ViewEvent", "onTouchEvent");
            return true;
        }
      • 执行结果如下,可见view已经夺得了滑动事件的控制权

ViewGroup

measure

  • ViewGroup是管理子View的, MeasureSpec.AT_MOST 模式时, ViewGroup会先子View进行遍历, 获取所有子View的大小, 然后决定自己的大小
  • 当子View测量完成后可通过调用 view.getLayoutParams() 获取宽高信息

layout

  • 可通过 view.layout(l, t, r, b); 设置子View的位置

draw

  • 容器没啥可绘制的
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值