Related
I have a gridview that contains an array of images in my drawable folder. I have it worked out right now to send the drawable to another activity where the user will view the image before setting a picture from the raw folder as the wallpaper. I can't use the drawable asset because of compression and a suitable image cause a crash from a lack of memory.
My MainActivity file with the gridview:
GridView androidGridView;
private Integer asset1 = R.drawable.asset1;
private Integer asset2 = R.drawable.asset2;
private Integer asset3 = R.drawable.asset1;
private Integer asset4 = R.drawable.asset2;
private Integer asset5 = R.drawable.asset1;
private Integer asset6 = R.drawable.asset2;
private Integer[] images = {
asset1, asset2, asset3,
asset4, asset5, asset6
};
Integer[] imagesIDs = {
R.raw.asset1, R.raw.asset2, R.drawable.asset1,
R.drawable.asset1, R.drawable.asset1, R.drawable.asset1,
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
androidGridView = findViewById(R.id.gridview_android_example);
androidGridView.setAdapter(new ImageAdapterGridView(this));
androidGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent,
View v, int position, long id) {
int imageRes = images[position];
Intent intent = new Intent(MainActivity.this, ViewActivity.class);
intent.putExtra("IMAGE_RES", imageRes);
startActivity(intent);
}
});
}
public class ImageAdapterGridView extends BaseAdapter {
private Context mContext;
public ImageAdapterGridView(Context c) {
mContext = c;
}
public int getCount() {
return images.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView mImageView;
if (convertView == null) {
mImageView = new ImageView(mContext);
mImageView.setLayoutParams(new GridView.LayoutParams(525, 350));
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
mImageView.setPadding(16, 16, 16, 16);
} else {
mImageView = (ImageView) convertView;
}
mImageView.setImageResource(images[position]);
return mImageView;
}
My ViewActivity file where the user will preview the image before setting it as the wallpaper:
private Integer asset1 = R.raw.asset1;
private Integer asset2 = R.raw.asset2;
private Integer asset3 = R.raw.asset1;
private Integer asset4 = R.raw.asset2;
private Integer asset5 = R.raw.asset1;
private Integer asset6 = R.raw.asset2;
private Integer[] images = {
asset1, asset2, asset3,
asset4, asset5, asset6
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
Bundle extras = getIntent().getExtras();
int imageRes = extras.getInt("IMAGE_RES");
ImageView preview = findViewById(R.id.preview);
preview.setImageResource(imageRes);
preview.setScaleType(ImageView.ScaleType.CENTER_CROP);
Button set = findViewById(R.id.setButton);
set.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
}
I'm not sure whether or not I'm on the right track, but if anyone can point me in the right direction that would be great!
A lot has been written on SO about Out of Memory errors when working with bitmaps and images in android apps: here, here and here, for example.
For the special purpose of setting the wallpaper on a device, you might try this sort of approach. I don't guarantee that you'll always avoid OOM errors doing it this way, but it should prevent most of them.
It does that by trying to stay within the app's current free memory when it decodes the resource into a bitmap. It also recycles the bitmap at the end.
One advantage is that you don't have to come up with the required width and height of the output bitmap. It does that for you, based on free memory. (That's also a disadvantage -- you're not free to choose whatever bitmap dimensions you want. They might be too large and cause a crash.)
It can take some time to do the decoding, which is why it's done on a background thread.
Anyway, this works for me:
Add an ExecutorService and the method decodeBitmapWithinFreeMemory to your ViewActivity:
private ExecutorService executor = Executors.newSingleThreadExecutor();
...
// adapted from https://developer.android.com/topic/performance/graphics/load-bitmap.html
private Bitmap decodeResourceWithinFreeMemory(Resources resources, int resourceId, float requiredAspectRatio) {
// get just the size of the resource image
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, resourceId, options);
// estimate number of pixels we can work with in current free memory
long freeMem = Runtime.getRuntime().freeMemory();
long spaceForARGV8888Px = freeMem / 4; // est. number of ARGV_8888 pixels that can be stored
// calculate the sides of a rectangle with approximately that number of pixels
long squareRootLowerBound = (long) Math.floor(Math.pow(spaceForARGV8888Px, 0.5));
int requestedWidth = (int) Math.floor(squareRootLowerBound * requiredAspectRatio);
int requestedHeight = (int) Math.floor(squareRootLowerBound / requiredAspectRatio);
// find the right sample size by aggressively increasing sampleSize var: require only that
// _one_ of the output dimensions be greater than the corresponding requested dimension
int sampleSize = 1;
while ((options.outHeight / (2 * sampleSize) ) >= requestedHeight
|| (options.outWidth / (2 * sampleSize) ) >= requestedWidth) {
sampleSize *= 2;
}
// output the bitmap by sampling the input resource at the calculated sampleSize
options.inSampleSize = sampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(resources, resourceId, options);
}
Invoke decodeBitmapWithinFreeMemory inside the button's onClick method, feeding it the device's screen aspect ratio:
DisplayMetrics metrics = getResources().getDisplayMetrics();
final float screenAspectRatio = (float)metrics.widthPixels/(float)metrics.heightPixels;
executor.submit(new Runnable() {
#Override
public void run() {
try {
Bitmap drawableAsBitmap = decodeResourceWithinFreeMemory(getResources(),
R.raw.asset1, screenAspectRatio);
WallpaperManager.getInstance(MainActivity.this).setBitmap(drawableAsBitmap);
drawableAsBitmap.recycle();
} catch (IOException ioe) {
Log.e(TAG, "Could not set wallpaper to bitmap", ioe);
}
}
});
Also note that you can optionally add a BroadcastReceiver to your Activities to be notified that the wallpaper has been set. (See the documentation for setBitmap.)
I managed to save the image from imageview to gallery with the onlongclicklistner() with the help of code given below. But the problem is that it always save the last image dosent matters which image i try to save.
public class CapturePhotoUtils {
public final String insertImage(ContentResolver cr,
Bitmap source,
String title,
String description) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, title);
values.put(MediaStore.Images.Media.DISPLAY_NAME, title);
values.put(MediaStore.Images.Media.DESCRIPTION, description);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
// Add the date meta data to ensure the image is added at the front of the gallery
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis());
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
Uri url = null;
String stringUrl = null; /* value to be returned */
try {
url = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (source != null) {
OutputStream imageOut = cr.openOutputStream(url);
try {
source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
} finally {
imageOut.close();
}
long id = ContentUris.parseId(url);
// Wait until MINI_KIND thumbnail is generated.
Bitmap miniThumb = MediaStore.Images.Thumbnails.getThumbnail(cr, id, MediaStore.Images.Thumbnails.MINI_KIND, null);
// This is for backward compatibility.
storeThumbnail(cr, miniThumb, id, 50F, 50F, MediaStore.Images.Thumbnails.MICRO_KIND);
} else {
cr.delete(url, null, null);
url = null;
}
} catch (Exception e) {
if (url != null) {
cr.delete(url, null, null);
url = null;
}
}
if (url != null) {
stringUrl = url.toString();
}
return stringUrl;
}
private final Bitmap storeThumbnail(
ContentResolver cr,
Bitmap source,
long id,
float width,
float height,
int kind) {
// create the matrix to scale it
Matrix matrix = new Matrix();
float scaleX = width / source.getWidth();
float scaleY = height / source.getHeight();
matrix.setScale(scaleX, scaleY);
Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
source.getWidth(),
source.getHeight(), matrix,
true
);
ContentValues values = new ContentValues(4);
values.put(MediaStore.Images.Thumbnails.KIND,kind);
values.put(MediaStore.Images.Thumbnails.IMAGE_ID,(int)id);
values.put(MediaStore.Images.Thumbnails.HEIGHT,thumb.getHeight());
values.put(MediaStore.Images.Thumbnails.WIDTH,thumb.getWidth());
Uri url = cr.insert(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
try {
OutputStream thumbOut = cr.openOutputStream(url);
thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
thumbOut.close();
return thumb;
} catch (FileNotFoundException ex) {
return null;
} catch (IOException ex) {
return null;
}
}
}
I am putting images from the viewpager getting images from array of drawables
class CustomPagerAdapter extends PagerAdapter {
Context mContext;
LayoutInflater mLayoutInflater;
public CustomPagerAdapter(Context context) {
mContext = context;
mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return mResources.length;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((LinearLayout) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
View itemView = mLayoutInflater.inflate(R.layout.image_slider_item, container, false);
imageView = (TouchImageView) itemView.findViewById(R.id.imageView);
imageView.setImageResource(mResources[position]);
imageView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
CapturePhotoUtils photoUtils = new CapturePhotoUtils();
imageView.setDrawingCacheEnabled(true);
Bitmap b = imageView.getDrawingCache();
photoUtils.insertImage(Full_Screen_Slider.this.getContentResolver(),
b, "1image", "this is downloaded image sample");
Toast.makeText(mContext, "longpress ", Toast.LENGTH_SHORT).show();
return true;
}
});
container.addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((LinearLayout) object);
}
}
Replace this
#Override
public Object instantiateItem(ViewGroup container,final int position) {
final View itemView = mLayoutInflater.inflate(R.layout.image_slider_item, container, false);
final TouchImageView imageView = (TouchImageView) itemView.findViewById(R.id.imageView);
imageView.setImageResource(mResources[position]);
imageView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
CapturePhotoUtils photoUtils = new CapturePhotoUtils();
imageView.setDrawingCacheEnabled(true);
Bitmap b = imageView.getDrawingCache();
photoUtils.insertImage(Full_Screen_Slider.this.getContentResolver(),
b, "1image", "this is downloaded image sample");
Toast.makeText(mContext, "longpress ", Toast.LENGTH_SHORT).show();
return true;
}
});
container.addView(itemView);
return itemView;
}
it is replacing your view each time it calls instantiateItem so make it final which will help.
This is the class and the bitmaps in question are in the surface changed method. Is there a way that I can load them that will take up less memory because at the moment all I can do is load those and it uses a majority of my memory and I get a too much output process error.
public class Screen extends SurfaceView {
private boolean click = false;
public static boolean show;
public int numL;
private MainThread main;
private Context context;
public static int width, height;
Canvas c = new Canvas();
public boolean screenCreated = false;
public static Random rn = new Random();
public static int l = rn.nextInt(3);
public Screen(Context context) {
super(context);
this.context = context;
setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
main = new MainThread(this);
SurfaceHolder holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
if (!main.running) {
main.running = true;
main.start();
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Screen.width = width;
Screen.height = height;
Bitmap bg = BitmapFactory.decodeResource(getResources(), R.drawable.circle);
int h = Screen.height; // Height in pixels
int w =Screen.width; // Width in pixels
bgscaled = Bitmap.createScaledBitmap(bg, w, h, false);
bg.recycle();
Bitmap redl = BitmapFactory.decodeResource(getResources(), R.drawable.redl);
redlscaled = Bitmap.createScaledBitmap(redl, w, h, false);
redl.recycle();
Bitmap yellowl = BitmapFactory.decodeResource(getResources(), R.drawable.yellowl);
yellowlscaled = Bitmap.createScaledBitmap(yellowl, w, h, false);
yellowl.recycle();
Bitmap bluel = BitmapFactory.decodeResource(getResources(), R.drawable.bluel);
bluelscaled = Bitmap.createScaledBitmap(bluel, w, h, false);
bluel.recycle();
Bitmap greenl = BitmapFactory.decodeResource(getResources(), R.drawable.greenl);
greenlscaled = Bitmap.createScaledBitmap(greenl, w, h, false);
greenl.recycle();
screenCreated = true;
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
click = false;
}
});
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(click) return false;
click = true;
return false;
}
});
}
Bitmap bgscaled;
Bitmap redlscaled;
Bitmap bluelscaled;
Bitmap greenlscaled;
Bitmap yellowlscaled;
public void render(Canvas c) {
if (c == null || !screenCreated) return;
if(l == 0) {
c.drawBitmap(greenlscaled, 0, 0, null);
}else if(l == 1){
c.drawBitmap(redlscaled, 0, 0, null);
}else if(l == 2){
c.drawBitmap(yellowlscaled, 0, 0, null);
}else if(l == 3){
c.drawBitmap(bluelscaled,0,0,null);
}else{
c.drawBitmap(bgscaled, 0,0, null);
}
}
}
No. A Bitmap object takes 4*width*height bytes, because its an uncompressed bitmap for use with a bitblt command to draw to the screen. Generally the answer is not to use a bitmap when possible (use a Drawable for a solid color, for example). If that isn't possible, use an LRUCache for the bitmaps so only a fixed amount of memory is used for bitmaps, and deal with cache misses by loading it into memory as needed.
I want to get a "full page" screenshot of the activity. The view contains a RecyclerView with many items.
I can take a screenshot of the current view with this function:
public Bitmap getScreenBitmap() {
View v= findViewById(R.id.container).getRootView();
v.setDrawingCacheEnabled(true);
v.buildDrawingCache(true);
Bitmap b = Bitmap.createBitmap(v.getDrawingCache());
v.setDrawingCacheEnabled(false); // clear drawing cache
return b;
}
But it contains only the items I can view normally (as expected).
Is there some way to make the RecyclerView magically show in full length (display all items at once) when I take the screenshot?
If not, how should I approach this problem?
Inspired from Yoav's answer. This code works for recyclerview item types and probably regardless of it's size.
It was tested with a recyclerview having linearlayout manager and three item types. Yet to check it with other layout managers.
public Bitmap getScreenshotFromRecyclerView(RecyclerView view) {
RecyclerView.Adapter adapter = view.getAdapter();
Bitmap bigBitmap = null;
if (adapter != null) {
int size = adapter.getItemCount();
int height = 0;
Paint paint = new Paint();
int iHeight = 0;
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
LruCache<String, Bitmap> bitmaCache = new LruCache<>(cacheSize);
for (int i = 0; i < size; i++) {
RecyclerView.ViewHolder holder = adapter.createViewHolder(view, adapter.getItemViewType(i));
adapter.onBindViewHolder(holder, i);
holder.itemView.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight());
holder.itemView.setDrawingCacheEnabled(true);
holder.itemView.buildDrawingCache();
Bitmap drawingCache = holder.itemView.getDrawingCache();
if (drawingCache != null) {
bitmaCache.put(String.valueOf(i), drawingCache);
}
// holder.itemView.setDrawingCacheEnabled(false);
// holder.itemView.destroyDrawingCache();
height += holder.itemView.getMeasuredHeight();
}
bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888);
Canvas bigCanvas = new Canvas(bigBitmap);
bigCanvas.drawColor(Color.WHITE);
for (int i = 0; i < size; i++) {
Bitmap bitmap = bitmaCache.get(String.valueOf(i));
bigCanvas.drawBitmap(bitmap, 0f, iHeight, paint);
iHeight += bitmap.getHeight();
bitmap.recycle();
}
}
return bigBitmap;
}
Here is my solution for LinearLayoutManager when all the items are on same size and there is only one type of item. This solution is based on This answer.
Note: It can possibly lead to out of memory error.
public static Bitmap getRecyclerViewScreenshot(RecyclerView view) {
int size = view.getAdapter().getItemCount();
RecyclerView.ViewHolder holder = view.getAdapter().createViewHolder(view, 0);
view.getAdapter().onBindViewHolder(holder, 0);
holder.itemView.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight());
Bitmap bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), holder.itemView.getMeasuredHeight() * size,
Bitmap.Config.ARGB_8888);
Canvas bigCanvas = new Canvas(bigBitmap);
bigCanvas.drawColor(Color.WHITE);
Paint paint = new Paint();
int iHeight = 0;
holder.itemView.setDrawingCacheEnabled(true);
holder.itemView.buildDrawingCache();
bigCanvas.drawBitmap(holder.itemView.getDrawingCache(), 0f, iHeight, paint);
holder.itemView.setDrawingCacheEnabled(false);
holder.itemView.destroyDrawingCache();
iHeight += holder.itemView.getMeasuredHeight();
for (int i = 1; i < size; i++) {
view.getAdapter().onBindViewHolder(holder, i);
holder.itemView.setDrawingCacheEnabled(true);
holder.itemView.buildDrawingCache();
bigCanvas.drawBitmap(holder.itemView.getDrawingCache(), 0f, iHeight, paint);
iHeight += holder.itemView.getMeasuredHeight();
holder.itemView.setDrawingCacheEnabled(false);
holder.itemView.destroyDrawingCache();
}
return bigBitmap;
}
Note 2: It has originally been written in Kotlin. Here is the original code used by me.
Take the Screenshot of complete Recyclerview regardless of its items and item types:
This piece of code works like a charm.
Here is a clean way to do this short and precise:
recyclerView.measure(
View.MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
Bitmap bm = Bitmap.createBitmap(recyclerView.getWidth(), recyclerView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
recyclerView.draw(new Canvas(bm));
saveImage(bm);
ImageView im
= new ImageView(getActivity());
im.setImageBitmap(bm);
new AlertDialog.Builder(getActivity()).setView(im).show();
The best solution I found is
create a new NestedScrollView
add it to recyclerview's parent
remove recyclerview from its parent "it's important to add it to a new parent"
add recyclerview to nestedscrollview
take screenshot of nestedscrollview
add recyclerview to its main parent.
nestedScreenShot = new NestedScrollView(getContext());
constraintLayout.removeView(recyclerView);
ConstraintLayout.LayoutParams params =new
Constraints.LayoutParams(viewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
nestedScreenShot.setLayoutParams(params);
Drawable scrollBackground = scrollView.getBackground();
if (scrollBackground != null) {
tempNestedScreenShot.setBackground(scrollBackground);
}
tempNestedScreenShot.setPadding(scrollView.getLeft(),
scrollView.getTop(), scrollView.getRight(), scrollView.getBottom());
constraintLayout.addView(nestedScreenShot);
nestedScreenShot.addView(recyclerView, params);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
nestedScreenShot.removeView(recyclerView);
constraintLayout.removeView(nestedScreenShot);
nestedScreenShot = null;
constraintLayout.addView(recyclerView, params);
}
}, 8000);
takescreenshotOfNested(nestedScreenShot);
This method will return screenshot of nestedscrollview
private Bitmap takescreenshotOfNested(View u) {
NestedScrollView viewNested = null;
ScrollView viewScroll = null;
if (u instanceof NestedScrollView) {
viewNested = (NestedScrollView) u;
} else if (u instanceof ScrollView) {
viewScroll = (ScrollView) u;
}
Bitmap bitmap;
if (viewNested != null) {
viewNested.setDrawingCacheEnabled(false);
viewNested.invalidate();
viewNested.getChildAt(0).setDrawingCacheEnabled(false);
viewNested.getChildAt(0).invalidate();
bitmap = Bitmap.createBitmap(viewNested.getChildAt(0).getWidth(),
viewNested.getChildAt(0).getHeight() + 8, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(activity.getResources().getColor(R.color.layout_background));
viewNested.draw(canvas);
} else {
try {
viewScroll.setDrawingCacheEnabled(false);
viewScroll.invalidate();
} catch (Exception ignored) {
}
viewScroll.getChildAt(0).setDrawingCacheEnabled(false);
viewScroll.getChildAt(0).invalidate();
bitmap = Bitmap.createBitmap(viewScroll.getChildAt(0).getWidth(),
viewScroll.getChildAt(0).getHeight() + 8, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(activity.getResources().getColor(R.color.layout_background));
viewScroll.draw(canvas);
}
return bitmap;
}
don't forget to recycle the bitmap after using it
I wrote a method to get a screenshot of a few different views:
private static Bitmap getBitmapFromView(View view) {
if (view.getVisibility() != View.VISIBLE) {
view = getNextView(view);
Log.d(TAG, "New view id: " + view.getId());
}
//Define a bitmap with the same size as the view
Bitmap returnedBitmap;
if (view instanceof ScrollView) {
returnedBitmap = Bitmap.createBitmap(((ViewGroup) view).getChildAt(0).getWidth(), ((ViewGroup) view).getChildAt(0).getHeight(), Bitmap.Config.ARGB_8888);
} else if (view instanceof RecyclerView) {
view.measure(
View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
} else {
returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
}
//Bind a canvas to it
Canvas canvas = new Canvas(returnedBitmap);
//Get the view's background
Drawable bgDrawable = view.getBackground();
if (bgDrawable != null) {
//has background drawable, then draw it on the canvas
bgDrawable.draw(canvas);
} else {
//does not have background drawable, then draw white background on the canvas
canvas.drawColor(Color.WHITE);
}
// draw the view on the canvas
view.draw(canvas);
//return the bitmap
return returnedBitmap;
}
/**
* If the base view is not visible, then it has no width or height.
* This causes a problem when we are creating a PDF based on its size.
* This method gets the next visible View.
*
* #param view The invisible view
* #return The next visible view after the given View, or the original view if there's no more
* visible views.
*/
private static View getNextView(View view) {
if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) {
ViewGroup group = (ViewGroup) view.getParent();
View child;
boolean getNext = false;
//Iterate through all views from parent
for (int i = 0; i < group.getChildCount(); i++) {
child = group.getChildAt(i);
if (getNext) {
//Make sure the view is visible, else iterate again until we find a visible view
if (child.getVisibility() == View.VISIBLE) {
Log.d(TAG, String.format(Locale.ENGLISH, "CHILD: %s : %s", child.getClass().getSimpleName(), child.getId()));
view = child;
}
}
//Iterate until we find out current view,
// then we want to get the NEXT view
if (child.getId() == view.getId()) {
getNext = true;
}
}
}
return view;
}
I have created an app in which I have used SPenSdk libraries, My app is simple, What I do is on the start of the app I open a canvasView with a pen, eraser, undo and redo option.
I have two buttons, one save the canvas as an image in my SD card and another button is used to load the saved images.
Now my issue is, whenever I load the saved image and edit the saved images and again save the images, it is saved as new images instead of updating the saved images, why it happens?
I put my code here. Help me to solve this out, if possible with an example.
Code For java File
public class CanvasActivity extends Activity {
private CanvasView m_CanvasView;
private SettingView m_SettingView;
private Button m_PenButton, m_EraserButton, m_UndoButton, m_RedoButton, m_SaveButton;
private int mButtonTextNormalColor;
public static final String DEFAULT_APP_IMAGEDATA_DIRECTORY = "/mnt/sdcard/SmemoExample";
private File m_Folder = null;
public static final String SAVED_FILE_EXTENSION = "png";
public static final String EXTRA_IMAGE_PATH = "path";
public static final String EXTRA_IMAGE_NAME = "filename";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
m_PenButton = (Button) findViewById(R.id.pen_button);
m_PenButton.setOnClickListener(mBtnClickListener);
mButtonTextNormalColor = m_PenButton.getTextColors().getDefaultColor();
m_EraserButton = (Button) findViewById(R.id.erase_button);
m_EraserButton.setOnClickListener(mBtnClickListener);
m_UndoButton = (Button) findViewById(R.id.undo_button);
m_UndoButton.setOnClickListener(undoNredoBtnClickListener);
m_UndoButton.setEnabled(false);
m_RedoButton = (Button) findViewById(R.id.redo_button);
m_RedoButton.setOnClickListener(undoNredoBtnClickListener);
m_RedoButton.setEnabled(false);
m_SaveButton = (Button) findViewById(R.id.save_button);
m_SaveButton.setOnClickListener(mBtnClickListener);
m_CanvasView = (CanvasView) findViewById(R.id.canvas_view);
m_SettingView = (SettingView) findViewById(R.id.setting_view);
m_CanvasView.setSettingView(m_SettingView);
m_CanvasView.setOnHistoryChangeListener(historyChangeListener);
m_CanvasView.setInitializeFinishListener(mInitializeFinishListener);
m_Folder = new File(DEFAULT_APP_IMAGEDATA_DIRECTORY);
String mFileName = getIntent().getStringExtra(EXTRA_IMAGE_NAME);
loadCanvas(mFileName);
}
private OnClickListener undoNredoBtnClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
if (v == m_UndoButton) {
m_CanvasView.undo();
} else if (v == m_RedoButton) {
m_CanvasView.redo();
}
m_UndoButton.setEnabled(m_CanvasView.isUndoable());
m_RedoButton.setEnabled(m_CanvasView.isRedoable());
}
};
OnClickListener mBtnClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
if (v.getId() == m_PenButton.getId()) {
m_CanvasView.changeModeTo(CanvasView.PEN_MODE);
m_PenButton.setSelected(true);
m_PenButton.setTextColor(Color.WHITE);
m_EraserButton.setSelected(false);
m_EraserButton.setTextColor(mButtonTextNormalColor);
if (m_PenButton.isSelected()) {
m_SettingView.showView(AbstractSettingView.PEN_SETTING_VIEW);
}
} else if (v.getId() == m_EraserButton.getId()) {
m_CanvasView.changeModeTo(CanvasView.ERASER_MODE);
m_EraserButton.setSelected(true);
m_EraserButton.setTextColor(Color.WHITE);
m_PenButton.setSelected(false);
m_PenButton.setTextColor(mButtonTextNormalColor);
if (m_EraserButton.isSelected()) {
m_SettingView.showView(AbstractSettingView.ERASER_SETTING_VIEW);
}
} else if(v.getId() == m_SaveButton.getId()) {
saveCanvas();
}
}
};
public boolean saveCanvas() {
byte[] buffer = m_CanvasView.getData();
if (buffer == null)
return false;
if (!(m_Folder.exists()))
m_Folder.mkdirs();
String savePath = m_Folder.getPath() + '/' + UtilitiesActivity.getUniqueFilename(m_Folder, "image", SAVED_FILE_EXTENSION);
if (UtilitiesActivity.writeBytedata(savePath, buffer))
return true;
else
return false;
}
public boolean loadCanvas(String fileName) {
String loadPath = m_Folder.getPath() + '/' + fileName;
byte[] buffer = UtilitiesActivity.readBytedata(loadPath);
if (buffer == null)
return false;
m_CanvasView.setData(buffer);
return true;
}
private CanvasView.OnHistoryChangeListener historyChangeListener = new CanvasView.OnHistoryChangeListener() {
#Override
public void onHistoryChanged(boolean bUndoable, boolean bRedoable) {
m_UndoButton.setEnabled(bUndoable);
m_RedoButton.setEnabled(bRedoable);
}
};
CanvasView.InitializeFinishListener mInitializeFinishListener = new CanvasView.InitializeFinishListener() {
#Override
public void onFinish() {
Bitmap bg = BitmapFactory.decodeResource(getResources(), R.drawable.canvas_bg);
m_CanvasView.setBackgroundImage(bg);
bg.recycle();
}
};
}
While you save an image, it takes a new name and save that. Old image wont be overwritten. So you can edit the save canvas function. The method shown below
public boolean saveCanvas()
{
byte[] buffer = mCanvasView.getData();
if(buffer == null)
return false;
String savePath = mFolder.getPath() + '/' + ExampleUtils.getUniqueFilename(mFolder, "image", SAVED_FILE_EXTENSION);
Log.d(TAG, "Save Path = " + savePath);
if(ExampleUtils.writeBytedata(savePath, buffer))
return true;
else
return false;
}
function "getUniqueFilename" generates a new name.So if you update an image, just avoid the function
ExampleUtils.getUniqueFilename(mFolder, "image", SAVED_FILE_EXTENSION);
and save with old name. Then it will be overwritten.