ViewGroup的实践以及事件分发

139 阅读3分钟

1. 实践:Tablayout(流式布局)

1.1 测绘以及排版

ViewGroup的自定义首先需要解决的问题是子View的排版以及ViewGroup的测绘。也就是说会设计到两个重要的方法:onMeasure以及onLayout

1. 测绘

在流式布局中,最显著的特征是一行一行地往下排view如果在该行中,不能塞下了,则另起一行。
同时值得注意的是margin怎样去获取。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 清空集合
        mChildViews.clear();
        /*获取高度以及宽度*/
        /*这个没有太大的变化*/
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = getPaddingTop() + MeasureSpec.getSize(heightMeasureSpec);
        /*计算lineWidth*/
        int lineWidth = getPaddingLeft();
        int childCount = getChildCount();
        ArrayList<View> childViews = new ArrayList<>();
        mChildViews.add(childViews);
        // 子View高度不一致的情况下
        int maxHeight = 0;
        for (int i = 0; i < childCount; i++) {
            /*获取childView*/
            View childView = getChildAt(i);
            if(childView.getVisibility()==GONE){
                continue;
            }
            /*这段话执行完之后,可以获取子view的宽gao,因为会调用子View的onMeasure方法。*/
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            /*根据子View计算和指定自己的布局*/
            // margin值 ViewGroup.LayoutParams 没有 就用系统的MarginLayoutParams
            // 想想 LinearLayout为什么有?
            // LinearLayout有自己的 LayoutParams  会复写一个非常重要的方法
            ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
            /*换行计算*/
            if (lineWidth + (childView.getMeasuredWidth() + params.rightMargin + params.leftMargin) > width) {
                // 换行,累加高度  加上一行条目中最大的高度
                height += childView.getMeasuredHeight() + params.bottomMargin + params.topMargin;
                lineWidth = childView.getMeasuredWidth() + params.rightMargin + params.leftMargin;
                childViews = new ArrayList<>();
                mChildViews.add(childViews);
            } else {
                /*不换行*/
                lineWidth += childView.getMeasuredWidth() + params.rightMargin + params.leftMargin;
                maxHeight = Math.max(childView.getMeasuredHeight() + params.bottomMargin + params
                        .topMargin, maxHeight);
            }
            childViews.add(childView);
        }
        /*获取该行View中的最大值作为View的高*/
        height += maxHeight;
        /*计算ViewGroup自己的宽高*/
        setMeasuredDimension(width, height);
    }
    
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

2. 排版

在代码块中有几个值得注意的点:

  1. 这里为了不重复测量,使用了一个List<List<View>>的集合,每行的view塞进一个集合。因为有集合,同时onMeasure可能会被多次调用,所以每次进入前先清空mChilsViews中的数据是必不可少的。

   /**
    * 指定子view的位置
    *
    * @param changed
    * @param l
    * @param t
    * @param r
    * @param b
    */
   @Override
   protected void onLayout(boolean changed, int l, int t, int r, int b) {

       int childCount = getChildCount();
       int left, top = getPaddingTop(), right, bottom;
       for (List<View> childViews : mChildViews) {

           /*每行中的View*/
           left = getPaddingLeft();
           int maxHeight = 0;
           for (View view : childViews) {
               if(view.getVisibility() == GONE){
                   continue;
               }
               ViewGroup.MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
               left += params.leftMargin;
               int childTop = top + params.topMargin;
               right = left + view.getMeasuredWidth();
               bottom = childTop + view.getMeasuredHeight();
               /*摆放*/
               view.layout(left, top, right, bottom);
               /*叠加*/
               left += view.getMeasuredWidth() + params.rightMargin;
               // 不断的叠加top值
               int childHeight = view.getMeasuredHeight()+ params.topMargin+params.bottomMargin;
               maxHeight = Math.max(maxHeight,childHeight);
           }
           // 不断的叠加top值
           top += maxHeight;
       }
   }

还有个值得注意的是:因为有margin,所以在每行中应该取子view的高最高的那个高作为该行的高。

3. 思考

如果数据是从后台获取的,怎样设置TAg呢,这就需要适配器了。

public abstract class TabBaseAdapter {
    public abstract int getCount();
    public abstract View getView(int position, ViewGroup parent);
    public void notifyDatasetChanged(){}

}

// tablayout中的
    public void setmAdapter(TabBaseAdapter mAdapter) {
        if (mAdapter==null){
            /*空指针异常*/
            throw new NullPointerException("TabBaseAdapter is not allowed to be null");
        }
        /*清空所有的子View*/
        removeAllViews();
        this.mAdapter=null;
        this.mAdapter = mAdapter;
        int childCount=mAdapter.getCount();
        for(int i=0;i<childCount;i++){
            View view=mAdapter.getView(i,this);
            addView(view);
        }
    }
    // 在activity中调用TabLayout
     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_day9);
        mTabLayout=findViewById(R.id.tab_layout);
        mTabLayout.setmAdapter(new TabBaseAdapter() {
            @Override
            public int getCount() {
                return 0;
            }

            @Override
            public View getView(int position, ViewGroup parent) {
                View view=LayoutInflater.from(Day9Activity.this).inflate(R.layout.item_tag,parent,false);
                return view;
            }
        });
    }
}

ViewGroup的事件分发

|---ViewGroup
|   |---dispatchTouchEvent
|   |---onInterceptTouchEvent

事件分发流程

// 第一次 down
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent
//第二次 move
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent
// 第三次 up
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent->onClick

如果在activity中不使用onclick

ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent->viewGroup.onTouchEvent

它不会进入view的touchevent的事件分发

在view的onTouchEvent()返回true.

// 第一次 down
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent
//第二次 move
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent
// 第三次 up(不调用 super.onTouchEvent(event))
// 不会调用onClick
ViewGroup.dispatchTouchEvent->onInterceptTouchEvent->View.dispatchTouchEvent->View.onTouch->view.onTouchEvent

在viewGroup中的onIntercept返回true

// 会进入ViewGroup.onTouchEvent()
ViewGroup.dispatchTouchEvent->ViewGroup.onInterceptTouchEvent->ViewGroup.onTouchEvent

源码分析