需求
繼續(xù)死磕Launcher的文件夾模式,按照UI的設(shè)計(韓國團(tuán)隊,果然思考的非主流),對類APUS文件夾滑動顯示的要求是:當(dāng)前的文件夾名字要居中高亮大字體(如果是第一個文件夾也是要居中,左側(cè)空白;如果不是第一個文件夾,那么左右顯示的文件夾名字都是小字體,而且距離當(dāng)前文件夾名字的間距相同)。如下圖所示:
話說看到這個設(shè)計的時候,我的內(nèi)心是崩潰的,為啥不用主流的顯示方式,類似頭條新聞,APUS之類的,順序排列,哪個當(dāng)前顯示,哪個下面有下劃線即可。現(xiàn)在有很多寫好的控件,拿來直接用即可。但是,還是要實現(xiàn)這個效果。
分析
需要實現(xiàn)如下的功能:
- 當(dāng)前展示的文件夾名字居中,其他文件夾跟它等距離相間
- 點擊當(dāng)前顯示的文件夾名字可以改變文件夾名
- 點擊非當(dāng)前的文件夾名字,顯示這個干文件夾的內(nèi)容,并且此文件夾名字居中高亮(有下劃線)
文件夾名字顯示的實現(xiàn)
參考了github上ViewPagerIndicator其中TitlePageIndicator的實現(xiàn),即通過直接繼承View來實現(xiàn)上訴的需求。因為現(xiàn)有的控件不足于讓我們完成title的繪制和展示。
首先我們自定義的TitlePageIndicator要繼承View并且實現(xiàn)ViewPager的接口OnPageChangeListener,這樣viewpager滑動的時候,我們可以通過回調(diào)函數(shù)來控制相關(guān)title的滑動顯示。
public class TitlePageIndicator extends View implements OnPageChangeListener {
因為本身TitlePageIndicator是個自定義的view,所以我們把它加到我們的布局文件中
<com.android.launcher3.TitlePageIndicator android:id="@+id/folder_group_indicator" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10.0dp" > </com.android.launcher3.TitlePageIndicator> <com.android.launcher3.FolderViewPager android:id="@+id/viewpager" android:layout_below="@id/folder_group" android:layout_width="match_parent" android:layout_height="match_parent" > </com.android.launcher3.FolderViewPager>
這樣,在TitlePageIndicator的onDraw函數(shù)里面就可以展示我們的文件夾名稱了。
所以重點在onDraw里面怎么畫出這些titles。
- 首先要先算出各個title的left/right/top/bottom(主要就是left和right,因為在文件夾名稱之間我們要加入固定的padding,這樣left/right都是絕對的坐標(biāo),即有可能是在屏幕外),然后存在一個ArrayList上。這個操作每次調(diào)用onDraw的時候都要重新算,因為當(dāng)前顯示的title不一樣,那么以這個title為中心,其他title的位置就要算出來,另外隨著手指的滑動,這些值是動態(tài)變化的。難點1是這個。
- 然后是要判斷當(dāng)前的真實位置(即有可能是在拖拽滑動中),這用要從OnPageChangeListener里面的回調(diào)函數(shù)中獲得:onPageScrollStateChanged中獲得是否在滑動isScrolling;onPageScrolled中獲得當(dāng)前的頁碼mCurrentPage,和偏移量mPageOffset。當(dāng)然,在這些函數(shù)的最后都要調(diào)用invalidate來觸發(fā)onDraw來更新UI。
- 這樣我們的onDraw里面就有了當(dāng)前的狀態(tài):是否在滑動isScrolling,當(dāng)前顯示的頁碼mCurrentPage,偏移量mPageOffset。利用這幾個變量,我們要算出來各個title的相對之前算出來的坐標(biāo)的偏移量,即得到實際要在哪里顯示title。這里分三種情況:
1. 是否是在滑動,如果不滑動,直接按照之前算出來的坐標(biāo)顯示即可。
2. 如果滑動,當(dāng)前頁不是最后一頁,要通過當(dāng)前頁的下一頁來算偏移量。
3. 如果滑動,當(dāng)前頁為最后一頁,那么要通過當(dāng)前頁的前一頁來算偏移量。
最后調(diào)用canvas.drawText來完成title的繪制。
canvas.drawText(pageTitle, 0, pageTitle.length(), bound.left + offset, bound.bottom + mTopPadding, mPaintText);
我們最后關(guān)心的只有offset這個參數(shù)(不滑動的時候為0,不表),需要單獨算出來。
- 非最后一頁的offset
gap = (rightBound.right + rightBound.left) / 2 - halfWidth(屏幕寬的一半);
offset = (int)(gap * mPageOffset);
- 最后一頁的offset
float gap = (halfWidth - w / 2) - leftBound.left;
offset = (int)(gap * (1 - mPageOffset));
Note:
關(guān)于ViewPager左右滑動,mCurrentPage的變化是不一樣的。所以通過gap算offset的公式也不一樣。
- 手指向左滑動,頁面向右走的時候,mCurrentPage是不變的。直到滑到下個頁面的時候,才變化。
- 手指向右滑動,頁面向左走的時候,mCurrentPage立刻減一。
關(guān)于點擊文件夾名字切換文件夾的實現(xiàn)
重寫了onTouchEvent函數(shù),當(dāng)ACTION_UP的時候,說明完成了點擊事件。那么這個時候判斷點擊的位置落到哪個title上,然后調(diào)用viewpager的setCurrentItem函數(shù)來完成切換。比較簡單。
最后的效果
后記
實現(xiàn)的還是比較粗糙,后面還要繼續(xù)優(yōu)化。感覺好的UI控件還是要慢慢雕琢才可以。