当前位置:首页 > 开发教程 > 手机开发 >

Android开发 — 自定义扇形展开卫星菜单控件

时间:2016-08-25 17:19 来源:互联网 作者:源码搜藏 收藏

自定义属性文件 attrs.xml ?xml version=1.0 encoding=utf-8? resources attr name = position enum name = left_top value = 0 / enum name = left_bottom value = 1 / enum name = right_top value = 2 / enum name = right_bottom value = 3 / / attr att

自定义属性文件 attrs.xml

<xml version="1.0" encoding="utf-8">
<resources>

    <attr name="position">
        <enum name="left_top" value="0"/>
        <enum name="left_bottom" value="1"/>
        <enum name="right_top" value="2"/>
        <enum name="right_bottom" value="3"/>
    </attr>

    <attr name="radius" format="dimension"/>

    <declare-styleable name="ArcMenuButton">
        <attr name="position"/>
        <attr name="radius"/>
    </declare-styleable>

</resources>
  1. attr 代表属性,name 为属性的名称

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

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

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

在布局文件中使用

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

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

<cn.koreylee.arcmenubutton.ArcMenuButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:position="right_bottom"
    app:radius="100dp">
    
</cn.koreylee.arcmenubutton.ArcMenuButton>

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

在自定义控件中读取

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);
}
  1. TypedValue.applyDimension() 方法可以得到带单位的尺寸,本例中即得到 100dip

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

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

public ArcMenuButton(Context context) {
    this(context, null);
}

public ArcMenuButton(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

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

完成在 ViewGroup 中的布局

    <cn.koreylee.arcmenubutton.ArcMenuButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:position="left_top"
        app:radius="125dp">

        <ImageView
            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"/>

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_brightness_5_black_24dp"
            android:tag="1"/>

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_brightness_5_black_24dp"
            android:tag="2"/>

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_brightness_5_black_24dp"
            android:tag="3"/>

    </cn.koreylee.arcmenubutton.ArcMenuButton>

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_widthlayout_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);
}

手机开发阅读排行

最新文章