I'm trying to draw over an image, which i'll save later with the drawings. My xml only have the ImageView and I tried to overwrite onDraw. It only made de Canvas works, but with no image.
public class Body1 extends View {
public LayoutParams params;
private Path path = new Path();
private Paint brush = new Paint();
public Body1(Context context) {
super(context);
brush.setAntiAlias(true);
brush.setColor(Color.MAGENTA);
brush.setStyle(Paint.Style.STROKE);
brush.setStrokeJoin(Paint.Join.ROUND);
brush.setStrokeWidth(8f);
params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float pointX = event.getX();
float pointY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(pointX, pointY);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(pointX, pointY);
break;
default:
return false;
}
postInvalidate();
return false;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, brush);
}
}
OK you are on the right path.
What you are missing is invalidate calls that will result in the OS calling the OnDraw of the view.
Call invalidate every time you want to update the view. But notice that your class has only a single path object and so will only draw the last segment.
If I'm not mistaken you should hold an ArrayList of Path objects.
In onDraw you should loop over them and draw them all one after the other.
Related
I've tried many different solutions and none worked.
So, how do I drag and drop a Bitmap?
Here is a possible solution that also doesn't work, but I would love to know why:
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.our_image);
And then activate the OnTouchListener:
#Override
public boolean onTouch (View v, MotionEvent event)
{
Imageview iv = new ImageView(this);
iv.setImageBitmap(bm);
if (v == iv)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
eventX = event.getXPrecision();
eventY = event.getYPrecision();
break;
case MotionEvent.ACTION_MOVE:
distX = event.getX() - eventX;
distY = event.getY() - eventY;
iv.setX(distX + eventX);
iv.setY(distY + eventY);
eventX = event.getXPrecision();
eventY = event.getYPrecision();
}
return true;
}
return false;
}
and when I move the ImageView, I update the Bitmap's location to it's coordinates
by creating a canvas and redrawing the Bitmap:
Paint p = new Paint()
Canvas canvas = new Canvas(bm);
canvas.drawBitmap(bm, iv.getLeft(), iv.getTop(), p);
Do I need to put invalidate() here? And if so, why?
Any kind of help would be greatly appreciated! ^_^
I implemented a canvas for drawing on Android, but the drawing view fills the screen. I want to adjust the size of this screen. What should I do?
Once I set MyView to show with setContentView in oncreate method.
MainActivity.java
MyView view = new MyView(this);
setContentView(view);
And here is the part of painting on the canvas. However, as mentioned earlier, I want to adjust the range in which I can draw. I don't want the whole screen to be a drawing view.
MyView.java
public class MyView extends View {
private Paint paint = new Paint();
private Path path = new Path();
private int x,y;
public MyView(Context context){
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
paint.setColor(Color.BLACK);
//STROKE속성을 이용하여 테두리...선...
paint.setStyle(Paint.Style.STROKE);
//두께
paint.setStrokeWidth(3);
//path객체가 가지고 있는 경로를 화면에 그린다...
canvas.drawPath(path,paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
x = (int)event.getX();
y = (int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
path.moveTo(x,y);
break;
case MotionEvent.ACTION_MOVE:
x = (int)event.getX();
y = (int)event.getY();
Log.e("s",x+""+y);
path.lineTo(x,y);
break;
}
//View의 onDraw()를 호출하는 메소드...
invalidate();
return true;
}
}
You can set width and height like this:
MyView view = new MyView(this);
setContentView(view);
view.setLayoutParams(new FrameLayout.LayoutParams(500, 500));
Where the FrameLayout.LayoutParams can vary for the parent ViewGroup. If the parent is LinearLayout, it should be LinearLayout.LayoutParams, and so on.
I have an android app with two seekbars, and I've set them up so that the app doesn't respond unless both of them are pressed. My problem is that when they are both pressed down, the method onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) starts continuously running in an infinite loop. This happens even though I am holding my fingers completely still on the bars (which means that the progress is not changing on either of the bars, and therefore the onProgressChanged method shouldn't be being called). Does anyone have any insights as to why this could be happening?
Here is some relevant code:
void setupLRSpeedBars(final int UIID, final Bool bar1, final Bool bar2) {
SeekBar sb = (SeekBar) findViewById(UIID);
sb.setProgress(9);
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (bar1.isTrue()) {
progress -= 9;
transmit((UIID == R.id.leftSpeedBar ? "L" : "R") +
(progress >= 0 ? "+" : "") +
Integer.toString(progress));
}
}
public void onStartTrackingTouch(SeekBar seekBar) {
bar2.set(true);
}
public void onStopTrackingTouch(SeekBar seekBar) {
bar2.set(false);
seekBar.setProgress(9);
//transmit((UIID == R.id.leftSpeedBar ? "L" : "R") + "+0");
transmit("s");
}
});
}
Also note that I've used a custom Bool class instead of a primitive boolean, in order for me to get around the restriction that all variables outside of the OnSeekBarChangeListener be marked final. (I need to send information between the onSeekBarChangeListeners in order to work out whether the user is holding their fingers down on both seekbars at the same time)
I have a hunch that this might be the cause of my problems, but I don't see how.
class Bool {
private boolean bool;
public Bool () {}
public Bool(boolean bool) { this.bool = bool; }
public void set (boolean bool) { this.bool = bool; }
public boolean get () { return bool; }
public boolean isTrue () { return bool; }
}
EDIT:
I'll also note that I'm using a custom, vertical seekbar. Here is the code:
package android.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class VerticalSeekBar extends SeekBar {
private OnSeekBarChangeListener myListener;
public VerticalSeekBar(Context context) {
super(context);
}
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public VerticalSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
#Override
public void setOnSeekBarChangeListener(OnSeekBarChangeListener mListener){
this.myListener = mListener;
}
#Override
public synchronized void setProgress(int progress){
super.setProgress(progress);
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
protected void onDraw(Canvas c) {
c.rotate(-90);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(myListener!=null)
myListener.onStartTrackingTouch(this);
break;
case MotionEvent.ACTION_MOVE:
setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
onSizeChanged(getWidth(), getHeight(), 0, 0);
myListener.onProgressChanged(this, getMax() - (int) (getMax() * event.getY() / getHeight()), true);
break;
case MotionEvent.ACTION_UP:
myListener.onStopTrackingTouch(this);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
}
I think that the problem is in the onTouchEvent implementation of your VerticalSeekBar because you are processing every MotionEvent.ACTION_MOVE received.
From the documentation:
A new onTouchEvent() is triggered with an ACTION_MOVE event whenever the current touch contact position, pressure, or size changes. As described in Detecting Common Gestures, all of these events are recorded in the MotionEvent parameter of onTouchEvent().
Because finger-based touch isn't always the most precise form of interaction, detecting touch events is often based more on movement than on simple contact. To help apps distinguish between movement-based gestures (such as a swipe) and non-movement gestures (such as a single tap), Android includes the notion of "touch slop." Touch slop refers to the distance in pixels a user's touch can wander before the gesture is interpreted as a movement-based gesture. For more discussion of this topic, see Managing Touch Events in a ViewGroup.
That is, you think that your fingers are completely still but your seek bars are receiving ACTION_MOVE events.
In your case, the "touch slop" approximation is now a good idea because the calculated touch slop is huge for your purposes, as touch slop is defined as:
"Touch slop" refers to the distance in pixels a user's touch can wander before the gesture is interpreted as scrolling. Touch slop is typically used to prevent accidental scrolling when the user is performing some other touch operation, such as touching on-screen elements.
To solve your problem you can calculate the distance between the last managed position and the current one to trigger your onProgressChanged:
private static final float MOVE_PRECISION = 5; // You may want to tune this parameter
private float lastY;
// ...
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = event.getY();
if (myListener != null)
myListener.onStartTrackingTouch(this);
break;
case MotionEvent.ACTION_MOVE:
if (calculateDistanceY(event) > MOVE_PRECISION) {
setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
onSizeChanged(getWidth(), getHeight(), 0, 0);
myListener.onProgressChanged(this, getMax() - (int) (getMax() * event.getY() / getHeight()), true);
lastY = event.getY();
}
break;
case MotionEvent.ACTION_UP:
myListener.onStopTrackingTouch(this);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
private float calculateDistanceY (MotionEvent event) {
return Math.abs(event.getY() - lastY);
}
I have this code in the ACTION_DOWN of my onTouch(View v) method as follows...
if (event.getAction() == MotionEvent.ACTION_DOWN) {
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, view, 0);
myOnClickListener.onClick(view);
return true;
}
Now my view in this case is a ChessPiece. I have chessPiece.setBackground(R.color.black) and chessPiece.setImageBitmap(pawnBitmap).
Currently when I drag the chess piece, it drags the background color with it, which I dont want. So is there anyway to remove the background of the view when it's dragging so that the DragShadow is just of the bitmap?
You have to create your own dragshadow. This example helped me when I wanted to play with the dragshadow: http://lemonycode.blogspot.be/2012/11/using-custom-dragshadowbuidler-in.html
As you can see, there are two important methods:
public void onProvideShadowMetrics(Point shadowSize, Point touchPoint)
and:
public void onDrawShadow(Canvas canvas)
The first method is used to size the dragshadow and the second to actually draw your shadow. In your case, your chess piece. You could use
Bitmap bitmap = ((BitmapDrawable)image.getDrawable()).getBitmap();
to get the bitmap and alter it before drawing it on the canvas.
On OnDragListener()
private final class MyDragListener implements OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
getResources().getDrawable(android.graphics.Color.TRANSPARENT);
break;
....
}
return true;
}
}
This should work. Good luck!
First of all, thanks in advance for any help you can provide. This has been driving me nuts and google's been no friend of mine for this.
I've created a DrawView so the user can paint something in the app. What I'd like to do is place that DrawView within a layout XML file, so I can place a TextView title on the top and two buttons on the bottom, one of which will save what the user painted as an image file. So far, all I've been able to do is create an area for the user to paint in. Is there any way to place this DrawView view into an XML file? If not, what alternative solutions do I have?
Also, this is an optional question as I'm sure I'll find the solution by googling, at some point, but is there a better way to smoothly draw along the user's finger movement, other than the canvas.drawLine() method I am currently using?
public class Draw extends Activity {
DrawView drawView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set full screen view
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
requestWindowFeature(Window.FEATURE_NO_TITLE);
drawView = new DrawView(this);
setContentView(drawView);
drawView.requestFocus();
}
}
public class DrawView extends View implements OnTouchListener {
private static final String TAG = "DrawView";
ArrayList <Float> nikpointx = new ArrayList<Float>();
ArrayList <Float> nikpointy = new ArrayList<Float>();
Bitmap nikbmp;
Paint paint = new Paint();
public DrawView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
paint.setStrokeWidth(4);
}
#Override
public void onDraw(Canvas canvas) {
//I'm doing this, because the drawCircle method places circles at different points
//in the screen, rather than drawing smooth lines. I haven't found a more elegant
//solution yet :-(
canvas.drawLine(nikpointx.get(i-1), nikpointy.get(i-1), nikpointx.get(i), nikpointy.get(i), paint);
canvas.setBitmap(nikbmp);
}
}
public boolean onTouch(View view, MotionEvent event) {
// if(event.getAction() != MotionEvent.ACTION_DOWN)
// return super.onTouchEvent(event);
Point point = new Point();
point.x = event.getX();
point.y = event.getY();
points.add(point);
invalidate();
Log.d(TAG, "point: " + point);
nikpointx.add(event.getX());
nikpointy.add(event.getY());
return true;
}
}
You can place you custom view in the xml layout like this:
<full.package.to.customview.DrawView android:id="#+id/idOfCustom"
//other attributes
/>
You can also add custom attributes to your view to set directly from the xml file.
I don't have any solutions for your second problem.
edit: after a quick search on this site i found this -> Android How to draw a smooth line following your finger