天天看點

自定義側滑菜單

同步發表于http://avenwu.net/customlayout/2014/12/16/sliding-menu/

Fork on github https://github.com/avenwu/support

無圖無真相,完整代碼

思路

從前兩年側滑菜單出現到火熱,到現在成為一種很常見的互動布局,作為開發者的我們其實選擇非常多了,既有開源的也有官方的。

  • Android support v4擴充包的DrawerLayout
  • Android support v4擴充包的SlidingPaneLayout
  • 比較知名的三方開源庫SlidingMenu
  • 其他...

這些控件的底層使用的技術實際上是類似的,為了更深入的悉知這些輪子是怎麼造的,本文将着手實作一個簡易的側滑控件。

設計思路

首先可以一起分析一下,實作一個最基礎的菜單需要解決那些技術點。

  • 界面分為菜單區和内容區,通過滑動顯示、隐藏菜單
  • 手勢分發處理
  • 根據滑動停止後,根據位置自動完成顯示、隐藏操作

實作細節

根據前面提到的幾個技術點,現在開始逐一處理。

區域劃為

這裡選擇FrameLayout作為基類,這樣可以免去處理菜單視圖和内容視圖的層級關。

main = new FrameLayout(getContext());
main.setId(R.id.main);
main.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
main.setBackgroundColor(getResources().getColor(android.R.color.holo_blue_bright));
addView(main);

left = new FrameLayout(getContext());
left.setId(R.id.menu);
left.setLayoutParams(new FrameLayout.LayoutParams(MENU_WIDTH, ViewGroup.LayoutParams.MATCH_PARENT));
addView(left);
           

手勢分發

手勢處理包括touch event分發和消耗,在onInterceptTouchEvent中可以簡單判斷目前是否處于菜單滑動狀态,是的話攔截後續的手勢。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    	if (!mSlidable) return false;
        final int action = ev.getAction();
        if (action != MotionEvent.ACTION_DOWN && isSliding) return true;
        switch (action) {
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
    	                return false;
            	case MotionEvent.ACTION_MOVE:
                    	if (Math.abs(ev.getX() - mSrcX) > touchSlop) {
                            	mSrcX = ev.getX();
                                isSliding = true;
    	                }
            	        break;
                case MotionEvent.ACTION_DOWN:
    	                isSliding = false;
            	        mSrcX = ev.getX();
                    	break;
        }
        return isSliding;
}
           

手勢處理

現在需要處理菜單的滑動位置,在滑動過程中,MotionEvent.ACTION_MOVE不斷被觸發,是以可以在這裡改變菜單view的位置,此處利用Scroller負責位置的變化;MotionEvent.ACTION_UP中判斷手勢擡起時的菜機關置狀态,如果滑動位置已經達到菜單寬度的1/2,那麼認為菜單需要繼續打開,反之收起。

@Override
public boolean onTouchEvent(MotionEvent event) {
    	d("UIView", "event:" + event.toString());
        final int action = event.getAction();
    	switch (action) {
            	case MotionEvent.ACTION_DOWN:
                    	mSrcX = event.getX();
                        break;
    	        case MotionEvent.ACTION_MOVE:
            	        int oldx = left.getScrollX();
                    	float dx = mSrcX - event.getX();
                        if (dx != 0) {
    	                        left.setVisibility(VISIBLE);
            	                float x = oldx + dx;
                    	        d("onTouchEvent", "move, oldx=" + oldx + ", dx=" + dx + ", left=" +
                            	                left.getLeft() + ", right=" + left.getRight() + ", getX=" + event.getX() + ", mSrcX=" + mSrcX);
                                d("onTouchEvent", "before x=" + x);
    	                        if (MENU_WIDTH < x) {
            	                        x = MENU_WIDTH;
                    	        }
                            	if (0 > x) {
                                    	x = 0;
                                }
    	                        d("onTouchEvent", "after x=" + x);
            	                left.scrollTo((int) x, 0);
                    	        mSrcX = event.getX();
                        }
    	                break;
            	case MotionEvent.ACTION_UP:
                    	int currentX = left.getScrollX();
                        if (currentX + mSrcX - event.getX() >= MENU_WIDTH / 2.0) {
    	                        int duration = (int) (Math.abs(MENU_WIDTH - currentX + 0.5f) / MENU_WIDTH * 1000);
            	                scroller.startScroll(currentX, 0, MENU_WIDTH - currentX, 0, duration);
                    	        invalidate();
                        } else {
    	                        int duration = (int) (Math.abs(currentX + 0.5f) / MENU_WIDTH * 1000);
            	                scroller.startScroll(currentX, 0, 0 - currentX, 0, duration);
                    	        invalidate();
                        }
    	                break;
            	case MotionEvent.ACTION_CANCEL:
                    	break;
        }
    	return true;
}
           

初始化視圖位置

除了手勢問題,還需要将視圖在容器中初始化位置,這裡需要複寫onLayout,由于隻有兩個子view(菜單區,内容區),預設菜單處于關閉狀态。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    	int count = getChildCount();
        for (int i = 0; i < count; i++) {
    	        View child = getChildAt(i);
            	if (child.getVisibility() == GONE) continue;
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    	        if (child.getId() == R.id.menu) {
            	        final int childWidth = child.getMeasuredWidth();
                    	final int childHeight = child.getMeasuredHeight();
                        int childLeft = 0;
    	                child.layout(childLeft, lp.topMargin, childLeft + childWidth, lp.topMargin + childHeight);
            	        child.scrollTo(MENU_WIDTH, 0);
                } else if (child.getId() == R.id.main) {
    	                child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
            	}
        }
}
           

平滑滾動

前面已經提到改變菜機關置是利用了Scroller,主要是考慮到平滑滾動問題。

@Override
public void computeScroll() {
    	d("computeScroll", "computeScroll");
        if (scroller.computeScrollOffset()) {
    	        int oldx = left.getScrollX();
            	int x = scroller.getCurrX();
                d("computeScroll", "try scroll, oldx=" + oldx + ", x=" + x);
    	        if (oldx != x) {
            	        //this can only effect on the content view inside of left
                    	left.scrollTo(x, 0);
                        left.invalidate();
    	        }
            	invalidate();
        } else {
    	        scroller.abortAnimation();
        }
}
           

小結

至此自定義一個簡易的側滑菜單涉及的主要技術點都解決了,其他細節可以看完整代碼

作者:小文字

出處:http://www.cnblogs.com/avenwu/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.