一款自定义的热门搜索组件

自定义热门组件

为了节约屏幕空间,尽可能的添加更多的热门关键词,该组件设计成可以收缩和扩大,并且可以通过上下滑动来选取热门关键词;先上图:


show

设计过程思想:

  1. 首先,组件能填充许多小的字符组件如TextView,所以它必须是一个ViewGroup
  2. 其次, 关键词的布局问题,诸多的组件能够在viewgroup空间里面按照一定的格式布局出来,不出现排列混乱的情况,保证child之间的间隔的padding等;这点需要自行完成onMeasure和onLayout的位置问题,这点需要关注
  3. 滑动问题, 支持上下滑动,需要我们重写onTouchEvent事件,与view的scrollTo和scrollBy来完成
  4. 扩张和缩放(上图,点击查看更多就会扩大或缩小热门搜索大小)
  5. 最后一个是监听的,点击关键词,触发相应的事件,这个可以在外部使用的时候做,不需要过多关心
    下面就将上面的几个关键点:

child之间的布局问题

测量 -- onMeausre测量组件自身的大小

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        childCount = getChildCount();

        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        view_default_height = (view_default_height == 0) ? sizeHeight : view_default_height;        //view_default_height用于保存组件的原始高度,后续改为扩张高度

        measureChildren(widthMeasureSpec, heightMeasureSpec);

        setMeasuredDimension(sizeWidth, view_default_height);

    }

主要是给定当前组件一个固定的高度view_default_height,并且这个高度在后续的扩张和缩小会用到

布局放置 -- onLayout放置每个child的位置

布局思想就是:
宽度的布局: 依次测量每个child的宽度并累加,如果宽度和大于viewgroup的宽度,就把前面几个child拿来进行一行的布局,在此条件下还有可能出现剩余空间,将剩余空间分摊到每个组件上去即可;如下图:

一款自定义的热门搜索组件_第1张图片
布局

高度的布局: 记录每一行的的高度布局位置,下次布局从上次的高度布局开始向下布局即可,
实现代码如下:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        parent_bottom = b;
        parent_width = r;

        int childViewWidth = l;                                                                     //一行child宽度和,用于判断是否超出父的宽度
        int start_index = 0;
        int end_index = 0;
        int startX = l;
        int startY = t;
        int space;
        for(int i = 0; i < childCount; i++){
            View child = getChildAt(i);

            int sizeWidth = child.getMeasuredWidth();
            int sizeHeight = child.getMeasuredHeight();
            space = r - childViewWidth;                                                             //一行里面的剩余空间
            childViewWidth = childViewWidth + default_space + sizeWidth;                            //一行view的宽度和 = 组件宽度 + 间隔
            if(childViewWidth > r - 30){
                end_index = i;
                onLayoutChildView(start_index, end_index, startX, startY, space);
                startY = startY + default_space + sizeHeight;
                childViewWidth = l + sizeWidth;
                start_index = i;
            }

        }

        /**
         * 说明还有一部分没有布局的child
         */
        if(end_index != childCount){
            onLayoutChildView(start_index, childCount, l, startY, 0);
        }
        second_enter++;
    }

    /**
     * 布局一行的组件视图
     * @param start_index  其实child
     * @param end_index    结束child
     * @param start_x      x开始的位置
     * @param start_y      y开始的位置
     * @param space        一行剩余的空间
     */
    private void onLayoutChildView(int start_index, int end_index, int start_x, int start_y, int space){

        int endX = 0;
        int endY = 0;
        int sub_space = 0;                                                                           //需要将每行的剩余空间分摊到每个组件上去,减去30是一个选取的值,防止计算大小的精确问题,超出右边父的最长宽度
        if(space - 30 > 0){
            int view_numbers = end_index - start_index + 1;
            sub_space = (space - 30)/ view_numbers;
        }
        int i;
        for(i = start_index; i < end_index; i++){
            View child = getChildAt(i);
            endX = start_x + child.getMeasuredWidth() + sub_space;
            endY = start_y + child.getMeasuredHeight();
            if(second_enter < 2){                                                                   //分摊只需要在前两次进行分摊,后续不在分摊;因为后续布局都已经完成,再次分摊会照成重新获取padding值,该值会累加的
                int paddingLR = child.getPaddingLeft() + sub_space / 2;
                int paddingTB = child.getPaddingBottom();
                child.setPadding(paddingLR, paddingTB, paddingLR, paddingTB);
            }
                                                                                                    //每排最后一个必须等于右边限制的位置,对齐;除了最后一排单独几个那种
            if(i == end_index - 1 && space != 0){
                endX = parent_width - 30;
            }
            child.layout(start_x, start_y, endX, endY);

            start_x = endX + default_space;
        }
        if(i == childCount){                                                                        //最后一行时,计算父组件最大值和child最下面的Y值,计算差值作为向上滑动的最大距离
            moveUpDistance = endY - parent_bottom + default_space + 40;
        }

    }

触摸滑动

设计思想:
看图就明白了,主要是判断向上和向下的滑动距离,要限制其滑动的最大距离

一款自定义的热门搜索组件_第2张图片
这里写图片描述

代码很简单,如下:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        //只有在展开的情况下才能进行滑动操作

        if(!isExpand){
            return super.onTouchEvent(event);
        }

        int action = event.getAction();

        switch (action){
            case MotionEvent.ACTION_DOWN:
                downY = (int)event.getY();
                break;

            case MotionEvent.ACTION_MOVE:

                moveY = (int)event.getY();

                int dy = downY - moveY;                                                             //需要移动的距离
                int need_move_y = getScrollY() + dy;                                                //getScrollY()会得到一个距离值,该距离值=原始组件位置和偏移后组件的差值
                if(need_move_y < 0){
                    scrollTo(0, 0);
                }else if (need_move_y > moveUpDistance){
                    scrollTo(0, moveUpDistance);
                }else{
                    scrollBy(0, dy);
                }
                downY = moveY;
                break;

            case MotionEvent.ACTION_UP:
                break;

            default:
                break;
        }
        return true;
    }
}

完成到这里,组件就可以上下滑动了;但是在这儿有个问题,我也没搞懂,当你添加的child设置了setOnclick后滑动就会受干扰,如果是addTouchListener的话就能正常的上下滑动,根据监听事件的传递机制是:dispatch -- onTouch -- intecptTouch -- onTouchEvent -- onClick,而且这又涉及了很多child我怀疑是某个child消费了滑动事件导致的,但是还没找到解决方法,哪位能解决了,还请告知

至此,组件就设计完成了,源码在下面:
https://github.com/JackZhous/HotSearchViewGroup

你可能感兴趣的:(一款自定义的热门搜索组件)