I've two imageview of dots in my activity. I can move one of them and I'd like to show the path between the two dots. Any suggestions?
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
final float eventX = event.getRawX();
final float eventY = event.getRawY();
if(dotEnabled){
dot.setX(eventX);
dot.setY(eventY);
}
if(directionSet){
direction.setX(eventX);
direction.setY(eventY);
}
return false;
}
Add some properties to your paint:
Paint paint = new Paint();
paint.setStrokeWidth(30); //here you can put any width you need
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
If above code doesn't work below this part of code:
super(context);
paint.setColor(Color.BLUE);
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
call invalidate();
The easiest way is to extend FrameLayout, because you already use it as the outer ViewGroup in your layout.
To make this work, you have to declare two more constructors. The standard constructor is not used, when you declare an element in XML. So you have to set your Paint in all three of these constructors. To reduce redundancy, I moved this to a private method 'initiatePaint()'.
The function 'declarePath()' has to be called, when a new location for 'setDirection' is known.
public class DrawLayout extends FrameLayout {
Paint mPaint = new Paint();
float mX1 = 0;
float mY1 = 0;
float mX2 = 0;
float mY2 = 0;
public DrawLayout(Context context) {
super(context);
this.initiatePaint();
}
public DrawLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.initiatePaint();
}
public DrawLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.initiatePaint();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLine(x1, y1, x2, y2, paint);
}
public void declarePath(float x1, float y1, float x2, float y2) {
this.mX1 = x1;
this.mY1 = y1;
this.mX2 = x2;
this.mY2 = y2;
this.invalidate();
}
private void initiatePaint() {
this.mPaint.setColor(Color.BLUE);
this.mPaint.setStrokeWidth(10);
this.mPaint.setStyle(Paint.Style.STROKE);
}
}
To use it in your layout, just use it instead of FrameLayout.
<com.example.DrawLayout
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_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fl"
tools:context="it.uniroma3.sensorlog">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#mipmap/mappa"
android:id="#+id/map"/>
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:src="#android:drawable/ic_notification_overlay"
android:id="#+id/dot" />
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:visibility="invisible"
android:src="#android:drawable/ic_notification_overlay"
android:id="#+id/setDirection" />
</DrawLayout>
In your Activity, you have to do something:
#Override
protected void onCreate(Bundle savedInstanceState) {
[...]
this.mBackground = (DrawLayout) findViewById(R.id.fl);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
final float eventX = event.getRawX();
final float eventY = event.getRawY();
if(dotEnabled){
dot.setX(eventX);
dot.setY(eventY);
}
if(directionSet){
direction.setX(eventX);
direction.setY(eventY);
}
this.mBackground.declarePath(**Here you have to pass the coordinates of your points**);
return false;
}
Related
I created a custom view to draw some shapes on it but my problem is that nothing is showed on the view. I get no error messages or crash. My custom class looks the like:
public class DrawableView extends View {
private Paint mPaint;
private MyDrawable mDrawableObj; // object to draw
private RectF mRectBounds;
public DrawableView(Context context) {
super(context);
init();
}
public DrawableView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DrawableView(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(w, h);
}
public void setDrawableObj(MyDrawable obj, boolean forcePrepare) {
final boolean needPrepare = forcePrepare || (mDrawableObj != null) ;
mDrawableObj = obj;
mDrawableObj.setView(this);
if (needPrepare) {
prepareObject();
invalidate();
}
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
final int padLeft = getPaddingLeft();
final int padTop = getPaddingTop();
final float xpad = (float) (padLeft + getPaddingRight());
final float ypad = (float) (padTop + getPaddingBottom());
final float ww = (float) w - xpad - 1;
final float hh = (float) h - ypad - 1;
mRectBounds = new RectF(0, 0, ww, hh);
mRectBounds.offsetTo(padLeft, padTop);
prepareObject();
}
public void prepareObject() {
if (mDrawableObj != null)
mDrawableObj.prepare(mRectBounds);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawableObj != null)
mDrawableObj.draw(canvas, mRectBounds);
else {
canvas.drawRect(mRectBounds, mPaint);
}
}
My layout file is the following:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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:id="#+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.hello.myApp.Draw">
<DrawableView.DrawableView
android:id="#+id/drawtest_drawableView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar"
app:layout_constraintLeft_toRightOf="#+id/leftMenu"
android:padding="4dp" />
</android.support.constraint.ConstraintLayout>
Do you have any idea why I have just a white screen?
use this
public void init() {
if (isInEditMode())
return;
//code
//...
}
I copy/pasted your code into my IDE and ran it (though I had to create a fake MyDrawable interface to satisfy the compiler). I'm just putting an instance of DrawableView into a LinearLayout and I see it drawing just fine.
My layout xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/myLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.stackoverflow.DrawableView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="12dp"/>
</LinearLayout>
What I see:
It makes sense that I see only the blue rectangle, because this is your onDraw() implementation:
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawableObj != null)
mDrawableObj.draw(canvas, mRectBounds);
else {
canvas.drawRect(mRectBounds, mPaint);
}
}
Given that setDrawableObj(MyDrawable obj, boolean forcePrepare) is never called in the code you posted, the else is always what's being executed/drawn.
I have been studying custom views recently and trying to make one specific example work out from a tutorial located here: http://flavienlaurent.com/blog/2013/08/28/each-navigation-drawer-hides-a-viewdraghelper/
I will add the relevant code to prevent information loss from dead link. The author of the tutorial explains how to create a ViewDragHelper similar to YouTube's android app player behavior and these are the codes he provided
activity_main.xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="list"
/>
<com.example.vdh.YoutubeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/youtubeLayout"
android:orientation="vertical"
android:visibility="visible">
<TextView
android:id="#+id/viewHeader"
android:layout_width="match_parent"
android:layout_height="128dp"
android:fontFamily="sans-serif-thin"
android:textSize="25sp"
android:tag="text"
android:gravity="center"
android:textColor="#android:color/white"
android:background="#AD78CC"/>
<TextView
android:id="#+id/viewDesc"
android:tag="desc"
android:textSize="35sp"
android:gravity="center"
android:text="Loreum Loreum"
android:textColor="#android:color/white"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF00FF"/>
</com.example.vdh.YoutubeLayout>
YoutubeLayout.java
public class YoutubeLayout extends ViewGroup {
private final ViewDragHelper mDragHelper;
private View mHeaderView;
private View mDescView;
private float mInitialMotionX;
private float mInitialMotionY;
private int mDragRange;
private int mTop;
private float mDragOffset;
public YoutubeLayout(Context context) {
this(context, null);
}
public YoutubeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
#Override
protected void onFinishInflate() {
mHeaderView = findViewById(R.id.viewHeader);
mDescView = findViewById(R.id.viewDesc);
}
public YoutubeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());
}
public void maximize() {
smoothSlideTo(0f);
}
boolean smoothSlideTo(float slideOffset) {
final int topBound = getPaddingTop();
int y = (int) (topBound + slideOffset * mDragRange);
if (mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)) {
ViewCompat.postInvalidateOnAnimation(this);
return true;
}
return false;
}
private class DragHelperCallback extends ViewDragHelper.Callback {
#Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mHeaderView;
}
#Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
mTop = top;
mDragOffset = (float) top / mDragRange;
mHeaderView.setPivotX(mHeaderView.getWidth());
mHeaderView.setPivotY(mHeaderView.getHeight());
mHeaderView.setScaleX(1 - mDragOffset / 2);
mHeaderView.setScaleY(1 - mDragOffset / 2);
mDescView.setAlpha(1 - mDragOffset);
requestLayout();
}
#Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int top = getPaddingTop();
if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {
top += mDragRange;
}
mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
}
#Override
public int getViewVerticalDragRange(View child) {
return mDragRange;
}
#Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - mHeaderView.getHeight() - mHeaderView.getPaddingBottom();
final int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
}
#Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (( action != MotionEvent.ACTION_DOWN)) {
mDragHelper.cancel();
return super.onInterceptTouchEvent(ev);
}
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mDragHelper.cancel();
return false;
}
final float x = ev.getX();
final float y = ev.getY();
boolean interceptTap = false;
switch (action) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = x;
mInitialMotionY = y;
interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
break;
}
case MotionEvent.ACTION_MOVE: {
final float adx = Math.abs(x - mInitialMotionX);
final float ady = Math.abs(y - mInitialMotionY);
final int slop = mDragHelper.getTouchSlop();
if (ady > slop && adx > ady) {
mDragHelper.cancel();
return false;
}
}
}
return mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
mDragHelper.processTouchEvent(ev);
final int action = ev.getAction();
final float x = ev.getX();
final float y = ev.getY();
boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = x;
mInitialMotionY = y;
break;
}
case MotionEvent.ACTION_UP: {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
if (dx * dx + dy * dy < slop * slop && isHeaderViewUnder) {
if (mDragOffset == 0) {
smoothSlideTo(1f);
} else {
smoothSlideTo(0f);
}
}
break;
}
}
return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) || isViewHit(mDescView, (int) x, (int) y);
}
private boolean isViewHit(View view, int x, int y) {
int[] viewLocation = new int[2];
view.getLocationOnScreen(viewLocation);
int[] parentLocation = new int[2];
this.getLocationOnScreen(parentLocation);
int screenX = parentLocation[0] + x;
int screenY = parentLocation[1] + y;
return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mDragRange = getHeight() - mHeaderView.getHeight();
mHeaderView.layout(
0,
mTop,
r,
mTop + mHeaderView.getMeasuredHeight());
mDescView.layout(
0,
mTop + mHeaderView.getMeasuredHeight(),
r,
mTop + b);
}
}
The author notes that onLayoutand onMeasure are badly written and I assume these (or one of them) might be the cause of a problem with one of the children.
For my objective, I replaced the mDescView with a Framelayout view containing the respective TextView. Both of them have their height set to match_parent and the parent (mDescView) does set its height correctly, but its children (TextView inside mDescView) ignore the parent height and stretch to fit their height equal to the screen height (or the custom view height, can't tell the difference). This is a problem because the mDescView children will never adjust their height correctly according to the parent through match_parent and I have been looking for a solution for days, but none was found and through research I couldn't find a reason for why this was happening.
This is the result of this problem, notice how the TextView height is bigger than its parent mDescView despite both having their height set to match_parent
So my problem is, how can I get the child (children) of that parent (or any parents) to match their parents' height like they should.
As an additional request, if possible, can anyone explain why the author thinks some of his methods are not the best/right ones and how they should be done correctly/better instead.
I have finally corrected this problem, although I am still unaware if this is the right way to do this. My solution was to set both views' height during onLayout
I have also set this to only run on the first time this method is called, so the specific statement only runs once and only during the first time (when firstRun is true)
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mDragRange = getHeight() - mHeaderView.getHeight();
if (firstRun) {
firstRun = false;
mDescView.getLayoutParams().height = getHeight() - mHeaderView.getMeasuredHeight();
}
mHeaderView.layout(
0,
mTop,
r,
mTop + mHeaderView.getMeasuredHeight());
mDescView.layout(
0,
mTop + mHeaderView.getMeasuredHeight(),
r,
mTop + b);
}
i made custom Image View, where a simple line is drawn. This drawing is triggeed by a button.
When triggered the onClicklistener isnt working anymore. But on the screen the button remains clickable, the buttons changes to a blueish color when clicked (default Android button). I think the problem might be in the "drawLine()" when the "setContentView(R.layout.activity_main);" is called, but not sure why and how to get rid of it. Hope you can help.
MainActivity.java
public class MainActivity extends ActionBarActivity implements OnTouchListener,
OnClickListener {
private int number;
private Handler handler;
private boolean Running = true;
private int endX = 50;
private int endY = 500;
private int startX = 50;
private int startY = 50;
private int frames = 25;
ImageView Line01;
Button buttonLineDrawer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Line01 = (ImageView) findViewById(R.id.Line01);
Line01.setVisibility(View.GONE);
Button buttonLineDrawer;
buttonLineDrawer = (Button) findViewById(R.id.buttonLineDrawer);
buttonLineDrawer.setOnClickListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return false;
}
#Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "klick", Toast.LENGTH_LONG)
.show();
Running = true;
number = 0;
drawLine();
}
public void drawLine() {
Line01.setVisibility(View.VISIBLE);
handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
while (Running) {
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
#Override
public void run() {
int coordX = ((((endX - startX) / frames) * number))
+ startX;
int coordY = ((((endY - startY) / frames) * number))
+ startY;
number += 1;
CustomDraw.setCoordinates(startX, startY, coordX,
coordY);
setContentView(R.layout.activity_main);
if ((coordX - endX) == 0 && coordY - endY == 0) {
Running = false;
}
}
});
}
}
};
new Thread(runnable).start();
}}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF8800"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="de.trialar.linedrawer.MainActivity" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Button
android:id="#+id/buttonLineDrawer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
<de.trialar.linedrawer.CustomDraw
android:id="#+id/Line01"
android:layout_width="400dp"
android:layout_height="400dp" />
</LinearLayout></RelativeLayout>
CustomDraw.java
public class CustomDraw extends ImageView {
Paint paint = new Paint();
private static int endX= 500;
private static int endY= 500;
private static int startX= 50;
private static int startY= 50;
static Context context;
public CustomDraw(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
}
public CustomDraw(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomDraw(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(10);
canvas.drawLine(startX, startY, endX, endY, paint);
}
public static void setCoordinates(int startX, int startY, int endX, int endY) {
System.out.println("SetCoordinates");
CustomDraw.endX = endX;
CustomDraw.endY = endY;
CustomDraw.startX = startX;
CustomDraw.startY = startY;
}}
i think i found the problem: when you start the handler.run() you set a new Layout:
public void run() {
int coordX = ((((endX - startX) / frames) * number)) + startX;
int coordY = ((((endY - startY) / frames) * number)) + startY;
number += 1;
CustomDraw.setCoordinates(startX, startY, coordX, coordY);
//maybe this is wrong
setContentView(R.layout.activity_main);
//try instead:
CustomDraw.invalidate();
if ((coordX - endX) == 0 && coordY - endY == 0) {
Running = false;
}
}
i don't get why you set a new layout... i guess you want to repaint the button... that's why i added a 'repaint'-line in that code-piece above
I am trying to save custom view as image but this is not happening it only gives black image when i open it in android device gallery. The custom view is like this
public class page extends View
{
private static final String TAG = "DrawView";
private boolean clearflag = false;
private static final float MINP = 0.25f;
private static final float MAXP = 0.75f;
private Context context;
private Paint mPaint = new Paint();
private int[] pencolor = { Color.BLUE, Color.GREEN, Color.MAGENTA,
Color.BLACK, Color.CYAN, Color.GRAY, Color.RED, Color.DKGRAY,
Color.LTGRAY, Color.YELLOW };
private Canvas mCanvas;
private Path mPath;
private ArrayList<Path> paths = new ArrayList<Path>();
public void ClearScreen()
{
clearflag = true;
invalidate();
}
public void resetPenColor()
{
Random random = new Random();
random.setSeed(System.currentTimeMillis());
int color_index = random.nextInt(pencolor.length);
// Toast.makeText(context, "Pen Color "+color_index, Toast.LENGTH_SHORT).show();
mPaint.setColor(pencolor[color_index]);
}
public void init(Context context) {
//super(context);
this.context = context;
setFocusable(true);
setFocusableInTouchMode(true);
//this.setOnTouchListener(this);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(6);
mCanvas = new Canvas();
mPath = new Path();
paths.add(mPath);
}
public page(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
this.context = context;
}
public page(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(context);
this.context = context;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas)
{
if(clearflag)
{
canvas.drawColor(Color.WHITE);
clearflag = false;
paths.clear();
paths.add(mPath);
Log.v("Clear Screen", "---");
}
else
{
for (Path p : paths)
{
canvas.drawPath(p, mPaint);
Log.v("draw", "---line");
}
}
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y)
{
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
Log.v("Touch", "Start");
}
private void touch_move(float x, float y)
{
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
{
mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
mX = x;
mY = y;
}
Log.v("Touch", "Move");
}
private void touch_up()
{
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, mPaint);
// kill this so we don't double draw
mPath = new Path();
paths.add(mPath);
Log.v("Touch", "Move");
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
super.onTouchEvent(event);
float x = event.getX();
float y = event.getY();
switch (event.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
case MotionEvent.ACTION_POINTER_DOWN:
resetPenColor();
// Toast.makeText(context, "Multitouch", Toast.LENGTH_SHORT).show();
invalidate();
break;
}
return true;
}
}
and the main activity in which i am saving bitmap is like this
public class Book extends Activity
{
private ImageButton refresh;
private ImageButton save;
private page pg;
public void init()
{
refresh = (ImageButton) findViewById(R.id.refresh);
save = (ImageButton) findViewById(R.id.save);
pg = (page) findViewById(R.id.page);
}
public void onCreate(Bundle onsavestate)
{
super.onCreate(onsavestate);
setContentView(R.layout.book);
init();
addListeners();
}
public void addListeners()
{
refresh.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View arg0)
{
// TODO Auto-generated method stub
pg.ClearScreen();
}
});
save.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
// TODO Auto-generated method stub
saveautograph();
}
});
}
public void saveautograph()
{
Bitmap returnedbitmap = Bitmap.createBitmap(pg.getWidth(), pg.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedbitmap);
pg.draw(canvas);
/*Drawable bgDrawable = pg.getBackground();
if(bgDrawable != null)
{
bgDrawable.draw(canvas);
}*/
OutputStream stream = null;
try
{
stream = new FileOutputStream(Environment.getExternalStorageDirectory()+"/autograph.jpeg");
}
catch (FileNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
try
{
stream.flush();
stream.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
if(stream == null)
{
Log.v("Stream", "null");
}
if(returnedbitmap == null)
{
Log.v("bitmap", "null");
}
returnedbitmap.compress(CompressFormat.PNG, 90, stream);
}
}
and here is the xml file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rl"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<LinearLayout
android:id="#+id/buttonlayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#drawable/buttoncontainer"
android:gravity="bottom|center"
android:orientation="horizontal" >
<ImageButton
android:id="#+id/refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#color/white"
android:src="#drawable/refresh"
android:layout_marginLeft="4dp" />
<ImageButton
android:id="#+id/save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#color/white"
android:src="#drawable/save"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp" />
</LinearLayout>
<com.gsmappstabs.autographplease.page
android:id="#+id/page"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/buttonlayout" />
</RelativeLayout>
Help please
Update your saveautograph method as follows:
public void saveautograph()
{
pg.setDrawingCacheEnabled(true);
pg.buildDrawingCache();
Bitmap returnedBitmap = pg.getDrawingCache();
try {
FileOutputStream out = new FileOutputStream(Environment.getExternalStorageDirectory()+"/autograph.jpeg");
returnedBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
pg.setDrawingCacheEnabled(false);
}
Basically, you can get the view as bitmap using the getDrawingCache method. Then directly save the bitmap to file..
I believe you are trying to save the signatures captured on a view to an image. I have something ready which I re-use in my projects.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Environment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.leelasofttech.android.pcm.R;
import com.leelasofttech.android.pcm.util.Constants;
public class DrawView extends View {
protected static final String TAG = "DrawView";
protected Bitmap mBitmap;
protected Canvas mCanvas;
protected Path mPath;
protected Paint mBitmapPaint;
protected Paint paint;
protected int bgColor;
public DrawView(Context context) {
super(context);
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
bgColor = context.getResources().getColor(R.color.edit_box_normal);
setupPaint();
}
public DrawView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
bgColor = context.getResources().getColor(R.color.edit_box_normal);
setupPaint();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, getPaint());
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touchStart(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touchMove(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touchUp() {
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, getPaint());
// kill this so we don't double draw
mPath.reset();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchStart(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touchMove(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touchUp();
invalidate();
break;
}
return true;
}
protected void setupPaint() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setColor(bgColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(4);
}
public String toJPEGFile(String folderName, String fileName) {
File folder = new File(Environment.getExternalStorageDirectory() + File.separator + Constants.APP_FOLDER + File.separator + folderName + File.separator);
if (!folder.exists()) {
folder.mkdirs();
}
File file = null;
try {
this.setDrawingCacheEnabled(true);
file = new File(folder.getPath() + File.separator + fileName + Constants.FILE_TYPE_JPEG);
FileOutputStream fos = new FileOutputStream(file);
Bitmap bitmap = this.getDrawingCache();
bitmap.compress(CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
return file.getAbsolutePath();
}
catch (FileNotFoundException ex) {
Log.e(TAG, ex.getMessage(), ex);
}
catch (IOException ex) {
Log.e(TAG, ex.getMessage(), ex);
}
catch (Exception ex) {
Log.e(TAG, ex.getMessage(), ex);
}
return null;
}
public Paint getPaint() {
return paint;
}
public void setPaint(Paint paint) {
this.paint = paint;
}
public void changePaintStroke(float stroke) {
paint.setStrokeWidth(stroke);
}
public void changePaintColor(int newColor) {
paint.setColor(newColor);
}
}
// Signature view
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.util.AttributeSet;
public class SignatureView extends DrawView {
public SignatureView(Context context) {
super(context);
paint.setColor(Color.BLACK);
}
public SignatureView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
paint.setColor(Color.BLACK);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, getPaint());
}
public void resetSignatures() {
mPath = new Path();
invalidate();
}
}
Hope this helps.
I'm trying to implement a personal way of undo/redo in a finger paint-like app.
I have in synthesis three objects: the Main class (named ScorePadActivity), the relative Main Layout (with buttons, menus, etc, as well as a View object where I create my drawings), and a third object named ArrayList where i'm writing the undo/redo code.
The problem is, when I press the undo button nothing happens, but if I draw anything again "one-time" and press undo, the screen is updated. If I draw many times, to see any changes happen on screen I have to press the undo button the same number of times I have drawn.
Seems like (as in title) when I add a bitmap to the array list the last element is duplicated in previous indexes, and for some strange reason, everytime I press the Undo Button, the system is ok for one time, but starts to duplicate until the next undo.
The index increase is verified with a series of System.out.println inserted in code.
Now when I draw something on screen, the array list is updated with the code inserted after the invocation of touchup(); method in motionEvent
touch_up(); }
this.arrayClass.incrementArray(mBitmap);
mPath.rewind();
invalidate();
and in ArrayList activity;
public void incrementArray(Bitmap mBitmap) {
this._mBitmap=mBitmap;
_size=undoArray.size();
undoArray.add(_size, _mBitmap);
}
(All Logs removed for clear reading)
The undo button in ScorePadActivity calls the undo method in View activity:
Button undobtn= (Button)findViewById(R.id.undo);
undobtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mView.undo();
}
});
in View activity:
public void undo() {
this.mBitmap= arrayClass.undo();
mCanvas = new Canvas(mBitmap);
mPath.rewind();
invalidate();
}
that calls the relative undo method in ArrayList activity:
public Bitmap undo() {
// TODO Auto-generated method stub
_size=undoArray.size();
if (_size>1) {
undoArray.remove(_size-1);
_size=undoArray.size();
_mBitmap = ((Bitmap) undoArray.get(_size-1)).copy(Bitmap.Config.ARGB_8888,true);
}
return _mBitmap;
}
return mBitmap and invalidate:
Due to my bad English I have made a scheme to make the problem more clear:
I have tried with HashMap, with a simple array, I have tried to change mPath.rewind(); with reset();, new Path(); etc but nothing.
Why?
Sorry for the complex answer, i want give you a great thanks in advance.
Best regards
Edit
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:id="#+id/buttonlayout"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
some other layouts nested an buttons to form a upper toolbar
</LinearLayout>
<RelativeLayout
android:id="#+id/viewlayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#+id/buttonlayout"
android:background="#drawable/desk_wood" >
<com.example.android.touchexample.MyView
android:id="#+id/viewout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
</RelativeLayout>
</RelativeLayout>
This the Main Activity ScorePadActivity
public class ScorePadActivity extends Activity {
MyView mView;
public void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mView = (MyView)findViewById(R.id.viewout);
Button undobtn= (Button)findViewById(R.id.undo);
undobtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mView.undo();
}
});
This is the View Activity:
public class MyView extends View {
MyView myView;
Context context;
final ArrayClass arrayClass= new ArrayClass();
private Bitmap mBitmap;
private Bitmap savedBmp;
private static Canvas mCanvas;
private static Path mPath;
private static Paint mPaint;
/*
* some other variables here
*/
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(color);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(bSize);
mPaint.setAlpha(255);
mPath = new Path();
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap((int) bmWidth, (int) bmHeight,Bitmap. Config.ARGB_8888);}
/*
* here add a blank bitmap at the start of the array at index 0
*/
arrayClass.incrementArray(mBitmap);
mCanvas = new Canvas(mBitmap);
}
public void onDraw(Canvas canvas) {
canvas.save();
bx= (((width/mScaleFactor)-width)/2)+center;
by= ((height/mScaleFactor)-height)/2;
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
canvas.drawBitmap(penta, bx, by, null);
mCanvas.drawPath(mPath, mPaint);
canvas.drawBitmap(mBitmap, bx, by, null);
lastmPosX=mPosX;
lastmPosY=mPosY;
lastmScaleFactor=mScaleFactor;
canvas.restore();
}
private void touch_start(float x, float y) {
x=((x/mScaleFactor)-bx)-(mPosX/mScaleFactor);
y=((y/mScaleFactor)-by)-(mPosY/mScaleFactor);
mPath.rewind();
mPath.moveTo(x, y);
move=false;
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
x=((x/mScaleFactor)-bx)-(mPosX/mScaleFactor);
y=((y/mScaleFactor)-by)-(mPosY/mScaleFactor);
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
move=true;
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
// mPath.rewind();
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
x = ev.getX();
y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
// Here update the arraylist in the ArrayList activity
this.arrayClass.incrementArray(mBitmap);
mPath.rewind();
invalidate();
break;}
return true;
}
/*
* more methods here
*/ switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
public void undo() {
// Here recall the last mBitmap from Arraylist activity
this.mBitmap= arrayClass.undo();
mCanvas = new Canvas(mBitmap);
mPath.rewind();
invalidate();
}
}
and this is my ArrayList activity:
public class ArrayClass {
ArrayList<Bitmap> undoArray =new ArrayList<Bitmap>();
private int _size;
private Bitmap _mBitmap;
public void incrementArray(Bitmap mBitmap) {
this._mBitmap=mBitmap;
_size=undoArray.size();
// undoArray.add(_size, _mBitmap);
undoArray.add(_size, Bitmap.createBitmap(_mBitmap));
}
public Bitmap undo() {
// TODO Auto-generated method stub
_size=undoArray.size();
if (_size>1) {
undoArray.remove(_size-1);
_size=undoArray.size();
_mBitmap = ((Bitmap) undoArray.get(_size-1)).copy(Bitmap.Config.ARGB_8888,true);
}
return _mBitmap;
}
public Bitmap redo() {
// TODO
return null;
}
}
Thanks again
Here the solution, edit the ArrayClass in this way:
public class ArrayClass {
ArrayList<Bitmap> undoArray =new ArrayList<Bitmap>();
private int _size;
private Bitmap _mBitmap;
public void incrementArray(Bitmap mBitmap) {
this._mBitmap=mBitmap;
_size=undoArray.size();
// undoArray.add(_size, _mBitmap); replace with:
undoArray.add(_size, Bitmap.createBitmap(_mBitmap));
}
public Bitmap undo() {
// TODO Auto-generated method stub
_ size=undoArray.size();
if (_size>1) {
undoArray.remove(_size-1);
_size=undoArray.size();
_mBitmap = ((Bitmap) undoArray.get(_size-1)).copy(Bitmap.Config.ARGB_8888,true);
}
return _mBitmap;
}
public Bitmap redo() {
// TODO
return null;
}
}