【自定義控件】自定義彈出按鈕菜單動畫

懶惰是人之本性玻粪,而我隅津,過年后已經(jīng)變成了一條廢魚,所以決定要寫點什么東西劲室,那么寫什么呢伦仍?突然想起之前有個彈出菜單的效果挺好的,所以我就寫了這么一個控件很洋!來看下效果唄


ezgif.com-0795f713d2.gif
  1. 動畫效果包括四個方向垂直彈出和斜邊彈出充蓝,周圍彈出
  2. 支持動態(tài)更改數(shù)量和自定義動畫效果

好吧,下面我們直接進入進入擼代碼的環(huán)節(jié)喉磁!

  1. 繼承一個ViewGroup谓苟,獲取到子View和顯示隱藏的按鈕
  2. 計算子View的寬高并初始化位置
  3. 聲明啟動點擊動畫效果,并支持動畫的擴展

我們新建一個類繼承自FrameLayout协怒,由于FrameLayout的特點是后來的在上面涝焙,所以我們的MenuView就是FrameLayout里面的最后一個子View。
<pre>
public class PopMenu extends FrameLayout {
private ArrayList<View> views = new ArrayList<>();
private View menuView;//顯示隱藏按鈕
private int measureHeight;//menuView的寬高

public PopMenu(Context context, AttributeSet attrs) {
    super(context, attrs);
}

}
</pre>

那么我們要測量子View的寬高和放置位置
<pre>
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
if (menuView != null) {
measureHeight = menuView.getMeasuredHeight();
}
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
initView();
for (int i = 0; i < views.size(); i++) {
View itemView = views.get(i);
itemView.layout(
menuView.getLeft() + (menuView.getMeasuredWidth() - itemView.getMeasuredWidth()) / 2,
menuView.getTop() + (menuView.getMeasuredHeight() - itemView.getMeasuredHeight()) / 2,
menuView.getLeft() + (menuView.getMeasuredWidth() + itemView.getMeasuredWidth()) / 2,
menuView.getTop() + (menuView.getMeasuredHeight() + itemView.getMeasuredHeight()) / 2
);

        /**
         * 在這里添加View動畫配置
         */
        
    }
}

//初始化參數(shù)變量
public void initView() {
if (getChildCount() < 1) return;
views.clear();

    for (int i = 0; i < getChildCount() - 1; i++) {
        views.add(getChildAt(i));
        getChildAt(i).setAlpha(0);
    }

    menuView = getChildAt(getChildCount() - 1);
    menuView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            startAnim();
        }
    });
}

</pre>
由于我們點擊到MenuView后孕暇,菜單對象會播放動畫仑撞,我們需要給MenuView添加點擊效果監(jiān)聽
<pre>
menuView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startAnim();
}
});
/**
* 開始動畫
*/
public void startAnim() {
if (popAnimator.isShow) {
popAnimator.doAnimIn();
showShade();
popAnimator.isShow = false;
} else {
popAnimator.doAnimOut();
showShade();
popAnimator.isShow = true;
}
}
</pre>

我們獲取到子View對象后,就可以對他們進行動畫的處理了妖滔,那么播放動畫需要什么參數(shù)呢隧哮?我們聲明一個Config類來保存動畫的相關(guān)參數(shù),另外聲明一個父類保存動畫座舍,之后如果有新的動畫效果沮翔,可以直接繼承父類,再注入簸州。
<pre>
我們寫父類的時候鉴竭,需要對里面的數(shù)據(jù)進行初始化和配置更新處理歧譬,另外需要給外部提供動畫的顯示和隱藏效果岸浑!
public class PopAnimator {
public int measureHeight = 0;//菜單按鈕高度
public ArrayList<View> views = new ArrayList<>();//彈出按鈕列表
public boolean isShow = false;//當前顯示狀態(tài)
public long duration = 400;
public int alpha = 1;
public int radius = 20;

public void loadConfig(AnimatorConfigBuild config) {
    measureHeight = config.getMeasureHeight();
    views = config.getViews();
    isShow = config.isShow();
    duration = config.getDuration();
    alpha = config.getAlpha();
    radius = config.getRadius();
}

public AnimatorConfigBuild saveConfig() {
    return new AnimatorConfigBuild().setAlpha(alpha)
            .setDuration(duration)
            .setMeasureHeight(measureHeight)
            .setRadius(radius)
            .setShow(isShow)
            .setViews(views);
}

/**
 * 重寫動畫效果
 */
public void doAnimOut() {
    if (views.isEmpty()) return;
    int start = 0;
    int degree = 360 / views.size();
    for (int i = 0; i < views.size(); i++) {
        View view = views.get(i);
        view.animate()
                .setDuration(duration)
                .translationY((float) ((radius + measureHeight) * Math.sin(Math.toRadians(start + degree * i))))
                .translationX((float) ((radius + measureHeight) * Math.cos(Math.toRadians(start + degree * i))))
                .rotation(360)
                .alphaBy(0)
                .alpha(alpha);
    }
}

public void doAnimIn() {
    for (int i = 0; i < views.size(); i++) {
        View view = views.get(i);
        view.animate()
                .setDuration(duration)
                .translationY(0)
                .translationX(0)
                .rotation(0)
                .alpha(0);
    }
}

}
</pre>
<pre>
//通過鏈式聲明的方式來寫動畫配置類
public class AnimatorConfigBuild {
public int measureHeight = 0;//菜單按鈕高度
public ArrayList<View> views = new ArrayList<>();//彈出按鈕列表
public boolean isShow = false;//當前顯示狀態(tài)
public long duration = 400;
public int alpha = 1;
public int radius = 20;

/**

  • 配置動畫效果
    */
    public AnimatorConfigBuild build(PopAnimator animator) {
    if (animator == null) {
    animator = new PopAnimator();
    }
    animator.loadConfig(this);
    return this;
    }
    }
    </pre>

我們把配置類寫好之后搏存,回到PopMenu類,在onlayout里面把動畫對象實例化并賦予內(nèi)容參數(shù)
<pre> /**
* 初始化內(nèi)容參數(shù)
*/
new AnimatorConfigBuild()
.setMeasureHeight(measureHeight)
.setViews(views).build(popAnimator);</pre>
那么我們的簡單的自定義彈出菜單就完成了矢洲!有很多地方都還需要改進

下面我們就實現(xiàn)擴展的動畫類,由于動畫隱藏的效果都是回到最初的位置璧眠,所以我們主要還是實現(xiàn)View彈出效果,就可以了
<pre>
public class PopUpAnim extends PopAnimator {
private int padding = 8;
private int type;//左上右下
int distance;

public void doAnimOut() {
    if (views.isEmpty()) return;
    for (int i = 0; i < views.size(); i++) {
        View view = views.get(i);
        if (i == 0) {
            distance = measureHeight / 2 - view.getMeasuredHeight() / 2;
        }
        distance = distance + view.getMeasuredHeight() + padding;
        view.animate()
                .setDuration(duration)
                .translationY(getDirection() ? 0 : getDistance())
                .translationX(getDirection() ? getDistance() : 0)
                .rotation(360)
                .alphaBy(0)
                .alpha(alpha);
    }
}

/**
 * 計算距離
 *
 * @return
 */
public int getDistance() {
    return type > 1 ? distance : -distance;
}

/**
 * 獲取方向
 *
 * @return
 */
public boolean getDirection() {
    //返回橫向為true
    return type % 2 == 0;
}

public PopUpAnim setType(int type) {
    type %= 4;
    this.type = type;
    return this;
}

public void setPadding(int padding) {
    this.padding = padding;
}

}
</pre>
接下來读虏,我們把它放到Activity里面就可以看到效果了,xml布局里面添加幾個按鈕和PopMenu控件
<pre>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
    android:id="@+id/btnChange"
    android:onClick="onBtnChange"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="換一個效果"
    android:layout_alignParentBottom="true"
    />

<Button
    android:id="@+id/btnAdd"
    android:onClick="onBtnAdd"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="添加"
    android:layout_alignParentBottom="true"
    />

<Button
    android:id="@+id/btnMul"
    android:onClick="onBtnMul"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="減少"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    />

<com.marco.floatpopbutton.widget.PopMenu
    android:id="@+id/menu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_above="@id/btnChange"
    >

    <Button
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:background="@mipmap/ic_launcher"
        />

    <Button
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:background="@mipmap/ic_launcher"/>

    <Button
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:background="@mipmap/ic_launcher"/>

    <Button
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_gravity="center"
        android:background="@android:color/holo_purple"
        />
</com.marco.floatpopbutton.widget.PopMenu>

</RelativeLayout>
</pre>

然后在Activity添加邏輯代碼

<pre>
public class MainActivity extends AppCompatActivity {

PopMenu menu;
ArrayList<PopAnimator> animators = new ArrayList<>();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    menu = (PopMenu) findViewById(R.id.menu);
    animators.add(new PopUpAnim().setType(0));
    animators.add(new PopCircleAnim());
    animators.add(new PopUpAnim().setType(1));
    animators.add(new PopLeftTopAnim());
    animators.add(new PopUpAnim().setType(2));
    animators.add(new PopCircleAnim());
    animators.add(new PopUpAnim().setType(3));
    animators.add(new PopLeftTopAnim());
}

/**
 * 切換效果
 *
 * @param view
 */
int index = 0;

public void onBtnChange(View view) {
    index = (++index) % animators.size();
    menu.setPopAnimator(animators.get(index));
}

Handler handler = new Handler();

public void onBtnAdd(View view) {
    Button button = new Button(this);
    button.setLayoutParams(new FrameLayout.LayoutParams(60, 60));
    button.setBackgroundResource(R.mipmap.ic_launcher);
    menu.addView(button, 0);
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            menu.startAnim();
        }
    }, 100);
}

public void onBtnMul(View view) {
    if (menu.getChildCount() > 1) {
        menu.removeViewAt(0);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                menu.startAnim();
            }
        }, 100);
    }
}

}
</pre>

代碼地址:https://github.com/Mark911105/PopAnimButton责静,歡迎大家Fork and Star !

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盖桥,一起剝皮案震驚了整個濱河市灾螃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌揩徊,老刑警劉巖腰鬼,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異塑荒,居然都是意外死亡熄赡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門齿税,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彼硫,“玉大人,你說我怎么就攤上這事凌箕∨±海” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵牵舱,是天一觀的道長他托。 經(jīng)常有香客問我,道長仆葡,這世上最難降的妖魔是什么赏参? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮沿盅,結(jié)果婚禮上把篓,老公的妹妹穿的比我還像新娘。我一直安慰自己腰涧,他們只是感情好韧掩,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著窖铡,像睡著了一般疗锐。 火紅的嫁衣襯著肌膚如雪坊谁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天滑臊,我揣著相機與錄音口芍,去河邊找鬼。 笑死雇卷,一個胖子當著我的面吹牛鬓椭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播关划,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼小染,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贮折?” 一聲冷哼從身側(cè)響起裤翩,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎调榄,沒想到半個月后踊赠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡振峻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年臼疫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扣孟。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡烫堤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凤价,到底是詐尸還是另有隱情鸽斟,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布利诺,位于F島的核電站富蓄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏慢逾。R本人自食惡果不足惜立倍,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侣滩。 院中可真熱鬧口注,春花似錦、人聲如沸君珠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至材部,卻和暖如春毫缆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乐导。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工苦丁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兽叮。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓芬骄,卻偏偏與公主長得像猾愿,于是被迫代替她去往敵國和親鹦聪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,262評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫蒂秘、插件泽本、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,117評論 4 61
  • 1、2017倒計時20天姻僧。 2规丽、今日金句: 只有在追求夢想的道路上全力以赴的人,才能活成光彩奪目的樣子撇贺。 3赌莺、每天...
    向著美好奔跑閱讀 155評論 0 0
  • 高妙容在本書里面說出場的也不早,但是每一次的出場都能夠在評論區(qū)里面掀起千層浪松嘶,評論區(qū)里面都把她白蓮花艘狭。其實用現(xiàn)在的...
    qiling閱讀 22,227評論 0 51
  • 一泓溪流靈動了亂石 誰把思念像落雨 那楓樹的心事還沒寫入紅紙 誰把這一道石門打開 藏住了憂傷卻藏不住秘密 迎面的風...
    秋燭滴蓮閱讀 113評論 0 0