Lollipop ViewPager 中的自定义视图仅在不可见时呈现

Custom View inside Lollipop ViewPager only renders when not visible

我构建了一个简单的自定义 View class,它显示五个彩色圆圈 and/or 十字,即:

它占据了一个Fragment的顶部,作为一个RecyclerView之上的固定header。整个东西放在一个 ViewPager 和另一个 Fragment 里面(只有两个 FragmentViewPager 里。这个是第一个)。

我遇到的问题发生在我的 Lollipop 设备上。每当 UI 加载时,无论是第一次加载还是来自配置更改,此 View 都会拒绝呈现。非常奇怪的是,如果我将我的 ViewPager 移动到第二个 Fragment (这样包含这个 View 的那个不再可见)并启动配置更改,当我跳转时返回 View 突然正常渲染。

我很困惑。我已经在布局中尝试了其他自定义 View classes 并且它们都正确呈现,因此这表明我的代码对于此 class.[=29 特别有问题=]

这是我的代码 View class:

public class HintTrackerView extends View {

private static final int DEFAULT_NO_OF_CIRCLES = 5;

private static final float DEFAULT_RADIUS_PERCENTAGE = 0.9F;

private static final int[] DEFAULT_ACTIVE_COLOURS = {
        PRIMARY_GREEN,
        createInterimColor(PRIMARY_GREEN, PRIMARY_ORANGE, 0.25F),
        createInterimColor(PRIMARY_GREEN, PRIMARY_ORANGE, 0.5F),
        createInterimColor(PRIMARY_GREEN, PRIMARY_ORANGE, 0.75F),
        PRIMARY_ORANGE
};

private static final int DEFAULT_INACTIVE_COLOR = PRIMARY_GREY;

private int[] activeColors;
private int inactiveColor;

private Paint mFillPaint, mInactivePaint;

private RectF mRectF;

private float mFillRadius;
private int mNoOfCircles;

public HintTrackerView(Context context) {
    super(context, null);
    init();
}

public HintTrackerView(Context context, int[] activeColors) {
    super(context, null);
    init(activeColors);
}

public HintTrackerView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

private void init() {
    init(DEFAULT_ACTIVE_COLOURS);
}

private void init(int[] colors) {
    this.activeColors = colors;
    inactiveColor = DEFAULT_INACTIVE_COLOR;

    if (mFillRadius == 0) mFillRadius = DEFAULT_RADIUS_PERCENTAGE;
    if (mNoOfCircles == 0) mNoOfCircles = DEFAULT_NO_OF_CIRCLES;

    if (colors.length != mNoOfCircles)
        throw new IllegalArgumentException("Number of colours must match number of circles");

    mFillPaint = new Paint();
    mFillPaint.setAntiAlias(true);
    mFillPaint.setStyle(Paint.Style.FILL);
    mFillPaint.setColor(colors[0]);

    mInactivePaint = new Paint();
    mInactivePaint.setAntiAlias(true);
    mInactivePaint.setDither(true);
    mInactivePaint.setStyle(Paint.Style.STROKE);
    mInactivePaint.setStrokeCap(Paint.Cap.ROUND);
    mInactivePaint.setColor(inactiveColor);

    mRectF = new RectF();
}

@Override
protected void onDraw(Canvas canvas) {

    final int subCanvasSize = getWidth()/ mNoOfCircles;

    for (int i = 0; i < mNoOfCircles; i++) {
        mRectF.set(mRectF.right, 0, mRectF.right + subCanvasSize, getHeight());

        mFillPaint.setColor(activeColors[i]);

        //If hint is has been used, replace symbol with cross
        if (i<3) {
            drawCircle(canvas, mRectF);
        } else {
            drawCross(canvas, mRectF);
        }
    }
}

private void drawCross(Canvas canvas, RectF subCanvasBounds) {

    mInactivePaint.setStrokeWidth(MathUtils.getHypotenuse(Math.min(subCanvasBounds.width(), subCanvasBounds.height()) * 0.1F));

    float innerSubCanvasBounds = Math.min(subCanvasBounds.width(), subCanvasBounds.height());
    float crossLength = MathUtils.getLengthOfFourtyFiveDegreeIsosceles(innerSubCanvasBounds);
    float startX, startY, stopX, stopY;

    startX = subCanvasBounds.centerX() - (crossLength*0.45F);
    startY = subCanvasBounds.centerY() - (crossLength*0.45F);
    stopX = subCanvasBounds.centerX() + (crossLength*0.45F);
    stopY = subCanvasBounds.centerY() + (crossLength*0.45F);

    canvas.drawLine(startX, startY, stopX, stopY, mInactivePaint);

    startX = subCanvasBounds.centerX() + (crossLength*0.45F);
    startY = subCanvasBounds.centerY() - (crossLength*0.45F);
    stopX = subCanvasBounds.centerX() - (crossLength*0.45F);
    stopY = subCanvasBounds.centerY() + (crossLength*0.45F);

    canvas.drawLine(startX, startY, stopX, stopY, mInactivePaint);

}

private void drawCircle(Canvas canvas, RectF subCanvasBounds) {

    float centerX, centerY, radius, viewSize;

    centerX = subCanvasBounds.centerX();
    centerY = subCanvasBounds.centerY();
    viewSize = Math.min(getHeight(), subCanvasBounds.width());
    radius = (viewSize/2) * mFillRadius;

    canvas.drawCircle(centerX, centerY, radius, mFillPaint);

}

}

以及我MathUtilsclass中使用的两种方法:

public static float getHypotenuse(float equalLengths) {
    return getHypotenuse(equalLengths, equalLengths);
}

public static float getHypotenuse(float lengthOne, float lengthTwo) {
    return (float) (Math.sqrt(Math.pow(lengthOne, 2) + Math.pow(lengthTwo, 2)));
}

public static float getLengthOfFourtyFiveDegreeIsosceles(float hypotenuse) {
    return (float) (0.5F*hypotenuse*Math.sqrt(2));
}

知道为什么这个 View 会导致问题吗?

问题是由于 onDraw() 在某些情况下被调用了两次。我只是检查以确保 onDraw() 被调用,因为我错误地认为每次 onDraw() 被调用时, class 构造函数也会被调用。然而,当我添加 Log 调用以查看每个 circle/cross 如何测量其边界时,我看到了这个输出:

10-12 17:53:30.106  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 115.0
    top = 0
    Right = 230.0
    Bottom = 36
10-12 17:53:30.106  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 230.0
    top = 0
    Right = 345.0
    Bottom = 36
10-12 17:53:30.106  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 345.0
    top = 0
    Right = 460.0
    Bottom = 36
10-12 17:53:30.106  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 460.0
    top = 0
    Right = 575.0
    Bottom = 36
10-12 17:53:30.106  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 575.0
    top = 0
    Right = 690.0
    Bottom = 36
10-12 17:53:30.106  13829-13829/? E/TAG﹕ onDraw
10-12 17:53:30.344  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 690.0
    top = 0
    Right = 805.0
    Bottom = 36
10-12 17:53:30.345  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 805.0
    top = 0
    Right = 920.0
    Bottom = 36
10-12 17:53:30.345  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 920.0
    top = 0
    Right = 1035.0
    Bottom = 36
10-12 17:53:30.345  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 1035.0
    top = 0
    Right = 1150.0
    Bottom = 36
10-12 17:53:30.345  13829-13829/? E/TAG﹕ Dimens for rectF:
    left = 1150.0
    top = 0
    Right = 1265.0
    Bottom = 36

Viewwidth690px,但是当第二次调用 onDraw 时,而不是重置 mRectF 的值回到 0,使用以前的值。这意味着所有 circles/crosses 都绘制在 View.

的边界之外

onDraw 的开头和 for 循环更正问题之前添加以下行:

mRectF.set(0,0,0,0);