Android用Scroller實(shí)現(xiàn)一個(gè)可向上滑動(dòng)的底部導(dǎo)航欄
靜靜等了5分鐘竟不知道如何寫(xiě)我這第一篇文章。每次都想好好的學(xué)習(xí)學(xué)習(xí),有時(shí)間多敲敲代碼,寫(xiě)幾篇自己的文章。今天終于開(kāi)始實(shí)行了,還是有點(diǎn)小激動(dòng)的。哈哈!
好了廢話就不多收了。我今天想實(shí)現(xiàn)的一個(gè)功能就是一個(gè)可以上滑底部菜單欄。為什么我會(huì)想搞這么個(gè)東西呢, 還是源于一年前,我們app 有這么個(gè)需求,當(dāng)時(shí)百度也好谷歌也好,都沒(méi)有找到想要的效果,其實(shí)很簡(jiǎn)單的一個(gè)效果。但是當(dāng)時(shí)我也是真的太菜了,所有有關(guān)自定義的控件真的是不會(huì),看別人的代碼還好,真要是自己寫(xiě),一點(diǎn)頭緒都沒(méi)有。因?yàn)槲以囍鴮?xiě)了,真的不行啊。當(dāng)時(shí)覺(jué)得自己好廢啊 ,所有效果都是別人的,自己都不會(huì)寫(xiě)。也下定決定要好好搞自定義控件,一拖再拖就到了現(xiàn)在。在了解了一段時(shí)間的自定義控件后又想起來(lái)這個(gè)效果,然后給他搞了出來(lái)。
看下效果:
首先這個(gè)控件是滑動(dòng)的肯定就會(huì)用到scrollTo()或者scrollBy(),和Scroller類。那么先簡(jiǎn)單介紹一點(diǎn)這三個(gè)東西。
scrollTo(int, int)與scrollBy(int, int)
- scrollTo是讓View的content滾動(dòng)到相對(duì)View初始位置的(x, y)處。
- scrollBy是讓View的content滾動(dòng)到相對(duì)于View當(dāng)前位置的(x, y)處。
Scroller類
Scroller是手指滑動(dòng)中比較重要的一個(gè)輔助類,它可以幫助開(kāi)發(fā)者完成一個(gè)順滑的滾動(dòng)。其主要包括:
startScroll(int startX, int startY, int dx, int dy)
和startScroll(int startX, int - startY, int dx, int dy, int duration)
。- startX,x方向從哪里開(kāi)始移動(dòng)。
- startY,y方向從哪里開(kāi)始移動(dòng)。
- dx,x方向移動(dòng)多遠(yuǎn)。
- dy,y方向移動(dòng)多遠(yuǎn)。
- duration,這個(gè)移動(dòng)操作需要多少時(shí)間執(zhí)行完,默認(rèn)是250毫秒。
如果真正的想使用這個(gè)類,還需要配合computeScroll()方法。重寫(xiě)此方法
@Override public void computeScroll() { if (mScroller.computeScrollOffset()) { // 計(jì)算新位置,并判斷上一個(gè)滾動(dòng)是否完成。 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate();// 再次調(diào)用computeScroll。 } }
computeScrollOffset()
這個(gè)方法用來(lái)計(jì)算當(dāng)前你想知道的一個(gè)新位置,Scroller會(huì)自動(dòng)根據(jù)標(biāo)記時(shí)的坐標(biāo)、時(shí)間、當(dāng)前位置計(jì)算出一個(gè)新位置,記錄到內(nèi)部,我們可以通過(guò)Scroller#getCurrX()和Scroller#getCurrY()獲取的新的位置。
要知道的是,它計(jì)算出的新位置是一個(gè)閉區(qū)間[x, y],而且會(huì)在你調(diào)用startScroll傳入的時(shí)間內(nèi)漸漸從你指定的int startX和int startY移動(dòng)int dx和int dy的距離,所以我們每次調(diào)用Scroller#computeScrollOffset()后再調(diào)用View的scrollTo(int, int)然后傳入Scroller#getCurrX()和Scroller#getCurrY()就可以得到一個(gè)漸漸移動(dòng)的效果。
同時(shí)這個(gè)方法有一個(gè)返回值是boolean類型的,內(nèi)部是用一個(gè)boolean來(lái)記錄是否完成的,在調(diào)用Scroller#startScroll)時(shí)會(huì)把這個(gè)boolean參數(shù)置為false。內(nèi)部邏輯是先判斷startScroll()動(dòng)畫(huà)是否還在繼續(xù),如果沒(méi)有完成則計(jì)算最新位置,計(jì)算最新位置前會(huì)對(duì)duration做判斷,第一如果時(shí)間沒(méi)到,則真正的計(jì)算位置,并且返回true,第二如果時(shí)間到了,把記錄是否繼續(xù)的boolean成員變量標(biāo)記完成,并直接賦值最新位置為最終目的位置,并且返回true;如果startScroll()已經(jīng)完成則直接返回false。我們判斷Scroller#computeScrollOffset()是true時(shí)說(shuō)明還沒(méi)完成,此時(shí)拿到Scroller#getCurrX()和Scroller#getCurrY()做一個(gè)滾動(dòng)。
Scroller#getCurrX()
Scroller#getCurrY()
這兩個(gè)方法就是拿到通過(guò)Scroller#computeScrollOffset()計(jì)算出的新的位置,上面也解釋過(guò)了。
Scroller.isFinished()
上次的動(dòng)畫(huà)是否完成。
Scroller.abortAnimation()
取消上次的動(dòng)畫(huà)。
好了,了解了這些下面開(kāi)始實(shí)現(xiàn)這個(gè)效果。
首先先搞一個(gè)布局,包括底部導(dǎo)航欄的頭和導(dǎo)航欄的內(nèi)容體。
<com.study.androidtest.BottomBar android:orientation="vertical" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:background="@color/colorAccent" android:layout_width="match_parent" android:layout_height="50dp"> </LinearLayout> <LinearLayout android:background="@color/colorPrimaryDark" android:layout_width="match_parent" android:layout_height="200dp"> </LinearLayout> </com.study.androidtest.BottomBar>
配上效果圖。
很簡(jiǎn)單的一個(gè)效果(這里只看效果,不看UI啦),藍(lán)色的就是內(nèi)容,紅色的就是頭部。
那么我想實(shí)現(xiàn)一個(gè)什么效果呢,就是開(kāi)始的時(shí)候是看不到藍(lán)色部分的,點(diǎn)擊或者滑動(dòng)紅色部分可以顯示藍(lán)色部分,一個(gè)上拉和下拉的效果。那么現(xiàn)在肯定要實(shí)現(xiàn)一個(gè)自定的viewGroup去實(shí)現(xiàn)這個(gè)布局。
首先我去建一個(gè)類BottomBar.class, 為了簡(jiǎn)單我直接用它去繼承LinearLayout。重寫(xiě)它的onLayout()方法。因?yàn)槲乙グ阉{(lán)色部分隱去,只留紅色部分。怎么做呢 ,代碼如下:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); bottomBar.layout(0, getMeasuredHeight() - bottomBar.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight()); bottomContent.layout(0, getMeasuredHeight(), getMeasuredWidth(), bottomBar.getBottom() + bottomContent.getMeasuredHeight()); }
通過(guò)onLayout()
方法改變其位置讓其吧藍(lán)色部分隱藏。
接下來(lái)就是處理滑動(dòng)事件了。我要按住紅色部分上下滑動(dòng)去顯示和隱藏藍(lán)色部分,那么肯定是要有手勢(shì)識(shí)別,重寫(xiě)onTouchEvent()
,再配合view的scrollTo()
方法就可以實(shí)現(xiàn)這個(gè)簡(jiǎn)單的效果。
@Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i("", "--------->x="+event.getX() + ", y="+event.getY()); downX = (int) event.getX(); downY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: int endY = (int) event.getY(); int dy = (int) (endY - downY); int toScroll = getScrollY() - dy; if(toScroll < 0){ toScroll = 0; } else if(toScroll > bottomContent.getMeasuredHeight()){ toScroll = bottomContent.getMeasuredHeight(); } scrollTo(0, toScroll); downY = (int) event.getY(); break; case MotionEvent.ACTION_UP: scrollOffset = getScrollY(); if(scrollOffset > bottomContent.getMeasuredHeight() / 2){ showNavigation(); } else { closeNavigation(); } break; } return true; }
代碼就懶了沒(méi)有注釋,但是我會(huì)在下面解釋一下,都是一些簡(jiǎn)單的邏輯,首先ACTION_DOWN里面的代碼,只是記錄了按下的坐標(biāo),沒(méi)什么的。然后是ACTION_MOVE的代碼。首先應(yīng)該了解getScrollY(),它是控件滑動(dòng)的距離,初始值為0??梢钥吹轿艺{(diào)用scrollTo(0, toScroll),而toScroll = getScrolly() - dy;,dy是手指滑動(dòng)的一個(gè)偏移量。通過(guò)了這些計(jì)算你會(huì)發(fā)現(xiàn)toScroll就是藍(lán)色部分的高度。那么效果就已經(jīng)實(shí)現(xiàn)了,很簡(jiǎn)單吧??戳酥竽銈儠?huì)不會(huì)有這樣一個(gè)疑問(wèn)哈,也是我當(dāng)時(shí)的一個(gè)疑問(wèn),那就是為什么我們不直接用dy也就是手指滑動(dòng)的一個(gè)距離來(lái)當(dāng)作toScroll 的值呢(不考慮下面對(duì)downY的賦值,單純是手指滑動(dòng)的距離)。其實(shí)是可以的,控件會(huì)隨著手指滑動(dòng)的。但是,當(dāng)手指離開(kāi)屏幕再次點(diǎn)擊的時(shí)候,菜單又會(huì)回到原來(lái)的狀態(tài)再進(jìn)行滑動(dòng)。那么為什么會(huì)造成這樣的效果呢,仔細(xì)看過(guò)你會(huì)發(fā)現(xiàn),每次點(diǎn)擊的時(shí)候dy都是0,所以每次調(diào)用scrollTo(x, y)的時(shí)候x, y都是0,自然菜單就會(huì)回到初始位置。所以getScrollY() - dy其實(shí)就是再記錄上一次的位置,使在下一次點(diǎn)擊的時(shí)候'y'的值不是0。前提是需要每次對(duì)downY重新進(jìn)行賦值。好了有了這些上拉下拉的效果就有了。但是只有這樣還是不行,我們要做到它自動(dòng)的彈出和收回。接下來(lái)就是ACTION_UP時(shí)做處理,我調(diào)用了showNavigation();和colseNavigation();,代碼如下都是簡(jiǎn)單的邏輯不做解釋。
private void showNavigation(){ int dy = bottomContent.getMeasuredHeight() - scrollOffset; mScroller.startScroll(getScrollX(), getScrollY(), 0, dy, 500); invalidate(); } private void closeNavigation(){ int dy = 0 - scrollOffset; mScroller.startScroll(getScrollX(), getScrollY(), 0, dy, 500); invalidate(); }
效果實(shí)現(xiàn)啦,哈哈。真的很簡(jiǎn)單。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android用文件存儲(chǔ)數(shù)據(jù)的方法
這篇文章主要為大家詳細(xì)介紹了Android用文件存儲(chǔ)數(shù)據(jù)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10完美解決客戶端webview持有的頁(yè)面緩存,不會(huì)立即釋放的問(wèn)題
下面小編就為大家?guī)?lái)一篇完美解決客戶端webview持有的頁(yè)面緩存,不會(huì)立即釋放的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12基于Flutter實(shí)現(xiàn)圖片選擇和圖片上傳
Flutter?的圖片選擇插件很多,包括了官方的?image_picker,multi_image_picker(基于2.0出了?multi_image_picker2)等等。本文將利用這些插件實(shí)現(xiàn)圖片選擇和圖片上傳,需要的可以參考一下2022-03-03Android開(kāi)發(fā)MQTT協(xié)議的模型及通信淺析
這篇文章主要W為大家介紹了Android開(kāi)發(fā)MQTT協(xié)議的模型及通信淺析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Kotlin圖文講解多語(yǔ)言支持實(shí)現(xiàn)方法
這篇文章主要介紹了Kotlin多語(yǔ)言支持實(shí)現(xiàn)方法,在Android開(kāi)發(fā)中,我們?nèi)绾沃С侄嗾Z(yǔ)言APP呢,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值2023-02-02Android自定義Drawable實(shí)現(xiàn)圓形和圓角
這篇文章主要為大家詳細(xì)介紹了Android自定義Drawable實(shí)現(xiàn)圓形和圓角,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Android自定義Dialog實(shí)現(xiàn)通用圓角對(duì)話框
這篇文章主要為大家詳細(xì)介紹了Android自定義Dialog實(shí)現(xiàn)通用圓角對(duì)話框,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11