View布局系列(1)-半圆弧形菜单

在前期的绘制系列文章中,我们主要学习如何在View内部使用画笔工具自行绘制,以达到UI稿页面效果,但并不是所有效果都适合通过onDraw实现。
以下图为例:

View布局系列(1)-半圆弧形菜单



界面上有六个按钮,其中一个位于页面底部居中,其他五个呈半圆形拱卫。
这种情况下使用绘制系列中相关的知识去实现,就会发现很麻烦,主要体现在以下几点:

  • 绘制的控件点击事件响应

  • 在点击事件发生后,外部五个按钮的收起展开动画效果

View绘制系列(3)-自定义View简介 一文中,我们已经介绍了自定义View的常见实现方式以及主要过程,其中就提到我们可以有三种实现自定义View的方式:

  • 继承自View

  • 继承自ViewGroup

  • 继承已有控件(ImageViewTextViewLinearLayout等诸如此类系统控件)
    这里我们就可以通过继承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 = 0; 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 / 2;
        mChildrenCenterY = h;
    }

布局中心View

上一步中已经获取到了中心View的放置坐标(mChildrenCenterX,mChildrenCenterY),随后调用layout方法进行View放置,代码如下:

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

        view.layout(mChildrenCenterX - centerItemWidth / 2, mChildrenCenterY - centerItemHeight, mChildrenCenterX + centerItemWidth / 2, 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 = 1; 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 - 2) * (i-1)));
            int menuPositionY = (int) (mChildrenCenterY + mRadius * Math.sin(-Math.PI / (count - 2) * (i-1)));

            menuItem.layout(menuPositionX - menuItemWidth / 2, menuPositionY - menuItemHeight, menuPositionX + menuItemWidth / 2, 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() <= 0) {
        return;
    }

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


原文始发于微信公众号(小海编码日记):View布局系列(1)-半圆弧形菜单

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/67867.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!