自定義可摺疊按鈕

*本篇文章已授權微信公衆號 guolin_blog (郭霖)獨家發佈

這幾天無聊系列之——自定義可摺疊按鈕

效果如下:

注意看到沒,有個浮動按鈕,上滑到頂縮小,下滑到某個地方就展開。暫且不說上拉跟下滑的時候。主要時按鈕伸縮效果如何實現呢?這個就是我們今天的主題了。

思路分析

一、按鈕拉伸思路步驟詳解

如下(繪畫的很粗糙,忽略):

主要是由兩個圓,然後中間是一個矩形。當按鈕縮小時,就是右邊這個圓向左邊這個圓靠近,圓心不斷向左移,中間矩形寬度不斷減少,可以看到矩形的寬度就是右邊圓圓心的x座標減去左邊圓心x座標

開始動手擼代碼。

1.創建一個叫StretchableFloatingButton的類並且繼承ViewGroup,並實現相應的方法。

2.在onMeasure方法終測量控件的寬高度,以及圓心半徑,矩形右邊的x座標。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (width == 0){
        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);

        //獲取圓的半徑
        center = height/2;
        //矩形右邊x座標
        x = width -center;

    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

3.確定左邊圓圓心,右邊圓圓心,以及矩形範圍,並繪製出來。(注意ViewGroup繪製在dispatchDraw方法裏)

  • 左邊圓心:(center,center)半徑爲center
  • 右邊圓心:(width-center,center) 半徑爲center
  • 矩形範圍(RectF)確定左上角和右下角座標:(center,0,width-center,height)
@Override
protected void dispatchDraw(Canvas canvas) {
    //畫左邊圓
    canvas.drawCircle(center, center, center, paint);

    //畫矩形
    rectF = new RectF(center, 0, width-center, height);
    canvas.drawRect(rectF, paint);

    //畫右邊圓
    canvas.drawCircle(width-center, center, center, paint);

    super.dispatchDraw(canvas);
}

4.在佈局中引用,看效果

5.實現縮小和展開效果

在上面我們分析到,縮小(展開),是右邊圓心不斷向左邊靠近(遠離);矩形寬度縮小(變大),也就是右邊圓心x座標不斷減小(增大),達到位置時動畫就結束。

  • 改變的就是矩形範圍(center,0,x,height),x 就是我們減小(增大)的值。
  • 右邊圓(x,center),x跟矩形x一致。

如何不斷減小(增大)x的值?

我們可以通過handler不斷髮送消息,減少(增加)x的值並重繪,當x到達左邊圓心x座標或到達矩形右邊原本x座標位置,停止發送。

定義一個方法讓外部調用

看實現效果:

二、內部小圓放大縮小思路步驟詳解

1.先把小圓繪製出來。

  • 確定圓心位置,跟左邊的圓圓心一致
  • 小圓半徑爲centet-y,這裏y指的是圓環寬度,設置初始值爲20,等下我們要操作的圓環寬度y,來控制小圓的變化。

效果如下:

2.當整個控件放大縮小時控制圓的縮小和放大

這裏要確定個比值:小圓最小時距離左邊圓的寬度是固定 我們設爲y,而控件減小或增大的距離也是確定的,我們設爲x。當出現變化時,x減少多少,y就根據x/y這個比值減少多少。

y/x = 圓環最大寬度/控件拉伸總距離

  • 在onMesure中確定這個比值

  • 在控件拉伸活着放大時,的到具體的y值。

效果如下:

三、內部文字拉伸思路步驟詳解

在考慮文字拉伸的時候如果用canvas 畫文字,控制拉伸比較難控制,所以這個控件是繼承ViewGroup。默認我在裏面寫了個TextView,通過onLayout去控制文本寬度。

1.創建一個TextView,並在onMesaure中測量寬高度。

2.通過onLayout擺放TextView位置。確定兩個座標左上角,右下角座標

  • 左上角(ceter*2 +5,center-tHeight/2),+5的目的是設置間距

  • 右下角 (tWidth + center*2+10,(center+tHeight/2)) +10設置間距

注:tX 在後面代碼中來控制文本寬度。默認初始爲 tX = tWidth + center*2+10

效果如下:

3.設置TextView文本寬度變化比,其實就是TextView右下角的x座標值的大小,達到拉伸目的。

TextView最大拉伸距離就是自身,控件拉伸距離在上面我們也已經確定是矩形的寬度。當控件拉伸多少根據這個比值,TextView對應拉伸的距離,即我我們所要求的tX。

確定比值:TextView最大寬度/控件拉伸總距離

根據比值得到tX,因爲是改變子控件位置,需要調用requestLayout方法

效果如下:

四、小圓中圖片旋轉思路步驟詳解

最終到了我們這個控件的最後一個思路環節了。就是小圖標的旋轉以及變化,在原效果中,當控件拉伸時,小圖標跟着旋轉,並且在效果結束之後變換圖片。

這裏涉及到了canvas的drawBitamp方法,以及canvas的roatate旋轉方法。

1.先把小圖標畫出來。

  • 確定小圖標寬高度,這裏我設置了固定值:小圓的半徑 - 5,-5設置間距。

  • 確定小圖標左上角座標位置。(center - iconWidth / 2,center - iconWidth / 2)

效果如下:

2.處理旋轉,這裏我設置默認旋轉90度。因爲原效果摺疊時是逆時針旋轉所以是-90度。

同樣的我們也是要求出旋轉比。根據旋轉比得出旋轉值。

旋轉比: 總旋轉角度/控件拉伸距離

3.根據旋轉比求出旋轉角度

最終效果如下:

優化

一、設置自定義屬性

我們寫了這麼多,裏面的角度,圖標,文字,顏色等等都是固定的,應該可以在xml代碼中動態去設置它。

這裏需要寫自定義屬性,在valuse文件夾創建一個attrs的XML文件,編寫自定義屬性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="FlodAbleButton">
        <!--字體大小-->
        <attr name="text_size" format="float"/>
        <!--摺疊速度-->
        <attr name="speed" format="float"/>
        <!--控件背景顏色-->
        <attr name="bac_color" format="color"/>
        <!--字體顏色-->
        <attr name="text_color" format="color"/>
        <!--小圓顏色-->
        <attr name="inner_circle_color" format="color"/>
        <!--文本-->
        <attr name="text" format="string"/>
        <!--展開圖標-->
        <attr name="open_icon" format="reference"/>
        <!--掛壁圖標-->
        <attr name="close_icon" format="reference"/>
        <!--圖標旋轉角度-->
        <attr name="degrees" format="float"/>
    </declare-styleable>
</resources>

在代碼中獲取自定義屬性值

在xml中使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary"
    tools:context="com.roy.stretchablefloatingbutton.MainActivity">

    <com.roy.library.FlodableButton
        android:id="@+id/fb"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_margin="10dp"
        app:bac_color="#ff0"
        app:close_icon="@mipmap/icon_2"
        app:inner_circle_color="@color/black"
        app:open_icon="@mipmap/icon"
        app:speed="60"
        app:text="地鐵/景區/商圈/城市"
        app:text_color="@color/black"
        app:text_size="16"
        app:degrees="90"/>

</LinearLayout>

二、設置摺疊監聽事件

當控件摺疊或者展開成功時,設置一個監聽事件,把摺疊狀態以及控件自身回調出去

public void setFoldListener(FoldListener foldListener) {
    this.foldListener = foldListener;
}

//摺疊展開監聽
public interface FoldListener{
    void onFold(boolean isIncrease,FlodableButton sfb);
}

使用

sfb.setFoldListener(new FlodableButton.FoldListener() {
    @Override
    public void onFold(boolean isIncrease, FlodableButton sfb) {
        String text = isIncrease? "展開了":"摺疊了";
        Toast.makeText(MainActivity.this,text,Toast.LENGTH_SHORT).show();
    }
});

效果展示

三、設置自己的點擊監聽事件

本來有自己的點擊事件,爲什麼也要設置自己的點擊事件呢。原因如下:

在效果可以看到,當我們摺疊時,點擊空白處,還有點擊事件響應,這是我們不需要的。
這個效果出現的原因是我們的控件設置寬度是match_parent,所以一般設置點擊事件還是會響應。

如何解決這個問題呢?

我們可以在onTouchEvent事件中做攔截,這裏我們要區分兩種狀況:

  • 摺疊狀態時,可點擊區域是個圓,需要判斷點擊的時的 x座標和y座標是否在圓內。在圓內響應點擊事件,不在圓內不做攔截處理。
  • 伸展狀態時,可點擊區域時整個控件。

    代碼如下:

在Activity中使用

sfb.setOnClickListener(new FlodableButton.OnClickListener() {
    @Override
    public void onClick(FlodableButton sfb) {
        //點擊事件處理
    }
}

效果展示

最後展示下最終的完成效果

最後附上源碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章