你們好,又到了新的一次需求分析,此次咱們的需求是:在不一樣的條件的前提下,點擊一個菜單按鈕,出來不一樣的菜單。前端
好比:下面是一系列的公司列表(固然也能夠是不一樣的地區,不一樣的城市,等等),而後當你選擇好某個以後,咱們點擊菜單按鈕,這時候出來不一樣的菜單
git
而後咱們出來的菜單是:github
而後你們說了。這不是很簡單的事麼。作4個佈局,分別做爲這四個公司的菜單,而後選擇哪一個公司,就彈出哪一個公司的菜單。
少年,Too Young Too Simple,好比咱們一共有10項中的業務,某個A公司有咱們的三個功能,而後你前段界面寫死,B公司有五項功能,而後你這時候寫了二個界面,這時候,忽然A公司說我升級了。我也要跟B公司同樣有五項功能,而後你又去改界面? 一共有A,B,C...W 公司,難道你就去寫A-W個佈局??(同理,若是是城市劃分,好比在不一樣的城市可能支持的功能業務不一樣,出現不一樣的菜單。大城市覆蓋的功能更全,小城市功能更少)bash
因此這裏咱們公司的數量,公司相對於的功能,功能名字,功能圖片名字,都是後臺傳到前端,咱們只須要準備一個界面,而後在不一樣狀況下,去顯示不一樣的菜單功能便可。ide
好比後臺傳給咱們:佈局
{
"companys": [
{
"公司1": [
{
"name": "吃飯",
"iconName": "icon_xxx",
"typeId": "1"
}
]
}
]
}複製代碼
這樣咱們就很大程序前段就自由了。那咱們的難點就變成了:
既然咱們是動態的顯示這個菜單,拿到這些數據後怎麼來呈現呢
不少人應該作這麼個界面會以爲簡單,可是若是是一個根據數量自動排好的菜單界面就有點不知所措了。因此這裏咱們的難點就變成了。若是給了咱們N個數據,咱們要在這個彈框中顯示出N個,那咱們的問題也就變成了:可否提供一個自定義的ViewGroup,而後我傳入幾個View對象,能夠按照必定的規則幫我自動排布,這樣咱們拿到N個數據後,只須要新建相應的View對象,而後添加到這個ViewGroup就好了。動畫
既然咱們要作的是一個自動按照上面圖片顯示排布規則的ViewGroup,系統確定是沒有自帶的。因此咱們就須要自定義一個ViewGroup。ui
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();
}
}
}複製代碼
說明下三個參數:spa
private float mAngleOffset;//擺放子View的角度偏移值
private float mAngleRange;//子VIew能夠擺放的角度範圍,好比最可能是360度
private int mInnerRadius;//子View距離這個ViewGroup中心點的距離複製代碼
2.實現onMeasure方法:3d
@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.實現onLayout方法:
這個很重要,我重點也是講解這個onLayout方法,由於若是你繼承ViewGroup,會提示你必定要實現這個方法。並且咱們也知道了咱們最主要的點就在於怎麼根據傳進來的子View的個數來進行相應的擺放。因此咱們直接來看onLayout的具體實現。
咱們假設咱們的自定義ViewGroup是佔滿整個屏幕的,都是match_parent。而後就以下圖所示:
這時候若是咱們想擺四個子View(四個的分析起來簡單點),這時候的界面應該是:
這時候你們確定想,這有什麼規則嗎,徹底沒想法啊。。哈哈不要急,看我一步操做,你立刻懂:
咱們移動畫布,等價咱們讓座標系移動了中間,這時候你是否是恍然大悟,咱們只須要按照角度來不就能夠了嗎。
咱們繼續往下看:
好的。咱們能夠看到每一個子View分到的角度應該是(360 / 4 = 90),而這個子View的中心點又是子View分到的角度的一半:(90/2)。並且這些子View 的中心離原點的距離,都是這個我畫的圓形的半徑。好了因此如今咱們就知道了。
咱們假設是寬比高小,咱們的圓形的半徑就是寬(也就是說圓形的半徑取得是(寬和高中的偏小的值))子View的擺放位置的中心點就是這個圓形的
半徑R(在此處也就是viewGroup.Width/2)
,而這個子View的top值就是(半徑R*sin(相應的角度) - 子View高度/2)
,子View的left值就是(半徑R*cos(相應的角度) - 子View寬度/2)
,子View的bottom值就是(半徑R*sin(相應角度) + 子View高度/2)
,子View的right值就是(半徑R*cos(相應角度) + 子View寬度/2)
,
還記不記得咱們前面有自定義三個屬性,就是:
private float mAngleOffset;//擺放子View的角度偏移值
private float mAngleRange;//子VIew能夠擺放的角度範圍,好比最可能是360度
private int mInnerRadius;//子View距離這個ViewGroup中心點的距離複製代碼
那咱們再外加上着三個屬性:
(viewGroup.Width /2)
,如今變爲((viewGroup.Width - mInnerRadius) / 2)
,也就是說離座標系的中間的的距離更近了。子View之間也就更近了。最終的代碼:
@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();
}複製代碼