*本篇文章已授權微信公衆號 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) {
//點擊事件處理
}
}
效果展示
最後展示下最終的完成效果
最後附上源碼地址