getDrawingCache() returns null after layout - android

Please help me understand why getDrawingCache() is returning null when called on my image button view (appcompatimagebutton) from within my ontouchlistner attached to my image button view.
Mainly, in trying to solve this, I have been referring to this question:
Android View.getDrawingCache returns null, only null
which has not solved my problem. I am sure I am overlooking something or not fully understanding something. Please help me figure out what it is.
Notes:
1) it seems to make no difference weather i use ImageButton or AppCompatImageButton (but i am using appcompat because in the actual project I need features it offers that ImageButton does not).
2) I do not see why i should need to call the view's measure and layout methods since the layout is already painted to the screen by the time a user would fire ontouch() but thought i'd include it anyway as many answers i found about seemed to indicate this was required.. it doesn't work if i take them out either.
3) it does not appear to matter if I place the setEnableDrawingCache and buildDrawingCache calls in the onTouch or in the onCreate with the AppCompatImageButton view initialization. In either case, my ontouchlistener call to the image button view's getDrawingCache returns null.
here is the code sample,
Thanks!
public class MainActivity extends AppCompatActivity {
AppCompatImageButton mImageButton;
Bitmap mBitmapDrawingCache;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//create image button
mImageButton = new AppCompatImageButton(this);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(270, 270);
mImageButton.setLayoutParams(layoutParams);
//getDrawingCache() in ontouchlistener returns null regardless if this is set
int mseWidth = View.MeasureSpec.makeMeasureSpec(270, View.MeasureSpec.EXACTLY);
int mseHeight = View.MeasureSpec.makeMeasureSpec(270, View.MeasureSpec.EXACTLY);
mImageButton.measure(mseWidth, mseHeight);
mImageButton.layout(0, 0, 270, 270);
/* this does not work either
int mseWidth = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int mseHeight = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mImageButton.measure(mseWidth, mseHeight);
mImageButton.layout(0, 0, mImageButton.getMeasuredWidth(), mImageButton.getMeasuredHeight());
*/
//getDrawingCache() in ontouchlistner returns null regardless if either, both or none of these are set
//mImageButton.buildDrawingCache();
mImageButton.setDrawingCacheEnabled(true);
//attach image button view to content view
((FrameLayout) findViewById(android.R.id.content)).addView(mImageButton);
//set touch listener
mImageButton.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
//why is this line returning null instead of a Bitmap ?
mBitmapDrawingCache = Bitmap.createBitmap(v.getDrawingCache());
Log.d("TAG_A", mBitmapDrawingCache.toString());
return false;
}
});
}
}

In order to get bitmap out from your View, the order in which you generate cache is something like this:
mImageButton.setDrawingCacheEnabled(true);
mImageButton.buildDrawingCache();
mBitmapDrawingCache = Bitmap.createBitmap(mImageButton.getDrawingCache());
You need to call this all together inside OnTouchListener or more preferably OnClickListener (if you want to generate bitmap only when clicked).
However, I'm not sure why you are calling all the MeasureSpec stuff... If you are loading the image through setContentView(R.layout.activity_main); then don't be bothered with all manual measuring.

Related

onGlobalLayout differentiate between various invocations

I have a logo view, which is a full screen fragment containing single ImageView.
I have to perform some operations after the logo image is completely visible.
Following code is used to invoke the special task
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ImageView logoImageMaster = new ImageView(getContext());
//logoImageMaster.setImageResource(resID); //even after removing this, i am getting the callback twice
try {
// get input stream
InputStream ims = getActivity().getAssets().open("product_logo.png");
// load image as Drawable
Drawable d = Drawable.createFromStream(ims, null);
// set image to ImageView
logoImageMaster.setImageDrawable(d);
}
catch(IOException ex) {
}
logoImageMaster.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() { //FIXME get called twice. Check this out, no info to distinguish first from second
// Log.e("PANEL", "onGlobalLayout of Logo IV ---------------------------------");
activityInterface.doSpecialLogic();
}
});
return logoImageMaster;
}
My exact problem is, onGlobalLayout is called twice for this view hierarchy.
I know that onGlobalLayout is invoked in performTraversal of View.java hence this is expected.
For my use case of Single parent with Single child view, I want to distinguish the view attributes such that doSpecialLogic is called once[onGlobalLayout is called twice] , after the logo image is completely made visible.
Please suggest some ideas.
OnGlobalLayoutListener gets called every time the view layout or visibility changes. Maybe you reset the views in your doSpecialLogic call??
edit
as #Guille89 pointed out, the two set calls cause onGlobalLayout to be called two times
Anyhow, if you want to call OnGlobalLayoutListener just once and don't need it for anything else, how about removing it after doSpecialLogic() call??
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
//noinspection deprecation
logoImageMaster.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
logoImageMaster.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
activityInterface.doSpecialLogic();
It seems to be called one time for each set done over the imageView
logoImageMaster.setImageResource(resID);
logoImageMaster.setImageDrawable(d);
You should Try using kotlin plugin in android
This layout listener is usually used to do something after a view is measured, so you typically would need to wait until width and height are greater than 0. And we probably want to do something with the view that called it,in your case
Imageview
So generified the function so that it can be used by any object that extends View and also be able to access to all its specific functions and properties from the function
[kotlin]
inline fun <T: View> T.afterMeasured(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
f()
}
}
})
}
[/kotlin]
Note:
make sure that ImageView is described properly in the layout. That is its layout_width and layout_height must not be wrap_content. Moreover, other views must not result in this ImageView has 0 size.

TextView does not refresh the bitmap for getDrawingCache

I have done quite some research on this and answers did not help 100%, I did find a way to make it work but it's awful and I don't understand the behavior.
This is what I finally have:
private Bitmap generateBitmapFromTxt() {
mTxtEmojicon.setDrawingCacheEnabled(true);
Bitmap bmap = Bitmap.createBitmap(mTxtEmojicon.getDrawingCache());
mTxtEmojicon.setDrawingCacheEnabled(false);
}
But the bitmap is not refreshing. I also did try:
invalidate()
buildDrawingCache() destroyDrawingCache()
...
I have a custom class that extends from TextView and the bitmap generated did NOT wrap words unless dynamically forcing to calculate it's size. Like this:
private void prepareTxtViewSize() {
int specWidth = View.MeasureSpec.makeMeasureSpec(mEdt.getMeasuredWidth(), View.MeasureSpec.AT_MOST);
int specHeight = View.MeasureSpec.makeMeasureSpec(mEdt.getMeasuredHeight(), View.MeasureSpec.AT_MOST);
mTxt.measure(specWidth, specHeight);
mTxt.layout(0, 0, mTxt.getMeasuredWidth(), mTxt.getMeasuredHeight());
}
My custom TextView class does NOT override onDraw().
Now, my solution, awful solution, was to call prepareTxtViewSize() twice before calling generateBitmapFromTxt() and it works, the bitmap is refreshed and I always get the correct one.
But I am sure there is something I am missing and I am just lucky it works. So does somebody know what am I missing and/or why calling prepareTxtViewSize() twice actually makes the drawingCache refresh?

Android: hidden View and onDraw() event

The event onDraw() is emitted every time the View need to be repainted cause some graphic changes inside.
Unfortunately if the View is hidden (invisible) this event is not emitted since, obviously, there is no need to repaind anything. However I would to know if there is some trick to "cheat" the View to emit the onDraw() event and redraw itself exactly like it would really showed inside the screen.
Basically I need to capture screenshot status of a View component in all its changes but whitout show it (running it in background).
I guess it would be very hard to get such result but, just in case, I'll try to ask.
Thank you
I believe a View's visibility is checked by its parent and not the View itself. You can pass in a Canvas backed by a Bitmap straight in to View#draw(Canvas canvas) and it will draw itself on to the Bitmap. However, the based on the source code of View#setVisibility(), the View's background will still be invisible.
public void setVisibility(int visibility) {
setFlags(visibility, VISIBILITY_MASK);
if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false);
}
Everything else should appear in the View as is (unless it's children are also set to invisible of course).
EDIT:
Converting a view to Bitmap without displaying it in Android?
There are examples there on how to do that.
public static Bitmap getBitmapFromView(View view) {
Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedBitmap);
view.draw(canvas);
return returnedBitmap;
}
EDIT 2:
Since setVisibility() is not part of the View, you could override it and simply not set the View to be invisible. Something like:
boolean isInvisible = false;
#Override
public void setVisibility(int visibility) {
if (visibility == View.INVISIBLE) {
invisible = true;
} else {
invisible = false;
super.setVisibility();
}
}
#Override
public void onDraw(Canvas canvas) {
// change state code
if (!invisible) {
// draw code
super.onDraw(canvas);
}
}
#Override
public void draw(Canvas canvas) {
if (!invisible) {
// draw code
super.draw(canvas);
}
}
I have no idea what side-effects this would cause so be extremely careful and weary. Perhaps someone else would have a better solution. Another solution is you can simply call onDraw() on a different canvas whenever you want to draw it. This would require you to create a super class that is the parent layout View of the View you want to draw. Then in the parent's onDraw() method, call the child's onDraw() method separately if it's visibility is set to INVISIBLE.
If someone is interested I managed to solve this problem by override the invalidate() method. Instead of onDraw() that is called by the system only if the view is currently visible the invalidate() function is called "internally" and can be used to check if the view need to be reapinted in the same way.

getWidth Returns 0 in Fragment, getPaddingLeft Returns Non-Zero

I am trying to convert my Android app to Fragments to support multiple screen sizes and to use the new ICS tabs correctly. Previously I used the onWindowFocusChanged() method and ran the following code inside of it - basically this did some dynamic formatting of my layout after it was created.
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
LinearLayout theLayout = (LinearLayout)inflater.inflate(R.layout.tab_frag2_layout, container, false);
getWidthEditButton = (ImageButton) theLayout.findViewById(R.id.buttonEditPoints);
buttonAddPointsManual = (ImageView) theLayout.findViewById(R.id.buttonAddPointsManual);
linearPointsUsed = (LinearLayout) theLayout.findViewById(R.id.linearLayoutPointsUsed);
int paddingLeftForTracker = linearPointsUsed.getPaddingLeft();
int paddingRightForTracker = getWidthEditButton.getWidth();
linearPointsUsed.setPadding(paddingLeftForTracker, 0, paddingRightForTracker, 0);
}
Now that I have moved to Fragments and for some reason my paddingRightForTracker returns 0. I ran into an issue previously where I was trying to get width too early, hence my move to onWindowFocusChanged previously, but that is not available to Fragments. The strange thing is that paddingLeftForTracker actually returns a non-zero value.
If I set paddingRightForTracker manually, the change takes place so I know the code is running. Just can't figure out why my getWidth is returning 0.
Any help would be greatly appreciated.
You could try doing it in onActivityCreated(). So, you would save a reference to those views in onCreateView, and then access them in onActivityCreated(). I think the view isn't completed created when you're trying to access it, which is why it returns no width.
http://developer.android.com/reference/android/app/Fragment.html#onActivityCreated(android.os.Bundle)
Ok, so I found out about another way to get the width. I, too, cannot get a button width on neither onViewCreated, onCreateView, nor onResume. I found this, tried it, and it's returning a value, so maybe it'll work for you!
How to get height and width of Button
ViewTreeObserver vto = button.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
width = button.getWidth();
height = button.getHeight();
}
});
FYI, I ran this code in onResume, so I'm not exactly sure where else it could work.
This works for me and it looks cleaner (I am also using lambda but it's not required):
v.post(() -> {
int width = v.getWidth();
doSomething(width>300);
});

How to retrieve the dimensions of a view?

I have a view made up of TableLayout, TableRow and TextView. I want it to look like a grid. I need to get the height and width of this grid. The methods getHeight() and getWidth() always return 0. This happens when I format the grid dynamically and also when I use an XML version.
How to retrieve the dimensions for a view?
Here is my test program I used in Debug to check the results:
import android.app.Activity;
import android.os.Bundle;
import android.widget.TableLayout;
import android.widget.TextView;
public class appwig extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.maindemo); //<- includes the grid called "board"
int vh = 0;
int vw = 0;
//Test-1 used the xml layout (which is displayed on the screen):
TableLayout tl = (TableLayout) findViewById(R.id.board);
tl = (TableLayout) findViewById(R.id.board);
vh = tl.getHeight(); //<- getHeight returned 0, Why?
vw = tl.getWidth(); //<- getWidth returned 0, Why?
//Test-2 used a simple dynamically generated view:
TextView tv = new TextView(this);
tv.setHeight(20);
tv.setWidth(20);
vh = tv.getHeight(); //<- getHeight returned 0, Why?
vw = tv.getWidth(); //<- getWidth returned 0, Why?
} //eof method
} //eof class
I believe the OP is long gone, but in case this answer is able to help future searchers, I thought I'd post a solution that I have found. I have added this code into my onCreate() method:
EDITED: 07/05/11 to include code from comments:
final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
LayerDrawable ld = (LayerDrawable)tv.getBackground();
ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
ViewTreeObserver obs = tv.getViewTreeObserver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
obs.removeOnGlobalLayoutListener(this);
} else {
obs.removeGlobalOnLayoutListener(this);
}
}
});
First I get a final reference to my TextView (to access in the onGlobalLayout() method). Next, I get the ViewTreeObserver from my TextView, and add an OnGlobalLayoutListener, overriding onGLobalLayout (there does not seem to be a superclass method to invoke here...) and adding my code which requires knowing the measurements of the view into this listener. All works as expected for me, so I hope that this is able to help.
I'll just add an alternative solution, override your activity's onWindowFocusChanged method and you will be able to get the values of getHeight(), getWidth() from there.
#Override
public void onWindowFocusChanged (boolean hasFocus) {
// the height will be set at this point
int height = myEverySoTallView.getMeasuredHeight();
}
You are trying to get width and height of an elements, that weren't drawn yet.
If you use debug and stop at some point, you'll see, that your device screen is still empty, that's because your elements weren't drawn yet, so you can't get width and height of something, that doesn't yet exist.
And, I might be wrong, but setWidth() is not always respected, Layout lays out it's children and decides how to measure them (calling child.measure()), so If you set setWidth(), you are not guaranteed to get this width after element will be drawn.
What you need, is to use getMeasuredWidth() (the most recent measure of your View) somewhere after the view was actually drawn.
Look into Activity lifecycle for finding the best moment.
http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle
I believe a good practice is to use OnGlobalLayoutListener like this:
yourView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (!mMeasured) {
// Here your view is already layed out and measured for the first time
mMeasured = true; // Some optional flag to mark, that we already got the sizes
}
}
});
You can place this code directly in onCreate(), and it will be invoked when views will be laid out.
Use the View's post method like this
post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "width " + MyView.this.getMeasuredWidth());
}
});
I tried to use onGlobalLayout() to do some custom formatting of a TextView, but as #George Bailey noticed, onGlobalLayout() is indeed called twice: once on the initial layout path, and second time after modifying the text.
View.onSizeChanged() works better for me because if I modify the text there, the method is called only once (during the layout pass). This required sub-classing of TextView, but on API Level 11+ View. addOnLayoutChangeListener() can be used to avoid sub-classing.
One more thing, in order to get correct width of the view in View.onSizeChanged(), the layout_width should be set to match_parent, not wrap_content.
Are you trying to get sizes in a constructor, or any other method that is run BEFORE you get the actual picture?
You won't be getting any dimensions before all components are actually measured (since your xml doesn't know about your display size, parents positions and whatever)
Try getting values after onSizeChanged() (though it can be called with zero), or just simply waiting when you'll get an actual image.
As F.X. mentioned, you can use an OnLayoutChangeListener to the view that you want to track itself
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Make changes
}
});
You can remove the listener in the callback if you only want the initial layout.
I guess this is what you need to look at: use onSizeChanged() of your view. Here is an EXTENDED code snippet on how to use onSizeChanged() to get your layout's or view's height and width dynamically http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html
ViewTreeObserver and onWindowFocusChanged() are not so necessary at all.
If you inflate the TextView as layout and/or put some content in it and set LayoutParams then you can use getMeasuredHeight() and getMeasuredWidth().
BUT you have to be careful with LinearLayouts (maybe also other ViewGroups). The issue there is, that you can get the width and height after onWindowFocusChanged() but if you try to add some views in it, then you can't get that information until everything have been drawn. I was trying to add multiple TextViews to LinearLayouts to mimic a FlowLayout (wrapping style) and so couldn't use Listeners. Once the process is started, it should continue synchronously. So in such case, you might want to keep the width in a variable to use it later, as during adding views to layout, you might need it.
Even though the proposed solution works, it might not be the best solution for every case because based on the documentation for ViewTreeObserver.OnGlobalLayoutListener
Interface definition for a callback to be invoked when the global layout state or the visibility of views within the view tree changes.
which means it gets called many times and not always the view is measured (it has its height and width determined)
An alternative is to use ViewTreeObserver.OnPreDrawListener which gets called only when the view is ready to be drawn and has all of its measurements.
final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnPreDrawListener(new OnPreDrawListener() {
#Override
public void onPreDraw() {
tv.getViewTreeObserver().removeOnPreDrawListener(this);
// Your view will have valid height and width at this point
tv.getHeight();
tv.getWidth();
}
});
Height and width are zero because view has not been created by the time you are requesting it's height and width . One simplest solution is
view.post(new Runnable() {
#Override
public void run() {
view.getHeight(); //height is ready
view.getWidth(); //width is ready
}
});
This method is good as compared to other methods as it is short and crisp.
You should rather look at View lifecycle: http://developer.android.com/reference/android/view/View.html Generally you should not know width and height for sure until your activity comes to onResume state.
You can use a broadcast that is called in OnResume ()
For example:
int vh = 0;
int vw = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.maindemo); //<- includes the grid called "board"
registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
TableLayout tl = (TableLayout) findViewById(R.id.board);
tl = (TableLayout) findViewById(R.id.board);
vh = tl.getHeight();
vw = tl.getWidth();
}
}, new IntentFilter("Test"));
}
protected void onResume() {
super.onResume();
Intent it = new Intent("Test");
sendBroadcast(it);
}
You can not get the height of a view in OnCreate (), onStart (), or even in onResume () for the reason that kcoppock responded
Simple Response: This worked for me with no Problem.
It seems the key is to ensure that the View has focus before you getHeight etc. Do this by using the hasFocus() method, then using getHeight() method in that order. Just 3 lines of code required.
ImageButton myImageButton1 =(ImageButton)findViewById(R.id.imageButton1);
myImageButton1.hasFocus();
int myButtonHeight = myImageButton1.getHeight();
Log.d("Button Height: ", ""+myButtonHeight );//Not required
Hope it helps.
Use getMeasuredWidth() and getMeasuredHeight() for your view.
Developer guide: View
CORRECTION:
I found out that the above solution is terrible. Especially when your phone is slow.
And here, I found another solution:
calculate out the px value of the element, including the margins and paddings:
dp to px:
https://stackoverflow.com/a/6327095/1982712
or dimens.xml to px:
https://stackoverflow.com/a/16276351/1982712
sp to px:
https://stackoverflow.com/a/9219417/1982712 (reverse the solution)
or dimens to px:
https://stackoverflow.com/a/16276351/1982712
and that's it.

Categories

Resources