I am working on an android application. I have an activity, say A, which fills the entire screen with views..On a button click in A I want to start another activity, say B, which also has some views and controls. I want activity B to be offscreen , and want to take the screenshot of B from A . Is it possible?
Note: I am successful in taking the screenshot of page A by saving the drawing cache in to a bitmap, but struggling to take the offscreen page's screenshot.
Yes it is possible...You should extend ActivityGroup in Activity 'A'.
Then do this in your button click event...
View view =getLocalActivityManager().startActivity("B",new Intent(this,B.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)).getDecorView();
then convert that view as bitmap...
I think this is helpful for you...
Well I have achieved what I want. These are the steps I used.
Start activity B with startActivityForResult() instead of
startActivity().
Intent bIntent = new Intent(A.this,B.class);
startActivityForResult(bIntent,B_REQUEST_CODE);
In onCreate of activity B take mainLayout of B and enable its
drawing cache
final LinearLayout layout = (LinearLayout)
findViewById(R.id.app_parent_layout);
layout.setDrawingCacheEnabled(true);
layout.setDrawingCacheQuality(LinearLayout.DRAWING_CACHE_QUALITY_HIGH);
In activity B wait till its layout is drawn completely(This is very
important). For that in onCreate() of activity B, get mainLayout of
B, use ViewTreeObserver to get a call back when its layout is drawn
completely.
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
getDrawingBitmap();
}
});
Now getDrawingBitmap() gets called when layout is drawn completely.
There take your screenshot.
public void getDrawingBitmap(){
LinearLayout mainLayout = (LinearLayout)
findViewById(R.id.app_parent_layout);
Bitmap b = mainLayout.getDrawingCache();
File file = saveBitmapAsFile(b);
Intent resultIntent = new Intent();
resultIntent.putExtra("FullFileName",file.getAbsolutePath());
setResult(Activity.RESULT_OK,resultIntent);
finish();
}
Here I am taking bitmap and saving it as a file and returning the
filename as a result code. I am sure there are better method to send
the bitmap to parent activity than saving as a file and sending
filename. But I have a requirement anyway to save bitmap for
sometime. So For me this is the easier method. After setting result
finish activity.
Now on Activity A onActivityResult() retrieve the filename.
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == B_REQUEST_CODE){
if (resultCode == Activity.RESULT_OK) {
String newText = data.getStringExtra("FullFileName");
File dir = new File (newText);
}
}
}
I think it would be very hard to do so, as I know no way to get in control of the drawing methods of those views, and the android OS won't draw them either unless visible.
However it may be possible to foreground the other View for only a short time to fill its drawing cache. Maybe it can be put background again before it even becomes visible, as for smooth experience the OS seems to draw views offscreen before they are actually composed to foreground.
Another trick could be to attach your foreground View as an child View onto the background, and give it a very slight alpha transparency. The WebView for example can be superimposed this way very well, actually forcing both the WebView and its background to be drawn. Maybe this works with other Views too.
i m using this way its working great but there is an issue i m just taking snapshots of one layout first time when i take the snapshoot it gives me right pic but after some changes i take it again it gives me previous snapshoot.
RelativeLayout layout = (RelativeLayout)findViewById(R.id.mainview);
layout.setDrawingCacheEnabled(true);
layout.setDrawingCacheQuality(LinearLayout.DRAWING_CACHE_QUALITY_HIGH);
layout.buildDrawingCache();
Bitmap bitmap = layout.getDrawingCache();
File file = new File(Environment.getExternalStorageDirectory().toString() + "/sPenimg.png");
FileOutputStream ostream;
try {
ostream = new FileOutputStream(file);
bitmap.compress(CompressFormat.PNG, 100, ostream);
ostream.flush();
ostream.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
This issue is solved to add
((RelativeLayout)findViewById(R.id.mainview)).destroyDrawingCache();
Related
I have searched for hours on the subject of memory leaks and I cant seem to solve my problem. After exactly 9 rotations my app crashes with an OutOfMemory error. I have both Bitmaps and Drawables with Bitmaps in my app. I have tried putting in code that removes callbacks to drawables, I have tried setting all Bitmaps to null as well as manually calling the garbage collector. I had all of this code in the onSaveInstanceState() method since I assume that is called whenever the screen changes and before destruction of the view. None of these solutions worked.
I got some results with the Bitmaps turned to null, however that only added about another 9 screen rotations before another memory leak. Where is my leak? What am I doing wrong? I was under the impression that when a screen is rotated, everything is destroyed and recreated, that must obviously be false.
I dont want to post my code because A. there is a lot of it and B. its close to finishing and so becomes a company secret per se. If there is something you must absolutely see to solve this then I will post it. I think I just need a really good explanation of what is actually going on when the screen is rotated and how to correctly handle bitmaps and drawables. Note: This error does not pop up if I leave the app and then go back in, it only happens when the view is resized upon screen rotation.
I have about 7 bitmaps and 7 drawables, all created at launch and resized when the screen rotates. DOing that several times stops the app.
Some simplified code:
How I set up a Bitmap to a drawable. This one sets to a ClipDrawable as well:
//Full Bar
colorbarMap = BitmapFactory.decodeResource(res, R.drawable.colorbar);
colorbarDraw = new BitmapDrawable(res, colorbarMap);
colorbarDraw.setBounds(barX, barY, barX2, barY2);
colorbarClip = new ClipDrawable(colorbarDraw, Gravity.BOTTOM, ClipDrawable.VERTICAL);
colorbarClip.setBounds(barX, barY, barX2, barY2);
colorbarClip.setLevel(currentLevel);
//Empty Bar
colorbaremptyMap = BitmapFactory.decodeResource(res, R.drawable.colorempty);
colorbaremptyDraw = new BitmapDrawable(res, colorbaremptyMap);
colorbaremptyDraw.setBounds(barX, barY, barX2, barY2);
The above code runs once at the start of the view initializing based on this code:
private void init(){
//System
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
res = getResources();
options = new BitmapFactory.Options();
viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
view_width = getWidth();
view_height = getHeight();
afterLayout();
}
});
}
}
The first code snippet runs under the method: afterLayout()
Nothing is done with the bitmaps after this. Each bitmap is initialized with an x and y location based on the view width and height. That location is edited using a rectangle to set its bounds for example a moving object.
If the Bitmap that to be displayed is small enough than use this or you don't need a high res image
bitmapOption = new BitmapFactory.Options();
bitmapOption.inScaled = true;
bitmapOption.inSampleSize = 2;
imageBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bitmapOption);
this will reduce the amount of size of bitmap see the bald guys explanation
optimizing bitmap
put your bitmap loading code in try finally block and use this at the end
try {
...
}finally {
if (imageBitmap != null) {
//recycle the bitmap to improve performance and avoid OO Exception
imageBitmap.recycle();
imageBitmap = null;
}
}
This will recycler the bitmap space taken by the image whenever GC is called
I want to capture or save a particular layout and display in another layout Imageview
I save one RelativeLayout with three ImageViews.
In these ImageViews, I set three different images from the camera or Gallery.
After I set them, I want that this RelativeLayout is Shown in another Activity's Imageview.
Capture screenshot of your layout on some event. use below code. and send it using intent to next activity.
yourlayout.setDrawingCacheEnabled(true);
Bitmap myBitmap = yourlayout.getDrawingCache();
Intent intent=new Intent(this,NextActivity.class);
intent.putExtra("data", myBitmap )
startActivity(intent);
According to me there is not any easy way to pass layout to another activity in android.
What you can do is convert your images to bitmap and then send bitmap to an another activity via intent because a Bitmap implements Parcelable.
For example
intent.putExtra("data", bitmap)
Try this.
RelativeLayout relative= (RelativeLayout) findViewById(R.id.allview);
if (tabLayout != null) {
Bitmap image = Bitmap.createBitmap(tabLayout.getWidth(),
tabLayout.getHeight(), Config.ARGB_8888);
Canvas b = new Canvas(image);
relative.draw(b);
}
I'm adding images to a gridview, everything works perfectly until I turn the phone sideways, all the images in the GridView disapper and this error appears on the log
SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length
The images I'm using for my GridView are taken with the phone camera like this
Intent camera_intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(camera_intent, CAMERA_PIC_REQUEST);
After that I call the image Adapter
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode){
case 0:
if(resultCode==RESULT_OK){
Bitmap thumbnail = (Bitmap) data.getExtras().get("data");
Bitmap.createScaledBitmap(thumbnail, 100, 100, true);
Drawable drawable = new BitmapDrawable(getResources(), thumbnail);
list.add(drawable);
GridView gv = (GridView)findViewById(R.id.GridV1);
gv.setAdapter(new ImageAdapter(this, list));
}
}
}
And finally this is where I add my images in the ImageAdapter
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(130, 130));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(10, 10, 10, 10);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageDrawable(mThumbIds.get(position));
return imageView;
}
I think it has something to do with the scale, but I'm not sure, can't find a solution to this problem.
Like Sochimickox said, your layout gets redrawn when you turn it. So there is two way that i see, first is disabling the redrawn. Since we know your error's cause is "spans", one of these should help you:
First one
Or this one
Probably the first would do it, but i recommend designing the app for both landscape and portait modes. To do that, you should crete one more folder in your resource named layout-port. Than you can simply copy the xml file here, select the portait look from top bar at "Graphical Layout" bar. I say read here first. It's from Android Developer site, just incase, I'm putting another tutorial link too.
Just to be clear, layout-port is for portait mode. If you want to design for lanscape, use layout-land. You can design for different screen size with this way too. Like layout-sw600dp
The images dissapear because when the configuration changes, the layout gets redrawn.
You can set landscape mode by default on your app by adding this to OnCreate:
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
Or set it to portrait mode:
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
EDIT
Try this:
#Override
public void onConfigurationChanged(Configuration newConfig) {
newConfig.orientation = getResources().getConfiguration().orientation;
super.onConfigurationChanged(newConfig);
}
I'm using the loadermanager for my app. On Orientation change my app can reload the TextView, but the imagestream for my drawable is not loaded the second time. On the first load the picture shows ,but after an orientation change it stays in its unchanged state. (in my case , a white square)
#Override
public void onLoadFinished(Loader<Person> loader, Person p) {
tvProfileName.setText(p.getName());
Log.i("img stream", ""+p.getImageStream());
Bitmap b = BitmapFactory.decodeStream(p.getImageStream());
Drawable d = new BitmapDrawable(getResources(), b);
ivMenuPhoto.setImageDrawable(d);
}
The setText is working and my name is loaded, even after the orientation change. But the setimagedrawable is not working. The p.getImageStream is not null...
Is my drawable maybe set before the layout is finished changing? I'm just very confused, because my TextView is working.
edited for my solution:
I added a Drawable attribute in the Person Object. So the loader can also hold that part. Then I created the getters and setters. And then I added the onResume to also set my drawable after the orientation change
#Override
public void onLoadFinished(Loader<Person> loader, Person p) {
tvProfileName.setText(p.getName());
Drawable d = new BitmapDrawable(getResources(), p.getPersonphoto());
this.personPhoto = d;
ivMenuPhoto.setImageDrawable(d);
}
#Override
protected void onResume() {
super.onResume();
ivMenuPhoto.setImageDrawable(this.personPhoto);
}
i am retriving around 20 images url from an xml , and appearing them in gridview.
thing going nice except one.
when i scroll down new images start loading , but when i again scrolls up. the previously loaded images again stars loading .
how can i resolve this bug. the previously loaded image should be retained in the layout , why it is not retaining itself .
I am using the adapter extends the BaseAdapter to view the images
Gridview like any other views that inherit AdapterView only uses the amount of views that you see on the screen. So when you scroll your views going to be reused. The Adapter's responsibility is to reassign the content to the views.
So, you need to cache your bitmaps. There is a very nice tutorial about this on the Android developer side.
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
(I uses grid view in the example)
Every time you scroll, getView() method of the adapter is called.
So every time one view come in to the screen is built in the getView() method.
You build the new view in that method, I suppose you download the image when that method is called, then one solution is caching the images. If you download few images and they are small size, a simply Map with key url and value bitmap solves the problem, if not, use database.
Every time getView() is called, check first if image is in cache, if image is in cache use it to build the view, if not download it.
use LoaderImageView from this blog http://blog.blundell-apps.com/imageview-with-loading-spinner/ & add following function
public void setImageDrawable(final MyGridViewItem obj) {
mDrawable = null;
if(obj.mDrawable != null){
mDrawable = obj.mDrawable
imageLoadedHandler.sendEmptyMessage(COMPLETE);
return;
}
mSpinner.setVisibility(View.VISIBLE);
mImage.setVisibility(View.GONE);
new Thread(){
public void run() {
try {
mDrawable = getDrawableFromUrl(imageUrl);
obj.mDrawable = mDrawable;
imageLoadedHandler.sendEmptyMessage(COMPLETE);
} catch (MalformedURLException e) {
imageLoadedHandler.sendEmptyMessage(FAILED);
} catch (IOException e) {
imageLoadedHandler.sendEmptyMessage(FAILED);
}
};
}.start();
}