1

根据 Google 设计模式,我一直在使用 Google 在 Google IO 应用程序中使用的DashboardLayout.java文件来实现仪表板布局。这在使用按钮时工作正常,但是一旦我添加自定义视图,由 DashboardLayout.java 文件生成的网格视图就会分崩离析:

在没有自定义视图的情况下工作:

在此处输入图像描述

不使用自定义视图:

在此处输入图像描述

自定义视图的代码是:

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;
}
}

我正在使用的 DashboardLayout 代码:

/**
 * 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;
    }
}
}

最后但并非最不重要的布局:

<?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>

对发布的代码量表示歉意,但我希望它可以更容易地查看问题。

4

1 回答 1

0

此后,我在自定义视图的 onMeasureWidth 函数中发现了该错误。代替:

 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 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;
}
于 2012-06-06T18:34:41.313 回答