在前期的绘制系列文章中,我们主要学习如何在View
内部使用画笔工具自行绘制,以达到UI稿页面效果,但并不是所有效果都适合通过onDraw
实现。
以下图为例:
界面上有六个按钮,其中一个位于页面底部居中,其他五个呈半圆形拱卫。
这种情况下使用绘制系列中相关的知识去实现,就会发现很麻烦,主要体现在以下几点:
-
绘制的控件点击事件响应
-
在点击事件发生后,外部五个按钮的收起展开动画效果
在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 = 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