一误澳、QQ效果與最終效果比較
二浮创、分析
從效果大致可以看出兩個(gè)規(guī)律:
- 字體的矩形面積越來越小
- 字體大小越來越小
很像廢話吧集侯,不是的。除了字體试躏,我們還能看到文字有豎向排行有橫向排列猪勇,而且沒有規(guī)律。
2.1 問題分解
假設(shè)我們只有一個(gè)標(biāo)簽文字颠蕴,可以選擇自定義View
(當(dāng)然可以選擇自定義ViewGroup)泣刹,然后隨機(jī)標(biāo)簽文字的left
和top
助析,文字大小從30sp開始,然后在onDraw
里面繪制矩形椅您,在矩形里面繪制文字外冀。
繪制第一個(gè)標(biāo)簽文字之后,我們想繪制第二個(gè)標(biāo)簽文字掀泳,如果我們還在當(dāng)前的View
里面去隨機(jī)一個(gè)Rect
雪隧,可能會和第一個(gè)標(biāo)簽重合,那怎么辦员舵?我們想到了裁剪脑沿,看下圖:
沿著標(biāo)簽我們可以將View
切成Rect
①、Rect
②马僻、Rect
③庄拇、Rect
④,這個(gè)時(shí)候我們分別將四塊矩形看成新的View
去繪制一個(gè)標(biāo)簽文字巫玻。
這樣大問題就化解成了許許多多的小問題丛忆。
2.2 如果Rect
寬大于高
- 如果標(biāo)簽文字的高度大于
Rect
的高度祠汇,我們可以遞減標(biāo)簽文字的TextSize
仍秤,一直到標(biāo)簽文字的高度小于Rect
的高度,我們直接從Rect
的Left
開始繪制標(biāo)簽就可以可很,看圖:
第一個(gè)標(biāo)簽繪制完成之后诗力,繼續(xù)在這個(gè)標(biāo)簽的右邊重復(fù)繪制第一個(gè)標(biāo)簽大小的標(biāo)簽,一直到Rect
剩余的空間不足以繪制一個(gè)當(dāng)前的大小的標(biāo)簽我抠。
- 如果文字的寬度大于
Rect
的寬度苇本,同樣的我們也遞減標(biāo)簽文字的TextSize
,一直到標(biāo)簽文字的寬度小于Rect
的寬度菜拓,我們直接從Rect
的Top
開始繪制標(biāo)簽就可以瓣窄,看圖:
第一個(gè)標(biāo)簽繪制完成之后,繼續(xù)在這個(gè)標(biāo)簽的下邊重復(fù)繪制第一個(gè)標(biāo)簽大小的標(biāo)簽纳鼎,一直到Rect
剩余的空間不足以繪制一個(gè)當(dāng)前的大小的標(biāo)簽俺夕。
- 如果以上都不滿足,說明標(biāo)簽的寬高都遠(yuǎn)小于
Rect
的寬高贱鄙,那就變成了我們最開始的大問題劝贸,隨機(jī)標(biāo)簽文字的left
和top
,再切四個(gè)Rect
出來逗宁,重復(fù)以上步驟映九。
2.3 如果Rect
高大于寬
Rect
高大于寬,標(biāo)簽適合豎向排列瞎颗,豎向排列考慮起來比較簡單件甥,不需要隨機(jī)一個(gè)位置開始豎向捌议,就從Rect
的Left
開始排列,看起來整齊引有,看圖:
第一個(gè)標(biāo)簽繪制完成之后禁灼,繼續(xù)在標(biāo)簽的右邊重復(fù)繪制第一個(gè)標(biāo)簽大小的標(biāo)簽,一直到Rect
右邊剩余的空間不足以繪制一個(gè)當(dāng)前的大小的標(biāo)簽轿曙,然后將剩下的空間切成Rect
①和Rect
②弄捕,重復(fù)以上步驟。
三导帝、核心代碼
3.1 定義Tag對象
public class Tag {
private String name;
private int left;
private int top;
private int right;
private int bottom;
private int textsize;
// 省略構(gòu)造函數(shù)和setter getter
}
這個(gè)class
的作用類似記錄器守谓,記錄每一個(gè)Tag
的位置和文字大小信息。
3.2 核心函數(shù)
public void computeSingleRect(List<String> tags, int textSize, int pLeft, int pTop, int pRight, int pBottom) {
if (tags == null || tags.size() == 0 || textSize < MIN_TEXT_SIZE || pBottom == 0 || pRight == 0 || pLeft >= pRight || pTop >= pBottom) {
return;
}
int cLeft = 0;
int cTop = 0;
int cRight = 0;
int cBottom = 0;
int textWidth = 0;
int textHeight = 0;
int size = tags.size();
int index = (int) (Math.random() * (size - 1));
String name = tags.get(index);
//計(jì)算當(dāng)前rect的寬高
int rectWidth = pRight - pLeft;
int rectHeight = pBottom - pTop;
if (rectWidth > rectHeight) {
//父布局長大于高您单,橫向布局合適
paint.setTextSize(textSize);
textWidth = (int) paint.measureText(name);
textHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top);
if (textHeight > rectHeight) {
//記錄之前的textsize
int beforeTextSize = textSize;
while (textHeight > rectHeight) {
textSize--;
paint.setTextSize(textSize);
textHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top);
}
textWidth = (int) paint.measureText(name);
while (textWidth > rectWidth) {
textSize--;
paint.setTextSize(textSize);
textWidth = (int) paint.measureText(name);
}
if(textSize < MIN_TEXT_SIZE){
return;
}
textHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top);
cLeft = pLeft;
cTop = pTop;
cRight = textWidth + pLeft;
cBottom = textHeight + pTop;
showTags.add(new Tag(name, textSize, cLeft, cTop, cRight, cBottom));
textWidth = (int) paint.measureText(name);
if (pRight - cRight > textWidth) {
//右
computeSingleRect(tags, beforeTextSize, cRight, pTop, pRight, pBottom);
} else {
//右
computeSingleRect(tags, --textSize, cRight, pTop, pRight, pBottom);
}
} else {
if (textWidth >= rectWidth) {
while (textWidth > rectWidth) {
textSize--;
paint.setTextSize(textSize);
textWidth = (int) paint.measureText(name);
}
if(textSize < MIN_TEXT_SIZE){
return;
}
textHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top);
cLeft = pLeft;
cTop = pTop;
cRight = pRight;
cBottom = cTop + textHeight;
showTags.add(new Tag(name, textSize, cLeft, cTop, cRight, cBottom));
//下
textSize += 4;
computeSingleRect(tags, textSize, cLeft, cBottom, cRight, pBottom);
} else {
cLeft = (int) (Math.random() * (rectWidth / 3)) + pLeft; // 除以3是為了盡快找到合適的位置
while (cLeft + textWidth > pRight) {
cLeft--;
}
cTop = (int) (Math.random() * (rectHeight / 2)) + pTop;
while (cTop + textHeight > pBottom) {
cTop--;
}
cRight = cLeft + textWidth;
cBottom = cTop + textHeight;
showTags.add(new Tag(name, textSize, cLeft, cTop, cRight, cBottom));
//左
computeSingleRect(tags, --textSize, pLeft, pTop, cLeft, cBottom);
//上
computeSingleRect(tags, --textSize, cLeft, pTop, pRight, cTop);
//右
computeSingleRect(tags, --textSize, cRight, cTop, pRight, pBottom);
//下
computeSingleRect(tags, --textSize, pLeft, cBottom, cRight, pBottom);
}
}
} else {
//父布局高大于長斋荞,縱向布局合適
int beforeTextSize = textSize;
paint.setTextSize(textSize);
textHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top);
while (textHeight * name.length() > rectHeight) {
textSize--;
paint.setTextSize(textSize);
textHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top);
}
if(textSize < MIN_TEXT_SIZE){
return;
}
textWidth = (int) (paint.measureText(name) / name.length());
int length = name.length();
if (pLeft + textWidth > pRight) {
//右 右邊空間不足
computeSingleRect(tags, --textSize, pLeft, pTop, pRight, pBottom);
return;
}
for (int i = 0; i < length; i++) {
cLeft = pLeft;
cTop = pTop + i * textHeight;
cRight = cLeft + textWidth;
cBottom = cTop + textHeight;
showTags.add(new Tag(String.valueOf(name.charAt(i)), textSize, cLeft, cTop, cRight, cBottom));
}
if (pRight - cRight > textWidth) {
//右
computeSingleRect(tags, beforeTextSize, cRight, pTop, pRight, cBottom);
//下
computeSingleRect(tags, --textSize, pLeft, cBottom, pRight, pBottom);
} else {
//右
computeSingleRect(tags, --textSize, cRight, pTop, pRight, cBottom);
//下
computeSingleRect(tags, --textSize, pLeft, cBottom, pRight, pBottom);
}
}
}
很清楚的看到,是一個(gè)遞歸函數(shù)虐秦,一開始就是遞歸的結(jié)束條件平酿。注意里面的切割Rect
的方法,pLeft
悦陋、pTop
蜈彼、pRight
、pBottom
代表父Rect
的邊界俺驶,cLeft
幸逆、cTop
、cRight
暮现、cBottom
代表Tag
的邊界还绘。里面有一個(gè)很巧妙的記錄進(jìn)入條件時(shí)候的TextSize
,目的是讓下一次遞歸還繼續(xù)進(jìn)入這個(gè)條件下栖袋,也就做到了重復(fù)繪制相同大小的Tag
的目的拍顷。
但是在textWidth >= rectWidth
這個(gè)條件下記錄TextSize
卻容易造成最后一個(gè)Tag
繪制不出來,導(dǎo)致留白區(qū)域大塘幅,有一點(diǎn)瑕疵昔案,但是整體目的達(dá)到了。
附上Github地址晌块,喜歡的給個(gè)Star
爱沟,謝謝。