Android: 手绘在ImageView之上
Android: Hand drawing on top of ImageView
我想在 ImageView
之上实现手绘功能。
这是我的布局:
<android.support.constraint.ConstraintLayout
android:id="@+id/constraintLayoutEditImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<RelativeLayout
android:id="@+id/relativeLayoutEditImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginRight="0dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
app:layout_constraintBottom_toTopOf="@+id/constraintLayoutEditImageToolbar">
<ImageView
android:id="@+id/imageViewEditImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/light_blue" />
<mobileclient.Droid.HandDrawingCanvasView
android:id="@+id/canvasViewEditImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"/>
</RelativeLayout>
<android.support.constraint.ConstraintLayout
........> //this is the toolbar, etc
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
我关注Multi-Touch Tracking in Android example HandDrawingCanvasView
Class:
public class HandDrawingCanvasView: View
{
// Two collections for storing polylines
Dictionary<int, HandDrawingPolyline> InProgressPolylines = new Dictionary<int, HandDrawingPolyline>();
List<HandDrawingPolyline> CompletedPolylines = new List<HandDrawingPolyline>();
Paint paint = new Paint(PaintFlags.AntiAlias);
public HandDrawingCanvasView(Context context) : base(context)
{
Initialize();
}
public HandDrawingCanvasView(Context context, IAttributeSet attrs) :
base(context, attrs)
{
Initialize();
}
void Initialize()
{
}
// External interface accessed from MainActivity
public Color StrokeColor { set; get; } = Color.Red;
public float StrokeWidth { set; get; } = 2;
public void ClearAll()
{
CompletedPolylines.Clear();
Invalidate();
}
// Overrides
public override bool OnTouchEvent(MotionEvent args)
{
// Get the pointer index
int pointerIndex = args.ActionIndex;
// Get the id to identify a finger over the course of its progress
int id = args.GetPointerId(pointerIndex);
// Use ActionMasked here rather than Action to reduce the number of possibilities
switch (args.ActionMasked)
{
case MotionEventActions.Down:
case MotionEventActions.PointerDown:
// Create a Polyline, set the initial point, and store it
HandDrawingPolyline polyline = new HandDrawingPolyline
{
Color = StrokeColor,
StrokeWidth = StrokeWidth
};
polyline.Path.MoveTo(args.GetX(pointerIndex),
args.GetY(pointerIndex));
InProgressPolylines.Add(id, polyline);
break;
case MotionEventActions.Move:
// Multiple Move events are bundled, so handle them differently
for (pointerIndex = 0; pointerIndex < args.PointerCount; pointerIndex++)
{
id = args.GetPointerId(pointerIndex);
InProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
args.GetY(pointerIndex));
}
break;
case MotionEventActions.Up:
case MotionEventActions.Pointer1Up:
InProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
args.GetY(pointerIndex));
// Transfer the in-progress polyline to a completed polyline
CompletedPolylines.Add(InProgressPolylines[id]);
InProgressPolylines.Remove(id);
break;
case MotionEventActions.Cancel:
InProgressPolylines.Remove(id);
break;
}
// Invalidate to update the view
Invalidate();
// Request continued touch input
return true;
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
// Clear canvas to white
paint.SetStyle(Paint.Style.Fill);
paint.Color = Color.Transparent;
canvas.DrawPaint(paint);
// Draw strokes
paint.SetStyle(Paint.Style.Stroke);
paint.StrokeCap = Paint.Cap.Round;
paint.StrokeJoin = Paint.Join.Round;
// Draw the completed polylines
foreach (HandDrawingPolyline polyline in CompletedPolylines)
{
paint.Color = polyline.Color;
paint.StrokeWidth = polyline.StrokeWidth;
canvas.DrawPath(polyline.Path, paint);
}
// Draw the in-progress polylines
foreach (HandDrawingPolyline polyline in InProgressPolylines.Values)
{
paint.Color = polyline.Color;
paint.StrokeWidth = polyline.StrokeWidth;
canvas.DrawPath(polyline.Path, paint);
}
}
}
结果如下:
如您所见,我什至可以在图像外绘制(蓝色区域是 ImageView
背景)。如何将可绘制区域限制在图片边界内?
您首先需要在显示它的 ImageView
中找到绘制图像的边界。
一旦你有了它,你就可以丢弃任何超出该区域的触摸事件。您的 onTouchEvent
的伪代码将位于以下行中的某处:
Overrides
public override bool OnTouchEvent(MotionEvent touchEvent){
if(!isInsideDesiredArea(touchEvent.getX(), touchEvent.getY()){
return false;
}
... //same with what you have now
}
要查找由 ImageView
绘制的可绘制对象的边界,您可以使用 Rect r = ImageView.getDrawable.copyBounds()
,它将在 r
中写入边界。
最后,isInsideDesiredArea(...)
看起来像这样:
private boolean isInsideDesiredArea(float x, float y) {
//get the image view
ImageView imageView = (ImageView) findViewById(R.id.my_imageview_id);
//get the transform it applied on the drawable
float[] imageViewTransformMatrixValues = new float[9];
imageView.getImageMatrix().getValues(imageViewTransformMatrixValues);
//get the bounds of the drawn drawable, before the transforms are applied to it, and in local coordinates
Rect drawableRect = imageView.getDrawable().copyBounds();
//get the drawable scale & translation, from its matrix
float scaleX = imageViewTransformMatrixValues[Matrix.MSCALE_X];
float scaleY = imageViewTransformMatrixValues[Matrix.MSCALE_Y];
float translationX = imageViewTransformMatrixValues[Matrix.MTRANS_X];
float translationY = imageViewTransformMatrixValues[Matrix.MTRANS_Y];
//compute the actual bounds of the drawable, within the image view, in image view local coordinates
Rect actualImageRect = new Rect();
actualImageRect.top = Math.round(translationY + scaleY * drawableRect.top);
actualImageRect.left = Math.round(translationX + scaleX * drawableRect.left);
actualImageRect.bottom = Math.round(translationY + scaleY * drawableRect.bottom);
actualImageRect.right = Math.round(translationX + scaleX * drawableRect.right);
//finally check if the touch events are within the rectangle defined by the drawable
return actualImageRect.contains((int) x, (int) y);
}
我在一个小示例项目上对其进行了测试,它工作正常。
希望对您有所帮助。
我想在 ImageView
之上实现手绘功能。
这是我的布局:
<android.support.constraint.ConstraintLayout
android:id="@+id/constraintLayoutEditImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<RelativeLayout
android:id="@+id/relativeLayoutEditImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginRight="0dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
app:layout_constraintBottom_toTopOf="@+id/constraintLayoutEditImageToolbar">
<ImageView
android:id="@+id/imageViewEditImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/light_blue" />
<mobileclient.Droid.HandDrawingCanvasView
android:id="@+id/canvasViewEditImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"/>
</RelativeLayout>
<android.support.constraint.ConstraintLayout
........> //this is the toolbar, etc
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
我关注Multi-Touch Tracking in Android example HandDrawingCanvasView
Class:
public class HandDrawingCanvasView: View
{
// Two collections for storing polylines
Dictionary<int, HandDrawingPolyline> InProgressPolylines = new Dictionary<int, HandDrawingPolyline>();
List<HandDrawingPolyline> CompletedPolylines = new List<HandDrawingPolyline>();
Paint paint = new Paint(PaintFlags.AntiAlias);
public HandDrawingCanvasView(Context context) : base(context)
{
Initialize();
}
public HandDrawingCanvasView(Context context, IAttributeSet attrs) :
base(context, attrs)
{
Initialize();
}
void Initialize()
{
}
// External interface accessed from MainActivity
public Color StrokeColor { set; get; } = Color.Red;
public float StrokeWidth { set; get; } = 2;
public void ClearAll()
{
CompletedPolylines.Clear();
Invalidate();
}
// Overrides
public override bool OnTouchEvent(MotionEvent args)
{
// Get the pointer index
int pointerIndex = args.ActionIndex;
// Get the id to identify a finger over the course of its progress
int id = args.GetPointerId(pointerIndex);
// Use ActionMasked here rather than Action to reduce the number of possibilities
switch (args.ActionMasked)
{
case MotionEventActions.Down:
case MotionEventActions.PointerDown:
// Create a Polyline, set the initial point, and store it
HandDrawingPolyline polyline = new HandDrawingPolyline
{
Color = StrokeColor,
StrokeWidth = StrokeWidth
};
polyline.Path.MoveTo(args.GetX(pointerIndex),
args.GetY(pointerIndex));
InProgressPolylines.Add(id, polyline);
break;
case MotionEventActions.Move:
// Multiple Move events are bundled, so handle them differently
for (pointerIndex = 0; pointerIndex < args.PointerCount; pointerIndex++)
{
id = args.GetPointerId(pointerIndex);
InProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
args.GetY(pointerIndex));
}
break;
case MotionEventActions.Up:
case MotionEventActions.Pointer1Up:
InProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
args.GetY(pointerIndex));
// Transfer the in-progress polyline to a completed polyline
CompletedPolylines.Add(InProgressPolylines[id]);
InProgressPolylines.Remove(id);
break;
case MotionEventActions.Cancel:
InProgressPolylines.Remove(id);
break;
}
// Invalidate to update the view
Invalidate();
// Request continued touch input
return true;
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
// Clear canvas to white
paint.SetStyle(Paint.Style.Fill);
paint.Color = Color.Transparent;
canvas.DrawPaint(paint);
// Draw strokes
paint.SetStyle(Paint.Style.Stroke);
paint.StrokeCap = Paint.Cap.Round;
paint.StrokeJoin = Paint.Join.Round;
// Draw the completed polylines
foreach (HandDrawingPolyline polyline in CompletedPolylines)
{
paint.Color = polyline.Color;
paint.StrokeWidth = polyline.StrokeWidth;
canvas.DrawPath(polyline.Path, paint);
}
// Draw the in-progress polylines
foreach (HandDrawingPolyline polyline in InProgressPolylines.Values)
{
paint.Color = polyline.Color;
paint.StrokeWidth = polyline.StrokeWidth;
canvas.DrawPath(polyline.Path, paint);
}
}
}
结果如下:
如您所见,我什至可以在图像外绘制(蓝色区域是 ImageView
背景)。如何将可绘制区域限制在图片边界内?
您首先需要在显示它的 ImageView
中找到绘制图像的边界。
一旦你有了它,你就可以丢弃任何超出该区域的触摸事件。您的 onTouchEvent
的伪代码将位于以下行中的某处:
Overrides
public override bool OnTouchEvent(MotionEvent touchEvent){
if(!isInsideDesiredArea(touchEvent.getX(), touchEvent.getY()){
return false;
}
... //same with what you have now
}
要查找由 ImageView
绘制的可绘制对象的边界,您可以使用 Rect r = ImageView.getDrawable.copyBounds()
,它将在 r
中写入边界。
最后,isInsideDesiredArea(...)
看起来像这样:
private boolean isInsideDesiredArea(float x, float y) {
//get the image view
ImageView imageView = (ImageView) findViewById(R.id.my_imageview_id);
//get the transform it applied on the drawable
float[] imageViewTransformMatrixValues = new float[9];
imageView.getImageMatrix().getValues(imageViewTransformMatrixValues);
//get the bounds of the drawn drawable, before the transforms are applied to it, and in local coordinates
Rect drawableRect = imageView.getDrawable().copyBounds();
//get the drawable scale & translation, from its matrix
float scaleX = imageViewTransformMatrixValues[Matrix.MSCALE_X];
float scaleY = imageViewTransformMatrixValues[Matrix.MSCALE_Y];
float translationX = imageViewTransformMatrixValues[Matrix.MTRANS_X];
float translationY = imageViewTransformMatrixValues[Matrix.MTRANS_Y];
//compute the actual bounds of the drawable, within the image view, in image view local coordinates
Rect actualImageRect = new Rect();
actualImageRect.top = Math.round(translationY + scaleY * drawableRect.top);
actualImageRect.left = Math.round(translationX + scaleX * drawableRect.left);
actualImageRect.bottom = Math.round(translationY + scaleY * drawableRect.bottom);
actualImageRect.right = Math.round(translationX + scaleX * drawableRect.right);
//finally check if the touch events are within the rectangle defined by the drawable
return actualImageRect.contains((int) x, (int) y);
}
我在一个小示例项目上对其进行了测试,它工作正常。
希望对您有所帮助。