android 两层扇形菜单,Android — 自定义扇形展开卫星菜单控件

自定义属性文件 attrs.xml

attr 代表属性,name 为属性的名称

enum 为枚举类型,也就是说该属性有 enum 这些值可选

declare 是对属性的声明,使得其可以在 XML 的命名空间中使用

styleable 是指这个属性可以调用 style 或 theme 来作为 XML 属性的值

在布局文件中使用

xmlns:app="http://schemas.android.com/apk/res-auto"

在 Android Studio 的 IDE 下,用该代码引入命名空间

android:layout_width="wrap_content"

android:layout_height="wrap_content"

app:position="right_bottom"

app:radius="100dp">

在布局文件中配置控件的属性

在自定义控件中读取

public ArcMenuButton(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

float defExpandedRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100,

getResources().getDisplayMetrics());

this.mExpandedRadius = defExpandedRadius;

TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,

R.styleable.ArcMenuButton, defStyleAttr, 0);

int pos = typedArray.getInt(R.styleable.ArcMenuButton_position, 3);

switch (pos) {

case POS_LEFT_TOP:

mMenuButtonPosition = Position.LEFT_TOP;

break;

case POS_LEFT_BOTTOM:

mMenuButtonPosition = Position.LEFT_BOTTOM;

break;

case POS_RIGHT_TOP:

mMenuButtonPosition = Position.RIGHT_TOP;

break;

case POS_RIGHT_BOTTOM:

mMenuButtonPosition = Position.RIGHT_BOTTOM;

break;

}

mExpandedRadius = typedArray.getDimension(R.styleable.ArcMenuButton_radius, defExpandedRadius);

// Be sure to call recycle() when you are done with the array.

typedArray.recycle();

Log.d(TAG, "ArcMenuButton: " + "Position = " + mMenuButtonPosition + ", "

+ "Radius = " + mExpandedRadius);

}

TypedValue.applyDimension() 方法可以得到带单位的尺寸,本例中即得到 100dip

getTheme().obtainStyledAttributes() 方法可以得到在 XML 文件中配置的属性值

TypedArray 在使用完后需要调用 recycle() 方法来回收

public ArcMenuButton(Context context) {

this(context, null);

}

public ArcMenuButton(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

定义缺省构造方法,否则在 Inflate 时会出错。

完成在 ViewGroup 中的布局

android:layout_width="wrap_content"

android:layout_height="wrap_content"

app:position="left_top"

app:radius="125dp">

android:id="@+id/iv_center_button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:src="@drawable/ic_control_point_black_24dp"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/ic_brightness_5_black_24dp"

android:tag="1"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/ic_brightness_5_black_24dp"

android:tag="2"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/ic_brightness_5_black_24dp"

android:tag="3"/>

onMeasure() 方法

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int count = getChildCount();

for (int i = 0; i < count; i++) {

measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);

}

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

onMeasure() 方法用来确定 View 的大小

widthMeasureSpec 和 heightMeasureSpec 来源于 ViewGroup 的 layout_width, layout_height 等属性,当然也会受到其他属性的影响,例如 Margin, Padding, weight 等。

onLayout() 方法

@Override

protected void onLayout(boolean b, int i, int i1, int i2, int i3) {

int count = getChildCount();

// If changed.

if (b) {

layoutCenterButton();

// From '1' to 'count - 1'

for (int j = 1; j < count; j++) {

View childView = getChildAt(j);

// Invisible at first.

childView.setVisibility(View.GONE);

// Use left top as position to set first.

int childTop = (int) (mExpandedRadius *

Math.cos(Math.PI / 2 / (count - 2) * (j - 1)));

int childLeft = (int) (mExpandedRadius *

Math.sin(Math.PI / 2 / (count - 2) * (j - 1)));

int childWidth = childView.getMeasuredWidth();

int childHeight = childView.getMeasuredHeight();

// If position is bottom, make adjustment.

if (mMenuButtonPosition == Position.RIGHT_BOTTOM ||

mMenuButtonPosition == Position.LEFT_BOTTOM) {

childTop = getMeasuredHeight() - childTop - childHeight;

}

// If position is right, make adjustment.

if (mMenuButtonPosition == Position.RIGHT_BOTTOM ||

mMenuButtonPosition == Position.RIGHT_TOP) {

childLeft = getMeasuredWidth() - childLeft - childWidth;

}

childView.layout(childLeft, childTop, childLeft + childWidth,

childTop + childHeight);

}

}

}

我们来分段分析一下

for (int j = 1; j < count; j++)

第 0 个元素是中心的按钮,所以从 1 开始。

View childView = getChildAt(j);

// Invisible at first.

childView.setVisibility(View.GONE);

// Use left top as position to set first.

int childTop = (int) (mExpandedRadius *

Math.cos(Math.PI / 2 / (count - 2) * (j - 1)));

int childLeft = (int) (mExpandedRadius *

Math.sin(Math.PI / 2 / (count - 2) * (j - 1)));

int childWidth = childView.getMeasuredWidth();

int childHeight = childView.getMeasuredHeight();

首先设置为不可见的 GONE,再通过三角函数得出横纵坐标。

// If position is bottom, make adjustment.

if (mMenuButtonPosition == Position.RIGHT_BOTTOM ||

mMenuButtonPosition == Position.LEFT_BOTTOM) {

childTop = getMeasuredHeight() - childTop - childHeight;

}

// If position is right, make adjustment.

if (mMenuButtonPosition == Position.RIGHT_BOTTOM ||

mMenuButtonPosition == Position.RIGHT_TOP) {

childLeft = getMeasuredWidth() - childLeft - childWidth;

}

之前的计算是以在左上角为例的,那么在其他位置需要做相应的补偿。

childView.layout(childLeft, childTop, childLeft + childWidth,

childTop + childHeight);

最后 layout 子视图

中心按钮方法类似,通过 layoutCenterButton() 方法来配置即可。

private void layoutCenterButton() {

mCenterButton = getChildAt(0);

mCenterButton.setOnClickListener(this);

int top = 0;

int left = 0;

int centerButtonWidth = mCenterButton.getMeasuredWidth();

int centerButtonHeight = mCenterButton.getMeasuredHeight();

switch (mMenuButtonPosition) {

case LEFT_TOP:

break;

case LEFT_BOTTOM:

top = getMeasuredHeight() - centerButtonHeight;

break;

case RIGHT_TOP:

left = getMeasuredWidth() - centerButtonWidth;

break;

case RIGHT_BOTTOM:

top = getMeasuredHeight() - centerButtonHeight;

left = getMeasuredWidth() - centerButtonWidth;

break;

}

mCenterButton.layout(left, top, left + centerButtonWidth, top + centerButtonHeight);

}

View.getMeasuredWidth() and View.getMeasuredHeight() represents the dimensions the view wants to be, before all views in the layout are calculated and laid in the screen.

After View.onMeasure(int, int) and View.onLayout(boolean, int, int, int, int), views measurements could be change to accommodate everything. These (possible) new values are then accessible through View#getWidth() and View#getHeight().

The size of a view is expressed with a width and a height. A view actually possess two pairs of width and height values.

The first pair is known as measured width and measured height. These dimensions define how big a view wants to be within its parent (see Layout for more details.) The measured dimensions can be obtained by calling getMeasuredWidth() and getMeasuredHeight().

The second pair is simply known as width and height, or sometimes drawing width and drawing height. These dimensions define the actual size of the view on screen, at drawing time and after layout. These values may, but do not have to, be different from the measured width and height. The width and height can be obtained by calling getWidth() and getHeight().

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用Android提供的Canvas和Paint类来绘制空心扇形统计图。具体步骤如下: 1. 创建一个继承自View的自定义View,重写onDraw方法。 2. 在onDraw方法中创建一个Paint对象,设置画笔的颜色和样式,如: ``` Paint paint = new Paint(); paint.setColor(Color.BLUE); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(5); ``` 3. 使用Canvas的drawArc方法来绘制扇形,需要传入扇形的矩形区域、起始角度和扫过的角度。如: ``` RectF rectF = new RectF(100, 100, 500, 500); canvas.drawArc(rectF, 0, 90, false, paint); ``` 其中,RectF对象用于指定扇形的位置和大小,前两个参数是左上角的坐标,后两个参数是右下角的坐标。第三个参数是起始角度,第四个参数是扫过的角度,第五个参数指定是否连接中心点。 4. 如果要绘制多个扇形,可以根据数据计算每个扇形的起始角度和扫过的角度。如: ``` float[] data = {30, 40, 50, 60}; float sum = 0; for (float value : data) { sum += value; } float startAngle = 0; for (float value : data) { float sweepAngle = value / sum * 360; canvas.drawArc(rectF, startAngle, sweepAngle, false, paint); startAngle += sweepAngle; } ``` 其中,data数组保存了每个扇形的数值,sum是所有数值的和,startAngle表示当前扇形的起始角度,sweepAngle表示当前扇形扫过的角度。 5. 最后,在布局文件中将自定义View添加到布局中即可。 以上是绘制空心扇形统计图的基本步骤,具体细节可以根据需要进行调整。 ### 回答2: 要实现Android自定义空心扇形统计图,你可以使用自定义View来绘制扇形和文字。 首先,创建一个继承自View的自定义View类,命名为PieChartView。在该类的构造方法中,初始化画笔和相关属性,如颜色、宽度等。 接下来,在PieChartView的onMeasure方法中确定View的大小,这取决于父容器给出的测量要求。 在PieChartView的onDraw方法中,使用Canvas和Paint来绘制扇形和文字。根据给定的数据,计算出每个扇形所占的角度,然后根据角度和颜色依次绘制扇形。 为了实现空心效果,你可以使用Canvas的drawArc方法来绘制空心扇形,设置Paint的Style为STROKE,并设置边框的宽度。这样,只会绘制扇形的边框而不会填充颜色。 接着,在绘制扇形时,你可以通过设置一个起始角度来绘制每个扇形,使它们按顺时针或逆时针排列。绘制文字时,可以根据扇形的中心角度和半径来确定文字的位置。 最后,你需要在使用PieChartView的Activity或Fragment中设置相关数据,如每个扇形的角度和颜色。然后将PieChartView添加到布局中。 通过这些步骤,你就能够实现一个简单的Android自定义空心扇形统计图。当然,你还可以进一步定制化,添加动画效果或触摸交互等功能,使统计图更加生动和实用。 ### 回答3: Android自定义空心扇形统计图可以通过继承View类,重写onDraw方法来实现。具体步骤如下: 1. 在XML布局文件中,声明一个自定义View的容器,如LinearLayout或RelativeLayout。 2. 在Java代码中,创建一个继承自View的类,重写onDraw方法。 3. 在onDraw方法中,先通过Canvas对象绘制一个圆形。 4. 再通过Path对象绘制一个扇形,可以通过Path.addArc方法来实现。 5. 设置扇形的起始角度和扇形的角度大小,可以根据所需的数据来计算。 6. 设置扇形的颜色,可以通过Paint对象的setColor方法来设置。 7. 设置扇形的样式为空心,可以通过Paint对象的setStyle方法来设置。 8. 设置扇形的边框宽度和颜色,可以通过Paint对象的setStrokeWidth和setColor方法来设置。 9. 最后调用Canvas对象的drawPath方法来绘制扇形。 10. 在主Activity中,实例化自定义View类的对象,并将其添加到容器中。 通过以上步骤,就可以实现一个自定义的空心扇形统计图。可以根据需要来设置扇形的起始角度、角度大小、颜色和边框样式等属性,以达到所需的效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值