
大家好,又到了新的一次需求分析,這次我們的需求是:在不同的條件的前提下,點(diǎn)擊一個(gè)菜單按鈕,出來不同的菜。
比如:下面是一系列的公司列表(當(dāng)然也可以是不同的地區(qū),不同的城市,等等),然后當(dāng)你選擇好某個(gè)之后,我們點(diǎn)擊菜單按鈕,這時(shí)候出來不同的菜單
然后我們出來的菜單是:







然后大家說了。這不是很簡(jiǎn)單的事么。做4個(gè)布局,分別作為這四個(gè)公司的菜單,然后選擇哪個(gè)公司,就彈出哪個(gè)公司的菜單。
少年,Too Young Too Simple,比如我們一共有10項(xiàng)中的業(yè)務(wù),某個(gè)A公司有我們的三個(gè)功能,然后你前段界面寫死,B公司有五項(xiàng)功能,然后你這時(shí)候?qū)懥硕€(gè)界面,這時(shí)候,突然A公司說我升級(jí)了。我也要跟B公司一樣有五項(xiàng)功能,然后你又去改界面? 一共有A,B,C...W 公司,難道你就去寫A-W個(gè)布局??(同理,如果是城市劃分,比如在不同的城市可能支持的功能業(yè)務(wù)不同,出現(xiàn)不同的菜單。大城市覆蓋的功能更全,小城市功能更少)
所以這里我們公司的數(shù)量,公司相對(duì)于的功能,功能名字,功能圖片名字,都是后臺(tái)傳到前端,我們只需要準(zhǔn)備一個(gè)界面,然后在不同情況下,去顯示不同的菜單功能即可。
比如后臺(tái)傳給我們:
{
"companys": [
{
"公司1": [
{
"name": "吃飯",
"iconName": "icon_xxx",
"typeId": "1"
}
]
}
]
}
這樣我們就很大程序前段就自由了。那我們的難點(diǎn)就變成了:
既然我們是動(dòng)態(tài)的顯示這個(gè)菜單,拿到這些數(shù)據(jù)后怎么來呈現(xiàn)呢
很多人應(yīng)該做這么個(gè)界面會(huì)覺得簡(jiǎn)單,但是如果是一個(gè)根據(jù)數(shù)量自動(dòng)排好的菜單界面就有點(diǎn)不知所措了。所以這里我們的難點(diǎn)就變成了。如果給了我們N個(gè)數(shù)據(jù),我們要在這個(gè)彈框中顯示出N個(gè),那我們的問題也就變成了:能否提供一個(gè)自定義的ViewGroup,然后我傳入幾個(gè)View對(duì)象,可以按照一定的規(guī)則幫我自動(dòng)排布,這樣我們拿到N個(gè)數(shù)據(jù)后,只需要新建相應(yīng)的View對(duì)象,然后添加到這個(gè)ViewGroup就行了。
答案當(dāng)然是能提供。(這波B裝的太累了。喘口氣。)
既然我們要做的是一個(gè)自動(dòng)按照上面圖片顯示排布規(guī)則的ViewGroup,系統(tǒng)肯定是沒有自帶的。所以我們就需要自定義一個(gè)ViewGroup。
- 自定義ViewGroup的第一步:繼承ViewGroup:
public class CircleLayout extends ViewGroup {
private float mAngleOffset;
private float mAngleRange;
private int mInnerRadius;
public CircleLayout(Context context) {
super(context);
}
public CircleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleLayout, 0, 0);
try {
mAngleOffset = a.getFloat(R.styleable.CircleLayout_angleOffset, -90f);
mAngleRange = a.getFloat(R.styleable.CircleLayout_angleRange, 360f);
mInnerRadius = a.getDimensionPixelSize(R.styleable.CircleLayout_innerRadius, 80);
} catch (Exception e) {
e.printStackTrace();
} finally {
a.recycle();
}
}
}
說明下三個(gè)參數(shù):
private float mAngleOffset;//擺放子View的角度偏移值
private float mAngleRange;//子VIew可以擺放的角度范圍,比如最多是360度
private int mInnerRadius;//子View距離這個(gè)ViewGroup中心點(diǎn)的距離
2.實(shí)現(xiàn)onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
}
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
int width = resolveSize(maxWidth, widthMeasureSpec);
int height = resolveSize(maxHeight, heightMeasureSpec);
if(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST){
setMeasuredDimension(1000, 1000);
}else if(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST){
setMeasuredDimension(1000, height);
}else if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST){
setMeasuredDimension(width, 1000);
}else{
setMeasuredDimension(width, height);
}
}
3.實(shí)現(xiàn)onLayout方法:
這個(gè)很重要,我重點(diǎn)也是講解這個(gè)onLayout方法,因?yàn)槿绻憷^承ViewGroup,會(huì)提示你一定要實(shí)現(xiàn)這個(gè)方法。而且我們也知道了我們最主要的點(diǎn)就在于怎么根據(jù)傳進(jìn)來的子View的個(gè)數(shù)來進(jìn)行相應(yīng)的擺放。所以我們直接來看onLayout的具體實(shí)現(xiàn)。
onLayout方法:
我們假設(shè)我們的自定義ViewGroup是占滿整個(gè)屏幕的,都是match_parent。然后就如下圖所示:
這時(shí)候如果我們想擺四個(gè)子View(四個(gè)的分析起來簡(jiǎn)單點(diǎn)),這時(shí)候的界面應(yīng)該是:
這時(shí)候大家肯定想,這有什么規(guī)則嗎,完全沒想法啊。。哈哈不要急,看我一步操作,你馬上懂:
我們移動(dòng)畫布,等價(jià)我們讓坐標(biāo)系移動(dòng)了中間,這時(shí)候你是不是恍然大悟,我們只需要按照角度來不就可以了嗎。
我們繼續(xù)往下看:
好的。我們可以看到每個(gè)子View分到的角度應(yīng)該是(360 / 4 = 90),而這個(gè)子View的中心點(diǎn)又是子View分到的角度的一半:(90/2)。而且這些子View 的中心離原點(diǎn)的距離,都是這個(gè)我畫的圓形的半徑。好了所以現(xiàn)在我們就知道了。
我們假設(shè)是寬比高小,我們的圓形的半徑就是寬(也就是說圓形的半徑取得是(寬和高中的偏小的值))子View的擺放位置的中心點(diǎn)就是這個(gè)圓形的
半徑R(在此處也就是viewGroup.Width/2),而這個(gè)子View的top值就是(半徑R*sin(相應(yīng)的角度) - 子View高度/2),子View的left值就是(半徑R*cos(相應(yīng)的角度) - 子View寬度/2),子View的bottom值就是(半徑R*sin(相應(yīng)角度) + 子View高度/2),子View的right值就是(半徑R*cos(相應(yīng)角度) + 子View寬度/2),
還記不記得我們前面有自定義三個(gè)屬性,就是:
private float mAngleOffset;//擺放子View的角度偏移值
private float mAngleRange;//子VIew可以擺放的角度范圍,比如最多是360度
private int mInnerRadius;//子View距離這個(gè)ViewGroup中心點(diǎn)的距離
那我們?cè)偻饧由现齻€(gè)屬性:
- mAngleOffset:角度偏移值,就用在原本的子View的中心角度(90/2)處,變成了(90/2 + mAngleOffset),這樣View 就等于轉(zhuǎn)過來了一定的角度。
- mAngleRange:總的角度,我們上面默認(rèn)是360度,所以每個(gè)子View 所占的角度范圍是(360/4 = 90 ),如果設(shè)置了這個(gè)值,我們就是(mAngleRange/4)。
- mInnerRadius:距離中心的距離,我們本來半徑是
(viewGroup.Width /2),現(xiàn)在變?yōu)?code>((viewGroup.Width - mInnerRadius) / 2),也就是說離坐標(biāo)系的中間的的距離更近了。子View之間也就更近了。
千萬別忘了。我們前面的討論的前提都是坐標(biāo)系已經(jīng)移動(dòng)到了這個(gè)屏幕的中間,所以我們最后要子View的X ,Y 都重新加上相應(yīng)的偏移值,也就是 (x+ width/2),(y + height /2 ),還有就是如果子View的數(shù)量是1的話,直接就放在了中心地方,也就是 width/2 和height /2處。
最終的代碼:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int childs = getChildCount();
final int width = getWidth();
final int height = getHeight();
final float minDimen = width > height ? height : width;
final float radius = (minDimen - mInnerRadius) / 2f;
float startAngle = mAngleOffset;
for (int i = 0; i < childs; i++) {
final View child = getChildAt(i);
final LayoutParams lp = child.getLayoutParams();
final float angle = mAngleRange / childs;
final float centerAngle = startAngle + angle / 2f;
int x;
int y;
if (childs > 1) {
x = (int) (radius * Math.cos(Math.toRadians(centerAngle))) + width / 2;
y = (int) (radius * Math.sin(Math.toRadians(centerAngle))) + height / 2;
} else {
x = width / 2;
y = height / 2;
}
final int halfChildWidth = child.getMeasuredWidth() / 2;
final int halfChildHeight = child.getMeasuredHeight() / 2;
final int left = lp.width != LayoutParams.MATCH_PARENT ? x - halfChildWidth : 0;
final int top = lp.height != LayoutParams.MATCH_PARENT ? y - halfChildHeight : 0;
final int right = lp.width != LayoutParams.MATCH_PARENT ? x + halfChildWidth : width;
final int bottom = lp.height != LayoutParams.MATCH_PARENT ? y + halfChildHeight : height;
child.layout(left, top, right, bottom);
startAngle += angle;
}
invalidate();
}