I'm trying to make a frame for TextView as a cloud. But the content area does not behave as expected. What am i doing wrong?
I have a suggestion that is not working properly because the content area less scale area. So sad. I remade it to handle 9-patch manually. Save pictures without .9.png. Get Bitmap. There are 9-line present. With getPixels calculated padding and set it on the TextView. After that calculating and set LayoutParams.width and LayoutParams.height. Looks a bit ugly, but it works quite quickly, and most importantly correctly.
private int startX=-1;
private int endX=-1;
private int contentW=-1;
private int contentH=-1;
Bitmap bmp=BitmapFactory.decodeResource(getResources(), mIconResId);
int[] pixels=new int[bmp.getWidth()*bmp.getHeight()];
bmp.getPixels(pixels, 0, bmp.getWidth(), 0, 0, bmp.getWidth(),bmp.getHeight());
for(int i=0;i<bmp.getWidth();i++){
if(startX==-1 && pixels[bmp.getWidth()*(bmp.getHeight()-1)+i]==Color.BLACK){
startX=i;
}
if(startX!=-1 && pixels[bmp.getWidth()*(bmp.getHeight()-1)+i]!=Color.BLACK){
endX=i;
break;
}
}
int startY=-1;
int endY=-1;
for(int i=0;i<bmp.getHeight();i++){
if(startY==-1 && pixels[bmp.getWidth()*(i+1)-1]==Color.BLACK){
startY=i;
}
if(startY!=-1 && pixels[bmp.getWidth()*(i+1)-1]!=Color.BLACK){
endY=i;
break;
}
}
setBackground(new BitmapDrawable(getResources(),Bitmap.createBitmap(bmp, 1, 1, bmp.getWidth()-2, bmp.getHeight()-2)));
contentW=endX-startX;
endX=bmp.getWidth()-endX;
contentH=endY-startY;
endY=bmp.getHeight()-endY;
new Handler().post(new Rannable(){
#Override
public void run() {
int w=textview.getWidth();
int h=textview.getHeight();
if(w>endX-startX){
float k=((float)w)/contentW;
startX=(int) (startX*k);
endX=(int) (endX*k);
}
if(h>endY-startY){
float k=((float)h)/contentH;
startY=(int) (startY*k);
endY=(int) (endY*k);
}
w+=startX+startX;
h+=startY+endY;
textview.setPadding(startX, startY, endX, endY);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(w,h);
textview.setLayoutParams(lp);
}
});
You set good values for right and bottom borders. You just have to set same values for left and top borders, left border = right border and top border = bottom border.
The result in draw9patch:
And here the 9-patch file:
For your information, your image is not really suitable for using with 9-patch format.
I extended/adapted #ahtartam code. I am not sure if it is the cleanest way but it works for me. If someone needs help, just contact me or ask in comments!
public void setTextLayout(int orgW, int orgH,int actW,int actH,int top,int left) {
int startX = -1;
int endX = -1;
int startY = -1;
int endY = -1;
int contentW;
int contentH;
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.image);
int[] pixels = new int[orgW * orgH];
bmp.getPixels(pixels, 0, orgW, 0, 0, orgW, orgH);
for (int i = 0; i < orgW; i++) {
if (startX == -1 && pixels[orgW * (orgH - 1) + i] == Color.BLACK) {
startX = i;
}
if (startX != -1 && pixels[orgW * (orgH - 1) + i] != Color.BLACK) {
endX = i;
break;
}
}
for (int i = 0; i < orgH; i++) {
if (startY == -1 && pixels[orgW * (i + 1) - 1] == Color.BLACK) {
startY = i;
}
if (startY != -1 && pixels[orgW * (i + 1) - 1] != Color.BLACK) {
endY = i;
break;
}
}
m_marvin.setImageDrawable(new BitmapDrawable(getResources(), Bitmap.createBitmap(bmp, 1, 1, orgW - 2, orgH - 2)));
RelativeLayout.LayoutParams rp = (RelativeLayout.LayoutParams) m_marvin.getLayoutParams();
contentW=endX- startX;
contentH=endY-startY;
endX=orgW-endX;
endY=orgH-endY;
double scaleX = ((double)actW) / bmp.getWidth();
double scaleY = ((double)actH) / bmp.getHeight();
startX = (int) (startX * scaleX);
endX = (int) (endX * scaleX);
startY = (int) (startY * scaleY);
endY = (int) (endY * scaleY) ;
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams((int)(contentW*scaleX),(int)(contentH*scaleY));
layoutParams.setMargins(startX+rp.leftMargin+left, startY+rp.topMargin+top, endX+rp.rightMargin, endY+rp.bottomMargin);
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL,RelativeLayout.TRUE);
m_text.setLayoutParams(layoutParams);
m_text.bringToFront();
}
Instead TextView I use SizeAwareImageView from -> https://stackoverflow.com/a/15538856/1438596
In my case it looks like this->
public class SizeAwareImageView extends ImageView {
MainActivity m_mainActivity;
public SizeAwareImageView(Context context,AttributeSet attrss){
super(context,attrss);
m_mainActivity = (MainActivity)context;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(m_mainActivity.getTextMeasured())return;
// Get image matrix values and place them in an array
float[] f = new float[9];
getImageMatrix().getValues(f);
// Extract the scale values using the constants (if aspect ratio maintained, scaleX == scaleY)
final float scaleX = f[Matrix.MSCALE_X];
final float scaleY = f[Matrix.MSCALE_Y];
// Get the drawable (could also get the bitmap behind the drawable and getWidth/getHeight)
final Drawable d = getDrawable();
final int origW = d.getIntrinsicWidth();
final int origH = d.getIntrinsicHeight();
// Calculate the actual dimensions
final int actW = Math.round(origW * scaleX);
final int actH = Math.round(origH * scaleY);
int top = (int) (imgViewH - actH)/2;
int left = (int) (imgViewW - actW)/2;
if(origW!=actW){
m_mainActivity.setTextMeasured(true);
m_mainActivity.setTextLayout(origW, origH, actW, actH,top,left);
}
}
}
You could use this tool for creating your nine-patch images.
i am progrmatically creating radio button, and it works fine,
RadioImageButton RadioImageButton = new RadioImageButton(this);
RadioImageButton.setGravity(Gravity.CENTER);
RadioImageButton.setId(buttonId);
RadioImageButton.setTextColor(Color.BLACK);
RadioImageButton.setCompoundDrawablesWithIntrinsicBounds(icon, null,null, null)// use this to set the icon
RadioImageButton.setBackgroundDrawable(drawable);
RadioGroup.LayoutParams radioImageButtonParams = new RadioGroup.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT, 1f);
radioImageButtonParams.setMargins(0, 0, 1, 0);
RadioGroup.addView(RadioImageButton, radioImageButtonParams);
In RadioImageButton class
Drawable image;
public RadioImageButton(Context context) {
super(context);
setButtonDrawable(android.R.color.transparent);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (image != null) {
image.setState(getDrawableState());
final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
final int height = image.getIntrinsicHeight();
int y = 0;
switch (verticalGravity) {
case Gravity.BOTTOM:
y = getHeight() - height;
break;
case Gravity.CENTER_VERTICAL:
y = (getHeight() - height) / 2;
break;
}
int buttonWidth = image.getIntrinsicWidth();
int buttonLeft = (getWidth() - buttonWidth) / 3;
image.setBounds(buttonLeft, y, buttonLeft + buttonWidth, y + height);
image.draw(canvas);
}
}
But the icon which is being displayed is big , i need to decrease the size of that icon.
I have tried with setHeight but its not working.
RadioImageButton.setCompoundDrawablesWithIntrinsicBounds(icon, null,null, null)
this is used to set the icon
ok i fixed it by myself , here is the answer
private Drawable resize(Drawable image) {
Bitmap b = ((BitmapDrawable)image).getBitmap();
Bitmap bitmapResized = Bitmap.createScaledBitmap(b, 50, 50, true);// filter attribute set to true
return new BitmapDrawable(bitmapResized);
}
The code above is to modify the size of drawable, and make sure the filter attribute is set to true so that the icons does not look blur.
Hope it helps some one in future. Happy Coding :)
I have to add an ImageView programatically to an RelativeLayout such that it's 100dp to right of the center. I did this for a specific screen size and resolution, but I want it to work on any size of phone and desnity. What I tried for this is as follows :
CODE :
float xCord, yCord;
float xCenter, yCenter;
float radius;
float angle;
double x, y;
TextView tv;
RelativeLayout container;
ImageView view;
RelativeLayout.LayoutParams viewParams;
DisplayMetrics metrics ;
float density;
int dps,pxs;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_circular_animation);
metrics = getApplicationContext().getResources().getDisplayMetrics();
float width = metrics.widthPixels;
float height = metrics.heightPixels;
tv = (TextView)findViewById(R.id.tv);
container = (RelativeLayout)findViewById(R.id.container);
xCenter = convertToDP(width)/2;
yCenter = convertToDP(height)/2;
radius = 100;
angle = 0;
x = xCenter + radius * Math.cos(angle * Math.PI / 180);
y = yCenter + radius * Math.sin(angle * Math.PI / 180);
view = new ImageView(this);
viewParams = new RelativeLayout.LayoutParams(50,50);
viewParams.leftMargin = (int) x;
viewParams.topMargin = (int) y;
view.setLayoutParams(viewParams);
view.setImageResource(R.drawable.square);
container.addView(view);
tv.setText(String.valueOf(xCenter) + " " + String.valueOf(yCenter));
}
public int convertToDP(float px) {
density = getApplicationContext().getResources().getDisplayMetrics().density;
dps = (int) ((px/density) + 0.5f);
return dps;
}
}
What I did is like, get the screen size and density and then divide it by 2 to get the center cords. Once i had the cords I used the formula to get the point on circumference of the circle at 0deg, but I am not getting the correct point. Any help would be great.
Try this:
public class sampleMediaPlayer extends Activity {
// Toast mToast;
// int i = 0;
// private boolean click;
// private Camera camera;
Handler mHandler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mainf);
}
#Override
protected void onResume() {
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
RelativeLayout rl = (RelativeLayout) findViewById(R.id.relativeLayout);
float r = DPtoPX(100);
float mScreenWidth = rl.getWidth() / 2; // DPtoPX(100);
float mScreenHeight = rl.getHeight() / 2;// DPtoPX(100);
for (int i = 0; i < 360; i = i + 30) {
addView(rl, (int) (mScreenWidth + r * Math.cos(i * Math.PI / 180F)),
(int) (mScreenHeight + (int) r * Math.sin(i * Math.PI / 180F)));
}
addView(rl, (int) mScreenWidth, (int) ((mScreenHeight)));
}
}, 50);
super.onResume();
}
void addView(ViewGroup rl, int x, int y) {
Log.i("NIMISH", x + " = " + y);
ImageView CenterImage = new ImageView(this);
RelativeLayout.LayoutParams viewParamsCenter = new RelativeLayout.LayoutParams(10, 10);
// viewParamsCenter.addRule(RelativeLayout.CENTER_IN_PARENT);
viewParamsCenter.leftMargin = x;
viewParamsCenter.topMargin = y;
CenterImage.setLayoutParams(viewParamsCenter);
CenterImage.setImageResource(R.drawable.ic_launcher);
rl.addView(CenterImage);
}
float DPtoPX(float dp) {
Resources r = getResources();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
}
}
Explanation:
float width = metrics.widthPixels;
float height = metrics.heightPixels;
Returns the screen size including the TitleBar and StatusBar, but when you plot the image it calculated relative to that of the RelativeLayout. So if you draw image with height 'metrics.heightPixels' you will observe image to be out of screen. This phenomenon affects calculation of true center.
there are some other ways to calculate but each require that view to be plotted on screen. Hence any call after onResume()( used handler). As you can observe the function of calculating titlebar heights returns 0 when called form onResume().
private float Correction() {
Rect rectgle = new Rect();
Window window = getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
int StatusBarHeight = rectgle.top;
int contentViewTop = window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
int TitleBarHeight = contentViewTop - StatusBarHeight;
Log.i("NIMISH", "StatusBar Height= " + StatusBarHeight + " , TitleBar Height = " + TitleBarHeight);
return contentViewTop;
}
If you want user to input the X,Y coordinated you can alter the first program by
float mScreenWidth = DPtoPX(100);
float mScreenHeight = DPtoPX(100);
Doing so, It will plot the image with reference to the Relative layout, So you can remove the handler.
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;
}
I'm trying to drawText on a Canvas using Paint and I can't seem to figure out the proper x and y to give it. If I log the getWidth and getHeight, I can see that the View is actually giving the width and height of the rotated canvas. Thus, its something like 20 X 240. What I have is close, but I just want the text to be centered both vertically and horizontally.
Any ideas on how I can do that?
Here's what it looks like now:
Here's my code:
public class VerticalText extends View {
private Paint mTextPaint;
private String mText;
private int mAscent;
public ListLabelView(Context context) {
super(context);
initListLabel();
this.setBackgroundColor(Color.BLUE);
}
private final void initListLabel() {
mTextPaint = new Paint();
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(12);
mTextPaint.setColor(Color.WHITE);
setPadding(3, 3, 3, 3);
invalidate();
}
/**
* sets the text to display in this label
* #param text
*/
public void setText(String text) {
mText = text;
requestLayout();
invalidate();
}
/**
* sets the text color for this label
* #param color
*/
public void setTextColor(int color) {
mTextPaint.setColor(color);
invalidate();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureHeight(widthMeasureSpec), measureWidth(heightMeasureSpec));
}
/**
* Determines the width of this view
* #param measureSpec A measureSpec packed into an int
* #return The width of the view, honoring constraints from measureSpec
*/
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 = (int) mTextPaint.measureText(mText) + getPaddingLeft()
+ getPaddingRight();
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;
}
/**
* Determines the height of this view
* #param measureSpec A measureSpec packed into an int
* #return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
mAscent = (int) mTextPaint.ascent();
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop()
+ getPaddingBottom();
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;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.rotate(-90, getWidth()/2, getHeight()/2);
//
mTextPaint.measureText(mText);
Log.v("width x height:", getWidth() + " X " + getHeight());
canvas.drawText(mText, (getWidth() - (getWidth())) - 50, getHeight()/2, mTextPaint);
super.onDraw(canvas);
}
}
I faced the same problem. Here's what worked for me:
Rect tb = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), tb);
canvas.drawText(mText, getWidth() / 2f, getHeight() / 2f + Math.abs(tb.top) / 2f, mTextPaint);
Your text should be centered:
mTextPaint.setTextAlign(Align.CENTER);