天天看點

View布局系列(1)-半圓弧形菜單

在前期的繪制系列文章中,我們主要學習如何在

View

内部使用畫筆工具自行繪制,以達到UI稿頁面效果,但并不是所有效果都适合通過

onDraw

實作。

以下圖為例:

View布局系列(1)-半圓弧形菜單

界面上有六個按鈕,其中一個位于頁面底部居中,其他五個呈半圓形拱衛。

這種情況下使用繪制系列中相關的知識去實作,就會發現很麻煩,主要展現在以下幾點:

  • 繪制的控件點選事件響應
  • 在點選事件發生後,外部五個按鈕的收起展開動畫效果

在View繪制系列(3)-自定義View簡介 一文中,我們已經介紹了自定義

View

的常見實作方式以及主要過程,其中就提到我們可以有三種實作自定義

View

的方式:

  • 繼承自

    View

  • 繼承自

    ViewGroup

  • 繼承已有控件(

    ImageView

    TextView

    LinearLayout

    等諸如此類系統控件)

    這裡我們就可以通過繼承

    ViewGroup

    ,重新排布其内部的子

    View

    來實作上圖中的UI效果。

繼承ViewGroup

建立

SemiCircleMenuView

繼承自

ViewGroup

,并重寫構造及

onLayout

方法,代碼如下:

public class SemiCircleMenuView extends ViewGroup {
    public SemiCircleMenuView(Context context) {
        super(context);
    }

    public SemiCircleMenuView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SemiCircleMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }
}
           

複制

測量子View

重寫

onMeasure

,調用

measureChild

方法進行子

View

大小測量,代碼如下:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //将布局參數應用到子View,驅動子View進行自身測量
        int count = getChildCount();
        for (int i = ; i < count; i++) {
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
           

複制

擷取拱衛中心坐标

從上圖中可以看出,最中心的控件位于(ViewGroup.Width/2,ViewGroup.Height)位置,故建立變量如下:

/**
     * ViewGroup的寬度高度
     */
    private int mWidth;
    private int mHeight;

    /**
     * 子View圍繞的圓心坐标
     */
    private int mChildrenCenterX;
    private int mChildrenCenterY;
           

複制

重寫

onSizeChanged

方法:

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        //指派中心點坐标
        mChildrenCenterX = w / ;
        mChildrenCenterY = h;
    }
           

複制

布局中心View

上一步中已經擷取到了中心

View

的放置坐标(

mChildrenCenterX

,

mChildrenCenterY

),随後調用

layout

方法進行

View

放置,代碼如下:

private void layoutCenterItem() {
        View view = getChildAt();
        int centerItemWidth = view.getMeasuredWidth();
        int centerItemHeight = view.getMeasuredHeight();

        view.layout(mChildrenCenterX - centerItemWidth / , mChildrenCenterY - centerItemHeight, mChildrenCenterX + centerItemWidth / , mChildrenCenterY);
    }
           

複制

布局周圍的拱衛View

由于周圍拱衛的菜單

View

位于同一圓周上,假設圓周半徑為

mRadius

,弧度為A,那麼拱衛菜單的位置坐标為:

positionX = mChildrenCenterX + (int)(mRadius * Math.cos(A))

positionY = mChildrenCenterY + (int)(mRadius*Math.sin(A))

由于在

Layout

過程中以水準向右順時針方向為正角,那麼要實作圖上效果,拱衛

View

是分布在弧度為0到-Math.PI的弧段上,進而A的取值為(0,-Math.PI).

布局拱衛菜單

View

的代碼如下:

private void layoutMenuItem() {
        int count = getChildCount();
        for (int i = ; i < count; i++) {
            View menuItem = getChildAt(i);
            int menuItemWidth = menuItem.getMeasuredWidth();
            int menuItemHeight = menuItem.getMeasuredHeight();

            int menuPositionX = (int) (mChildrenCenterX + mRadius * Math.cos(-Math.PI / (count - ) * (i-1)));
            int menuPositionY = (int) (mChildrenCenterY + mRadius * Math.sin(-Math.PI / (count - ) * (i-1)));

            menuItem.layout(menuPositionX - menuItemWidth / , menuPositionY - menuItemHeight, menuPositionX + menuItemWidth / , menuPositionY);
        }
    }
           

複制

這裡count-2的原因在于剩餘count-1個View會将180度的圓周均分成count-2份

這裡i-1的原因在于第一個角度為0度,而我們i取值從1開始

運作

在布局中使用該View:

<com.poseidon.testapplication.SemiCircleMenuView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher_round"></ImageButton>

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher_round"></ImageButton>

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher_round"></ImageButton>

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher_round"></ImageButton>

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher_round"></ImageButton>

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher_round"></ImageButton>


    </com.poseidon.testapplication.SemiCircleMenuView>
           

複制

在onlayout中調用布局方法并運作,檢視效果,代碼如下:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (getChildCount() <= ) {
        return;
    }

    //中心位置放置首個子元素
    layoutCenterItem();
    layoutMenuItem();
}
           

複制