自定義熱門組件
為了節(jié)約屏幕空間座咆,盡可能的添加更多的熱門關(guān)鍵詞泛烙,該組件設(shè)計(jì)成可以收縮和擴(kuò)大店量,并且可以通過(guò)上下滑動(dòng)來(lái)選取熱門關(guān)鍵詞;先上圖:
設(shè)計(jì)過(guò)程思想:
- 首先,組件能填充許多小的字符組件如TextView玉雾,所以它必須是一個(gè)ViewGroup
- 其次翔试, 關(guān)鍵詞的布局問題,諸多的組件能夠在viewgroup空間里面按照一定的格式布局出來(lái)复旬,不出現(xiàn)排列混亂的情況垦缅,保證child之間的間隔的padding等;這點(diǎn)需要自行完成onMeasure和onLayout的位置問題驹碍,這點(diǎn)需要關(guān)注
- 滑動(dòng)問題壁涎, 支持上下滑動(dòng),需要我們重寫onTouchEvent事件志秃,與view的scrollTo和scrollBy來(lái)完成
- 擴(kuò)張和縮放(上圖怔球,點(diǎn)擊查看更多就會(huì)擴(kuò)大或縮小熱門搜索大小)
- 最后一個(gè)是監(jiān)聽的浮还,點(diǎn)擊關(guān)鍵詞竟坛,觸發(fā)相應(yīng)的事件,這個(gè)可以在外部使用的時(shí)候做钧舌,不需要過(guò)多關(guān)心
下面就將上面的幾個(gè)關(guān)鍵點(diǎn):
child之間的布局問題
測(cè)量 -- onMeausre測(cè)量組件自身的大小
@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用于保存組件的原始高度担汤,后續(xù)改為擴(kuò)張高度
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(sizeWidth, view_default_height);
}
主要是給定當(dāng)前組件一個(gè)固定的高度view_default_height,并且這個(gè)高度在后續(xù)的擴(kuò)張和縮小會(huì)用到
布局放置 -- onLayout放置每個(gè)child的位置
布局思想就是:
寬度的布局: 依次測(cè)量每個(gè)child的寬度并累加延刘,如果寬度和大于viewgroup的寬度漫试,就把前面幾個(gè)child拿來(lái)進(jìn)行一行的布局,在此條件下還有可能出現(xiàn)剩余空間碘赖,將剩余空間分?jǐn)偟矫總€(gè)組件上去即可驾荣;如下圖:
高度的布局: 記錄每一行的的高度布局位置,下次布局從上次的高度布局開始向下布局即可普泡,
實(shí)現(xiàn)代碼如下:
@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;
}
}
/**
* 說(shuō)明還有一部分沒有布局的child
*/
if(end_index != childCount){
onLayoutChildView(start_index, childCount, l, startY, 0);
}
second_enter++;
}
/**
* 布局一行的組件視圖
* @param start_index 其實(shí)child
* @param end_index 結(jié)束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; //需要將每行的剩余空間分?jǐn)偟矫總€(gè)組件上去,減去30是一個(gè)選取的值,防止計(jì)算大小的精確問題撼班,超出右邊父的最長(zhǎng)寬度
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){ //分?jǐn)傊恍枰谇皟纱芜M(jìn)行分?jǐn)偲缧伲罄m(xù)不在分?jǐn)偅灰驗(yàn)楹罄m(xù)布局都已經(jīng)完成砰嘁,再次分?jǐn)倳?huì)照成重新獲取padding值件炉,該值會(huì)累加的
int paddingLR = child.getPaddingLeft() + sub_space / 2;
int paddingTB = child.getPaddingBottom();
child.setPadding(paddingLR, paddingTB, paddingLR, paddingTB);
}
//每排最后一個(gè)必須等于右邊限制的位置,對(duì)齊矮湘;除了最后一排單獨(dú)幾個(gè)那種
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){ //最后一行時(shí)斟冕,計(jì)算父組件最大值和child最下面的Y值,計(jì)算差值作為向上滑動(dòng)的最大距離
moveUpDistance = endY - parent_bottom + default_space + 40;
}
}
觸摸滑動(dòng)
設(shè)計(jì)思想:
看圖就明白了缅阳,主要是判斷向上和向下的滑動(dòng)距離磕蛇,要限制其滑動(dòng)的最大距離
代碼很簡(jiǎn)單,如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
//只有在展開的情況下才能進(jìn)行滑動(dòng)操作
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; //需要移動(dòng)的距離
int need_move_y = getScrollY() + dy; //getScrollY()會(huì)得到一個(gè)距離值,該距離值=原始組件位置和偏移后組件的差值
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;
}
}
完成到這里秀撇,組件就可以上下滑動(dòng)了超棺;但是在這兒有個(gè)問題,我也沒搞懂呵燕,當(dāng)你添加的child設(shè)置了setOnclick后滑動(dòng)就會(huì)受干擾棠绘,如果是addTouchListener的話就能正常的上下滑動(dòng),根據(jù)監(jiān)聽事件的傳遞機(jī)制是:dispatch -- onTouch -- intecptTouch -- onTouchEvent -- onClick,而且這又涉及了很多child我懷疑是某個(gè)child消費(fèi)了滑動(dòng)事件導(dǎo)致的再扭,但是還沒找到解決方法弄唧,哪位能解決了,還請(qǐng)告知
至此霍衫,組件就設(shè)計(jì)完成了候引,源碼在下面:
https://github.com/JackZhous/HotSearchViewGroup