溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android中怎么自定義組件衛(wèi)星菜單

發(fā)布時(shí)間:2021-06-28 16:36:10 來(lái)源:億速云 閱讀:121 作者:Leah 欄目:移動(dòng)開發(fā)

Android中怎么自定義組件衛(wèi)星菜單,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。

首先如果要想自定義組件

1.那么第一件事就是賦予自定義組件的屬性,從效果圖上看出,該組件可以存在屏幕的各個(gè)角落點(diǎn),那么位置是其屬性之一。

2.既然是衛(wèi)星菜單,那么主按鈕和其附屬的小按鈕之間的圍繞半徑也應(yīng)該作為其參數(shù)之一。

3.右圖得出,該組件包含很多按鈕,主按鈕和附屬按鈕,那么這個(gè)組件應(yīng)該繼承 ViewGroup。

一、定義衛(wèi)星菜單的屬性在 values 包下建立 attr 的 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>

  <!-- 尺寸屬性dp如果使用px可能會(huì)造成屏幕適配問題-->
  <attr name="radius" format="dimension" />
  <!-- 自定義屬性-->
  <declare-styleable name="ArcMenu">

    <attr name="position" />
    <attr name="radius" />


  </declare-styleable>


</resources>

二、編寫自定義組件

package com.lanou.dllo.arcmenudemo.arcmenu;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;

import com.lanou.dllo.arcmenudemo.R;

/**
 * Created by dllo on 16/3/25.
 * 1.首先ArcMenu是繼承ViewGroup,那么一個(gè)衛(wèi)星菜單包括一個(gè)大按鈕和其他的子按鈕群.
 */

public class ArcMenu extends ViewGroup implements View.OnClickListener {

  //設(shè)置常量,標(biāo)識(shí)成枚舉
  private static final int POS_LEFT_TOP = 0;
  private static final int POS_LEFT_BOTTOM = 1;
  private static final int POS_RIGHT_TOP = 2;
  private static final int POS_RIGHT_BOTTOM = 3;

  //以下5個(gè)成員變量是所需要的.
  //聲明兩個(gè)屬性 位置 還有半徑
  private Position mPosition = Position.RIGHT_BOTTOM;
  private int mRadius;

  /**
   * 菜單的狀態(tài)
   */
  private Status mCurrentStatus = Status.CLOSE;

  /**
   * 菜單的主按鈕
   */
  private View mCButton;

  //子菜單的回調(diào)按鈕
  private OnMenuItemClickListener mMenuItemClickListener;


  /**
   * 菜單的位置枚舉類,4個(gè)位置
   */
  public enum Position {
    LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM
  }

  public enum Status {
    OPEN, CLOSE
  }

  /**
   * 點(diǎn)擊子菜單項(xiàng),順便把位置傳遞過(guò)去
   */
  public interface OnMenuItemClickListener {
    void onClick(View view, int pos);
  }

  //3個(gè)構(gòu)造方法,相互傳遞.
  //注意別寫錯(cuò)誤.
  public ArcMenu(Context context) {
    this(context, null);
  }

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

  public ArcMenu(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //TypedValue.applyDimension是轉(zhuǎn)變標(biāo)準(zhǔn)尺寸的方法 參數(shù)一:單位  參數(shù)二:默認(rèn)值 參數(shù)三:可以獲取當(dāng)前屏幕的分辨率信息.
    mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP
        , 100, getResources().getDisplayMetrics());

    //獲取自定義屬性的值
    //參數(shù)1:attrs AttributeSet是節(jié)點(diǎn)的屬性集合
    //參數(shù)2:attrs的一個(gè)數(shù)組集
    //參數(shù)3:指向當(dāng)前theme 某個(gè)item 描述的style 該style指定了一些默認(rèn)值為這個(gè)TypedArray
    //參數(shù)4;當(dāng)defStyleAttr 找不到或者為0, 可以直接指定某個(gè)style
    TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
        R.styleable.ArcMenu, defStyleAttr, 0);
    int pos = a.getInt(R.styleable.ArcMenu_position, POS_RIGHT_BOTTOM);
    switch (pos) {
      case POS_LEFT_TOP:
        mPosition = Position.LEFT_TOP;
        break;
      case POS_LEFT_BOTTOM:
        mPosition = Position.LEFT_BOTTOM;
        break;
      case POS_RIGHT_TOP:
        mPosition = Position.RIGHT_TOP;
        break;
      case POS_RIGHT_BOTTOM:
        mPosition = Position.RIGHT_BOTTOM;
        break;
    }

    mRadius = (int) a.getDimension(R.styleable.ArcMenu_radius,
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP
            , 100, getResources().getDisplayMetrics()));

    Log.d("TAG", "Position = " + mPosition + ", radius" + mRadius);
    //使用完必須回收.
    a.recycle();

  }

  public void setOnMenuItemClickListener(OnMenuItemClickListener mMenuItemClickListener) {
    this.mMenuItemClickListener = mMenuItemClickListener;
  }

  /**
   * 測(cè)量方法
   */
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    for (int i = 0; i < count; i++) {
      //測(cè)量child的各個(gè)屬性.
      measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (changed) {
      layoutCButton();
      //獲得容器內(nèi)組件的個(gè)數(shù),并且包括這個(gè)主的組件(大按鈕)
      int count = getChildCount();
      for (int i = 0; i < count - 1; i++) {
        //這里直接獲取第一個(gè),是因?yàn)間etChildAt(0)是紅色的按鈕.
        View child = getChildAt(i + 1);
        //正常來(lái)說(shuō),如果設(shè)置按鈕動(dòng)畫,移動(dòng)出去后,是不能點(diǎn)擊的,這里給按鈕設(shè)置一個(gè)隱藏的屬性.等衛(wèi)星菜單飛過(guò)去,在讓它們顯示出來(lái).
        child.setVisibility(View.GONE);
        /**
         * 根據(jù)畫圖分析,得出每個(gè)子衛(wèi)星按鈕的夾角 a = 90°/(菜單的個(gè)數(shù)-1)
         * 假設(shè)menu總數(shù)為4,那么從左側(cè)數(shù)menu1的坐標(biāo)為(0,R);
         * menu2的坐標(biāo)為(R*sin(a),R*cos(a));
         * menu3的坐標(biāo)為(R*sin(2a),R*cos(2a));
         * ...
         * menuN的坐標(biāo)為(R,0);
         * 另:PI為π
         * */
        //測(cè)量每個(gè)子衛(wèi)星組件的在屏幕上面的坐標(biāo)距離
        //這里count-2,是因?yàn)閏ount包含了主按鈕
        //每個(gè)組件的坐標(biāo)為(cl,ct);
        int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));
        int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i));

        int cWidth = child.getMeasuredWidth();
        int cHeight = child.getMeasuredHeight();

        //如果衛(wèi)星菜單存在于底部,那么坐標(biāo)位置的計(jì)算方法,就完全相反.
        /**
         * 如果菜單位置在底部 左下 ,右下.坐標(biāo)會(huì)發(fā)生變化
         * */
        if (mPosition == Position.LEFT_BOTTOM || mPosition == Position.RIGHT_BOTTOM) {
          ct = getMeasuredHeight() - cHeight - ct;
        }

        /**
         * 右上,右下
         * */
        if (mPosition == Position.RIGHT_TOP || mPosition == Position.RIGHT_BOTTOM) {
          cl = getMeasuredWidth() - cWidth - cl;
        }


        //子布局的測(cè)量坐標(biāo);
        child.layout(cl, ct, cl + cWidth, ct + cHeight);


      }
    }

  }

  /**
   * 定位主菜單按鈕
   */
  private void layoutCButton() {
    // 給主按鈕設(shè)置監(jiān)聽
    mCButton = getChildAt(0);
    mCButton.setOnClickListener(this);

    //分別代表控件所處離左側(cè)和上側(cè)得距離
    int l = 0;
    int t = 0;
    int width = mCButton.getMeasuredWidth();
    int height = mCButton.getMeasuredHeight();

    /**
     * getMeasuredHeight()如果前面沒有對(duì)象調(diào)用,那么這個(gè)控件繼承ViewGroup,就意味著這是獲取容器的總高度.
     * getMeasuredWidth()也是同理.
     * 那么就可以判斷出控件在四個(gè)位置(根據(jù)坐標(biāo)系判斷.)
     * */
    switch (mPosition) {
      case LEFT_TOP:
        l = 0;
        t = 0;
        break;
      case LEFT_BOTTOM:
        l = 0;
        t = getMeasuredHeight() - height;
        break;
      case RIGHT_TOP:
        l = getMeasuredWidth() - width;
        t = 0;
        break;
      case RIGHT_BOTTOM:
        l = getMeasuredWidth() - width;
        t = getMeasuredHeight() - height;
        break;
    }

    //layout的四個(gè)屬性.分別代表主按鈕在不同位置距離屏幕左側(cè)和上側(cè)
    mCButton.layout(l, t, l + width, t + height);

  }


  @Override
  public void onClick(View v) {
    //主要確定mCButton的值
    mCButton = findViewById(R.id.id_button);
    if (mCButton == null) {
      mCButton = getChildAt(0);
    }

    //旋轉(zhuǎn)動(dòng)畫
    rotateCButton(v, 0f, 360f, 300);
    //判斷菜單是否關(guān)閉,如果菜單關(guān)閉需要給菜單展開,如果菜單是展開的需要給菜單關(guān)閉.
    toggleMenu(500);
  }

  /**
   * 切換菜單
   * 參數(shù):切換菜單的時(shí)間是可控的.
   */
  public void toggleMenu(int duration) {
    //為所有子菜單添加動(dòng)畫. :平移動(dòng)畫丶旋轉(zhuǎn)動(dòng)畫
    int count = getChildCount();
    for (int i = 0; i < count - 1; i++) {
      /**
       * 默認(rèn)位置左上的話,子菜單起始坐標(biāo)點(diǎn)為(-cl,-ct);
       *   位置右上的話,子菜單起始坐標(biāo)點(diǎn)為(+cl,-ct);
       *   位置左下的話,子菜單起始坐標(biāo)點(diǎn)為(-cl,+ct);
       *   位置右下的話,子菜單起始坐標(biāo)點(diǎn)為(+cl,+ct);**
       * */
      final View childView = getChildAt(i + 1);
      //不管按鈕是開還是關(guān),子菜單必須顯示才能出現(xiàn)動(dòng)畫效果.
      childView.setVisibility(View.VISIBLE);
      //平移 結(jié)束為止 0,0(以子菜單按鈕當(dāng)前位置,為坐標(biāo)系.)
      int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));
      int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i));

      //創(chuàng)建兩個(gè)判斷變量,判別起始位置.
      int xflag = 1;
      int yflag = 1;
      if (mPosition == Position.LEFT_TOP
          || mPosition == Position.LEFT_BOTTOM) {
        xflag = -1;
      }
      if (mPosition == Position.LEFT_TOP
          || mPosition == Position.RIGHT_TOP) {
        yflag = -1;
      }
      //多個(gè)動(dòng)畫同時(shí)使用使用,用到AnimationSet
      AnimationSet animset = new AnimationSet(true);
      Animation tranAnim = null;

      //to open 打開的情況下
      if (mCurrentStatus == Status.CLOSE) {
        tranAnim = new TranslateAnimation(xflag * cl, 0, yflag * ct, 0);
        //當(dāng)衛(wèi)星菜單打開的時(shí)候,按鈕就可以進(jìn)行點(diǎn)擊.
        childView.setClickable(true);
        childView.setFocusable(true);

      } else {//to close
        tranAnim = new TranslateAnimation(0, xflag * cl, 0, yflag * ct);
        //當(dāng)衛(wèi)星菜單關(guān)閉的時(shí)候,按鈕也不能隨之點(diǎn)擊.
        childView.setClickable(false);
        childView.setFocusable(false);
      }
      tranAnim.setFillAfter(true);
      tranAnim.setDuration(duration);
      //設(shè)置彈出速度.
      tranAnim.setStartOffset((i * 100) / count);
      //為動(dòng)畫設(shè)置監(jiān)聽 如果需要關(guān)閉的話,在動(dòng)畫結(jié)束的同時(shí),需要將子菜單的按鈕全部隱藏.
      tranAnim.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {

        }

        //在動(dòng)畫結(jié)束時(shí),進(jìn)行設(shè)置.
        @Override
        public void onAnimationEnd(Animation animation) {
          if (mCurrentStatus == Status.CLOSE) {
//            Log.d("動(dòng)畫結(jié)束狀態(tài)",mCurrentStatus +"");
            childView.setVisibility(View.GONE);
          }
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
      });

      //設(shè)置旋轉(zhuǎn)動(dòng)畫(轉(zhuǎn)兩圈)
      RotateAnimation rotateAnim = new RotateAnimation(0, 720,
          Animation.RELATIVE_TO_SELF, 0.5f,
          Animation.RELATIVE_TO_SELF, 0.5f);
      rotateAnim.setDuration(duration);
      rotateAnim.setFillAfter(true);

      //把兩個(gè)動(dòng)畫放到動(dòng)畫集里面
      //注意動(dòng)畫順序.先增加旋轉(zhuǎn)/在增加移動(dòng)./
      animset.addAnimation(rotateAnim);
      animset.addAnimation(tranAnim);
      childView.startAnimation(animset);

      final int pos = i + 1;
      //設(shè)置子菜單的點(diǎn)擊事件
      childView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
          if (mMenuItemClickListener != null) {
            mMenuItemClickListener.onClick(childView, pos);
          }
            menuItemAnim(pos - 1);
            //切換菜單狀態(tài)
            changeStatus();
        }
      });
    }
    /**
     * 當(dāng)所有子菜單切換完成后,那么菜單的狀態(tài)也發(fā)生了改變.
     * 所以changeStatus()必須放在循環(huán)外,
     * */
    //切換菜單狀態(tài)
    changeStatus();

  }


  /**
   * 切換菜單狀態(tài)
   */
  private void changeStatus() {
    //在執(zhí)行一個(gè)操作之后,如果按鈕是打開的在次點(diǎn)擊就會(huì)切換狀態(tài).
    mCurrentStatus = (mCurrentStatus == Status.CLOSE ? Status.OPEN :
        Status.CLOSE);
    Log.d("動(dòng)畫結(jié)束狀態(tài)", mCurrentStatus + "");
  }

  public boolean isOpen(){
    return mCurrentStatus ==Status.OPEN;
  }




  //設(shè)置旋轉(zhuǎn)動(dòng)畫繞自身旋轉(zhuǎn)一圈 然后持續(xù)時(shí)間為300
  private void rotateCButton(View v, float start, float end, int duration) {
    RotateAnimation anim = new RotateAnimation(start, end, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    anim.setDuration(duration);
    //保持動(dòng)畫旋轉(zhuǎn)后的狀態(tài).
    anim.setFillAfter(true);
    v.startAnimation(anim);
  }

  /**
   * 添加menuItem的點(diǎn)擊動(dòng)畫
   * */
  private void menuItemAnim(int pos) {
    for (int i = 0; i < getChildCount() - 1; i++) {
      View childView = getChildAt(i + 1);
      //在判斷條件下,寫入動(dòng)畫
      //當(dāng)其中一個(gè)子菜單被點(diǎn)擊后,自身變大并且消失
      //其他子菜單則變小消失.
      if (i == pos) {
        childView.startAnimation(scaleBigAnim(300));
      } else {
        childView.startAnimation(scaleSmallAnim(300));
      }

      //當(dāng)子菜單被點(diǎn)擊之后,其他子菜單就要變成不可被點(diǎn)擊和獲得焦點(diǎn)的狀態(tài),
      childView.setClickable(false);
      childView.setFocusable(false);
    }
  }

  /**
   * 為當(dāng)前點(diǎn)擊的Item設(shè)置變大和透明度降低的動(dòng)畫
   *
   * @param duration
   * @return
   */
  private Animation scaleBigAnim(int duration) {
    AnimationSet animationSet = new AnimationSet(true);
    ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f,
        Animation.RELATIVE_TO_SELF, 0.5f,
        Animation.RELATIVE_TO_SELF, 0.5f);
    AlphaAnimation alphaAnimation=new AlphaAnimation(1f,0.0f);

    animationSet.addAnimation(scaleAnimation);
    animationSet.addAnimation(alphaAnimation);

    animationSet.setDuration(duration);
    animationSet.setFillAfter(true);
    return animationSet;
  }

  private Animation scaleSmallAnim(int duration) {
    AnimationSet animationSet = new AnimationSet(true);
    ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f,
        Animation.RELATIVE_TO_SELF, 0.5f,
        Animation.RELATIVE_TO_SELF, 0.5f);
    AlphaAnimation alphaAnimation=new AlphaAnimation(1f,0.0f);
    animationSet.addAnimation(scaleAnimation);
    animationSet.addAnimation(alphaAnimation);
    animationSet.setDuration(duration);
    animationSet.setFillAfter(true);
    return animationSet;
  }

}

以上就是 衛(wèi)星菜單的編寫,上面的注釋量比較大。

這里需要注意的一點(diǎn)。衛(wèi)星菜單在屏幕不同位置,他的動(dòng)畫平移值是不一樣的。

如果實(shí)在不理解可以畫圖試試。

三、使用時(shí)注意賦予命名空間

<?xml version="1.0" encoding="utf-8"?>
<com.lanou.dllo.arcmenudemo.arcmenu.ArcMenu xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:arcmenu="http://schemas.android.com/apk/res/com.lanou.dllo.arcmenudemo"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/id_menu"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  arcmenu:position="left_top"
  arcmenu:radius="140dp"
>


    <!-- 主按鈕-->
    <RelativeLayout
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:background="@mipmap/composer_button">

      <ImageView
        android:id="@+id/id_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@mipmap/composer_icn_plus" />
    </RelativeLayout>


    <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@mipmap/composer_camera"
      android:tag="Camera"/>

    <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@mipmap/composer_music"
      android:tag="Music"/>

    <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@mipmap/composer_place"
      android:tag="Place"/>

    <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@mipmap/composer_sleep"
      android:tag="Sleep"/>

    <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@mipmap/composer_thought"
      android:tag="Sun"/>

    <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@mipmap/composer_with"
      android:tag="People"/>

</com.lanou.dllo.arcmenudemo.arcmenu.ArcMenu>

關(guān)于Android中怎么自定義組件衛(wèi)星菜單問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI