自定义侧滑菜单
同步發(fā)表于http://avenwu.net/customlayout/2014/12/16/sliding-menu/
Fork on github https://github.com/avenwu/support
無圖無真相,完整代碼
思路
從前兩年側(cè)滑菜單出現(xiàn)到火熱,到現(xiàn)在成為一種很常見的交互布局,作為開發(fā)者的我們其實(shí)選擇非常多了,既有開源的也有官方的。
- Android support v4擴(kuò)展包的DrawerLayout
- Android support v4擴(kuò)展包的SlidingPaneLayout
- 比較知名的三方開源庫SlidingMenu
- 其他...
這些控件的底層使用的技術(shù)實(shí)際上是類似的,為了更深入的悉知這些輪子是怎么造的,本文將著手實(shí)現(xiàn)一個(gè)簡易的側(cè)滑控件。
設(shè)計(jì)思路
首先可以一起分析一下,實(shí)現(xiàn)一個(gè)最基礎(chǔ)的菜單需要解決那些技術(shù)點(diǎn)。
- 界面分為菜單區(qū)和內(nèi)容區(qū),通過滑動(dòng)顯示、隱藏菜單
- 手勢分發(fā)處理
- 根據(jù)滑動(dòng)停止后,根據(jù)位置自動(dòng)完成顯示、隱藏操作
實(shí)現(xiàn)細(xì)節(jié)
根據(jù)前面提到的幾個(gè)技術(shù)點(diǎn),現(xiàn)在開始逐一處理。
區(qū)域劃為
這里選擇FrameLayout作為基類,這樣可以免去處理菜單視圖和內(nèi)容視圖的層級(jí)關(guān)。
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);手勢分發(fā)
手勢處理包括touch event分發(fā)和消耗,在onInterceptTouchEvent中可以簡單判斷當(dāng)前是否處于菜單滑動(dòng)狀態(tài),是的話攔截后續(xù)的手勢。
@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; }手勢處理
現(xiàn)在需要處理菜單的滑動(dòng)位置,在滑動(dòng)過程中,MotionEvent.ACTION_MOVE不斷被觸發(fā),所以可以在這里改變菜單view的位置,此處利用Scroller負(fù)責(zé)位置的變化;MotionEvent.ACTION_UP中判斷手勢抬起時(shí)的菜單位置狀態(tài),如果滑動(dòng)位置已經(jīng)達(dá)到菜單寬度的1/2,那么認(rèn)為菜單需要繼續(xù)打開,反之收起。
@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; }初始化視圖位置
除了手勢問題,還需要將視圖在容器中初始化位置,這里需要復(fù)寫onLayout,由于只有兩個(gè)子view(菜單區(qū),內(nèi)容區(qū)),默認(rèn)菜單處于關(guān)閉狀態(tài)。
@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());}} }平滑滾動(dòng)
前面已經(jīng)提到改變菜單位置是利用了Scroller,主要是考慮到平滑滾動(dòng)問題。
@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 leftleft.scrollTo(x, 0);left.invalidate();}invalidate();} else {scroller.abortAnimation();} }小結(jié)
至此自定義一個(gè)簡易的側(cè)滑菜單涉及的主要技術(shù)點(diǎn)都解決了,其他細(xì)節(jié)可以看完整代碼
總結(jié)
- 上一篇: 阿里云弹性裸金属服务器-神龙架构(X-D
- 下一篇: 多客服功能终于也向所有微信认证的订阅号开