自定义热门组件
为了节约屏幕空间,尽可能的添加更多的热门关键词,该组件设计成可以收缩和扩大,并且可以通过上下滑动来选取热门关键词;先上图:
设计过程思想:
- 首先,组件能填充许多小的字符组件如TextView,所以它必须是一个ViewGroup
- 其次, 关键词的布局问题,诸多的组件能够在viewgroup空间里面按照一定的格式布局出来,不出现排列混乱的情况,保证child之间的间隔的padding等;这点需要自行完成onMeasure和onLayout的位置问题,这点需要关注
- 滑动问题, 支持上下滑动,需要我们重写onTouchEvent事件,与view的scrollTo和scrollBy来完成
- 扩张和缩放(上图,点击查看更多就会扩大或缩小热门搜索大小)
- 最后一个是监听的,点击关键词,触发相应的事件,这个可以在外部使用的时候做,不需要过多关心
下面就将上面的几个关键点:
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