サイドスライドメニューのカスタマイズ

7636 ワード

同期はhttp://avenwu.net/customlayout/2014/12/16/sliding-menu/に発表されました
Fork on github https://github.com/avenwu/support図なし真相なし、完全なコード demo效果图
構想
2年前からサイドスライドメニューがヒットし、今ではよく見られるインタラクティブなレイアウトとなっていますが、開発者としての私たちは実は選択が非常に多く、オープンソースも公式もあります.
  • Android support v 4拡張パッケージのDrawerLayout
  • Android support v 4拡張パッケージの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を複写する必要があります.2つのサブ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();
            }
    }

    小結
    これで簡単なサイドスライドメニューをカスタマイズする主な技術点が解決し、その他の詳細は完全なコードを見ることができます.