| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.android.apis.view; |
| |
| import com.example.android.apis.R; |
| |
| import android.content.ClipData; |
| import android.content.Context; |
| import android.content.res.TypedArray; |
| import android.graphics.*; |
| import android.os.SystemClock; |
| import android.text.TextPaint; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.DragEvent; |
| import android.view.View; |
| import android.widget.TextView; |
| |
| public class DraggableDot extends View { |
| static final String TAG = "DraggableDot"; |
| |
| private boolean mDragInProgress; |
| private boolean mHovering; |
| private boolean mAcceptsDrag; |
| TextView mReportView; |
| |
| private Paint mPaint; |
| private TextPaint mLegendPaint; |
| private Paint mGlow; |
| private static final int NUM_GLOW_STEPS = 10; |
| private static final int GREEN_STEP = 0x0000FF00 / NUM_GLOW_STEPS; |
| private static final int WHITE_STEP = 0x00FFFFFF / NUM_GLOW_STEPS; |
| private static final int ALPHA_STEP = 0xFF000000 / NUM_GLOW_STEPS; |
| |
| int mRadius; |
| int mAnrType; |
| CharSequence mLegend; |
| |
| static final int ANR_NONE = 0; |
| static final int ANR_SHADOW = 1; |
| static final int ANR_DROP = 2; |
| |
| void sleepSixSeconds() { |
| // hang forever; good for producing ANRs |
| long start = SystemClock.uptimeMillis(); |
| do { |
| try { Thread.sleep(1000); } catch (InterruptedException e) {} |
| } while (SystemClock.uptimeMillis() < start + 6000); |
| } |
| |
| // Shadow builder that can ANR if desired |
| class ANRShadowBuilder extends DragShadowBuilder { |
| boolean mDoAnr; |
| |
| public ANRShadowBuilder(View view, boolean doAnr) { |
| super(view); |
| mDoAnr = doAnr; |
| } |
| |
| @Override |
| public void onDrawShadow(Canvas canvas) { |
| if (mDoAnr) { |
| sleepSixSeconds(); |
| } |
| super.onDrawShadow(canvas); |
| } |
| } |
| |
| public DraggableDot(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| |
| setFocusable(true); |
| setClickable(true); |
| |
| mLegend = ""; |
| |
| mPaint = new Paint(); |
| mPaint.setAntiAlias(true); |
| mPaint.setStrokeWidth(6); |
| mPaint.setColor(0xFFD00000); |
| |
| mLegendPaint = new TextPaint(); |
| mLegendPaint.setAntiAlias(true); |
| mLegendPaint.setTextAlign(Paint.Align.CENTER); |
| mLegendPaint.setColor(0xFFF0F0FF); |
| |
| mGlow = new Paint(); |
| mGlow.setAntiAlias(true); |
| mGlow.setStrokeWidth(1); |
| mGlow.setStyle(Paint.Style.STROKE); |
| |
| // look up any layout-defined attributes |
| TypedArray a = context.obtainStyledAttributes(attrs, |
| R.styleable.DraggableDot); |
| |
| final int N = a.getIndexCount(); |
| for (int i = 0; i < N; i++) { |
| int attr = a.getIndex(i); |
| switch (attr) { |
| case R.styleable.DraggableDot_radius: { |
| mRadius = a.getDimensionPixelSize(attr, 0); |
| } break; |
| |
| case R.styleable.DraggableDot_legend: { |
| mLegend = a.getText(attr); |
| } break; |
| |
| case R.styleable.DraggableDot_anr: { |
| mAnrType = a.getInt(attr, 0); |
| } break; |
| } |
| } |
| |
| Log.i(TAG, "DraggableDot @ " + this + " : radius=" + mRadius + " legend='" + mLegend |
| + "' anr=" + mAnrType); |
| |
| setOnLongClickListener(new View.OnLongClickListener() { |
| public boolean onLongClick(View v) { |
| ClipData data = ClipData.newPlainText("dot", "Dot : " + v.toString()); |
| v.startDrag(data, new ANRShadowBuilder(v, mAnrType == ANR_SHADOW), |
| (Object)v, 0); |
| return true; |
| } |
| }); |
| } |
| |
| void setReportView(TextView view) { |
| mReportView = view; |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| float wf = getWidth(); |
| float hf = getHeight(); |
| final float cx = wf/2; |
| final float cy = hf/2; |
| wf -= getPaddingLeft() + getPaddingRight(); |
| hf -= getPaddingTop() + getPaddingBottom(); |
| float rad = (wf < hf) ? wf/2 : hf/2; |
| canvas.drawCircle(cx, cy, rad, mPaint); |
| |
| if (mLegend != null && mLegend.length() > 0) { |
| canvas.drawText(mLegend, 0, mLegend.length(), |
| cx, cy + mLegendPaint.getFontSpacing()/2, |
| mLegendPaint); |
| } |
| |
| // if we're in the middle of a drag, light up as a potential target |
| if (mDragInProgress && mAcceptsDrag) { |
| for (int i = NUM_GLOW_STEPS; i > 0; i--) { |
| int color = (mHovering) ? WHITE_STEP : GREEN_STEP; |
| color = i*(color | ALPHA_STEP); |
| mGlow.setColor(color); |
| canvas.drawCircle(cx, cy, rad, mGlow); |
| rad -= 0.5f; |
| canvas.drawCircle(cx, cy, rad, mGlow); |
| rad -= 0.5f; |
| } |
| } |
| } |
| |
| @Override |
| protected void onMeasure(int widthSpec, int heightSpec) { |
| int totalDiameter = 2*mRadius + getPaddingLeft() + getPaddingRight(); |
| setMeasuredDimension(totalDiameter, totalDiameter); |
| } |
| |
| /** |
| * Drag and drop |
| */ |
| @Override |
| public boolean onDragEvent(DragEvent event) { |
| boolean result = false; |
| switch (event.getAction()) { |
| case DragEvent.ACTION_DRAG_STARTED: { |
| // claim to accept any dragged content |
| Log.i(TAG, "Drag started, event=" + event); |
| // cache whether we accept the drag to return for LOCATION events |
| mDragInProgress = true; |
| mAcceptsDrag = result = true; |
| // Redraw in the new visual state if we are a potential drop target |
| if (mAcceptsDrag) { |
| invalidate(); |
| } |
| } break; |
| |
| case DragEvent.ACTION_DRAG_ENDED: { |
| Log.i(TAG, "Drag ended."); |
| if (mAcceptsDrag) { |
| invalidate(); |
| } |
| mDragInProgress = false; |
| mHovering = false; |
| } break; |
| |
| case DragEvent.ACTION_DRAG_LOCATION: { |
| // we returned true to DRAG_STARTED, so return true here |
| Log.i(TAG, "... seeing drag locations ..."); |
| result = mAcceptsDrag; |
| } break; |
| |
| case DragEvent.ACTION_DROP: { |
| Log.i(TAG, "Got a drop! dot=" + this + " event=" + event); |
| if (mAnrType == ANR_DROP) { |
| sleepSixSeconds(); |
| } |
| processDrop(event); |
| result = true; |
| } break; |
| |
| case DragEvent.ACTION_DRAG_ENTERED: { |
| Log.i(TAG, "Entered dot @ " + this); |
| mHovering = true; |
| invalidate(); |
| } break; |
| |
| case DragEvent.ACTION_DRAG_EXITED: { |
| Log.i(TAG, "Exited dot @ " + this); |
| mHovering = false; |
| invalidate(); |
| } break; |
| |
| default: |
| Log.i(TAG, "other drag event: " + event); |
| result = mAcceptsDrag; |
| break; |
| } |
| |
| return result; |
| } |
| |
| private void processDrop(DragEvent event) { |
| final ClipData data = event.getClipData(); |
| final int N = data.getItemCount(); |
| for (int i = 0; i < N; i++) { |
| ClipData.Item item = data.getItemAt(i); |
| Log.i(TAG, "Dropped item " + i + " : " + item); |
| if (mReportView != null) { |
| String text = item.coerceToText(getContext()).toString(); |
| if (event.getLocalState() == (Object) this) { |
| text += " : Dropped on self!"; |
| } |
| mReportView.setText(text); |
| } |
| } |
| } |
| } |