Android View 相关知识梳理和总结

一、自定义View相关问题

1. 自定义View的绘制流程?

第一步:复写onMeasure方法。
先measureChild方法 测量出所有子控件的moMeasure。

//1,测量自身
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//2,为每个子view计算测量的限制信息
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//3,把上一步确定的限制信息,传递给每一个子view,然后子view开始measure自己的尺寸
int childCount = getChildCount();//子view的个数
for (int i = 0; i < childCount; i++) {
    View child = getChildAt(i);
    ViewGroup.LayoutParams lp = child.getLayoutParams();
    int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
    int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
    child.measure(childWidthSpec, childHeightSpec);
}  
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   for (int i = 0; i < childCount; i++) {
       View child = getChildAt(i);
       //测量子view 获取到当前子view的测量的宽度和高度
       measureChild(child, widthMeasureSpec, heightMeasureSpec);
   }  
}

调用 setMeasuredDimension(int measuredWidth, int measuredHeight)设置测绘后的大小.

// 保存自身的尺寸
setMeasuredDimension(width, height);

第二步:onLayout方法
在方法调用getChildCount方法 获取到子条目数量。
用for循环遍历出每一个子条目的对象。 通过对象.layout方法 给子控件设置摆放位置。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
   // 遍历子view的onLayout方法
   int left = 0;
   int top = 0;
   int right = 0;
   int bottom = 0;
   int childCount = getChildCount();
   for (int i = 0; i < childCount; i++) {
       View child = getChildAt(i);
       left = i * OFFSET;
       right = left + child.getMeasuredWidth();
       bottom = top + child.getMeasuredHeight();
       child.layout(left, top, right, bottom);//不考虑padding,margin等
       top += child.getMeasuredHeight();
   }
}

 

第三步:onDraw方法

首先调用ViewGroup的disPatchDraw方法绘制ViewGroup。然后调用View中的onDraw方进行绘制。

2. 自定义View是如何测量的 MeasureSpec

需要调用MeasureSpec类可以获取到View的模式和大小:MeasureSpec.getMode() 获取模式、MeasureSpec.getSize() 获取大小。?

这里View模式如下:

MeasureSpec.EXACTLY 精确值模式: 表示父控件已经确切的指定了子视图的大小。

MeasureSpec.AT_MOST 最大值模式:表示子查看具体大小没有尺寸限制,但是存在上限,上限一般为父视图大小。

MeasureSpec.UNSPECIFIED 父控件没有给子视图任何限制,子视图可以设置为任意大小。这个模式会用到呢,可以看一下:https://www.cnblogs.com/liushilin/p/11055741.html

3. requestLayout、invalidate和postInvalidate方法的区别?

requestLayout方法会导致View的onMeasure、onLayout、onDraw方法被调用。

invalidate方法则只会导致View的onDraw方法被调用。

invalidate方法和postInvalidate方法都是用于进行View的刷新,invalidate方法应用在UI线程中,而postInvalidate方法应用在非UI线程中,用于将线程切换到UI线程,postInvalidate方法实现了消息机制,最终调用的也是invalidate方法来刷新View。

我们在自定义View时,当需要刷新View时,如果是在UI线程中,那就直接调用invalidate方法,如果是在非UI线程中,那就通过postInvalidate方法来刷新View。

invalidate方法最终调用的是ViewRootImpl中的performTraversals来重新绘制View。

----------------------------------------------------------------------------------------------------------------

二、View滑动冲突解决相关问题

1. 如何解决点击事件和长按事件同时响应的问题?

从事件分发机制来分析,在响应事件的时候,如果同时绑定了长按和点击事件,长按事件触发时默认不做处理的话会优先响应长按事件,如果长按事件里面没有消费掉改事件,根据事件分发机制,该事件会继续传递,直到有控件把它消费了。明白了这个道理,长按事件和短按事件同时响应的问题就不难解决了,在同时绑定两个事件的情况下,如果先点击的是短按事件没问题,自然触发不到长按事件,但是如果先触发的是长按事件如果当前return false的话代表当前事件需要继续向下传递所以短按事件就被响应了,这种情况在onLongClick中返回true即可,代表当前有能力消费掉该事件,阻止事件向下传递,短按事件就不会被触发了。

//longClick事件
textView.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        onItemClickListener.onItemLongClick(myViewHolder.textView,position);
        return true;   //return true即可解决长按事件跟点击事件同时响应的问题
    }
});

2.ViewGroup的dispatchTouchEvent若返回false,此时事件分发顺序是怎样的?事件已经分发到某个viewgroup了

  • DOWN事件进来,清空状态(包括disallowintercept标记)
  • 为intercepted赋值
    • DOWN事件,根据disallowintercept标记和onItercept(),决定intercepted的值
    • mFirstTouchTarget不为空,也就是该事件不是down事件,并且这个ViewGroup的孩子中有孩子处理了之前的事件。跟上一条一样,根据disallowintercept标记和onItercept(),决定intercepted的值
    • 不是以上两种情况,也就是非down事件,并且mFirstTouchTarget为空,也就是这个事件序列中,down已经被该ViewGroup处理,所以现在这个事件也是由这个ViewGroup处理,所以intercepted直接就是true
  • 如果不拦截
    • 如果是down
      • 从孩子中找到可以接收事件的孩子,并且调用孩子的dispatchTouchEvent,也就是让他处理
        • return true,也就是孩子处理了,那就把孩子赋值给mFirstTouchTarget(TouchTarget是一个包裹了View的对象),并且把一把局部变量alreadyDispatchedToNewTouchTarget设为true,表示该事件是down,并且已经找到了处理他的孩子
        • return false,找其他孩子,如果有一个return true,就像上一步一样,如果全是false,那么mFirstTouchTarget不会被赋值,依旧是false,alreadyDispatchedToNewTouchTarget也是false
  • 上面的逻辑处理完之后来,来到这里
    • mFirstTouchTarget为空,表示,不管是down还是其他事件,总之就是没有孩子要处理,因为上一步中没有给mFirstTouchTarget赋值。所以直接让自己处理,调用super.dispatchtouchevent();
    • 否则,该事件序列已经有孩子处理了,但是分两种情况
      • 这个事件是down事件,孩子已经处理了,这个可以根据alreadyDispatchedToNewTouchTarget来决定,因为alreadyDispatchedToNewTouchTarget是局部变量,此时直接让handled为true即可
      • 这个事件是非down事件,该事件序列都是让mFirstTouchTarget处理,所以该事件也是,所以调用mFirstTouchTarget.dispatchtouchevent(),让孩子处理,(但是如果最开始的intercepted是true了,这里分发给孩子的时候action就是cancle,事件就被取消了,而且mFirstTouchTarget会一直被赋值,赋值为下一个child,直到最后,成为空,这样,就会让viewgroup处理事件了)
        • 这里孩子可能会返回true或者false
        • return true,孩子已经处理
        • return false,表示孩子不愿意处理,但是不同于上面第三节中的孩子不处理,第三节中,事件是down,孩子不处理,就让该viewgroup处理,而这里是非down事件,孩子不处理,事件就消失了,return false,那么上一级viewgroup也是return false,一路向上,最后就到activity的dispatchTouchEvent了。

----------------------------------------------------------------------------------------------------------------

Android View 相关知识梳理和总结

上一篇:Android--Google Map API V2使用


下一篇:筛素数 poj 2739