Lollipop ViewPager 中的自定义视图仅在不可见时呈现
Custom View inside Lollipop ViewPager only renders when not visible
我构建了一个简单的自定义 View
class,它显示五个彩色圆圈 and/or 十字,即:
它占据了一个Fragment
的顶部,作为一个RecyclerView
之上的固定header。整个东西放在一个 ViewPager
和另一个 Fragment
里面(只有两个 Fragment
在 ViewPager
里。这个是第一个)。
我遇到的问题发生在我的 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);
}
}
以及我MathUtils
class中使用的两种方法:
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
View
的 width
是 690px
,但是当第二次调用 onDraw
时,而不是重置 mRectF
的值回到 0
,使用以前的值。这意味着所有 circles/crosses 都绘制在 View
.
的边界之外
在 onDraw
的开头和 for
循环更正问题之前添加以下行:
mRectF.set(0,0,0,0);
我构建了一个简单的自定义 View
class,它显示五个彩色圆圈 and/or 十字,即:
它占据了一个Fragment
的顶部,作为一个RecyclerView
之上的固定header。整个东西放在一个 ViewPager
和另一个 Fragment
里面(只有两个 Fragment
在 ViewPager
里。这个是第一个)。
我遇到的问题发生在我的 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);
}
}
以及我MathUtils
class中使用的两种方法:
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
View
的 width
是 690px
,但是当第二次调用 onDraw
时,而不是重置 mRectF
的值回到 0
,使用以前的值。这意味着所有 circles/crosses 都绘制在 View
.
在 onDraw
的开头和 for
循环更正问题之前添加以下行:
mRectF.set(0,0,0,0);