diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index eddada39b..b425eaa11 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -1864,6 +1864,15 @@
+
+
+
+
+
+
+
diff --git a/samples/ApiDemos/res/layout/drag_layout.xml b/samples/ApiDemos/res/layout/drag_layout.xml
new file mode 100644
index 000000000..0dd193d03
--- /dev/null
+++ b/samples/ApiDemos/res/layout/drag_layout.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ApiDemos/res/values/attrs.xml b/samples/ApiDemos/res/values/attrs.xml
index 53f0034e8..4654d7e87 100644
--- a/samples/ApiDemos/res/values/attrs.xml
+++ b/samples/ApiDemos/res/values/attrs.xml
@@ -32,4 +32,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index e988a2f3b..4eb54c2a9 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -758,6 +758,12 @@
Cheese hunt
Expand
Iconify
+
+
+ Longpress on a dot to start a drag, then drop over another dot. The destination
+ dot will append the drag\'s textual conversion to the EditText.
+
+
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/DragAndDropDemo.java b/samples/ApiDemos/src/com/example/android/apis/view/DragAndDropDemo.java
new file mode 100644
index 000000000..7e8c076e4
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/view/DragAndDropDemo.java
@@ -0,0 +1,58 @@
+/*
+ * 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.app.Activity;
+import android.os.Bundle;
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.TextView;
+
+public class DragAndDropDemo extends Activity {
+ TextView mResultText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.drag_layout);
+
+ TextView text = (TextView) findViewById(R.id.drag_text);
+ DraggableDot dot = (DraggableDot) findViewById(R.id.drag_dot_1);
+ dot.setReportView(text);
+ dot = (DraggableDot) findViewById(R.id.drag_dot_2);
+ dot.setReportView(text);
+ dot = (DraggableDot) findViewById(R.id.drag_dot_3);
+ dot.setReportView(text);
+ dot = (DraggableDot) findViewById(R.id.drag_dot_4);
+ dot.setReportView(text);
+
+ mResultText = (TextView) findViewById(R.id.drag_result_text);
+ mResultText.setOnDragListener(new View.OnDragListener() {
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ final int action = event.getAction();
+ if (action == DragEvent.ACTION_DRAG_ENDED) {
+ final boolean dropped = event.getResult();
+ mResultText.setText(dropped ? "Dropped!" : "No drop");
+ }
+ return false;
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/DraggableDot.java b/samples/ApiDemos/src/com/example/android/apis/view/DraggableDot.java
new file mode 100644
index 000000000..b715ba1a9
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/view/DraggableDot.java
@@ -0,0 +1,266 @@
+/*
+ * 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;
+ boolean mLocalOnly;
+ CharSequence mLegend;
+
+ static final int ANR_NONE = 0;
+ static final int ANR_THUMBNAIL = 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);
+ }
+
+ // Thumbnail builder that can ANR if desired
+ class ANRThumbBuilder extends DragThumbnailBuilder {
+ boolean mDoAnr;
+
+ public ANRThumbBuilder(View view, boolean doAnr) {
+ super(view);
+ mDoAnr = doAnr;
+ }
+
+ @Override
+ public void onDrawThumbnail(Canvas canvas) {
+ if (mDoAnr) {
+ sleepSixSeconds();
+ }
+ super.onDrawThumbnail(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;
+
+ case R.styleable.DraggableDot_localOnly: {
+ mLocalOnly = a.getBoolean(attr, false);
+ } break;
+ }
+ }
+
+ Log.i(TAG, "DraggableDot @ " + this + " : radius=" + mRadius + " legend='" + mLegend
+ + "' anr=" + mAnrType + " local=" + mLocalOnly);
+
+ setOnLongClickListener(new View.OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ ClipData data = ClipData.newPlainText("dot", null, "Dot : " + v.toString());
+ v.startDrag(data, new ANRThumbBuilder(v, mAnrType == ANR_THUMBNAIL),
+ mLocalOnly, (Object)v);
+ 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.getItem(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);
+ }
+ }
+ }
+}
\ No newline at end of file