江苏7位数中二维有奖吗:『Android自定義View實戰』自定義弧形旋轉菜單欄——衛星菜單 [復制鏈接]

2019-6-4 16:38
Android小Y 閱讀:292 評論:0 贊:0
Tag:  

概述

体彩江苏7位数18148 www.zyvyo.com 現在很多App會在入口比較淺的頁面添加一些快捷操作入口,一方面是為了方便用戶操作,一方面是為了提高產品一些關鍵入口的使用率,讓用戶能夠在瀏覽信息流的過程中能快速切換至其他一些功能頁面。例如豆瓣的首頁 (右下角紅框選中部分):

圖片描述

本文將仿照這種菜單效果進行實現,最終效果如下:

圖片描述

需要定制的特性

1.菜單展開半徑

2.設置菜單主按鈕Icon

3.設置菜單子項的各個Icon

4.展開和收縮的動畫時長

5.所有菜單按鈕的寬高

6.是否在展開收縮的同時旋轉主按鈕

:理論上可以設置無數個菜單項,但是會出現重疊情況(空間有限),這種情況得自行調整按鈕數量和寬高。

實現思路

可以看到這個菜單是由多個按鈕組合而成,所以可以考慮用ViewGroup來作為載體,其中的子View再通過屬性動畫進行配合達成效果,而各個菜單項的彈出角度可以針對90°來進行弧度平分,再通過三角函數得到最終展開的目標坐標,關鍵要注意View的寬高邊距的計算,否則可能會出現超出邊界的情況。

1)初始化基本框架

由于菜單是由多個按鈕疊加在一個平面,所以可以考慮采用繼承FrameLayout,然后根據設置的Icon資源Id的數量來作為按鈕的數量進行初始化,代碼如下:


    List<ImageView> mImgViews = new ArrayList<>();
    List<Integer> mMenuItemResIds = new ArrayList<>();

    /**
     * 初始化主按鈕
     * @param context
     */
    private void initMenuView(Context context) {
        mMenuIv = new ImageView(context);
        mMenuIv.setImageResource(mMenuResId);
        FrameLayout.LayoutParams params = new LayoutParams(mMenuWidth, mMenuWidth);
        params.bottomMargin = mMenuItemWidth / 2;
        params.rightMargin = mMenuItemWidth / 2;
        params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
        addView(mMenuIv, params);
        mMenuIv.setOnClickListener(this);
    }

    /**
     * 初始化菜單子項按鈕
     * @param context
     */
    private void initMenuItemViews(Context context) {
        mImgViews.clear();
        for (int index = 0; index < mMenuItemResIds.size(); index++) {
ImageView menuItem = new ImageView(context);
menuItem.setImageResource(mMenuItemResIds.get(index));
FrameLayout.LayoutParams params = new LayoutParams(mMenuItemWidth, mMenuItemWidth);
params.bottomMargin = mMenuItemWidth / 2;
params.rightMargin = mMenuItemWidth / 2;
params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
menuItem.setTag(index);
menuItem.setOnClickListener(this);
addView(menuItem, params);
menuItem.setScaleX(0f);
menuItem.setScaleY(0f);

mImgViews.add(menuItem);
        }
    }

可以看到都設置在了父容器的右下角,且設置了margin值,這是由于我們點擊菜單瞬間有放大雙倍的效果,所以這里需要為其邊緣騰出一點空間,否則處于邊緣的菜單項放大時,會有部分被切掉影響觀感,記得為每個子項設置Tag(這里設置為下標),后面觸發點擊事件時會用到。

2)展開菜單

前面說過了,主要是根據平分弧度的思路來計算,從效果圖中可以看出,我們的整個展開角度是90°,那么每個菜單項的角度應該是90°/(菜單的數量-1),計算出這個角度有什么作用呢?可以先通過下圖幫忙理解:

圖片描述

可以看到,要做彈出動畫,就需要計算出彈出的橫向距離和縱向距離,剛才計算出來的角度在這就派上用場啦,利用三角函數可以得到:

tranX = 彈出半徑sin(90 i / (count - 1));
tranY = 彈出半徑cos(90 i / (count - 1));

再結合透明度和大小的變化,代碼如下:

    /**
     * 菜單展開動畫
     */
    private void startOpenAnim() {
        int count = mMenuItemResIds.size();
        List<Animator> animators = new ArrayList<>();
        for (int i = 0; i < count; i++) {
int tranX = -(int) (mRadius * Math.sin(Math.toRadians(90 * i / (count - 1))));
int tranY = -(int) (mRadius * Math.cos(Math.toRadians(90 * i / (count - 1))));
ObjectAnimator animatorX = ObjectAnimator.ofFloat(mImgViews.get(i), "translationX", 0f, tranX);
ObjectAnimator animatorY = ObjectAnimator.ofFloat(mImgViews.get(i), "translationY", 0f, tranY);
ObjectAnimator alpha = ObjectAnimator.ofFloat(mImgViews.get(i), "alpha", 0, 1);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleX", 0.1f, 1);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleY", 0.1f, 1);

animators.add(animatorX);
animators.add(animatorY);
animators.add(alpha);
animators.add(scaleX);
animators.add(scaleY);
        }
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(mDuration);
        animatorSet.playTogether(animators);
        animatorSet.start();
    }

3)收回菜單

上一步已經理解了如何展開菜單,回收菜單自然就容易多了,沒錯,就是反其道而行之:

    /**
     * 菜單收回動畫
     */
    private void startCloseAnim() {
        int count = mMenuItemResIds.size();
        List<Animator> animators = new ArrayList<>();
        for (int i = 0; i < count; i++) {
int tranX = -(int) (mRadius * Math.sin(Math.toRadians(90 * i / (count - 1))));
int tranY = -(int) (mRadius * Math.cos(Math.toRadians(90 * i / (count - 1))));
ObjectAnimator animatorX = ObjectAnimator.ofFloat(mImgViews.get(i), "translationX", tranX, 0f);
ObjectAnimator animatorY = ObjectAnimator.ofFloat(mImgViews.get(i), "translationY", tranY, 0f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(mImgViews.get(i), "alpha", 1, 0);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleX", 1, 0.3f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleY", 1, 0.3f);

animators.add(animatorX);
animators.add(animatorY);
animators.add(alpha);
animators.add(scaleX);
animators.add(scaleY);
        }
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(mDuration);
        animatorSet.playTogether(animators);
        animatorSet.start();
    }

其實主要就是在做位移動畫的時候,從tranX和tranY位移到0,回到原來的位置。

4)菜單子項點擊動畫

以上完成了菜單的展開和收縮,基本的模樣已經出來了,還可以為其子項添加一些點擊效果,讓整個View更為生動,代碼如下:

    /**
     * 菜單子項點擊動畫
     *
     * @param index 子項下標
     */
    private void startClickItemAnim(int index) {
        int count = mMenuItemResIds.size();
        List<Animator> animators = new ArrayList<>();
        //當前被點擊按鈕放大且逐漸變透明,造成消散效果
        ObjectAnimator clickItemAlpha = ObjectAnimator.ofFloat(mImgViews.get(index), "alpha", 1, 0);
        ObjectAnimator clickItemScaleX = ObjectAnimator.ofFloat(mImgViews.get(index), "scaleX", 1, 2);
        ObjectAnimator clickItemScaleY = ObjectAnimator.ofFloat(mImgViews.get(index), "scaleY", 1, 2);
        animators.add(clickItemAlpha);
        animators.add(clickItemScaleX);
        animators.add(clickItemScaleY);

        for (int i = 0; i < count; i++) {
if (index == i) {
    //過濾當前被點擊的子項
    continue;
}
//其他選項縮小且變透明
ObjectAnimator alpha = ObjectAnimator.ofFloat(mImgViews.get(i), "alpha", 1, 0);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleX", 1, 0.1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleY", 1, 0.1f);
animators.add(alpha);
animators.add(scaleX);
animators.add(scaleY);
        }
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(500);
        animatorSet.playTogether(animators);
        animatorSet.start();
        animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {

}

@Override
public void onAnimationEnd(Animator animator) {
    //點擊動畫結束之后要將所有子項歸位
    resetItems();
}

@Override
public void onAnimationCancel(Animator animator) {

}

@Override
public void onAnimationRepeat(Animator animator) {

}
        });
    }

首先傳進來一個index參數,其實就是之前我們在初始化的時候為每個子View設置的Tag,在每次onClick的時候,通過 view.getTag() 獲取到對應的下標,傳進來之后,循環遍歷所有子View,根據這個下標來判斷當前點擊的是哪個菜單項,將其做放大消散的動畫效果,其他菜單項則單純消散即可。
并且這里注意,要在動畫結束時,將所有子項設置回展開之前的位置,否則當再次點擊菜單按鈕時,菜單項會在圓弧上閃現了一下,體驗很差,因此要在onAnimationEnd的回調中重置所有子項,重置代碼如下:

     /**
     * 重置所有子項位置
     */
    private void resetItems() {
        int count = mImgViews.size();
        for (int i = 0; i < mImgViews.size(); i++) {
int tranX = (int) (mRadius * Math.sin(Math.toRadians(90 * i / (count - 1))));
int tranY = (int) (mRadius * Math.cos(Math.toRadians(90 * i / (count - 1))));
mImgViews.get(i).setTranslationX(tranX);
mImgViews.get(i).setTranslationY(tranY);
        }
        mIsOpen = false;
    }

5)旋轉主菜單按鈕

我們還可以在展開收縮的同時,還可以為菜單按鈕添加上一些花樣,將其旋轉一下,使整個動畫更加自然:

    /**
     * 旋轉主菜單按鈕
     *
     * @param startAngel 起始角度
     * @param endAngel   結束角度
     */
    private void rotateMenu(int startAngel, int endAngel) {
        if (!mCanRotate) {
return;
        }
        ObjectAnimator clickItemAlpha = ObjectAnimator.ofFloat(mMenuIv, "rotation", startAngel, endAngel);
        clickItemAlpha.setDuration(mDuration);
        clickItemAlpha.start();
    }

6)添加外部點擊監聽

提供一個供外界設置banner數據的方法:

    ClickMenuListener mItemListener;

    public void setClickItemListener(ClickMenuListener mItemListener) {
        this.mItemListener = mItemListener;
    }

    public interface ClickMenuListener {
        void clickMenuItem(int resId);
    }

    @Override
    public void onClick(View view) {
        if (view == mMenuIv) {
...
        } else {
...
if (mItemListener != null &amp;&amp; index < mMenuItemResIds.size()) {
    mItemListener.clickMenuItem(mMenuItemResIds.get(index));
}
        }
    }

就是正常的暴露接口,將菜單對應的資源id傳出去,供外界判斷點擊的是哪個菜單項。

應用

xml布局中引用(這里的寬高由設置的弧長半徑決定,只需設置wrap_conetnt即可):

<com.zjywidget.widget.arcmenu.YArcMenuView
        android:id="@+id/arc_menu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:spread_radius="150dp"
        app:duration="1000"
        app:menu_width="64dp"
        app:menu_item_width="64dp"
        app:can_rotate="true"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />

Acitivity中實例代碼如下:

        mArcMenuView = findViewById(R.id.arc_menu);
        List<Integer> menuItems = new ArrayList<>();
        menuItems.add(R.drawable.ic_menu_camera);
        menuItems.add(R.drawable.ic_menu_photo);
        menuItems.add(R.drawable.ic_menu_share);
        mArcMenuView.setMenuItems(menuItems);

        mArcMenuView.setClickItemListener(new YArcMenuView.ClickMenuListener() {
@Override
public void clickMenuItem(int resId) {
    switch (resId){
        case R.drawable.ic_menu_camera:
Toast.makeText(getApplicationContext(), "點擊了相機", Toast.LENGTH_SHORT).show();
break;
        case R.drawable.ic_menu_photo:
Toast.makeText(getApplicationContext(), "點擊了相冊", Toast.LENGTH_SHORT).show();
break;
        case R.drawable.ic_menu_share:
Toast.makeText(getApplicationContext(), "點擊了分享", Toast.LENGTH_SHORT).show();
break;
    }
}
        });

后續

最近有點沉迷于自定義View,其實很多看似很基礎的東西還是很重要的,底層基礎決定上層建筑,本文主要關鍵還是對屬性動畫的結合應用,由于時間比較短,可能還有些細節未優化處理,還待后期不斷更新,歡迎關注GitHub項目:

源碼傳送門GitHub-ZJYWidget

CSDN博客IT_ZJYANG
簡書Android小Y
里面還有很多實用的自定義View源碼及demo,會長期維護,歡迎Star~ 如有不足之處或建議還望指正,相互學習,相互進步,如果覺得不錯動動小手給個喜歡, 謝謝~


我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(0)
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反?。?29[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 体彩江苏7位数18148 )