在画布上绘制视图被拖动的任何位置

Posted

技术标签:

【中文标题】在画布上绘制视图被拖动的任何位置【英文标题】:Draw on canvas wherever view is dragged 【发布时间】:2018-11-21 04:43:46 【问题描述】:

我正在尝试使用画布创建一个简单的绘画应用程序。最终产品将是用户选择一种工具(笔、记号笔、橡皮擦等)并在画布上拖动该工具的任何位置进行相应的绘制或擦除。

到目前为止,我只有一支笔,我正在尝试在画布中拖动笔的任何位置画一条线。但是,我发现这项任务具有挑战性。我可以拖动笔,我可以画线,但我不能在拖动笔时画画。我正在联系 SO 社区以弥合这两个功能。

以下是我所拥有的:

MainActivity.java

public class MainActivity extends AppCompatActivity 

private ViewGroup rootLayout;
private int _xDelta;
private int _yDelta;
private RelativeLayout pl;
private ImageView w1;
private boolean clicked1 = false;

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    CanvasView canvasView = new CanvasView(MainActivity.this);

    w1 = (ImageView) findViewById(R.id.wand1);
    pl = (RelativeLayout) findViewById(R.id.coordAct);
    RelativeLayout rootLayout = (RelativeLayout) findViewById(R.id.coordAct);

    rootLayout.addView(canvasView);

    RelativeLayout.LayoutParams layoutParams1w2 = new RelativeLayout.LayoutParams(getScreenWidth(), getScreenHeight() / 2);
    layoutParams1w2.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
    canvasView.setLayoutParams(layoutParams1w2);

    w1.setOnTouchListener(new ChoiceTouchListener());


public static int getScreenWidth() 
    return Resources.getSystem().getDisplayMetrics().widthPixels;


public static int getScreenHeight() 
    return Resources.getSystem().getDisplayMetrics().heightPixels;


public class ChoiceTouchListener implements View.OnTouchListener 
    public boolean onTouch(View view, MotionEvent event) 
        if (!clicked1)
            rootLayout = (ViewGroup) w1.getParent();
            if (rootLayout != null) 
                // detach the child from parent
                rootLayout.removeView(w1);
            
            RelativeLayout.LayoutParams layoutParams1 = new RelativeLayout.LayoutParams(300, 300);
            pl.addView(w1);

            w1.setLayoutParams(layoutParams1);
            clicked1 = true;
        
        final int X = (int) event.getRawX();
        final int Y = (int) event.getRawY();

        switch (event.getAction() & MotionEvent.ACTION_MASK) 
            case MotionEvent.ACTION_DOWN:
                RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
                _xDelta = X - lParams.leftMargin;
                _yDelta = Y - lParams.topMargin;
                break;
            case MotionEvent.ACTION_MOVE:
                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view
                        .getLayoutParams();
                layoutParams.leftMargin = X - _xDelta;
                layoutParams.topMargin = Y - _yDelta;
                layoutParams.rightMargin = -250;
                layoutParams.bottomMargin = -250;
                view.setLayoutParams(layoutParams);
                break;
        
        rootLayout.invalidate();
        return true;
    

CanvasView.java

public class CanvasView extends View
Context context;
int width, height;
Bitmap bitmap;
Path path;
Canvas canvas;
Paint paint;
float mX, mY;
static final float TOLERANCE=4;
public CanvasView(Context context) 
    super(context);
    this.context=context;
    path=new Path();
    paint = new Paint();
    paint.setAntiAlias(true);
    paint.setColor(Color.BLACK);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeJoin(Paint.Join.ROUND);
    paint.setStrokeWidth(50);


@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) 
    super.onSizeChanged(w,h,oldw,oldh);
    bitmap=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
    canvas=new Canvas(bitmap);


public void startTouch(float x, float y) 
    path.moveTo(x, y);
    mX = x;
    mY = y;


public void moveTouch(float x, float y) 
    float dx = Math.abs(x-mX);
    float dy = Math.abs(y-mY);

    if(dx>=TOLERANCE || dy>= TOLERANCE) 
        path.quadTo(mX, mY, (x+mX)/2, (y+mY)/2);
        mX=x;
        mY=y;
    


//To clear canvas
public void clearCanvas() 
    path.reset();
    invalidate();


public void upTouch() 
    path.lineTo(mX,mY);

@Override
protected void onDraw(Canvas canvas)
    super.onDraw(canvas);
    canvas.drawPath(path,paint);


@Override
public boolean onTouchEvent(MotionEvent event) 
    float x = event.getX();
    float y = event.getY();

    switch (event.getAction())
    
        case MotionEvent.ACTION_DOWN:
            startTouch(x,y);
            invalidate();
            break;
        case MotionEvent.ACTION_MOVE:
            moveTouch(x,y);
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            upTouch();
            invalidate();
            break;
        default:
            return false;
    
    invalidate();
    return true;

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_
android:layout_
android:id="@+id/coordAct"
tools:context="com.simplepaintapp.MainActivity">

<RelativeLayout
    android:id="@+id/parLayout"
    android:layout_
    android:layout_
    android:orientation="horizontal"
    android:weightSum="3"
    android:background="#0000ff">



    <RelativeLayout
        android:layout_
        android:layout_
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:layout_marginBottom="28dp"
        android:background="#BA9DF7"
        android:gravity="bottom"
        android:orientation="vertical">

        <HorizontalScrollView
            android:id="@+id/backgd"
            android:layout_
            android:layout_
            android:weightSum="1">

            <LinearLayout
                android:id="@+id/parentLL"
                android:layout_
                android:layout_
                android:layout_weight="1"
                android:orientation="horizontal"
                android:weightSum="5">


                <ImageView
                    android:id="@+id/wand1"
                    android:layout_
                    android:layout_
                    android:layout_centerInParent="true"
                    android:layout_gravity="center"
                    android:src="@drawable/pen" />


            </LinearLayout>
        </HorizontalScrollView>
    </RelativeLayout>


</RelativeLayout>

Current bug after implementing the dispatchTouchEvent() method inside onTouch()

【问题讨论】:

您有 2 个不同的地方处理 TouchEvents,并且在您关心的实例中都返回 true。返回您已使用TouchEvent 的真实信号,无需进一步处理。 @BryanDormaier 所以你是说只结合 onTouchEvent() 和 onTouch() 方法? 不,你有两个不同的视图来处理 Touch。如果您希望它们能够相互合作,您需要确定何时应该返回 true 以表示您确实在使用 TouchEvent。另一种模式是 dispatchTouchEvent() 从一个视图到另一个视图,但就目前而言,您处理触摸的两个地方正在相互竞争。 @BryanDormaier 啊!我明白你在说什么。因此,因为我在 onTouch 和 onTouchEvent 上都返回 true,所以只有一个会执行……无论首先遇到哪个动作,绘制或拖动。如果我想实现 dispatchTouchEvent() 方法,我只需要调用 canvasView.dispatchTouchEvent() 吗? 如果你想从 w1 传递那个视图,它应该类似于 canvasView.dispatchTouchEvent(event) 【参考方案1】:

需要更新 MainActivity 和 CanvasView。请参阅下面的新源代码:

MainActivity.java

public class MainActivity extends AppCompatActivity 

private ViewGroup rootLayout;
private int _xDelta;
private int _yDelta;
private RelativeLayout pl;
private ImageView w1;
private boolean clicked1 = false;
CanvasView canvasView;
public static int X;
public static int Y;
@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    canvasView = new CanvasView(MainActivity.this);

    w1 = (ImageView) findViewById(R.id.wand1);
    pl = (RelativeLayout) findViewById(R.id.coordAct);
    RelativeLayout rootLayout = (RelativeLayout) findViewById(R.id.coordAct);

    rootLayout.addView(canvasView);

    RelativeLayout.LayoutParams layoutParams1w2 = new RelativeLayout.LayoutParams(getScreenWidth(), getScreenHeight() / 2);
    layoutParams1w2.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
    canvasView.setLayoutParams(layoutParams1w2);

    w1.setOnTouchListener(new ChoiceTouchListener());


public static int getScreenWidth() 
    return Resources.getSystem().getDisplayMetrics().widthPixels;


public static int getScreenHeight() 
    return Resources.getSystem().getDisplayMetrics().heightPixels;


public class ChoiceTouchListener implements View.OnTouchListener 
    public boolean onTouch(View view, MotionEvent event) 

        if (!clicked1)
            rootLayout = (ViewGroup) w1.getParent();
            if (rootLayout != null) 
                // detach the child from parent
                rootLayout.removeView(w1);
            
            RelativeLayout.LayoutParams layoutParams1 = new RelativeLayout.LayoutParams(300, 300);
            pl.addView(w1);

            w1.setLayoutParams(layoutParams1);
            clicked1 = true;
        
        X = (int) event.getRawX();
        Y = (int) event.getRawY();

        switch (event.getAction() & MotionEvent.ACTION_MASK) 
            case MotionEvent.ACTION_DOWN:
                RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) view.getLayoutParams();
                _xDelta = X - lParams.leftMargin;
                _yDelta = Y - lParams.topMargin;
                canvasView.dispatchTouchEvent(event);
                break;
            case MotionEvent.ACTION_MOVE:
                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view
                        .getLayoutParams();
                layoutParams.leftMargin = X - _xDelta;
                layoutParams.topMargin = Y - _yDelta;
                layoutParams.rightMargin = -250;
                layoutParams.bottomMargin = -250;
                view.setLayoutParams(layoutParams);
                canvasView.dispatchTouchEvent(event);
                break;
        
        rootLayout.invalidate();
        return true;
    

CanvasView.java:

public class CanvasView extends View
Context context;
int width, height;
Bitmap bitmap;
Path path;
public Canvas canvas;
Paint paint;
float mX, mY;
static final float TOLERANCE=4;
public static float x;
public static float y;
public CanvasView(Context context) 
    super(context);
    this.context=context;
    path=new Path();
    paint = new Paint();
    paint.setAntiAlias(true);
    paint.setColor(Color.BLACK);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeJoin(Paint.Join.ROUND);
    paint.setStrokeWidth(50);


@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) 
    super.onSizeChanged(w,h,oldw,oldh);
    bitmap=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
    canvas=new Canvas(bitmap);


public void startTouch(float x, float y) 
    path.moveTo(x, y);
    mX = x;
    mY = y;


public void moveTouch(float x, float y) 
    float dx = Math.abs(x-mX);
    float dy = Math.abs(y-mY);

    if(dx>=TOLERANCE || dy>= TOLERANCE) 
        path.quadTo(mX, mY, (x+mX)/2, (y+mY)/2);
        mX=x;
        mY=y;
    


//To clear canvas
public void clearCanvas() 
    path.reset();
    invalidate();


public void upTouch() 
    path.lineTo(mX,mY);

@Override
protected void onDraw(Canvas canvas)
    super.onDraw(canvas);
    canvas.drawPath(path,paint);


@Override
public boolean onTouchEvent(MotionEvent event) 
    float x = event.getX();
    float y = event.getY();
    switch (event.getAction())
    
        case MotionEvent.ACTION_DOWN:
            startTouch(MainActivity.X,MainActivity.Y-380);
            invalidate();
            break;
        case MotionEvent.ACTION_MOVE:
            moveTouch(MainActivity.X,MainActivity.Y-380);
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            upTouch();
            invalidate();
            break;
        default:
            return false;
    
    invalidate();
    return true;

【讨论】:

以上是关于在画布上绘制视图被拖动的任何位置的主要内容,如果未能解决你的问题,请参考以下文章

将图片绘制到视图的画布上啥都不做

自定义视图 Canvas onDraw() 不绘制任何东西

在画布上绘制片段视图

拖动底部子视图之一时平滑更新顶部子视图(iOS)

ViewFlipper:在视图上绘制画布

为啥我无法在画布上正确绘制 [重复]