今天分享一下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只是提供了一个"舞台"而已, 所有的绘制和事件处理都是我们自己实现的, 对于一些复杂的需求, 我们可以这样实现.