PieMenu.java revision a4befac241ec69791fd30e11355ed3dc10d7fb37
1376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb/* 2376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * Copyright (C) 2010 The Android Open Source Project 3376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * 4376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * Licensed under the Apache License, Version 2.0 (the "License"); 5376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * you may not use this file except in compliance with the License. 6376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * You may obtain a copy of the License at 7376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * 8376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * http://www.apache.org/licenses/LICENSE-2.0 9376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * 10376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * Unless required by applicable law or agreed to in writing, software 11376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * distributed under the License is distributed on an "AS IS" BASIS, 12376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * See the License for the specific language governing permissions and 14376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * limitations under the License. 15376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb */ 16376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 17376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbpackage com.android.browser.view; 18376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 19376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport com.android.browser.R; 20376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 21376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.content.Context; 22376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.content.res.Resources; 23376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Canvas; 24565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolbimport android.graphics.Paint; 25565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolbimport android.graphics.Path; 26376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.Point; 27376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.graphics.PointF; 28565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolbimport android.graphics.RectF; 292a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.graphics.drawable.Drawable; 30376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.util.AttributeSet; 31376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.view.MotionEvent; 322a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolbimport android.view.SoundEffectConstants; 33376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.view.View; 344be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reckimport android.view.ViewGroup; 35376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport android.widget.FrameLayout; 36376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 37376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.ArrayList; 38376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbimport java.util.List; 39376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 40376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolbpublic class PieMenu extends FrameLayout { 41376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 420860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private static final int MAX_LEVELS = 5; 43376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 44376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb public interface PieController { 45376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb /** 46376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * called before menu opens to customize menu 47376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * returns if pie state has been changed 48376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb */ 49376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb public boolean onOpen(); 50376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 510860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb 521acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb /** 531acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb * A view like object that lives off of the pie menu 541acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb */ 551acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb public interface PieView { 561acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb 57eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb public interface OnLayoutListener { 58eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb public void onLayout(int ax, int ay, boolean left); 59eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb } 60eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb 61eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb public void setLayoutListener(OnLayoutListener l); 62eb95db48b01b3db935601f25bd1a2358674b76daMichael Kolb 63a4befac241ec69791fd30e11355ed3dc10d7fb37Michael Kolb public void layout(int anchorX, int anchorY, boolean onleft, float angle, 64a4befac241ec69791fd30e11355ed3dc10d7fb37Michael Kolb int parentHeight); 651acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb 661acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb public void draw(Canvas c); 671acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb 681acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb public boolean onTouchEvent(MotionEvent evt); 691acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb 701acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb } 711acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb 72376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb private Point mCenter; 73376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb private int mRadius; 74376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb private int mRadiusInc; 75376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb private int mSlop; 76565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb private int mTouchOffset; 77376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 78376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb private boolean mOpen; 79376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb private PieController mController; 80376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 810860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private List<PieItem> mItems; 820860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private int mLevels; 830860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private int[] mCounts; 841acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb private PieView mPieView = null; 852a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb 860860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private Drawable mBackground; 87565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb private Paint mNormalPaint; 88565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb private Paint mSelectedPaint; 892a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb 900860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb // touch handling 910860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb PieItem mCurrentItem; 922a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb 93716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb private boolean mUseBackground; 94716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb 95376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb /** 96376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * @param context 97376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * @param attrs 98376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * @param defStyle 99376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb */ 100376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb public PieMenu(Context context, AttributeSet attrs, int defStyle) { 101376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb super(context, attrs, defStyle); 102376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb init(context); 103376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 104376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 105376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb /** 106376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * @param context 107376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * @param attrs 108376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb */ 109376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb public PieMenu(Context context, AttributeSet attrs) { 110376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb super(context, attrs); 111376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb init(context); 112376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 113376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 114376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb /** 115376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * @param context 116376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb */ 117376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb public PieMenu(Context context) { 118376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb super(context); 119376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb init(context); 120376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 121376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 122376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb private void init(Context ctx) { 1230860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mItems = new ArrayList<PieItem>(); 1240860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mLevels = 0; 1250860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mCounts = new int[MAX_LEVELS]; 126376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb Resources res = ctx.getResources(); 1270860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mRadius = (int) res.getDimension(R.dimen.qc_radius_start); 1280860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mRadiusInc = (int) res.getDimension(R.dimen.qc_radius_increment); 129376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb mSlop = (int) res.getDimension(R.dimen.qc_slop); 130565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb mTouchOffset = (int) res.getDimension(R.dimen.qc_touch_offset); 131376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb mOpen = false; 132376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb setWillNotDraw(false); 133376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb setDrawingCacheEnabled(false); 134376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb mCenter = new Point(0,0); 1350860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mBackground = res.getDrawable(R.drawable.qc_background_normal); 136565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb mNormalPaint = new Paint(); 137565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb mNormalPaint.setColor(res.getColor(R.color.qc_normal)); 138565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb mNormalPaint.setAntiAlias(true); 139565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb mSelectedPaint = new Paint(); 140565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb mSelectedPaint.setColor(res.getColor(R.color.qc_selected)); 141565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb mSelectedPaint.setAntiAlias(true); 142376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 143376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 144376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb public void setController(PieController ctl) { 145376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb mController = ctl; 146376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 147376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 148716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb public void setUseBackground(boolean useBackground) { 149716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb mUseBackground = useBackground; 150716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb } 151716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb 1520860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb public void addItem(PieItem item) { 153376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb // add the item to the pie itself 1540860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mItems.add(item); 1550860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb int l = item.getLevel(); 1560860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mLevels = Math.max(mLevels, l); 1570860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mCounts[l]++; 158376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 159376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 1600860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb public void removeItem(PieItem item) { 1610860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mItems.remove(item); 162376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 163376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 164376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb public void clearItems() { 1650860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mItems.clear(); 166376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 167376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 1680860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private boolean onTheLeft() { 1690860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb return mCenter.x < mSlop; 1700860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb } 171376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 1720860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb /** 1730860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb * guaranteed has center set 1740860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb * @param show 1750860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb */ 1760860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private void show(boolean show) { 177376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb mOpen = show; 178376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if (mOpen) { 179376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if (mController != null) { 180376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb boolean changed = mController.onOpen(); 181376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 1820860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb layoutPie(); 183376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 184376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if (!show) { 1850860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mCurrentItem = null; 1861acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb mPieView = null; 187376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 188376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb invalidate(); 189376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 190376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 191376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb private void setCenter(int x, int y) { 192376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if (x < mSlop) { 193376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb mCenter.x = 0; 194376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } else { 195376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb mCenter.x = getWidth(); 196376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 197376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb mCenter.y = y; 198376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 199376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 2000860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private void layoutPie() { 201565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb float emptyangle = (float) Math.PI / 16; 202565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb int rgap = 2; 203565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb int inner = mRadius + rgap; 204565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb int outer = mRadius + mRadiusInc - rgap; 2051acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb int radius = mRadius; 206565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb int gap = 1; 2070860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb for (int i = 0; i < mLevels; i++) { 2080860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb int level = i + 1; 209565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb float sweep = (float) (Math.PI - 2 * emptyangle) / mCounts[level]; 210565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb float angle = emptyangle + sweep / 2; 2110860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb for (PieItem item : mItems) { 2120860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb if (item.getLevel() == level) { 2130860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb View view = item.getView(); 2140860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb view.measure(view.getLayoutParams().width, 2150860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb view.getLayoutParams().height); 2160860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb int w = view.getMeasuredWidth(); 2170860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb int h = view.getMeasuredHeight(); 218565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb int r = inner + (outer - inner) * 2 / 3; 219565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb int x = (int) (r * Math.sin(angle)); 220565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb int y = mCenter.y - (int) (r * Math.cos(angle)) - h / 2; 2210860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb if (onTheLeft()) { 222565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb x = mCenter.x + x - w / 2; 2230860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb } else { 224565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb x = mCenter.x - x - w / 2; 2250860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb } 2260860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb view.layout(x, y, x + w, y + h); 2270860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb float itemstart = angle - sweep / 2; 228565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb Path slice = makeSlice(getDegrees(itemstart) - gap, 229565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb getDegrees(itemstart + sweep) + gap, 230565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb outer, inner, mCenter); 231565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb item.setGeometry(itemstart, sweep, inner, outer, slice); 2320860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb angle += sweep; 233376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 234376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 2350860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb inner += mRadiusInc; 2360860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb outer += mRadiusInc; 237376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 238376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 239376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 240565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb 241565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb /** 242565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb * converts a 243565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb * 244565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb * @param angle from 0..PI to Android degrees (clockwise starting at 3 245565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb * o'clock) 246565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb * @return skia angle 247565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb */ 248565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb private float getDegrees(double angle) { 249565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb return (float) (270 - 180 * angle / Math.PI); 250565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb } 251565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb 2520860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb @Override 2530860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb protected void onDraw(Canvas canvas) { 2540860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb if (mOpen) { 255565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb int state; 256716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb if (mUseBackground) { 257716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb int w = mBackground.getIntrinsicWidth(); 258716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb int h = mBackground.getIntrinsicHeight(); 259716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb int left = mCenter.x - w; 260716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb int top = mCenter.y - h / 2; 261716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb mBackground.setBounds(left, top, left + w, top + h); 262716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb state = canvas.save(); 263716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb if (onTheLeft()) { 264716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb canvas.scale(-1, 1); 265716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb } 266716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb mBackground.draw(canvas); 267716773853705e67fe5d5afbf9c3b917ff8a5e298Michael Kolb canvas.restoreToCount(state); 268376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 2690860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb for (PieItem item : mItems) { 270565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb Paint p = item.isSelected() ? mSelectedPaint : mNormalPaint; 271565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb state = canvas.save(); 272565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb if (onTheLeft()) { 273565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb canvas.scale(-1, 1); 274565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb } 275565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb drawPath(canvas, item.getPath(), p); 276565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb canvas.restoreToCount(state); 2770860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb drawItem(canvas, item); 278376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 2791acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb if (mPieView != null) { 2801acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb mPieView.draw(canvas); 2811acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb } 282376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 2832a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb } 2842a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb 2850860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private void drawItem(Canvas canvas, PieItem item) { 2860860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb int outer = item.getOuterRadius(); 2870860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb int left = mCenter.x - outer; 2880860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb int top = mCenter.y - outer; 2890860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb // draw the item view 2900860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb View view = item.getView(); 2910860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb int state = canvas.save(); 2920860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb canvas.translate(view.getX(), view.getY()); 2930860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb view.draw(canvas); 2940860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb canvas.restoreToCount(state); 295376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 296376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 297565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb private void drawPath(Canvas canvas, Path path, Paint paint) { 298565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb canvas.drawPath(path, paint); 299565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb } 300565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb 301565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb private Path makeSlice(float start, float end, int outer, int inner, Point center) { 302565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb RectF bb = 303565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb new RectF(center.x - outer, center.y - outer, center.x + outer, 304565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb center.y + outer); 305565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb RectF bbi = 306565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb new RectF(center.x - inner, center.y - inner, center.x + inner, 307565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb center.y + inner); 308565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb Path path = new Path(); 309565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb path.arcTo(bb, start, end - start, true); 310565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb path.arcTo(bbi, end, start - end); 311565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb path.close(); 312565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb return path; 313565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb } 314565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb 315376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb // touch handling for pie 316376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 317376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb @Override 318376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb public boolean onTouchEvent(MotionEvent evt) { 319376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb float x = evt.getX(); 320376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb float y = evt.getY(); 321376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb int action = evt.getActionMasked(); 322376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if (MotionEvent.ACTION_DOWN == action) { 323376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if ((x > getWidth() - mSlop) || (x < mSlop)) { 324376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb setCenter((int) x, (int) y); 325376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb show(true); 326376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb return true; 327376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 328376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } else if (MotionEvent.ACTION_UP == action) { 329376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if (mOpen) { 3301acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb boolean handled = false; 3311acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb if (mPieView != null) { 3321acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb handled = mPieView.onTouchEvent(evt); 3331acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb } 3340860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb PieItem item = mCurrentItem; 335376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb deselect(); 336376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb show(false); 3371acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb if (!handled && (item != null)) { 3380860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb item.getView().performClick(); 3390860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb } 340376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb return true; 341376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 342376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } else if (MotionEvent.ACTION_CANCEL == action) { 343376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if (mOpen) { 344376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb show(false); 345376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 346376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb deselect(); 347376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb return false; 348376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } else if (MotionEvent.ACTION_MOVE == action) { 3490860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb boolean handled = false; 350bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb PointF polar = getPolar(x, y); 3510860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb int maxr = mRadius + mLevels * mRadiusInc + 50; 3521acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb if (mPieView != null) { 3531acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb handled = mPieView.onTouchEvent(evt); 3541acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb } 3551acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb if (handled) { 3561acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb invalidate(); 3571acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb return false; 3581acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb } 3590860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb if (polar.y > maxr) { 360bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb deselect(); 3610860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb show(false); 3624be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck evt.setAction(MotionEvent.ACTION_DOWN); 3634be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck if (getParent() != null) { 3644be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck ((ViewGroup) getParent()).dispatchTouchEvent(evt); 3654be9bc7f7f38723ae8c4ca1d3203de212cf214bdJohn Reck } 366bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb return false; 367bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb } 3680860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb PieItem item = findItem(polar); 3690860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb if (mCurrentItem != item) { 3700860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb onEnter(item); 3711acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb if ((item != null) && item.isPieView()) { 3721acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb int cx = item.getView().getLeft() + (onTheLeft() 3731acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb ? item.getView().getWidth() : 0); 3741acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb int cy = item.getView().getTop(); 3751acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb mPieView = item.getPieView(); 376565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb layoutPieView(mPieView, cx, cy, 377565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb (item.getStartAngle() + item.getSweep()) / 2); 3781acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb } 379376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb invalidate(); 380376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 381376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 382376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb // always re-dispatch event 383376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb return false; 384376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 385376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 386565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb private void layoutPieView(PieView pv, int x, int y, float angle) { 387a4befac241ec69791fd30e11355ed3dc10d7fb37Michael Kolb pv.layout(x, y, onTheLeft(), angle, getHeight()); 3881acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb } 3891acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb 390376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb /** 391376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * enter a slice for a view 392376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb * updates model only 3930860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb * @param item 394376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb */ 3950860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private void onEnter(PieItem item) { 396376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb // deselect 3970860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb if (mCurrentItem != null) { 3980860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mCurrentItem.setSelected(false); 399376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 4000860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb if (item != null) { 401376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb // clear up stack 4022a56ecaf153d788a1acebc54b670347a1a58d693Michael Kolb playSoundEffect(SoundEffectConstants.CLICK); 4030860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb item.setSelected(true); 4041acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb mPieView = null; 405376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 4060860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mCurrentItem = item; 407376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 408376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 409376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb private void deselect() { 4100860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb if (mCurrentItem != null) { 4110860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mCurrentItem.setSelected(false); 412376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 4130860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb mCurrentItem = null; 4141acef69ffc079d1bc029ff7eb1f5043f7efd7f36Michael Kolb mPieView = null; 415376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 416376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 417bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb private PointF getPolar(float x, float y) { 418bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb PointF res = new PointF(); 419376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb // get angle and radius from x/y 420bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb res.x = (float) Math.PI / 2; 421376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb x = mCenter.x - x; 422376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if (mCenter.x < mSlop) { 423376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb x = -x; 424376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 425376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb y = mCenter.y - y; 426bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb res.y = (float) Math.sqrt(x * x + y * y); 427376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb if (y > 0) { 428bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb res.x = (float) Math.asin(x / res.y); 429376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } else if (y < 0) { 430bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb res.x = (float) (Math.PI - Math.asin(x / res.y )); 431376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 432bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb return res; 433bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb } 434bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb 435bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb /** 436bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb * 437bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb * @param polar x: angle, y: dist 4380860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb * @return the item at angle/dist or null 439bf9c4ee33fe87881793f84091274dc59c16f3881Michael Kolb */ 4400860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb private PieItem findItem(PointF polar) { 441376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb // find the matching item: 4420860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb for (PieItem item : mItems) { 443565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb if ((item.getInnerRadius() - mTouchOffset < polar.y) 444565752e1025129ad16c030ceca9dee7a695ed73eMichael Kolb && (item.getOuterRadius() - mTouchOffset > polar.y) 4450860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb && (item.getStartAngle() < polar.x) 4460860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb && (item.getStartAngle() + item.getSweep() > polar.x)) { 4470860d99a463f7645bcc9aaa246fd8852e90dbb5dMichael Kolb return item; 448376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 449376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 450376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb return null; 451376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb } 452376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb 453376b54116e38b3b94c4d64663d1bff38352b0e59Michael Kolb} 454