Android OnLayout and onMeasure not being Invoked - android

I have created a custom view group. I implemented the onLayout and onMeasured inside that group. But both these methods are only being called once, i.e for the first time. When i remove a child view and add a new child the onLayout and onMeasured are not being invoked. Any help would be appreciated.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// Iterate through all children, measuring them and computing our dimensions
// from their size.
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
// Measure the child.
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
}
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
setMeasuredDimension(displayMetrics.widthPixels,displayMetrics.heightPixels);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
// These are the far left and right edges in which we are performing layout.
int topPosition = 0;
int leftPosition = 0;
int rightPosition = 0;
int bottomPosition = 0;
int i;
int j;
int decrementX = 0;
int incrementX = 0;
int verticalGap = 75;
int width;
int height;
View child;
if( count > 0 ){
int medium = count/2;
int gap = getMeasuredWidth()/count;
if( count == 1 ){
child = this.getChildAt(0);
width = child.getMeasuredWidth();
height = child.getMeasuredHeight();
leftPosition = getMeasuredWidth()/2 - width/2;
rightPosition = getMeasuredWidth() - ( getMeasuredWidth() - ( leftPosition + width ));
topPosition = 10;
bottomPosition = getMeasuredHeight() - ( getMeasuredHeight() - (topPosition + height));
child.layout(leftPosition,topPosition,rightPosition,bottomPosition);
}
else if( count%2 == 0 ){
child = this.getChildAt(0);
width = child.getMeasuredWidth();
height = child.getMeasuredHeight();
gap = getMeasuredWidth()/(count+1);
decrementX = incrementX = getMeasuredWidth()/2 - width/2;
for(i=medium,j=medium-1;(i<count && j>=0);i++,j--){
child = this.getChildAt(i);
width = child.getMeasuredWidth();
height = child.getMeasuredHeight();
decrementX -= gap;
leftPosition = decrementX;
rightPosition = getMeasuredWidth() - (getMeasuredWidth() - ( leftPosition + width ));
topPosition += verticalGap;
bottomPosition = getMeasuredHeight() - ( getMeasuredHeight() - ( topPosition + height ));
child.layout(leftPosition,topPosition,rightPosition,bottomPosition);
child = this.getChildAt(j);
incrementX += gap;
leftPosition = incrementX;
rightPosition = getMeasuredWidth() - ( getMeasuredWidth() - ( leftPosition + width ));
child.layout(leftPosition,topPosition,rightPosition,bottomPosition);
}
}
else if( count%3 == 0 || count%3 == 2 ){
child = this.getChildAt(medium);
if( child.getVisibility() != GONE ){
width = child.getMeasuredWidth();
height = child.getMeasuredHeight();
topPosition = 10;
leftPosition = getMeasuredWidth()/2 - width/2;
bottomPosition = getMeasuredHeight() - ( getMeasuredHeight() - ( topPosition + height ) );
rightPosition = getMeasuredWidth() - (getMeasuredWidth() - (leftPosition + width));
child.layout(leftPosition,topPosition,rightPosition,bottomPosition);
decrementX = leftPosition;
incrementX = leftPosition;
}
for(i=medium+1,j=medium-1;(i<count && j>=0); i++,j--){
child = this.getChildAt(i);
width = child.getMeasuredWidth();
height = child.getMeasuredHeight();
decrementX -= gap;
leftPosition = decrementX;
rightPosition = getMeasuredWidth() - ( getMeasuredWidth() - ( width + leftPosition ));
topPosition += verticalGap;
bottomPosition = getMeasuredHeight() - ( getMeasuredHeight() - (height + topPosition));
child.layout(leftPosition,topPosition,rightPosition,bottomPosition);
child = this.getChildAt(j);
width = child.getMeasuredWidth();
height = child.getMeasuredHeight();
incrementX += gap;
leftPosition = incrementX;
rightPosition = getMeasuredWidth() - ( getMeasuredWidth() - (width + leftPosition));
child.layout(leftPosition,topPosition,rightPosition,bottomPosition);
// final View leftView = getChildAt(j);
}
}
}
The above are the onMeasure and onLayout methods.

Related

Custom ViewGroup, last child in a row Match parent to fill all empty space

I'm trying to build a custom ViewGroup base on DavidPizarro/AutoLabelUI
https://github.com/DavidPizarro/AutoLabelUI
My goal is to create a page that looks like this:
but this is the closest that I got:
This is the important part of my code:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
totalWidth = sizeWidth;
int width = 0;
int height = getPaddingTop() + getPaddingBottom();
int lineWidth = 0;
int lineHeight = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// child.setMinimumWidth(1080);
boolean lastChild = i == childCount - 1;
if (child.getVisibility() == View.GONE) {
if (lastChild) {
width = Math.max(width, lineWidth);
height += lineHeight;
}
continue;
}
Logger.Log("");
//child.setMinimumWidth(300);
measureChildWithMargins(child, widthMeasureSpec, lineWidth, heightMeasureSpec, height);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childWidthMode = MeasureSpec.AT_MOST;
int childWidthSize = sizeWidth;
int childHeightMode = MeasureSpec.AT_MOST;
int childHeightSize = sizeHeight;
if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthMode = MeasureSpec.EXACTLY;
childWidthSize -= lp.leftMargin + lp.rightMargin;
} else if (lp.width >= 0) {
childWidthMode = MeasureSpec.EXACTLY;
childWidthSize = lp.width;
}
if (lp.height >= 0) {
childHeightMode = MeasureSpec.EXACTLY;
childHeightSize = lp.height;
} else if (modeHeight == MeasureSpec.UNSPECIFIED) {
childHeightMode = MeasureSpec.UNSPECIFIED;
childHeightSize = 0;
}
Logger.Log("");
child.measure(
MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode),
MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode)
);
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
if (lineWidth + childWidth > sizeWidth) {
// NEW LINE!!! this code means that the width of the child is bןgger then the space left!
width = Math.max(width, lineWidth);
lineWidth = childWidth;
height += lineHeight;
lineHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
} else {
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
if (lastChild) {
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
// end of loop.
width += getPaddingLeft() + getPaddingRight();
setMeasuredDimension(
(modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width,
(modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height);
}
/**
* This method will place each label next to another. If there is not enough
* space for the next label, it will be added in a new new line.
* <p>
*/
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mLines.clear();
mLineHeights.clear();
mLineMargins.clear();
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
lineViews.clear();
float horizontalGravityFactor = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.bottomMargin + lp.topMargin;
if (lineWidth + childWidth > width) {
mLineHeights.add(lineHeight);
mLines.add(lineViews);
mLineMargins.add((int) ((width - lineWidth) * horizontalGravityFactor) + getPaddingLeft());
lineHeight = 0;
lineWidth = 0;
lineViews = new ArrayList<>();
}
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
lineViews.add(child);
}
// END OF LOOP.
mLineHeights.add(lineHeight);
mLines.add(lineViews);
mLineMargins.add((int) ((width - lineWidth) * horizontalGravityFactor) + getPaddingLeft());
int verticalGravityMargin = 0;
int numLines = mLines.size();
int left;
int top = getPaddingTop();
// now we are looping threw the number of row in total - mLines.size(), and we want to measure each tag!
for (int i = 0; i < numLines; i++) {
lineHeight = mLineHeights.get(i);
// lineViews = number of lines for child!
lineViews = mLines.get(i);
left = mLineMargins.get(i);
int children = lineViews.size();
for (int j = 0; j < children; j++) {
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE) {
continue;
}
LayoutParams lp = (LayoutParams) child.getLayoutParams();
// if height is match_parent we need to remeasure child to line height
if (lp.height == LayoutParams.MATCH_PARENT) {
int childWidthMode = MeasureSpec.AT_MOST;
int childWidthSize = lineWidth;
if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthMode = MeasureSpec.EXACTLY;
} else if (lp.width >= 0) {
childWidthMode = MeasureSpec.EXACTLY;
childWidthSize = lp.width;
}
child.measure(
MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode),
MeasureSpec.makeMeasureSpec(lineHeight - lp.topMargin - lp.bottomMargin,
MeasureSpec.EXACTLY)
);
}
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int gravityMargin = 0;
if (Gravity.isVertical(lp.gravity)) {
switch (lp.gravity) {
case Gravity.TOP:
default:
break;
case Gravity.CENTER_VERTICAL:
case Gravity.CENTER:
gravityMargin = (lineHeight - childHeight - lp.topMargin - lp.bottomMargin) / 2;
break;
case Gravity.BOTTOM:
gravityMargin = lineHeight - childHeight - lp.topMargin - lp.bottomMargin;
break;
}
}
child.layout(left + lp.leftMargin,
top + lp.topMargin + gravityMargin + verticalGravityMargin,
left + childWidth + lp.leftMargin,
top + childHeight + lp.topMargin + gravityMargin + verticalGravityMargin);
left += childWidth + lp.leftMargin + lp.rightMargin;
}
// end of loop
top += lineHeight;
}
// end of loop
}
I would appreciate if you can show me what I have to do to change the size of the child to make it stretched all the way to fill the empty space.
I honestly don't know what all that code you posted above was doing, too long, too little sense made, don't even know how it worked.
Out of curiosity, I'm gonna try solve this problem directly here, I hope it works.
First the onMeasure() method:
// maximum available width for children
int maxW = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
// you want all children to be as wide as they want, but not wider than the view group
int widthMS = MeasureSpec.makeMeasureSpec(w, MeasureSpec.AT_MOST);
// you probably want them all to have the same height.
// maybe define it in xml attrs?
int heightMS = MeasureSpec.makeMeasureSpec(rowHeight, MeasureSpec.EXACTLY);
// remaining width of current row, initially equals to w
int remW = w;
// index of first child in the current row
int rowFirstChild = 0;
// row count, used to calculate the view group's height
int rows = 0;
int i = 0;
while (i < getChildCount()) {
Child ch = getChildAt(i);
if (ch.getVisibility() != View.GONE) {
// ask how big does it want to be
child.measure(widthMS, heightMS);
int chWidth = ch.getMeasuredWidth();
// if it fits
if (remW - chWidth >= 0) {
// update remW, then go to next child
remW -= chWidth;
i++;
}
// if it does not fit
else {
// if it's not the first child in the row
if (i > rowFirstChild) {
stretchViews(rowFirstChild, i - 1, maxW, remW, heightMS);
// this will be the first child in the next row and take the space from
// that row's remW
rowFirstChild = i;
remW = maxW - chWidth;
}
// if it's the first and only child on this row
else {
// it should just take the full width and give first spot in the next
// row to the next child, and reset remW
rowFirstChild = i + 1;
remW = maxW;
}
// both of these scenarios result in the addition of a new row
rows++;
}
}
}
// finally, we want to take as much width as we can, and as minimum height as we can
int finalW = MeasureSpec.getSize(widthMeasureSpec);
int finalH = Math.min(
rows * rowHeight + getPaddingTop() + getPaddingBottom(),
MeasureSpec.getSize(heightMeasureSpec)
);
setMeasuredDimensions(finalW, finalH);
// note that this measurement ignores height measure spec and automatically takes the
// minimum height, and ignores the height measure spec of its children as well and
// just gives them height from its rowHeight attr.
// you are free to change this setting.
Now, onLayout() method. Here, since we already assigned measurements to our children, they are all set, we simply have to lay them out, not much work to do:
// maximum usable width in any row
int maxW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
// left starting point
int left = getPaddingLeft();
// top starting point
int top = getPaddingTop();
int i = 0;
while (i < getChildCount()) {
Child ch = getChildAt(i);
if (ch.getVisibility() != GONE) {
int chWidth = ch.getMeasuredWidth();
ch.layout(
left,
top,
left + chWidth,
top + rowHeight
);
// if this child is the last in row (-1 for error tolerance when rounding ints)
if (left + chWidth >= maxW - 1) {
// reset'left' back to its starting point
left = getPaddingLeft();
// move 'top' down by 'rowHeight'
top += rowHeight;
}
// if it wasn't the last child in the row
else {
// then simply advance the 'left' position
left += chWidth;
}
}
}
// Note that this method only supports LTR layout.
// You can make minor changes to add support for RTL.
// Also, margins between children are ignored, spend some time on that if you want
Let me know if this works
Update:
Never mind, I'll just do the stretching method anyway:
/**
* Stretches children of this view group horizontally, and proportionally to their widths
* to span the entire row width
*
* #param firstIndex index of first child to stretch (inclusive)
* #param lastIndex index of last child to stretch (inclusive)
* #param fullWidth the width to cover (max available width for row children)
* #param remWidth remaining width to cover
* #param heightMS the hight measure spec to reuse on these views
*/
private void stretchViews(int firstIndex, int lastIndex, int fullWdith, int
remWidth, int heightMS) {
for (int i = firstIndex; i <= lastIndex; i++) {
Child ch = getChildAt(i);
if (ch.getVisibility() != GONE) {
int measuredW = ch.getMeasuredWidth();
int widthMS = MeasureSpec.makeMeasureSpec(
measuredW + remWidth * measuredW / fullWidth, MeasureSpec.EXACTLY);
ch.measure(widthMS, heightMS);
}
}
}

Put View Into Circular Layout Corner Android

I am made a semi circle custom layout. In which i want to put the my view into the outer surface of the semi circle. but it i not showing exactly i want.
I the below code I have override the OnLayout method of view group.
I have posted my code below:-
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int mAngleRange = 180;
final int childs = getChildCount();
float totalWeight = 0f;
for (int i = 0; i < childs; i++) {
totalWeight += 1;
}
float startAngle = 270;
final int width = getWidth();
final int height = getHeight();
for (int i = 0; i < childs; i++) {
final View child = getChildAt(i);
final LayoutParams lp = layoutParams(child);
final float angle = mAngleRange / totalWeight * 1;
final float centerAngle = startAngle + angle / 2f;
final int x;
final int y;
if (childs > 1) {
int radius = getResources().getDimensionPixelOffset(R.dimen.item_radius);
x = (int) (radius * Math.cos(Math.toRadians(centerAngle)) + 0);
y = (int) (radius * Math.sin(Math.toRadians(centerAngle))) + height / 2;
} else {
x = width / 2;
y = height / 2;
}
final int left = lp.width != LayoutParams.MATCH_PARENT ? x : 0;
final int top = lp.height != LayoutParams.MATCH_PARENT ? y : 0;
final int right = lp.width != LayoutParams.MATCH_PARENT ? x + child.getMeasuredWidth() : width;
final int bottom = lp.height != LayoutParams.MATCH_PARENT ? y + child.getMeasuredHeight() : height;
Log.d(CircularOverlayLayout.class.getName(), "left =" + left + " top =" + top + " right =" + right + " bottom = " + bottom);
child.layout(left, top, right, bottom);
startAngle += angle;
}
}
<com.trignodev.customuidemo.CircularOverlayLayout
android:id="#+id/normalWithRange"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white">
<View
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ff0000" />
<View
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#00ff00" />
<View
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#0000ff" />
</com.trignodev.customuidemo.CircularOverlayLayout>

Custom view attribute wrap_content not work

A bit difficult to explain, hope everyone get my point.
At onMeasure() I done this to make wrap_content work as expect.
// Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
but deiredWidth required getHeight() method to calculate
which getHight() not ready so, I can't calculate correct value from here.
So, is there a way to get given width from layout_width attribute at onMeasure()
or do I misunderstand somethings.
All Relevant Code
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int desiredWidth = getTotalIndicatorWidth(getMeasuredHeight());
int desiredHeight = DEFAULT_HEIGHT;
// Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
setMeasuredDimension(width, height);
}
private int getTotalIndicatorWidth(int h) {
int width = 0;
int indicatorWidth = (h == 0) ? DEFAULT_HEIGHT : h;
for (int i = 0; i < indicatorSize; i++) {
if (i != 0) {
width = width + indicatorSpacing + indicatorWidth;
} else {
width = indicatorWidth;
}
}
return width;
}
#Override
protected void onDraw(Canvas canvas) {
pen.setStyle(Paint.Style.STROKE);
pen.setColor(indicatorColor);
pen.setStrokeWidth(indicatorStokeWidth);
pen.setAntiAlias(true);
drawIndicator(canvas, indicatorSize, 1);
}
private void drawIndicator(Canvas paper, int indicatorSize, int position) {
int indent = 0;
int cr = (getHeight() / 2) - getPaddingTop() - getPaddingBottom() - indicatorStokeWidth;
int cx = cr + getPaddingLeft() + indicatorStokeWidth;
int cy = (getHeight() / 2) + getPaddingTop() - getPaddingBottom();
for (int i = 0; i < indicatorSize; i++) {
paper.drawCircle(cx + indent, cy, cr, pen);
indent = (cr * 2) + (indicatorStokeWidth) + indent;
if ((i+1) < indicatorSize) {
indent += indicatorSpacing;
}
}
}
As you can see, the below image shown the area of custom view even it layout_width was set as wrap_content but it doesn't work for me now.
You say that you need to access getHeight() to calculate desiredWidth inside the onMeasure() method and also that getHeight() is not ready.
Yes, getHeight() or getWidth() can't be accessed from onMeasure() because these height and width are set in onMeasure() method. In your onMeause() method, you need to call setMeasuredDimension(). You have done that in your code.
setMeasuredDimension(width, height);
This width is what you will get when you call getWidth() and this height is what you will get when you call getHeight().
So if you want to access getHeight(), then use height instead.
The following code solve my problem of how can I get my layout_width or layout_height at onMesaure() so I can calculate my view correctly when user request for wrap_content
public PageIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
int[] attrsArray = new int[] {
android.R.attr.id, // 0
android.R.attr.background, // 1
android.R.attr.layout_width, // 2
android.R.attr.layout_height, // 3
};
TypedArray taa = context.obtainStyledAttributes(attrs, attrsArray);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PageIndicator, 0, 0);
try {
indicatorColor = ta.getColor(R.styleable.PageIndicator_indicator_color, Color.WHITE);
indicatorStokeWidth = (int) ta.getDimension(R.styleable.PageIndicator_indicator_stokeWidth, 4);
indicatorSpacing = (int) ta.getDimension(R.styleable.PageIndicator_indicator_spacing, 5);
indicatorSize = ta.getInteger(R.styleable.PageIndicator_indicator_size, 3);
indicatorPosition = ta.getInteger(R.styleable.PageIndicator_indicator_startPosition, 0);
mLayoutWidth = taa.getLayoutDimension(2, ViewGroup.LayoutParams.MATCH_PARENT);
mLayoutHeight = taa.getLayoutDimension(3, ViewGroup.LayoutParams.MATCH_PARENT);
} finally {
ta.recycle();
taa.recycle();
}
}

Google I/O apps 'DashboardLayout.java' code not working with custom view

As per the Google design patterns I have been implementing the dashboard layout by using the DashboardLayout.java file used by Google in there Google IO app. This has been working fine when using buttons, but as soon as I add a custom view the grid view produced by the DashboardLayout.java file falls apart:
Working without custom view:
Not working with custom view:
The code for the custom view is:
public class Countdown extends View {
int viewWidth;
int viewHeight;
Paint textPaint;
Paint titlePaint;
Paint labelPaint;
Paint rectanglePaint;
PeriodFormatter daysFormatter;
PeriodFormatter hoursFormatter;
PeriodFormatter minutesFormatter;
PeriodFormatter secondsFormatter;
DateTimeZone frenchTimeZone;
DateTime expiry;
Context ctx;
static int[] rectWidth;
static int[] rectHeight;
boolean flag = true;
public Countdown(Context context) {
super(context);
ctx = context;
init();
}
public Countdown(Context context, AttributeSet attrs) {
super(context, attrs);
ctx = context;
init();
}
public Countdown(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
ctx = context;
init();
}
private void init()
{
rectWidth = new int[]{0,0,0,0};
rectHeight = new int[]{0,0,0,0};
textPaint = new Paint();
titlePaint = new Paint();
labelPaint = new Paint();
rectanglePaint = new Paint();
frenchTimeZone = DateTimeZone.forID("Europe/Paris");
expiry = new DateTime(2012, 6, 17, 8, 30, frenchTimeZone);
//setup paints
//turn antialiasing on
textPaint.setAntiAlias(true);
int timerScaledSize = getResources().getDimensionPixelSize(R.dimen.text_size_dashboard_timer);
textPaint.setTextSize(timerScaledSize);
textPaint.setColor(Color.WHITE);
textPaint.setTextAlign(Align.CENTER);
labelPaint.setAntiAlias(true);
int labelScaledSize = getResources().getDimensionPixelSize(R.dimen.text_size_dashboard_timer_boxes_label);
labelPaint.setTextSize(labelScaledSize);
labelPaint.setColor(Color.BLACK);
labelPaint.setTextAlign(Align.CENTER);
labelPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
titlePaint.setAntiAlias(true);
int titleScaledSize = getResources().getDimensionPixelSize(R.dimen.text_size_dashboard_title);
titlePaint.setTextSize(titleScaledSize);
titlePaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
titlePaint.setColor(Color.WHITE);
rectanglePaint.setAntiAlias(true);
daysFormatter = new PeriodFormatterBuilder()
.printZeroIfSupported()
.minimumPrintedDigits(2)
.appendDays()
.toFormatter();
hoursFormatter = new PeriodFormatterBuilder()
.printZeroIfSupported()
.minimumPrintedDigits(2)
.appendHours()
.toFormatter();
minutesFormatter = new PeriodFormatterBuilder()
.printZeroIfSupported()
.minimumPrintedDigits(2)
.appendMinutes()
.toFormatter();
secondsFormatter = new PeriodFormatterBuilder()
.printZeroIfSupported()
.minimumPrintedDigits(2)
.appendSeconds()
.toFormatter();
}
#Override
public void onDraw(Canvas canvas)
{
DateTime now = new DateTime();
Period p = new Period(now, expiry, PeriodType.dayTime());
canvas.drawColor(Color.TRANSPARENT);
if(flag)
{
// To ensure the rectangles will be wide enough for all numbers we cheat and initially set the width based upon 00.
flag = false;
drawTextRectangle(0, textPaint, labelPaint, canvas, "00", "", scaleForDensity(20, ctx), scaleForDensity(33, ctx));
drawTextRectangle(1, textPaint, labelPaint, canvas, "00", "", scaleForDensity(53, ctx), scaleForDensity(33, ctx));
drawTextRectangle(2, textPaint, labelPaint, canvas, "00", "", scaleForDensity(87, ctx), scaleForDensity(33, ctx));
drawTextRectangle(3, textPaint, labelPaint, canvas, "00", "", scaleForDensity(120, ctx), scaleForDensity(33, ctx));
}
String title = "Countdown";
float textWidth = titlePaint.measureText(title);
float titleStartPositionX = (viewWidth - textWidth) / 2;
canvas.drawText(title, titleStartPositionX, viewHeight - scaleForDensity(5, ctx), titlePaint);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dashboard_counter);
canvas.drawBitmap(bitmap, 0, 0, null);
drawTextRectangle(0, textPaint, labelPaint, canvas, daysFormatter.print(p), "DAYS", scaleForDensity(20, ctx), scaleForDensity(33, ctx));
drawTextRectangle(1, textPaint, labelPaint, canvas, hoursFormatter.print(p), "HRS", scaleForDensity(53, ctx), scaleForDensity(33, ctx));
drawTextRectangle(2, textPaint, labelPaint, canvas, minutesFormatter.print(p), "MINS", scaleForDensity(87, ctx), scaleForDensity(33, ctx));
drawTextRectangle(3, textPaint, labelPaint, canvas, secondsFormatter.print(p), "SECS", scaleForDensity(120, ctx), scaleForDensity(33, ctx));
invalidate();
}
private void drawTextRectangle(int index, Paint paint, Paint labelPaint, Canvas canvas, String text, String label, float x, float y) {
paint.setTextAlign(Align.CENTER);
Rect bounds = new Rect();
bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
if(rectWidth[index] == 0)
{
rectWidth[index] = Math.abs(bounds.right - bounds.left);
rectWidth[index] += scaleForDensity(5, ctx);
}
if(rectHeight[index] == 0)
{
rectHeight[index] = Math.abs(bounds.bottom - bounds.top);
rectHeight[index] += scaleForDensity(5, ctx);
}
bounds.left = (int) (x - (rectWidth[index] / 2));
bounds.top = (int) (y - rectHeight[index]);
bounds.right = bounds.left + rectWidth[index];
bounds.bottom = (int) (bounds.top + rectHeight[index] + scaleForDensity(7, ctx));
Paint rectanglePaint = new Paint();
rectanglePaint.setAntiAlias(true);
rectanglePaint.setShader(new LinearGradient(bounds.centerX(), bounds.top, bounds.centerX(), bounds.bottom, 0xff8ed8f8, 0xff207d94, TileMode.MIRROR));
RectF boundsF = new RectF(bounds);
canvas.drawRoundRect(boundsF, 2f, 2f, rectanglePaint);
canvas.drawText(text, x, y, paint);
canvas.drawText(label, x, y + rectHeight[index], labelPaint);
}
public float scaleForDensity(float px, Context context)
{
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return px * metrics.density + .5f;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec, widthMeasureSpec);
viewWidth = width;
viewHeight = height;
setMeasuredDimension(width, height);
}
private int measureWidth(int measureSpec)
{
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = measureSpec;
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHeight(int measureSpecHeight, int measureSpecWidth) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpecHeight);
int specSize = MeasureSpec.getSize(measureSpecHeight);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = viewWidth;
/*if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}*/
}
return result;
}
}
The DashboardLayout code that I am using:
/**
* Custom layout that arranges children in a grid-like manner, optimizing for even horizontal and
* vertical whitespace.
*/
public class DashboardLayout extends ViewGroup {
private static final int UNEVEN_GRID_PENALTY_MULTIPLIER = 10;
boolean run = true;
private int mMaxChildWidth = 0;
private int mMaxChildHeight = 0;
public DashboardLayout(Context context) {
super(context, null);
}
public DashboardLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public DashboardLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(run)
{
run = false;
mMaxChildWidth = 0;
mMaxChildHeight = 0;
// Measure once to find the maximum child size.
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
mMaxChildWidth = Math.max(mMaxChildWidth, child.getMeasuredWidth());
mMaxChildHeight = Math.max(mMaxChildHeight, child.getMeasuredHeight());
}
// Measure again for each child to be exactly the same size.
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
mMaxChildWidth, MeasureSpec.EXACTLY);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
mMaxChildHeight, MeasureSpec.EXACTLY);
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
setMeasuredDimension(
resolveSize(mMaxChildWidth, widthMeasureSpec),
resolveSize(mMaxChildHeight, heightMeasureSpec));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
final int count = getChildCount();
// Calculate the number of visible children.
int visibleCount = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
++visibleCount;
}
if (visibleCount == 0) {
return;
}
// Calculate what number of rows and columns will optimize for even horizontal and
// vertical whitespace between items. Start with a 1 x N grid, then try 2 x N, and so on.
int bestSpaceDifference = Integer.MAX_VALUE;
int spaceDifference;
// Horizontal and vertical space between items
int hSpace = 0;
int vSpace = 0;
int cols = 1;
int rows;
while (true) {
rows = (visibleCount - 1) / cols + 1;
hSpace = ((width - mMaxChildWidth * cols) / (cols + 1));
vSpace = ((height - mMaxChildHeight * rows) / (rows + 1));
spaceDifference = Math.abs(vSpace - hSpace);
if (rows * cols != visibleCount) {
spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER;
}
if (spaceDifference < bestSpaceDifference) {
// Found a better whitespace squareness/ratio
bestSpaceDifference = spaceDifference;
// If we found a better whitespace squareness and there's only 1 row, this is
// the best we can do.
if (rows == 1) {
break;
}
} else {
// This is a worse whitespace ratio, use the previous value of cols and exit.
--cols;
rows = (visibleCount - 1) / cols + 1;
hSpace = ((width - mMaxChildWidth * cols) / (cols + 1));
vSpace = ((height - mMaxChildHeight * rows) / (rows + 1));
break;
}
++cols;
}
// Lay out children based on calculated best-fit number of rows and cols.
// If we chose a layout that has negative horizontal or vertical space, force it to zero.
hSpace = Math.max(0, hSpace);
vSpace = Math.max(0, vSpace);
// Re-use width/height variables to be child width/height.
width = (width - hSpace * (cols + 1)) / cols;
height = (height - vSpace * (rows + 1)) / rows;
int left, top;
int col, row;
int visibleIndex = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
row = visibleIndex / cols;
col = visibleIndex % cols;
left = hSpace * (col + 1) + width * col;
top = vSpace * (row + 1) + height * row;
child.layout(left, top,
(hSpace == 0 && col == cols - 1) ? r : (left + width),
(vSpace == 0 && row == rows - 1) ? b : (top + height));
++visibleIndex;
}
}
}
And last but not least the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
style="#style/HeaderTextView"
android:text="#string/header_dashboard" />
<View
android:layout_width="fill_parent"
android:layout_height="#dimen/content_divider_height"
android:layout_marginLeft="#dimen/content_divider_margin"
android:layout_marginRight="#dimen/content_divider_margin"
android:background="#color/content_divider_colour" />
<com.a.b.ui.DashboardLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
style="#style/Container">
<!-- The custom view that once un-commented cause the problem -->
<!-- <com.a.b.widget.Countdown
style="#style/DashboardButton" /> -->
<Button android:id="#+id/home_btn_news"
style="#style/DashboardButton"
android:text="A"
android:drawableTop="#drawable/dashboard_counter" />
<Button android:id="#+id/home_btn_feed"
style="#style/DashboardButton"
android:text="B"
android:drawableTop="#drawable/dashboard_counter" />
<Button android:id="#+id/home_btn_guide"
style="#style/DashboardButton"
android:text="C"
android:drawableTop="#drawable/dashboard_counter" />
<Button android:id="#+id/home_btn_sessions"
style="#style/DashboardButton"
android:text="D"
android:drawableTop="#drawable/dashboard_counter" />
<Button android:id="#+id/home_btn_events"
style="#style/DashboardButton"
android:text="E"
android:drawableTop="#drawable/dashboard_counter" />
</com.a.b.ui.DashboardLayout>
</LinearLayout>
Apologies over the amount of code posted, but I hope it makes it easier to see the issue(s).
I have since discovered the bug in the my onMeasureWidth function in the custom view. Instead of:
private int measureWidth(int measureSpec)
{
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = measureSpec;
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}
return result;
}
It should be:
private int measureWidth(int measureSpec)
{
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = viewWidth;
}
return result;
}

ToolBar on MonoDroid

Do you know any examples, screencasts that document how to develop with ToolBar on "mono for android" ?
I would like to develop a tiny toolbar and I'm searching for the proper way to code it?
Here is Dashboard, and if you or somebody else will need ActionBar here is a link!
public class DashboardLayout : ViewGroup
{
private const int UNEVEN_GRID_PENALTY_MULTIPLIER = 10;
private int _mMaxChildWidth;
private int _mMaxChildHeight;
public DashboardLayout(IntPtr doNotUse, JniHandleOwnership transfer) : base(doNotUse, transfer)
{
}
public DashboardLayout(Context context)
: base(context)
{
}
public DashboardLayout(Context context, IAttributeSet attrs)
: base(context, attrs)
{
}
public DashboardLayout(Context context, IAttributeSet attrs, int defStyle)
: base(context, attrs, defStyle)
{
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
_mMaxChildWidth = 0;
_mMaxChildHeight = 0;
// Measure once to find the maximum child size.
var childWidthMeasureSpec = MeasureSpec.MakeMeasureSpec(
MeasureSpec.GetSize(widthMeasureSpec), MeasureSpecMode.AtMost);
var childHeightMeasureSpec = MeasureSpec.MakeMeasureSpec(
MeasureSpec.GetSize(widthMeasureSpec), MeasureSpecMode.AtMost);
var count = ChildCount;
for (var i = 0; i < count; i++)
{
var child = GetChildAt(i);
if (child.Visibility == ViewStates.Gone)
{
continue;
}
child.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
_mMaxChildWidth = Math.Max(_mMaxChildWidth, child.MeasuredWidth);
_mMaxChildHeight = Math.Max(_mMaxChildHeight, child.MeasuredHeight);
}
// Measure again for each child to be exactly the same size.
childWidthMeasureSpec = MeasureSpec.MakeMeasureSpec(
_mMaxChildWidth, MeasureSpecMode.Exactly);
childHeightMeasureSpec = MeasureSpec.MakeMeasureSpec(
_mMaxChildHeight, MeasureSpecMode.Exactly);
for (int i = 0; i < count; i++)
{
var child = GetChildAt(i);
if (child.Visibility == ViewStates.Gone)
{
continue;
}
child.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
SetMeasuredDimension(
ResolveSize(_mMaxChildWidth, widthMeasureSpec),
ResolveSize(_mMaxChildHeight, heightMeasureSpec));
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
var width = r - l;
var height = b - t;
var count = ChildCount;
// Calculate the number of visible children.
var visibleCount = 0;
for (var i = 0; i < count; i++)
{
var child = GetChildAt(i);
if (child.Visibility == ViewStates.Gone)
{
continue;
}
++visibleCount;
}
if (visibleCount == 0)
{
return;
}
// Calculate what number of rows and columns will optimize for even horizontal and
// vertical whitespace between items. Start with a 1 x N grid, then try 2 x N, and so on.
var bestSpaceDifference = int.MaxValue;
// Horizontal and vertical space between items
int hSpace;
int vSpace;
var cols = 1;
int rows;
while (true)
{
rows = (visibleCount - 1) / cols + 1;
hSpace = ((width - _mMaxChildWidth * cols) / (cols + 1));
vSpace = ((height - _mMaxChildHeight * rows) / (rows + 1));
var spaceDifference = Math.Abs(vSpace - hSpace);
if (rows * cols != visibleCount)
{
spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER;
}
if (spaceDifference < bestSpaceDifference)
{
// Found a better whitespace squareness/ratio
bestSpaceDifference = spaceDifference;
// If we found a better whitespace squareness and there's only 1 row, this is
// the best we can do.
if (rows == 1)
{
break;
}
}
else
{
// This is a worse whitespace ratio, use the previous value of cols and exit.
--cols;
rows = (visibleCount - 1) / cols + 1;
hSpace = ((width - _mMaxChildWidth * cols) / (cols + 1));
vSpace = ((height - _mMaxChildHeight * rows) / (rows + 1));
break;
}
++cols;
}
// Lay out children based on calculated best-fit number of rows and cols.
// If we chose a layout that has negative horizontal or vertical space, force it to zero.
hSpace = Math.Max(0, hSpace);
vSpace = Math.Max(0, vSpace);
// Re-use width/height variables to be child width/height.
width = (width - hSpace * (cols + 1)) / cols;
height = (height - vSpace * (rows + 1)) / rows;
var visibleIndex = 0;
for (var i = 0; i < count; i++)
{
var child = GetChildAt(i);
if (child.Visibility == ViewStates.Gone)
{
continue;
}
var row = visibleIndex / cols;
var col = visibleIndex % cols;
var left = hSpace * (col + 1) + width * col;
var top = vSpace * (row + 1) + height * row;
child.Layout(left, top,
(hSpace == 0 && col == cols - 1) ? r : (left + width),
(vSpace == 0 && row == rows - 1) ? b : (top + height));
++visibleIndex;
}
}
}

Categories

Resources