Showing posts with label android-canvas. Show all posts
Showing posts with label android-canvas. Show all posts

Wednesday, July 4, 2018

How to draw several lines slowly in constant velocity on canvas by Android?

Leave a Comment

I need capture the mark to draw a figure on canvas in Android, and the effect just like the follow gif:

enter image description here

Well, as far, I can draw a side with constant velocity by ValueAnimator. However, I just only can draw one side at one time, because I can't save the last side when drawing the next side. So, is there a good way to solve the problem?

Code for draw a line slowly by ValueAnimator:

GraphicsView.java

public class GraphicsView extends View {     private int stepX, stepY = 0;     private int startX, startY, stopX, stopY = 0;     private Paint paint = null;      public GraphicsView(Context context) {         super(context);          // Paint         paint = new Paint();         paint.setAntiAlias(true);         paint.setColor(Color.RED);         paint.setStyle(Paint.Style.STROKE);          startX = 40;         startY = 397;         stopX  = 1040;          stopY  = 397;          Init();     }      public void Init(){         ValueAnimator animatorX = ValueAnimator.ofFloat(startX, stopX);         ValueAnimator animatorY = ValueAnimator.ofFloat(startY, stopY);          animatorX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {             @Override             public void onAnimationUpdate(ValueAnimator valueAnimator) {                 stepX = Math.round((Float)valueAnimator.getAnimatedValue()); invalidate();             }         });         animatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {             @Override             public void onAnimationUpdate(ValueAnimator valueAnimator) {                 stepY = Math.round((Float)valueAnimator.getAnimatedValue()); invalidate();             }         });          AnimatorSet set = new AnimatorSet();         LinearInterpolator l = new LinearInterpolator();         set.setInterpolator(l);         set.setDuration(3000);         set.playTogether(animatorX, animatorY);         set.start();     }      @Override     protected void onDraw(Canvas canvas) {         super.onDraw(canvas);         canvas.drawLine(startX, startY, stepX, stepY, paint);     } } 

MainActivity.java

public class MainActivity extends AppCompatActivity {        private Display display         = null;       private GraphicsView view       = null;       private ConstraintLayout layout = null;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);          display = getWindowManager().getDefaultDisplay();         layout = (ConstraintLayout)findViewById(R.id.main_layout);          view = new GraphicsView(this);         view.setMinimumWidth(display.getWidth());         view.setMinimumHeight(display.getHeight());            layout.addView(view);     } } 

1 Answers

Answers 1

you can use the ObjectAnimator class to callback to one of your class methods every time you'd like to draw a bit more of the path.

import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathEffect; import android.graphics.PathMeasure; import android.util.AttributeSet; import android.view.View; import android.util.Log;  public class PathView extends View {     Path path;     Paint paint;     float length;      public PathView(Context context)     {         super(context);     }      public PathView(Context context, AttributeSet attrs)     {         super(context, attrs);     }      public PathView(Context context, AttributeSet attrs, int defStyleAttr)     {         super(context, attrs, defStyleAttr);     }      public void init()     {         paint = new Paint();         paint.setColor(Color.BLUE);         paint.setStrokeWidth(10);         paint.setStyle(Paint.Style.STROKE);          path = new Path();         path.moveTo(50, 50);         path.lineTo(50, 500);         path.lineTo(200, 500);         path.lineTo(200, 300);         path.lineTo(350, 300);          // Measure the path         PathMeasure measure = new PathMeasure(path, false);         length = measure.getLength();          float[] intervals = new float[]{length, length};          ObjectAnimator animator = ObjectAnimator.ofFloat(PathView.this, "phase", 1.0f, 0.0f);         animator.setDuration(3000);         animator.start();     }      //is called by animtor object     public void setPhase(float phase)     {         Log.d("pathview","setPhase called with:" + String.valueOf(phase));         paint.setPathEffect(createPathEffect(length, phase, 0.0f));         invalidate();//will calll onDraw     }      private static PathEffect createPathEffect(float pathLength, float phase, float offset)     {         return new DashPathEffect(new float[] { pathLength, pathLength },             Math.max(phase * pathLength, offset));     }      @Override     public void onDraw(Canvas c)     {         super.onDraw(c);         c.drawPath(path, paint);     } } 

Then, just call init() to begin the animation, like this (or if you'd like it to start as soon as the view is inflated, put the init() call inside the constructors):

PathView path_view = (PathView) root_view.findViewById(R.id.path); path_view.init(); 

Also see this question here, and

Using Value Animator Example

Reference 1

Reference 2

Reference 3

Read More

Saturday, November 11, 2017

How to play sound effect of spin wheel co-originated with the speed or Arc line

Leave a Comment

I want to play sound on rotating the wheel According to angle and coordinate the speed of wheel rotating

I want to Sound Play on right time when the Pin touch on - line touch of Arc.

Also, Want to Animation on Pin when touch like Shaking.

Sound is Play but the Sound Continuously play and seeking not Coordinates According to Canvas wheel

 @Override protected void onDraw(Canvas canvas) {     super.onDraw(canvas);     drawWheelBackground(canvas);     initComponents();      float tempAngle = 0;     float sweepAngle = 360 / mWheelItems.size();     if (this.mWheelItems != null) {          Log.e("mWheelItems.size()", String.valueOf(mWheelItems.size()));         for (int i = 0; i < mWheelItems.size(); i++) {             archPaint.setColor(mWheelItems.get(i).color);             archPaint1.setColor(Color.WHITE);              canvas.drawArc(range, tempAngle, sweepAngle, true, archPaint2);             canvas.drawArc(range, tempAngle, sweepAngle, true, archPaint);              drawImage(canvas, tempAngle, mWheelItems.get(i).bitmap, mWheelItems.get(i).mvalue, sweepAngle);          }         if (!(this.resourcePlayer == null)) {              if (this.resourcePlayer.isPlaying()) {                 resourcePlayer.setVolume(100, 100);                 this.resourcePlayer.seekTo(0);              } else {                  this.resourcePlayer.start();              }         }      } } 

Rotation according to wheelItemCenter and 360 degrees

   final float wheelItemCenter = 180 - getAngleOfIndexTarget(target) + (360 / mWheelItems.size()) / 2;     animate().setInterpolator(new DecelerateInterpolator())             .setDuration(DEFAULT_ROTATION_TIME)             .rotation((360*5) + wheelItemCenter) 

enter image description here

1 Answers

Answers 1

So if I'm understanding correctly, you want to adjust the sound based on the wheel's speed and play it whenever it hits one of your pins.

Playing it whenever it hits one of the pins should be a math-question, if it plays continuously that would mean that your wheel is spinning fast and that the sound you're playing is longer than the duration between hitting two pins, I assume.

You can play around with the speed and pitch of your sound. If you're using a MediaPlayer you can change the PlaybackParams according to this - check out setPitch and setSpeed.

The faster your wheel is, the higher is the speed / pitch of your sound. The slower it gets, the more you also slow down the sound.

Read More

Wednesday, August 30, 2017

Drag/Scroll canvas not smooth after zoom [android coloring book app]

Leave a Comment

I'm trying to implement zoom feature in my coloring book app, the zoom working perfectly but I have 3 main problems in canvas after zoom:

  1. Drag/scroll the canvas is not smooth and difficult after zoom.
  2. I can't detect the pinch motion in order to avoid coloring during zoom process.
  3. After zoom it is not filling the exact tapped point so I would like to keep filling the tapped point.

Here is my code:

public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnClickListener { private static final int PERMISSION_REQUEST_CODE = 1; public static ArrayList<Point> drawnPoints = new ArrayList<Point>();  Paint paint; ImageView iv; int position, code; Button White, Black, Gray, lightOrange, Brown, Yellow, deepBlue, lightBlue, deepPurple, lightPurple,         deepGreen, lightGreen, deepPink, lightPink, Red, deepOrange; int h, w; Drawable drawable; private RelativeLayout drawingLayout; private MyView myView; private ArrayList<Path> paths = new ArrayList<Path>(); private InterstitialAd mInterstitialAd;  @Override public void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.activity_main);     getSupportActionBar().setDisplayHomeAsUpEnabled(true);      NativeExpressAdView adView = (NativeExpressAdView) findViewById(R.id.adView);     AdRequest request = new AdRequest.Builder().build();     adView.loadAd(request);     mInterstitialAd = new InterstitialAd(this);     mInterstitialAd.setAdUnitId(getString(R.string.interstitial_unit_id));     requestNewInterstitial();     mInterstitialAd.setAdListener(new AdListener() {         @Override         public void onAdLoaded() {             if (mInterstitialAd.isLoaded()) {                 mInterstitialAd.show();             }         }      });     iv = (ImageView) findViewById(R.id.coringImage);      Bundle extras = getIntent().getExtras();     if (extras != null) {         // code of category         //position of image in the category array         position = extras.getInt("position");         code = extras.getInt("code");     }      drawingLayout = (RelativeLayout) findViewById(R.id.relative_layout);     White = (Button) findViewById(R.id.white);     Black = (Button) findViewById(R.id.black);     Gray = (Button) findViewById(R.id.gray);     lightOrange = (Button) findViewById(R.id.light_orange);     Brown = (Button) findViewById(R.id.brown);     Yellow = (Button) findViewById(R.id.yellow);     deepBlue = (Button) findViewById(R.id.deep_blue);     lightBlue = (Button) findViewById(R.id.light_blue);     deepPurple = (Button) findViewById(R.id.deep_purple);     lightPurple = (Button) findViewById(R.id.light_purple);     deepGreen = (Button) findViewById(R.id.deep_green);     lightGreen = (Button) findViewById(R.id.light_green);     deepPink = (Button) findViewById(R.id.deep_pink);     lightPink = (Button) findViewById(R.id.light_pink);     Red = (Button) findViewById(R.id.red);     deepOrange = (Button) findViewById(R.id.deep_orange);      White.setOnClickListener(this);     Black.setOnClickListener(this);     Gray.setOnClickListener(this);     lightOrange.setOnClickListener(this);     Brown.setOnClickListener(this);     Yellow.setOnClickListener(this);     deepBlue.setOnClickListener(this);     lightBlue.setOnClickListener(this);     deepPurple.setOnClickListener(this);     lightPurple.setOnClickListener(this);     deepGreen.setOnClickListener(this);     lightGreen.setOnClickListener(this);     deepPink.setOnClickListener(this);     lightPink.setOnClickListener(this);     Red.setOnClickListener(this);     deepOrange.setOnClickListener(this);      myView = new MyView(this);     drawingLayout.addView(myView);  }  @Override public boolean onTouch(View v, MotionEvent event) {     // TODO Auto-generated method stub     return false; }  //MeunItems @Override public boolean onCreateOptionsMenu(Menu menu) {     // Inflate the menu; this adds items to the action bar if it is present.     getMenuInflater().inflate(R.menu.share_save_menu, menu);     return true; }  @Override public boolean onOptionsItemSelected(MenuItem item) {     switch (item.getItemId()) {         case R.id.action_share:             onShareImageItem();             break;         case R.id.action_save:             if (Build.VERSION.SDK_INT >= 23) {                 if (checkPermission()) {                     save(myView);                  } else {                     requestPermission(); // Code for permission                 }             } else {                 // Code for Below 23 API Oriented Device                 save(myView);             }             break;         case android.R.id.home:             this.finish();             break;       }     return super.onOptionsItemSelected(item); }  //Share the Image public void onShareImageItem() {     String package_name = getPackageName();      // Get access to the URI for the bitmap     Uri bmpUri = getLocalBitmapUri(iv);     if (bmpUri != null) {         // Construct a ShareIntent with link to image         Intent shareIntent = new Intent();         shareIntent.setAction(Intent.ACTION_SEND);         shareIntent.putExtra(Intent.EXTRA_STREAM, bmpUri);         shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.app_name) + "\n" + "https://play.google.com/store/apps/details?id=" + package_name);          shareIntent.setType("image/*");         startActivity(Intent.createChooser(shareIntent, getString(R.string.action_share)));     } else {     } }  // Returns the URI path to the Bitmap displayed in specified ImageView public Uri getLocalBitmapUri(ImageView iv) {     iv.setImageBitmap(myView.scaledBitmap);     // Extract Bitmap from ImageView drawable     drawable = iv.getDrawable();     Bitmap bmp = null;     if (drawable instanceof BitmapDrawable) {         bmp = ((BitmapDrawable) iv.getDrawable()).getBitmap();     } else {         return null;     }     // Store image to default external storage directory     Uri bmpUri = null;     try {         File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "share_image_" + System.currentTimeMillis() + ".png");         FileOutputStream out = new FileOutputStream(file);         bmp.compress(Bitmap.CompressFormat.PNG, 90, out);         out.close();         bmpUri = Uri.fromFile(file);     } catch (IOException e) {         e.printStackTrace();     }     return bmpUri; }  public void save(View view) {     File filename;     iv.setImageBitmap(myView.scaledBitmap);      try {          File filepath = Environment.getExternalStorageDirectory();         File dir = new File(filepath.getAbsolutePath() + "/" + getString(R.string.app_name) + "/");         dir.mkdirs();         String fileName = "/" + System.currentTimeMillis() + "image.jpg";         ;         filename = new File(dir, fileName);         FileOutputStream fos = new FileOutputStream(filename);         myView.scaledBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);         fos.flush();         fos.close();         Toast.makeText(getApplicationContext(), getString(R.string.save), Toast.LENGTH_LONG).show();         MediaScannerConnection.scanFile(MainActivity.this,                 new String[]{filename.toString()}, null,                 new MediaScannerConnection.OnScanCompletedListener() {                     public void onScanCompleted(String path, Uri uri) {                         Log.i("ExternalStorage", "Scanned " + path + ":");                         Log.i("ExternalStorage", "-> uri=" + uri);                     }                 });     } catch (Exception e) {         e.printStackTrace();     } }  @Override public void onClick(View view) {     enable();     switch (view.getId()) {         case R.id.white:             paint.setColor(getResources().getColor(R.color.white));             White.setSelected(true);             break;         case R.id.black:             paint.setColor(getResources().getColor(R.color.black));             Black.setSelected(true);             break;         case R.id.gray:             paint.setColor(getResources().getColor(R.color.gray));             Gray.setSelected(true);             break;         case R.id.light_orange:             paint.setColor(getResources().getColor(R.color.light_orange));             lightOrange.setSelected(true);             break;         case R.id.brown:             paint.setColor(getResources().getColor(R.color.brown));             Brown.setSelected(true);             break;         case R.id.yellow:             paint.setColor(getResources().getColor(R.color.yellow));             Yellow.setSelected(true);             break;         case R.id.deep_blue:             paint.setColor(getResources().getColor(R.color.deep_blue));             deepBlue.setSelected(true);             break;         case R.id.light_blue:             paint.setColor(getResources().getColor(R.color.light_blue));             lightBlue.setSelected(true);             break;         case R.id.deep_purple:             paint.setColor(getResources().getColor(R.color.deep_purple));             deepPurple.setSelected(true);             break;         case R.id.light_purple:             paint.setColor(getResources().getColor(R.color.light_purple));             lightPurple.setSelected(true);             break;         case R.id.deep_green:             paint.setColor(getResources().getColor(R.color.deep_green));             deepGreen.setSelected(true);             break;         case R.id.light_green:             paint.setColor(getResources().getColor(R.color.light_green));             lightGreen.setSelected(true);             break;         case R.id.deep_pink:             paint.setColor(getResources().getColor(R.color.deep_pink));             deepPink.setSelected(true);             break;         case R.id.light_pink:             paint.setColor(getResources().getColor(R.color.light_pink));             lightPink.setSelected(true);             break;         case R.id.red:             paint.setColor(getResources().getColor(R.color.red));             Red.setSelected(true);             break;         case R.id.deep_orange:             paint.setColor(getResources().getColor(R.color.deep_orange));             deepOrange.setSelected(true);             break;     }  }  public void enable() {     White.setSelected(false);     Gray.setSelected(false);     Black.setSelected(false);     lightOrange.setSelected(false);     Brown.setSelected(false);     Yellow.setSelected(false);     deepBlue.setSelected(false);     lightBlue.setSelected(false);     deepPurple.setSelected(false);     lightPurple.setSelected(false);     deepGreen.setSelected(false);     lightGreen.setSelected(false);     deepPink.setSelected(false);     lightPink.setSelected(false);     Red.setSelected(false);     deepOrange.setSelected(false);  }  private boolean checkPermission() {     int result = ContextCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);     if (result == PackageManager.PERMISSION_GRANTED) {         return true;     } else {         return false;     } }  private void requestPermission() {      if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {         Toast.makeText(MainActivity.this, getString(R.string.permission), Toast.LENGTH_LONG).show();     } else {         ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);     } }  @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {     switch (requestCode) {         case PERMISSION_REQUEST_CODE:             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {                 Log.e("value", "Permission Granted, Now you can use local drive .");             } else {                 Log.e("value", "Permission Denied, You cannot use local drive .");             }             break;     } }  private void requestNewInterstitial() {     AdRequest adRequest = new AdRequest.Builder().build();     mInterstitialAd.loadAd(adRequest); }  // flood fill public class MyView extends View {      Point p1 = new Point();     Bitmap mBitmap, scaledBitmap;     ProgressDialog pd;     Canvas canvas;     private Path path;     //Test 20/8/2017//     private ScaleGestureDetector scaleDetector;     private float scaleFactor = 1.f;     //////////////////      public MyView(Context context) {         super(context);         //Test 20/8/2017//         init(context);         //////////////////          this.path = new Path();         paint = new Paint();         paint.setAntiAlias(true);         pd = new ProgressDialog(context);          paint.setStyle(Paint.Style.STROKE);         paint.setStrokeJoin(Paint.Join.ROUND);         paint.setStrokeWidth(5f);         int id = getResources().getIdentifier("gp" + code + "_" + position, "drawable", getPackageName());         mBitmap = BitmapFactory.decodeResource(getResources(),                 id).copy(Bitmap.Config.ARGB_8888, true);         // Get the screen dimension         DisplayMetrics displaymetrics = new DisplayMetrics();         getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);         Display getOrient = getWindowManager().getDefaultDisplay();         int orientation = Configuration.ORIENTATION_UNDEFINED;         if (getOrient.getWidth() == getOrient.getHeight()) {             orientation = Configuration.ORIENTATION_SQUARE;         } else {             if (getOrient.getWidth() < getOrient.getHeight()) {                 orientation = Configuration.ORIENTATION_PORTRAIT;             } else {                 orientation = Configuration.ORIENTATION_LANDSCAPE;             }          }          if (orientation == Configuration.ORIENTATION_PORTRAIT) {              h = displaymetrics.heightPixels / 2 + 100;             w = displaymetrics.widthPixels;         }         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {             //for mdpi screen 760×1024 tablet             Display display = getWindowManager().getDefaultDisplay();             float density = context.getResources().getDisplayMetrics().density;             if (density >= 1.0 && display.getWidth() == 1024) {                 h = displaymetrics.heightPixels - 200;                 w = displaymetrics.widthPixels - 100;             } else {                 h = displaymetrics.heightPixels - 500;                 w = displaymetrics.widthPixels - 700;             }         }         scaledBitmap = Bitmap.createScaledBitmap(mBitmap, w, h, false);      }      //Test 20/8/2017//     public MyView(Context context, AttributeSet attrs) {         super(context, attrs);         init(context);     }      public MyView(Context context, AttributeSet attrs, int defStyle) {         super(context, attrs, defStyle);         init(context);     }      private void init(Context ctx) {         scaleDetector = new ScaleGestureDetector(ctx, new ScaleListener());     }     /////////////////      @Override     protected void onDraw(Canvas canvas) {         this.canvas = canvas;         super.onDraw(canvas);         DisplayMetrics metrics = new DisplayMetrics();         getWindowManager().getDefaultDisplay().getMetrics(metrics);         //Test 20/8/2017//         canvas.scale(this.scaleFactor, this.scaleFactor,    this.scaleDetector.getPreviousSpanX(),this.scaleDetector.getPreviousSpanY());           //////////////////         canvas.drawBitmap(scaledBitmap, 0, 0, paint);         for (Path p : paths) {             canvas.drawPath(p, paint);             canvas.save();          }       }      @Override     public boolean onTouchEvent(MotionEvent event) {         //Test 20/8/2017//         // scaleDetector.onTouchEvent(event);         //////////////////         float x = event.getX();         float y = event.getY();         switch (event.getAction()) {             case MotionEvent.ACTION_UP:                 try {                     p1 = new Point();                     p1.x = (int) x;                     p1.y = (int) y;                     drawnPoints.add(p1);                     final int sourceColor = scaledBitmap.getPixel((int) x,                       (int) y);                     final int targetColor = paint.getColor();                     new TheTask(scaledBitmap, p1, sourceColor,                     targetColor).execute();                     paths.add(path);                     invalidate();                 } catch (Exception e) {                     e.printStackTrace();                 }                 break;             case MotionEvent.ACTION_MOVE:                 scaleDetector.onTouchEvent(event);                 break;           }         return true;     }      public void clear() {         path.reset();         invalidate();     }      public int getCurrentPaintColor() {         return paint.getColor();     }      @Override     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {         super.onMeasure(widthMeasureSpec, heightMeasureSpec);          w = widthMeasureSpec - MeasureSpec.EXACTLY;         h = heightMeasureSpec - MeasureSpec.EXACTLY;          Log.d("Dim", Integer.toString(w) + " | " + Integer.toString(h));     }      class TheTask extends AsyncTask<Void, Integer, Void> {          Bitmap bmp;         Point pt;         int replacementColor, targetColor;          public TheTask(Bitmap bm, Point p, int sc, int tc) {             this.bmp = bm;             this.pt = p;             this.replacementColor = tc;             this.targetColor = sc;             pd.show();          }          @Override         protected void onPreExecute() {             pd.show();           }          @Override         protected void onProgressUpdate(Integer... values) {          }          @Override         protected Void doInBackground(Void... params) {             FloodFill f = new FloodFill();             f.floodFill(bmp, pt, targetColor, replacementColor);             return null;         }          @Override         protected void onPostExecute(Void result) {             pd.dismiss();             invalidate();         }     }      private class ScaleListener extends             ScaleGestureDetector.SimpleOnScaleGestureListener {         @Override         public boolean onScale(ScaleGestureDetector detector) {             scaleFactor *= detector.getScaleFactor();             scaleFactor = Math.max(0.1f, Math.min(scaleFactor, 10.0f));             invalidate();             return true;         }     } }   public class FloodFill {     public void floodFill(Bitmap image, Point node, int targetColor,                           int replacementColor) {         int width = image.getWidth();         int height = image.getHeight();         int target = targetColor;         int replacement = replacementColor;         if (target != replacement) {             Queue<Point> queue = new LinkedList<Point>();             do {                  int x = node.x;                 int y = node.y;                 while (x > 0 && image.getPixel(x - 1, y) == target) {                     x--;                  }                 boolean spanUp = false;                 boolean spanDown = false;                 while (x < width && image.getPixel(x, y) == target) {                     image.setPixel(x, y, replacement);                     if (!spanUp && y > 0                             && image.getPixel(x, y - 1) == target) {                         queue.add(new Point(x, y - 1));                         spanUp = true;                     } else if (spanUp && y > 0                             && image.getPixel(x, y - 1) != target) {                         spanUp = false;                     }                     if (!spanDown && y < height - 1                             && image.getPixel(x, y + 1) == target) {                         queue.add(new Point(x, y + 1));                         spanDown = true;                     } else if (spanDown && y < height - 1                             && image.getPixel(x, y + 1) != target) {                         spanDown = false;                     }                     x++;                 }             } while ((node = queue.poll()) != null);         }     } }  } 

So please I need help to resolve the above problems.

Kindly let me know in case you need more details.

Thanks in advance.

Best regards,

Leenah.

0 Answers

Read More

Friday, August 18, 2017

Getting Bitmap from WebView generates OutOfMemory crash

Leave a Comment

I have a custom WebView and I want to get a bitmap of its content (offscreen included). I used this code, which I got from here:

 public static Bitmap getBitmapFromWebviewV2(WebView webView) {     webView.measure(View.MeasureSpec.makeMeasureSpec(         View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED),         View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));     webView.layout(0, 0, webView.getMeasuredWidth(),             webView.getMeasuredHeight());     webView.setDrawingCacheEnabled(true);     webView.buildDrawingCache();     Bitmap bm = Bitmap.createBitmap(webView.getMeasuredWidth(),             webView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);      Canvas bigcanvas = new Canvas(bm);     Paint paint = new Paint();     int iHeight = bm.getHeight();     bigcanvas.drawBitmap(bm, 0, iHeight, paint);     webView.draw(bigcanvas);     return bm; } 

It works fine up to the point when I zoom in a lot, in which case I get OutOfMemory Crash. I tested this with the same picture (slightly zoomed and zoomed to the max) and it behaves in the same way I mentioned above.

I tried to counter this by adding

while(webView.canZoomOut()){       webView.zoomOut(); } 

at the start, but it doesn't help at all.

3 Answers

Answers 1

Okay, I've cracked this one meself. The problem indeed was in the fact that the function returned the Bitmap waay before the zooming out procedure was over, so I had to delay it.

I incorporated

Handler handler = new Handler();         handler.postDelayed(new Runnable() {             @Override             public void run() {                 //bulk of my code             }         }, 1000); 

which forced to change the structure of the function from public Bitmap to public void. Here's directly related thread. Despite some minor tweaks I still need to fix, the approach works like a charm

Answers 2

Quick fix:-

Set android:largeHeap="true" in your AndroidManifest.xml file.

<application     android:name="yourapplication"     android:allowBackup="true"     android:icon="@mipmap/ic_launcher_rounded"     android:label="@string/app_name"     android:largeHeap="true"     android:roundIcon="@mipmap/ic_launcher_rounded"     android:supportsRtl="true"     android:theme="@style/AppTheme"> 

Gud luck :)

Answers 3

You write code to capture Bitmap from Webview but code which creates outOfmemmory you should write onces.

webView.measure(View.MeasureSpec.makeMeasureSpec(             View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED),             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));  Bitmap bm = Bitmap.createBitmap(webView.getMeasuredWidth(),                 webView.getMeasuredHeight(), Bitmap.Config.ARGB_8888); 

So above code should be written once and create Bitmap object Global and check if it's not null then over right new drawing cache in Bitmap.

Because each time when you create new Bitmap it consumes a lot of memory.

Read More

Thursday, June 9, 2016

Android Custom View shows solid black after calling invalidate

Leave a Comment

In this code, I would like to draw a line between the top of two ImageViews. However, when running the app, the custom view is shown solid black after calling invalidate().

Here is my code:

public class ArrowView extends RelativeLayout {     public Paint paint;     public Bitmap eraser;     public Canvas cacheCanvas;      public float leftX;     public float leftY;      public float rightX;     public float rightY;      public boolean update = false;      public ImageView iv_leftArrow;     public ImageView iv_rightArrow;      private int w;     private int h;      LayoutInflater mInflater;      public ArrowView(Context context) {         super(context);         this.setWillNotDraw(false);         mInflater = LayoutInflater.from(context);         init();     }      public ArrowView(Context context, AttributeSet attrs) {         super(context, attrs);         this.setWillNotDraw(false);         mInflater = LayoutInflater.from(context);         init();     }      public ArrowView(Context context, AttributeSet attrs, int defStyle) {         super(context, attrs, defStyle);         this.setWillNotDraw(false);         mInflater = LayoutInflater.from(context);         init();     }      @Override     public void onSizeChanged(int w, int h, int oldW, int oldH) {         this.w = w;         this.h = h;         super.onSizeChanged(w, h, oldW, oldH);     }      public void init() {         View v = mInflater.inflate(R.layout.arrow_view, this, true);         paint = new Paint(Paint.ANTI_ALIAS_FLAG);         paint.setColor(Color.BLACK);         paint.setStrokeWidth(5);         v.setBackgroundColor(Color.TRANSPARENT);          eraser = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);          iv_leftArrow = (ImageView) v.findViewById(R.id.iv_leftarrow);         iv_rightArrow = (ImageView) v.findViewById(R.id.iv_rightArrow);          cacheCanvas = new Canvas();         cacheCanvas.setBitmap(eraser);     }      public void setCoordinates(float leftX, float leftY, float rightX, float rightY) {         this.leftX = leftX;         this.leftY = leftY;         this.rightX = rightX;         this.rightY = rightY;     }      @Override     public void onDraw(Canvas c) {         super.onDraw(c);         setCoordinates(iv_leftArrow.getX() + iv_leftArrow.getWidth() / 2, iv_leftArrow.getY(), iv_rightArrow.getX() + iv_rightArrow.getWidth() / 2, iv_rightArrow.getY() );         if (update) {             c.drawLine(leftX, leftY, rightX, rightY, paint);             update = false;         }         cacheCanvas.drawPath(p, paint);     } } 

Is there any reason why the custom view is showing as solid black after calling invalidate()?

1 Answers

Answers 1

As you know, invalidate() is used to update a view by a call to onDraw.

What I think you were doing was calling it before setting update to true - so your onDraw method ran with a false update by the sounds of it.

Read More

Tuesday, May 3, 2016

How does Canvas determine its clip bounds?

Leave a Comment

I've been doing some work with Android's Canvas, specifically trying to determine how its getClipBounds results are determined. I understand Canvas internally keeps a transform Matrix which is updated as I call translate, scale, etc. but trying to replicate that Matrix's results has baffled me.

@Override public void onDraw(Canvas canvas) {     Rect clipBounds;     RectF viewport;      canvas.save();     canvas.concat(translationMatrix);     //viewportMatrix.preConcat(canvas.getMatrix());     viewportMatrix.set(canvas.getMatrix());      clipBounds = canvas.getClipBounds();     viewport = GetViewport();      Log.d("clipBounds", clipBounds.toString() + " (" + clipBounds.width() + ", " + clipBounds.height() + ")");     Log.d("viewport", viewport.toString() + " (" + viewport.width() + ", " + viewport.height() + ")");      //drawing is done here      canvas.restore(); }  //viewport code modeled after http://stackoverflow.com/a/17142856/2245528 private RectF GetViewport() {     RectF viewport = new RectF();      viewportMatrix.mapRect(viewport, originalViewport);      return viewport; }  private void Translate(float x, float y) {     translationMatrix.postTranslate(x, y);      invalidate(); }  private void Scale(float scaleFactor, PointF focusPoint) {     if (focusPoint == null) {         translationMatrix.postScale(scaleFactor, scaleFactor);     }     //keep the focus point in focus if possible     else {         translationMatrix.postScale(scaleFactor, scaleFactor, focusPoint.x, focusPoint.y);     }      invalidate(); }  private final Matrix translationMatrix = new Matrix(); private final RectF originalViewport = new RectF(); private final Matrix viewportMatrix = new Matrix(); 

originalViewport is set to 0, 0, canvas.getWidth(), canvas.getHeight(). Translate and Scale are called from gesture event handlers, which are working correctly.

The part that confuses me is viewportMatrix. It doesn't seem to matter whether I do

viewportMatrix.set(canvas.getMatrix()); 

or

viewportMatrix.preConcat(canvas.getMatrix()); 

or even a one-time call to

viewportMatrix.set(canvas.getMatrix()); 

at the beginning followed by side-by-side Translate/Scale calls to the two matrixes. I've even tried completely ignoring the Canvas's built-in Matrix and rewriting GetViewport as

//viewport code modeled after http://stackoverflow.com/a/17142856/2245528 private RectF GetViewport() {     RectF viewport = new RectF();      translationMatrix.mapRect(viewport, originalViewport);      return viewport; } 

I can never seem to match getClipBounds(), and the discrepancies are fairly serious:

with viewportMatrix.set(canvas.getMatrix):

clipBounds: Rect(-97, -97 - 602, 452) (699, 549)
viewport: RectF(97.04178, 97.06036, 797.04175, 647.06036) (700.0, 550.0)

with viewportMatrix.preConcat(canvas.getMatrix):

clipBounds: Rect(-97, -96 - 602, 453) (699, 549)
viewport: RectF(2708.9663, 2722.2754, 3408.9663, 3272.2754) (700.0, 550.0)

with translationMatrix.mapRect:

clipBounds: Rect(-96, -96 - 603, 453) (699, 549)
viewport: RectF(96.73213, 96.85794, 796.7321, 646.8579) (700.0, 550.0)

with a one-shot call to viewportMatrix.preConcat(canvas.getMatrix()) followed by side-by-side Translate/Scale calls:

clipBounds: Rect(-96, -97 - 603, 452) (699, 549)
viewport: RectF(96.57738, 97.78168, 796.5774, 647.7817) (700.0, 550.0)

with a one-shot call to viewportMatrix.set(canvas.getMatrix()) followed by side-by-side Translate/Scale calls:

clipBounds: Rect(-96, -96 - 603, 453) (699, 549)
viewport: RectF(96.40051, 96.88153, 796.4005, 646.88153) (700.0, 550.0)

I can't even check the Canvas source, as all the Matrix code vanishes into private native calls whose code isn't shown.

Why are my GetViewport calls so grossly off, and what's going on behind-the-scenes with getClipBounds?

1 Answers

Answers 1

I read through this answer on GameDev SE, which uses matrix inversion to swap between screen and world coordinate systems:

To go from screen to world space simply use Vector2.Transform. This is commonly used to get the location of the mouse in the world for object picking.

Vector2.Transform(mouseLocation, Matrix.Invert(Camera.TransformMatrix)); 

To go from world to screen space simply do the opposite.

Vector2.Transform(mouseLocation, Camera.TransformMatrix); 

Inspired by this approach, I tried inverting the transformation matrix like this:

private RectF GetViewport() {     RectF viewport = new RectF();     Matrix viewportMatrix = new Matrix();      translationMatrix.invert(viewportMatrix);      viewportMatrix.mapRect(viewport, originalViewport);      return viewport; } 

This viewport correctly matches the results returned by getClipBounds.

This matrix tutorial explains what I'd noticed from the results of transform calls, but hadn't applied to my matrix:

In a computer program the camera doesn’t move at all and in actuality, the world is just moving in the opposite direction and orientation of how you would want the camera to move in reality.

In order to understand this correctly, we must think in terms of two different things:

  1. The Camera Transformation Matrix: The transformation that places the camera in the correct position and orientation in world space (this is the transformation that you would apply to a 3D model of the camera if you wanted to represent it in the scene).
  2. The View Matrix: This matrix will transform vertices from world-space to view-space. This matrix is the inverse of the camera’s transformation matrix described above.
Read More