How to allocate a bitmap in custom surfaceview after onMeasure? - android

I have a custom SurfaceView, the init function loads all Bitmaps and it's called from the constructor, but I want resize a Bitmap and I can do this only after onMeasure and before onDraw.This is my OnMeasure method inside my custom SurfaceView:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
scrollableBg = Bitmap.createScaledBitmap(srcScrollableBg, measuredWidth, measuredHeight, true);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
the IDE says "Avoid object allocations during draw/layout operations (preallocate and reuse instead)" for the scrollableBg allocating, but I can't do this in the init because I haven't the measuredWidth and the measuredHeight...

Consider using onLayout:
protected void onLayout (boolean changed, int left, int top, int
right, int bottom)
Added in API level 1 Called from layout when this view should assign a
size and position to each of its children. Derived classes with
children should override this method and call layout on each of their
children.
It’s called after onMeasure. A good practice could be to allocate your object only when changed == true: you will get the same warning from the IDE, but this way you can safely ignore it.
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed) {
// allocate bitmap
// get width with right - left
// get height with bottom - top
}
super.onLayout(changed, left, top, right, bottom);
}

Related

When should I draw in a custom layout based on its size

I am creating a new ViewGroup. The new view will have some circles drawn in it. The view is supposed to have 5 initial circles in it so I want to spread them evenly across the width of the view, and also keep track of them (their center's (x,y) position) in order to redraw them when the view is invalidated.
This is my onMeasure:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int desiredWidth = getPaddingLeft() + getPaddingRight() + PREFERED_SIZE;
int desiredHeight = getPaddingTop() + getPaddingBottom() + PREFERED_SIZE;
actualWidth = resolveSizeAndState(desiredWidth,widthMeasureSpec,0);
actualHeight = resolveSizeAndState(desiredHeight,heightMeasureSpec,0);
setMeasuredDimension(actualWidth, actualHeight);
}
What I'm not sure is when should I add these circles. onMeasure can be called several times and get a different width and height values so I'm not sure when should I calculate the (x,y) for the initial circles.. inside onMeasure? in the beginning of onDraw?
Just check on the docs. There're 3 callbacks on the measurement part and I guess you can do on the last one: https://developer.android.com/reference/android/view/View.html
onMeasure(int, int) Called to determine the size requirements for this view and all of its children.
onLayout(boolean, int, int, int, int) Called when this view should assign a size and position to all of its children.
onSizeChanged(int, int, int, int) Called when the size of this view has changed.
So I guess the best for your calculations is onSizeChanged.
You can use View.OnLayoutChangeListener to track the layout changes :
public class CustomView extends View implements View.OnLayoutChangeListener {
private int height;
private int width;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
// add the layout listener
addOnLayoutChangeListener(this);
}
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
height = getHeight();
width = getWidth();
}
}

If inflater is not blocking, then how do I know when it's done? [duplicate]

I have created a custom layout which extends ViewGroup. Everything is working fine and I am getting the layout as expected.
I want to dynamically change an element in the layout. However this is not working as I am calling it in onCreate and till that time the entire layout is not actually (drawn) inflated onto the screen and hence do not have actual size.
Is there any event which can be used to find out when the inflation of the layout is done? I tried onFinishInflate but that would not work as Viewgroup has multiple views and this would be called multiple times.
I am thinking of creating an interface in the Custom layout class but not sure when to fire it?
If I understand your requirements correctly, an OnGlobalLayoutListener may give you what you need.
View myView=findViewById(R.id.myView);
myView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//At this point the layout is complete and the
//dimensions of myView and any child views are known.
}
});
Usually when creating a custom layout extending View or ViewGroup, you have to override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) and protected void onLayout(boolean changed, int left, int top, int right, int bottom). These are called during the process of inflation in order to obtain the size and location information related to the view. Also, subsequently, if you are extending ViewGroup you are to call measure(int widthMeasureSpec, int heightMeasureSpec) and layout(int l, int t, int r, int b) on every child view contained within. (measure() is called in onMeasure() and layout() is called in onLayout()).
Anyway, in onMeasure(), you generally do something like this.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// Gather this view's specs that were passed to it
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int chosenWidth = DEFAULT_WIDTH;
int chosenHeight = DEFAULT_HEIGHT;
if(widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.EXACTLY)
chosenWidth = widthSize;
if(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.EXACTLY)
chosenHeight = heightSize;
setMeasuredDimension(chosenWidth, chosenHeight);
*** NOW YOU KNOW THE DIMENSIONS OF THE LAYOUT ***
}
In onLayout() you get the actual pixel coordinates of the View, so you can get the physical size like so:
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
// Android coordinate system starts from the top-left
int width = right - left;
int height = bottom - top;
}

force onLayout in a subView whose dimensions doesn't change

I have a Custom ViewGroup with some views inside.
In response to an event, a requestLayout is called and in OnLayout some of the views will
get a new layout position.
One of them maintain its position but needs to be rearranged inside. However this view's onlayout method will not be called since its layout position doesn't change. Trying to call requestLayout, forceLayout or invalidate in this view doesn't work.
The only dirty trick that I have found is to change some of its value position to force the layout change, but I think there should be a better way. So, by now I do something (horrible) like:
int patch = 0;
#Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
...
_myView.patchedLayout(patch);
_myview.layout(l1, t1 , r1, t1+patch);
patch = (patch+1) % 2;
...
}
Any way to get the same result in a better way?
I finally got the solution: I need to override onMeasure and be sure to call mesure in my view:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
_myview.measure(widthMeasureSpec, heightMeasureSpec);
...
It will set the LAYOUT_REQUIRED flag in the view's private field mPrivateFlags so it will force to call onLayout

Authorative way to override onMeasure()?

What's the correct way of overriding onMeasure()? I've seen various approaches. For example, Professional Android Development uses MeasureSpec to calculate the dimensions, then ends with a call to setMeasuredDimension(). For example:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
}
On the other hand, as per this post, the "correct" way is to use MeasureSpec, call setMeasuredDimensions(), followed by a call to setLayoutParams(), and ending with a call to super.onMeasure(). For example:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
So which is the right way? Neither approach has worked 100% for me.
I guess really what I'm asking is does anyone know of a tutorial that explains onMeasure(), layout, dimensions of child views etc.?
The other solutions are not comprehensive. They may work in some cases, and are a good place to start, but they may are not guaranteed to work.
When onMeasure gets called you may or may not have the rights to change the size. The values that are passed to your onMeasure (widthMeasureSpec, heightMeasureSpec) contain information about what your child view is allowed to do. Currently there are three values:
MeasureSpec.UNSPECIFIED - You can be as big as you'd like
MeasureSpec.AT_MOST - As big as you want (up to the spec size), This is parentWidth in your example.
MeasureSpec.EXACTLY - No choice. Parent has chosen.
This is done so that Android can make multiple passes to find the right size for each item, see here for more details.
If you do not follow these rules, your approach is not guaranteed to work.
For example if you want to check if you're allowed to change the size at all you can do the following:
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
Using this information you will know whether you can modify the values as in your code. Or if you are required to do something different. A quick and easy way to resolve your desired size is to use one of the following methods:
int resolveSizeAndState (int size, int measureSpec, int childMeasuredState)
int resolveSize (int size, int measureSpec)
While the first is only available on Honeycomb, the second is available on all versions.
Note: You may find that resizeWidth or resizeHeight are always false. I found this to be the case if I was requesting MATCH_PARENT. I was able to fix this by requesting WRAP_CONTENT on my parent layout and then during the UNSPECIFIED phase requesting a size of Integer.MAX_VALUE. Doing so gives you the max size your parent allows on the next pass through onMeasure.
The documentation is the authority on this matter: http://developer.android.com/guide/topics/ui/how-android-draws.html and http://developer.android.com/guide/topics/ui/custom-components.html
To summarize: at the end of your overridden onMeasure method you should call setMeasuredDimension.
You should not call super.onMeasure after calling setMeasuredDimension, that will just erase whatever you set. In some situations you might want to call the super.onMeasure first and then modify the results by calling setMeasuredDimension.
Don't call setLayoutParams in onMeasure. Layout happens in a second pass after measuring.
I think it depends on the parent which you are overriding.
For example, if you are extending a ViewGroup (like FrameLayout), when you have measured the size, you should call like below
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
because you may want to ViewGroup to do rest work (do some stuffs on child view)
If you are extending a View (like ImageView), you can just call this.setMeasuredDimension(width, height);, because the parent class will just do something like you have done usually.
In a word, if you want some features your parent class offers free you should call super.onMeasure() (pass MeasureSpec.EXACTLY mode measure spec usually), otherwise call this.setMeasuredDimension(width, height); is enough.
If changing the views size inside of onMeasure all you need is the setMeasuredDimension call. If you are changing the size outside of onMeasure you need to call setLayoutParams. For instance changing the size of a text view when the text is changed.
Depends on the control you are using. The instructions in the documentation work for some controls (TextView, Button, ...), but not for others (LinearLayout, ...). The way that worked very well for me was to call the super once I am done. Based on the article in the below link.
http://humptydevelopers.blogspot.in/2013/05/android-view-overriding-onmeasure.html
here is how I solved the problem:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
....
setMeasuredDimension( measuredWidth, measuredHeight );
widthMeasureSpec = MeasureSpec.makeMeasureSpec( measuredWidth, MeasureSpec.EXACTLY );
heightMeasureSpec = MeasureSpec.makeMeasureSpec( measuredHeight, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Moreover it was necessary for the ViewPager component
I guess, setLayoutParams and recalculating the measurements is a workaround to resize the child views correctly, as this is usually done in the derived class's onMeasure.
However, this rarely works correct (for whatever reason...), better invoke measureChildren (when deriving a ViewGroup) or try something similar when necessary.
you can take this piece of code as an example of onMeasure()::
public class MyLayerLayout extends RelativeLayout {
public MyLayerLayout(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
int currentChildCount = getChildCount();
for (int i = 0; i < currentChildCount; i++) {
View currentChild = getChildAt(i);
//code to find information
int widthPercent = currentChildInfo.getWidth();
int heightPercent = currentChildInfo.getHeight();
//considering we will pass height & width as percentage
int myWidth = (int) Math.round(parentWidth * (widthPercent / 100.0));
int myHeight = (int) Math.round(parentHeight * (heightPercent / 100.0));
//Considering we need to set horizontal & vertical position of the view in parent
AlignmentTraitValue vAlign = currentChildInfo.getVerticalLocation() != null ? currentChildlayerInfo.getVerticalLocation() : currentChildAlignmentTraitValue.TOP;
AlignmentTraitValue hAlign = currentChildInfo.getHorizontalLocation() != null ? currentChildlayerInfo.getHorizontalLocation() : currentChildAlignmentTraitValue.LEFT;
int topPadding = 0;
int leftPadding = 0;
if (vAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
topPadding = (parentHeight - myHeight) / 2;
} else if (vAlign.equals(currentChildAlignmentTraitValue.BOTTOM)) {
topPadding = parentHeight - myHeight;
}
if (hAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
leftPadding = (parentWidth - myWidth) / 2;
} else if (hAlign.equals(currentChildAlignmentTraitValue.RIGHT)) {
leftPadding = parentWidth - myWidth;
}
LayoutParams myLayoutParams = new LayoutParams(myWidth, myHeight);
currentChildLayoutParams.setMargins(leftPadding, topPadding, 0, 0);
currentChild.setLayoutParams(myLayoutParams);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

Is it possible to add views to layout in onLayout event?

Is it possible to add views to a layout during the onLayout event of one of its Childs?
i.e.
FrameLayout contains View, in View.onLayout() I want to add views to the parent FrameLayout.
This is because the views I need to draw on the FrameLayout needs the child View dimensions (width, height) to assign them to particular positions on the FrameLayout.
I already try to do so, but nothing is getting drawn. Do you know how can I accomplish the same effect? or if I'm doing something wrong. Don't know why I'm unable to draw the views, event if I call invalidate.
Thanks.
Yes, it's possible. I have solved similar problem (placing a checkpoint Button into FrameLayout over SeekBar) using the following code (overriden methods from SeekBar):
#Override
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
super.onLayout(changed, left, top, right, bottom);
View child = new Button(getContext());
//child measuring
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, 0, LayoutParams.WRAP_CONTENT); //mWidthMeasureSpec is defined in onMeasure() method below
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);//we let child view to be as tall as it wants to be
child.measure(childWidthSpec, childHeightSpec);
//find were to place checkpoint Button in FrameLayout over SeekBar
int childLeft = (getWidth() * checkpointProgress) / getMax() - child.getMeasuredWidth();
LayoutParams param = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
param.gravity = Gravity.TOP;
param.setMargins(childLeft, 0, 0, 0);
//specifying 'param' doesn't work and is unnecessary for 1.6-2.1, but it does the work for 2.3
parent.addView(child, firstCheckpointViewIndex + i, param);
//this call does the work for 1.6-2.1, but does not and even is redundant for 2.3
child.layout(childLeft, 0, childLeft + child.getMeasuredWidth(), child.getMeasuredHeight());
}
#Override
protected synchronized void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//we save widthMeasureSpec in private field to use it for our child measurment in onLayout()
mWidthMeasureSpec = widthMeasureSpec;
}
There is also ViewGroup.addViewInLayout() method (it's protected, so you can use it only if you override onLayout method of your Layout) which javadoc says its purpose is exactly what we discuss here, but I haven't understood why is it better than addView(). You can find it's usage in ListView.

Categories

Resources