Android Browser学习十 快捷菜单模块: PieMenu的实现

今天分享一下PieMenu的实现, 可以理解为在一个Framelayout上绘制一个PieMenu: 代码在PieMenu.java中

其添加到窗口的代码在 PieControlBase中

  protected void attachToContainer(FrameLayout container) {
        if (mPie == null) {
            mPie = new PieMenu(mActivity);
            LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
                    LayoutParams.MATCH_PARENT);
            mPie.setLayoutParams(lp);
            populateMenu();//添加pieitem
            mPie.setController(this);
        }
        container.addView(mPie);
    }


我们知道这个PieMenu是当用户按住屏幕边缘的时候出现的, 也就是是说应该在Touch的时候show了PieMenu:

看他的Action_down的时候:

if (MotionEvent.ACTION_DOWN == action) {//用户按下屏幕
            if ((x > getWidth() - mSlop) || (x < mSlop)) {//判断发现需要显示PieMenu, 于是执行show
                setCenter((int) x, (int) y); //设置pie的位置
                show(true);
                return true;//返回true表示将来的move 和up事件都用这个view来处理不用dispatch和intercept了.
            }
        }



通过show函数就可以把整个Menu显示出来了:

/**
     * guaranteed has center set
     * @param show
     * 显示piemenu
     */
    private void show(boolean show) {
        mOpen = show;
        if (mOpen) {
            if (mController != null) {
                boolean changed = mController.onOpen();
            }
            layoutPie();
        }
        if (!show) {
            mCurrentItem = null;
            mPieView = null;
        }
        invalidate();
    }


layoutPie会遍历每个item把他绘制到指定的位置, 可以看到核心是使用 path把每个扇形绘制出来的.

private void layoutPie() {//绘制每个pieitem的位置
        float emptyangle = (float) Math.PI / 16;//空白间隙
        int rgap = 2;
        int inner = mRadius + rgap;
        int outer = mRadius + mRadiusInc - rgap;
        int radius = mRadius;
        int gap = 1;
        for (int i = 0; i < mLevels; i++) {
            int level = i + 1;
            float sweep = (float) (Math.PI - 2 * emptyangle) / mCounts[level];//每个pieitem占用的度数 ,总的度数是pi/2 
            float angle = emptyangle + sweep / 2;//为了使 piememu上下对称
            for (PieItem item : mItems) {
                if (item.getLevel() == level) {
                    View view = item.getView();
                    view.measure(view.getLayoutParams().width,
                            view.getLayoutParams().height);
                    int w = view.getMeasuredWidth();//计算其宽度和高度
                    int h = view.getMeasuredHeight();
                    int r = inner + (outer - inner) * 2 / 3;//扇形的半径
                    int x = (int) (r * Math.sin(angle));//使用三角函数 计算其x y的位置 
                    int y = mCenter.y - (int) (r * Math.cos(angle)) - h / 2;
                    if (onTheLeft()) {//如果是在左边
                        x = mCenter.x + x - w / 2;
                    } else {
                        x = mCenter.x - x - w / 2;
                    }
                    view.layout(x, y, x + w, y + h); //设置区位置
                    float itemstart = angle - sweep / 2;
                    Path slice = makeSlice(getDegrees(itemstart) - gap,
                            getDegrees(itemstart + sweep) + gap,
                            outer, inner, mCenter); //绘制扇形
                    item.setGeometry(itemstart, sweep, inner, outer, slice);
                    angle += sweep; //下一个扇形在这个扇形的下一个位置
                }
            }
            inner += mRadiusInc;
            outer += mRadiusInc;
        }
    }


然后就是onDraw进行绘制每个扇形了:

  @Override
    protected void onDraw(Canvas canvas) {
        if (mOpen) {
            int state;
            if (mUseBackground) {
                int w = mBackground.getIntrinsicWidth();
                int h = mBackground.getIntrinsicHeight();
                int left = mCenter.x - w;
                int top = mCenter.y - h / 2;
                mBackground.setBounds(left, top, left + w, top + h);
                state = canvas.save();
                if (onTheLeft()) {
                    canvas.scale(-1, 1);//翻转
                }
                mBackground.draw(canvas);
                canvas.restoreToCount(state);
            }
            for (PieItem item : mItems) {//遍历每一个item将其绘制
                Paint p = item.isSelected() ? mSelectedPaint : mNormalPaint; //选择选择的画笔还是normal画笔?
                state = canvas.save();
                if (onTheLeft()) {//如果点击的在左边
                    canvas.scale(-1, 1);
                }
                drawPath(canvas, item.getPath(), p);
                canvas.restoreToCount(state);//绘制完了回到以前的状态绘制item
                drawItem(canvas, item);
            }
            if (mPieView != null) {//绘制piemenu的sbumenu 比如tab的缩略图
                mPieView.draw(canvas);
            }
        }
    }


然后是Move事件了, 在Movel事件的时候会判断用户手指落在哪里了,然后设置那个扇形为选中, 绘制不再赘述

else if (MotionEvent.ACTION_MOVE == action) {
            boolean handled = false;
            PointF polar = getPolar(x, y);//根据xy 求出点击的那个扇面.
            int maxr = mRadius + mLevels * mRadiusInc + 50;
            if (mPieView != null) {
                handled = mPieView.onTouchEvent(evt);
            }
            if (handled) {
                invalidate();
                return false;
            }
            if (polar.y > maxr) {//可能没有选中, 就恢复以前的状态
                deselect();
                show(false);
                evt.setAction(MotionEvent.ACTION_DOWN);
                if (getParent() != null) {
                    ((ViewGroup) getParent()).dispatchTouchEvent(evt);
                }
                return false;
            }
            PieItem item = findItem(polar);//根据给定的点 求出点击的那个扇面.
            if (mCurrentItem != item) {
                onEnter(item);
                if ((item != null) && item.isPieView()) {//类型tab, 含有二级的pieview, 就展现那个listview或者其他
                    int cx = item.getView().getLeft() + (onTheLeft()
                            ? item.getView().getWidth() : 0);
                    int cy = item.getView().getTop();
                    mPieView = item.getPieView();
                    layoutPieView(mPieView, cx, cy,
                            (item.getStartAngle() + item.getSweep()) / 2);
                }
                invalidate();
            }
        }



在用户放开手的时候也就是Up的时候会通知controller 选中了哪个扇形: , 绘制不再赘述

else if (MotionEvent.ACTION_UP == action) {
            if (mOpen) {//在手放开的时候如果piemenu在显示那么就开始执行相应的点击操作, 并恢复touch前的状态
                boolean handled = false;
                if (mPieView != null) {
                    handled = mPieView.onTouchEvent(evt);
                }
                PieItem item = mCurrentItem;
                deselect();//取消选中
                show(false);//关闭menu
                if (!handled && (item != null)) {
                    item.getView().performClick();//相应相关item的点击事件
                }
                return true;
            }
        }


完全自绘的viewgroup, 对我们学习还是帮助很大的.Framelayout只是提供了一个"舞台"而已, 所有的绘制和事件处理都是我们自己实现的, 对于一些复杂的需求, 我们可以这样实现.

你可能感兴趣的:(Android Browser学习十 快捷菜单模块: PieMenu的实现)