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

自定义热门组件

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


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拿来进行一行的布局,在此条件下还有可能出现剩余空间,将剩余空间分摊到每个组件上去即可;如下图:

布局

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

@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;
        }

    }

触摸滑动

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

这里写图片描述

代码很简单,如下:

 @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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容