靜靜等了5分鐘竟不知道如何寫我這第一篇文章。每次都想好好的學習學習,有時間多敲敲代碼,寫幾篇自己的文章。今天終于開始實行了,還是有點小激動的。哈哈!
?? 好了廢話就不多收了。我今天想實現的一個功能就是一個可以上滑底部菜單欄。為什么我會想搞這么個東西呢, 還是源于一年前,我們app 有這么個需求,當時百度也好谷歌也好,都沒有找到想要的效果,其實很簡單的一個效果。但是當時我也是真的太菜了,所有有關自定義的控件真的是不會,看別人的代碼還好,真要是自己寫,一點頭緒都沒有。因為我試著寫了,真的不行啊。當時覺得自己好廢啊 ,所有效果都是別人的,自己都不會寫。也下定決定要好好搞自定義控件,一拖再拖就到了現在。在了解了一段時間的自定義控件后又想起來這個效果,然后給他搞了出來。
看下效果:
首先這個控件是滑動的肯定就會用到scrollTo()或者scrollBy(),和Scroller類。那么先簡單介紹一點這三個東西。
scrollTo(int, int)與scrollBy(int, int)
- scrollTo是讓View的content滾動到相對View初始位置的(x, y)處。
- scrollBy是讓View的content滾動到相對于View當前位置的(x, y)處。
Scroller類
Scroller是手指滑動中比較重要的一個輔助類,它可以幫助開發者完成一個順滑的滾動。其主要包括:
startScroll(int startX, int startY, int dx, int dy)
和startScroll(int startX, int - startY, int dx, int dy, int duration)
。
- startX,x方向從哪里開始移動。
- startY,y方向從哪里開始移動。
- dx,x方向移動多遠。
- dy,y方向移動多遠。
- duration,這個移動操作需要多少時間執行完,默認是250毫秒。
如果真正的想使用這個類,還需要配合computeScroll()方法。重寫此方法
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) { // 計算新位置,并判斷上一個滾動是否完成。
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();// 再次調用computeScroll。
}
}
computeScrollOffset()
這個方法用來計算當前你想知道的一個新位置,Scroller會自動根據標記時的坐標、時間、當前位置計算出一個新位置,記錄到內部,我們可以通過Scroller#getCurrX()和Scroller#getCurrY()獲取的新的位置。
要知道的是,它計算出的新位置是一個閉區間[x, y],而且會在你調用startScroll傳入的時間內漸漸從你指定的int startX和int startY移動int dx和int dy的距離,所以我們每次調用Scroller#computeScrollOffset()后再調用View的scrollTo(int, int)然后傳入Scroller#getCurrX()和Scroller#getCurrY()就可以得到一個漸漸移動的效果。
同時這個方法有一個返回值是boolean類型的,內部是用一個boolean來記錄是否完成的,在調用Scroller#startScroll)時會把這個boolean參數置為false。內部邏輯是先判斷startScroll()動畫是否還在繼續,如果沒有完成則計算最新位置,計算最新位置前會對duration做判斷,第一如果時間沒到,則真正的計算位置,并且返回true,第二如果時間到了,把記錄是否繼續的boolean成員變量標記完成,并直接賦值最新位置為最終目的位置,并且返回true;如果startScroll()已經完成則直接返回false。我們判斷Scroller#computeScrollOffset()是true時說明還沒完成,此時拿到Scroller#getCurrX()和Scroller#getCurrY()做一個滾動。Scroller#getCurrX()
Scroller#getCurrY()
這兩個方法就是拿到通過Scroller#computeScrollOffset()計算出的新的位置,上面也解釋過了。Scroller.isFinished()
上次的動畫是否完成。Scroller.abortAnimation()
取消上次的動畫。
(引用)↑
好了,了解了這些下面開始實現這個效果。
首先先搞一個布局,包括底部導航欄的頭和導航欄的內容體。
<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>
配上效果圖。
很簡單的一個效果(這里只看效果,不看UI啦),藍色的就是內容,紅色的就是頭部。
那么我想實現一個什么效果呢,就是開始的時候是看不到藍色部分的,點擊或者滑動紅色部分可以顯示藍色部分,一個上拉和下拉的效果。那么現在肯定要實現一個自定的viewGroup去實現這個布局。
?? 首先我去建一個類BottomBar.class, 為了簡單我直接用它去繼承LinearLayout。重寫它的onLayout()方法。因為我要去把藍色部分隱去,只留紅色部分。怎么做呢 ,代碼如下:
@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());
}
通過onLayout()
方法改變其位置讓其吧藍色部分隱藏。
接下來就是處理滑動事件了。我要按住紅色部分上下滑動去顯示和隱藏藍色部分,那么肯定是要有手勢識別,重寫onTouchEvent()
,再配合view的scrollTo()
方法就可以實現這個簡單的效果。
@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;
}
代碼就懶了沒有注釋,但是我會在下面解釋一下,都是一些簡單的邏輯,首先ACTION_DOWN
里面的代碼,只是記錄了按下的坐標,沒什么的。然后是ACTION_MOVE
的代碼。首先應該了解getScrollY()
,它是控件滑動的距離,初始值為0。可以看到我調用scrollTo(0, toScroll)
,而toScroll = getScrolly() - dy;
,dy
是手指滑動的一個偏移量。通過了這些計算你會發現toScroll
就是藍色部分的高度。那么效果就已經實現了,很簡單吧。看了之后你們會不會有這樣一個疑問哈,也是我當時的一個疑問,那就是為什么我們不直接用dy
也就是手指滑動的一個距離來當作toScroll
的值呢(不考慮下面對downY
的賦值,單純是手指滑動的距離)。其實是可以的,控件會隨著手指滑動的。但是,當手指離開屏幕再次點擊的時候,菜單又會回到原來的狀態再進行滑動。那么為什么會造成這樣的效果呢,仔細看過你會發現,每次點擊的時候dy
都是0,所以每次調用scrollTo(x, y)
的時候x, y
都是0,自然菜單就會回到初始位置。所以getScrollY() - dy
其實就是再記錄上一次的位置,使在下一次點擊的時候'y'的值不是0。前提是需要每次對downY
重新進行賦值。好了有了這些上拉下拉的效果就有了。但是只有這樣還是不行,我們要做到它自動的彈出和收回。接下來就是ACTION_UP
時做處理,我調用了showNavigation();和colseNavigation();
,代碼如下都是簡單的邏輯不做解釋。
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();
}
效果實現啦,哈哈。真的很簡單。
換個思路還可以用它實現側滑刪除的效果,想想看。↓↓↓
給上Demo地址 這里