我有一个自定义视图类(PinView),它基于某些属性定位在自定义ViewGroup(RightAngleTriangleView)中。但是,当它相对于RelativeLayout中的另一个视图放置时,PinView不会显示。请参阅以下代码:
RightAngleTriangleView
public class RightAngleTriangleView extends ViewGroup {
private static final String TAG = "RightAngleTriangleView";
private int pinOrientation;
private boolean isRightFilled, diagonalIsTopRightBottomLeft;
private Paint trianglePaint, pinPaint;
private Path trianglePath, pinPath;
private PointF triStart, triMiddle, triEnd;
private PinView pinView;
private float pinLengthDiff, pinThickness;
public RightAngleTriangleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false); // remove default (non) drawing behaviour for ViewGroup
/*** extract XML attributes ***/
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RightAngleTriangleView);
int fillColour = a.getColor(R.styleable.RightAngleTriangleView_fillColour,
context.getResources().getColor(android.R.color.darker_gray));
int fillPosition = a.getInt(R.styleable.RightAngleTriangleView_fillPosition,
context.getResources().getInteger(
R.integer.RightAngleTriangleView_fillPosition_left));
int diagonal = a.getInt(R.styleable.RightAngleTriangleView_diagonal,
context.getResources().getInteger(
R.integer.RightAngleTriangleView_diagonal_topLeftToBottomRight));
isRightFilled = fillPosition == context.getResources().getInteger(
R.integer.RightAngleTriangleView_fillPosition_right);
diagonalIsTopRightBottomLeft = diagonal == getContext().getResources().getInteger(
R.integer.RightAngleTriangleView_diagonal_topRightToBottomLeft);
pinOrientation = a.getInt(R.styleable.RightAngleTriangleView_pinOrientation,
context.getResources().getInteger(
R.integer.RightAngleTriangleView_pinOrientation_none));
a.recycle();
/*** setup drawing related variables ***/
trianglePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
trianglePaint.setStyle(Paint.Style.FILL_AND_STROKE);
trianglePaint.setColor(fillColour);
trianglePath = new Path();
trianglePath.setFillType(Path.FillType.EVEN_ODD);
triStart = new PointF();
triMiddle = new PointF();
triEnd = new PointF();
pinPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
pinPaint.setStyle(Paint.Style.FILL_AND_STROKE);
pinPaint.setColor(context.getResources().getColor(R.color.pin_color));
pinPath = new Path();
pinPath.setFillType(Path.FillType.EVEN_ODD);
// create pinView (if present)
if(pinOrientation != context.getResources().getInteger(
R.integer.RightAngleTriangleView_pinOrientation_none)){
pinView = new PinView(context, UiUtils.makeAttributeSet(context, getResourceId()));
addView(pinView);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(hasPin()){
// measure child to obtain 'wrapped' valid dimension
measureChild(pinView, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout (boolean changed, int left, int top, int right, int bottom){
Log.d(TAG, "onLayout() changed = " + changed);
if(hasPin()){
// use 'wrapped' valid dimension as pinThickness
if(pinView.isHorizontal()) {
pinThickness = pinView.getMeasuredHeight() / 2;
pinLengthDiff = (pinThickness * getWidth()) / getHeight();
}
else{
pinThickness = pinView.getMeasuredWidth() / 2;
pinLengthDiff = (pinThickness * getHeight()) / getWidth();
}
placePinView(left, top, right, bottom);
}
}
@Override
protected void onDraw(Canvas canvas){
// draw pin 'edge' if applicable
if(hasPin()){
pinPath.reset(); // remove any previously drawn paths
if(pinView.isHorizontal()){
pinPath.addRect((getWidth() - pinLengthDiff) / 2, (getHeight() - pinThickness) / 2,
(getWidth() + pinLengthDiff) / 2, (getHeight() + pinThickness) / 2,
Path.Direction.CW);
}
else{
pinPath.addRect((getWidth() - pinThickness) / 2, (getHeight() - pinLengthDiff) / 2,
(getWidth() + pinThickness) / 2, (getHeight() + pinLengthDiff) / 2,
Path.Direction.CW);
}
canvas.drawPath(pinPath, pinPaint);
}
// determine triangle vertices
if(diagonalIsTopRightBottomLeft){
// draw diagonal
triStart.set(getWidth(), 0);
triMiddle.set(0, getHeight());
// determine triEnd based on fill position
if(isRightFilled){
triEnd.set(getWidth(), getHeight());
}
else{
triEnd.set(0, 0);
}
}
else{
// draw diagonal
triStart.set(0, 0);
triMiddle.set(getWidth(), getHeight());
// determine triEnd based on fill position
if(isRightFilled){
triEnd.set(getWidth(), 0);
}
else{
triEnd.set(0, getHeight());
}
}
// draw triangle
trianglePath.reset(); // remove any previously drawn paths
trianglePath.moveTo(triStart.x, triStart.y);
trianglePath.lineTo(triMiddle.x, triMiddle.y);
trianglePath.lineTo(triEnd.x, triEnd.y);
trianglePath.close(); // automatically draw third side
canvas.drawPath(trianglePath, trianglePaint);
}
public boolean hasPin(){
return pinView != null;
}
private void placePinView(int left, int top, int right, int bottom){
int l, t, r, b, pinPosition;
int trbl = diagonalIsTopRightBottomLeft ? 1<<2 : 0;
int rightFilled = isRightFilled ? 1<<1 : 0;
int horizontal = pinView.isHorizontal() ? 1 : 0;
int result = trbl + rightFilled + horizontal;
// determine pin size and position
switch (result){
case 0: // diagonal = top-left to bottom-right, left-filled, pin vertical
t = top;
b = t + (int) (getHeight() - pinLengthDiff) / 2;
l = left + (int) (getWidth() - pinThickness)/2;
r = l + pinView.getMeasuredWidth();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_top);
break;
case 1: // diagonal = top-left to bottom-right, left-filled, pin horizontal
l = left + (int) (getWidth() + pinLengthDiff)/2;
r = right;
b = top + (int) (getHeight() + pinThickness)/ 2;
t = b - pinView.getMeasuredHeight();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_right);
break;
case 2: // diagonal = top-left to bottom-right, right-filled, pin vertical
t = top + (int) (getHeight() + pinLengthDiff) / 2;
b = bottom;
r = left + (int) (getWidth() + pinThickness)/2;
l = r - pinView.getMeasuredWidth();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_bottom);
break;
case 3: // diagonal = top-left to bottom-right, right-filled, pin horizontal
l = left;
t = top + (int) (getHeight() - pinThickness)/ 2;
r = l + (int) (getWidth() - pinLengthDiff) / 2;
b = t + pinView.getMeasuredHeight();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_left);
break;
case 4: // diagonal = top-right to bottom-left, left-filled, pin vertical
t = top + (int) (getHeight() + pinLengthDiff) / 2;
b = bottom;
l = left + (int) (getWidth() - pinThickness)/2;
r = l + pinView.getMeasuredWidth();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_bottom);
break;
case 5: // diagonal = top-right to bottom-left, left-filled, pin horizontal
l = left + (int) (getWidth() + pinLengthDiff)/2;
t = top + (int) (getHeight() - pinThickness)/ 2;
r = right;
b = t + pinView.getMeasuredHeight();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_right);
break;
case 6: // diagonal = top-right to bottom-left, right-filled, pin vertical
t = top;
b = t + (int) (getHeight() - pinLengthDiff) / 2;
r = left + (int) (getWidth() + pinThickness)/2;
l = r - pinView.getMeasuredWidth();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_top);
break;
case 7: // diagonal = top-right to bottom-left, right-filled, pin horizontal
l = left;
r = l + (int) (getWidth() - pinLengthDiff) / 2;
b = top + (int) (getHeight() + pinThickness)/2;
t = b - pinView.getMeasuredHeight();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_left);
break;
default:
l = left;
t = top;
r = right;
b = bottom;
pinPosition = -1;
break;
}
// remeasure / resize pinView accounting for correct unspecified dimension
measureChild(pinView, MeasureSpec.makeMeasureSpec(r - l, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(b - t, MeasureSpec.EXACTLY));
pinView.setPosition(pinPosition); // ensure that pinName is in correct position
pinView.layout(l, t, r, b); // position pinView
}
private int getResourceId(){
int trbl = diagonalIsTopRightBottomLeft ? 1<<2 : 0;
int rightFilled = isRightFilled ? 1<<1 : 0;
int horizontal = pinOrientation == getContext().getResources().getInteger(
R.integer.RightAngleTriangleView_pinOrientation_horizontal) ? 1 : 0;
int result = trbl + rightFilled + horizontal;
Log.d(TAG, "getResourceId(): result = " + result);
switch (result){
case 0: // diagonal = top-left to bottom-right, left-filled, pin vertical
return R.xml.pinview_vertical_namebelow;
case 1: // diagonal = top-left to bottom-right, left-filled, pin horizontal
return R.xml.pinview_horizontal;
case 2: // diagonal = top-left to bottom-right, right-filled, pin vertical
return R.xml.pinview_vertical;
case 3: // diagonal = top-left to bottom-right, right-filled, pin horizontal
return R.xml.pinview_horizontal_namebelow;
case 4: // diagonal = top-right to bottom-left, left-filled, pin vertical
return R.xml.pinview_vertical_namebelow;
case 5: // diagonal = top-right to bottom-left, left-filled, pin horizontal
return R.xml.pinview_horizontal_namebelow;
case 6: // diagonal = top-right to bottom-left, right-filled, pin vertical
return R.xml.pinview_vertical;
case 7: // diagonal = top-right to bottom-left, right-filled, pin horizontal
return R.xml.pinview_horizontal;
default:
return -1;
}
}
}
PinView
public class PinView extends RelativeLayout {
private TextView pinNameView, signalTextView;
private boolean isHorizontal;
public PinView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
/*** extract XML attributes ***/
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.PinView);
boolean nameBelow = a.getBoolean(R.styleable.PinView_nameBelow, false);
int orientation = a.getInt(R.styleable.PinView_orientation, 0);
int position = a.getInt(R.styleable.PinView_position, -1);
isHorizontal = orientation == 0;
a.recycle();
/*** create children ***/
LinearLayout backgroundLayout = new LinearLayout(context);
backgroundLayout.setId(R.id.pinview_backgroundlayout);
backgroundLayout.setBackgroundColor(context.getResources().getColor(R.color.pin_color));
if(isHorizontal){
pinNameView = new TextView(context);
signalTextView = new TextView(context);
}
else{
pinNameView = new VerticalTextView(context);
signalTextView = new VerticalTextView(context);
}
pinNameView.setId(R.id.pinview_pinname);
pinNameView.setTextColor(context.getResources().getColor(android.R.color.black));
signalTextView.setId(R.id.pinview_signaltext);
signalTextView.setTextColor(context.getResources().getColor(android.R.color.black));
signalTextView.setBackgroundColor(context.getResources().getColor(R.color.pin_sig_color));
/*** determine children layouts and positions ***/
LayoutParams lpSigText = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
backgroundLayout.addView(signalTextView, lpSigText); // add signalView to backgroundLayout, NOT this view
LayoutParams lpBackgroundLayout;
if(isHorizontal){
lpBackgroundLayout = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
else{
lpBackgroundLayout = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
}
LayoutParams lpPinName = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
// place pin name accordingly
if(nameBelow){
addView(backgroundLayout, lpBackgroundLayout);
if(isHorizontal){
lpPinName.addRule(RelativeLayout.BELOW, backgroundLayout.getId());
}
else{
lpPinName.addRule(RelativeLayout.RIGHT_OF, backgroundLayout.getId());
}
addView(pinNameView, lpPinName);
setPosition(position);
}
else{
addView(pinNameView, lpPinName);
setPosition(position);
if(isHorizontal){
lpBackgroundLayout.addRule(RelativeLayout.BELOW, pinNameView.getId());
}
else{
lpBackgroundLayout.addRule(RelativeLayout.RIGHT_OF, pinNameView.getId());
}
addView(backgroundLayout, lpBackgroundLayout);
}
}
public void setPosition(int position){
// align pin name according to pin position on device
LayoutParams params = (RelativeLayout.LayoutParams) pinNameView.getLayoutParams();
switch(position){ // pin's position relative to parent device
case 2: // top
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
break;
case 3: // bottom
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
break;
case 0: // left
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
break;
case 1: // right
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
break;
default:
break;
}
}
public void setData(DevicePin pin){
pinNameView.setText(pin.name);
signalTextView.setText(pin.data);
// always create new animation, otherwise it will need to be reset
Animation anim = null;
switch (pin.direction){
case LEFT:
anim = AnimationUtils.loadAnimation(getContext(), R.anim.pin_transition_left);
break;
case RIGHT:
anim = AnimationUtils.loadAnimation(getContext(), R.anim.pin_transition_right);
break;
case UP:
anim = AnimationUtils.loadAnimation(getContext(), R.anim.pin_transition_up);
break;
case DOWN:
anim = AnimationUtils.loadAnimation(getContext(), R.anim.pin_transition_down);
break;
}
if(pin.startBehaviour == DevicePin.AnimStartBehaviour.DELAY){
if(pin.animationDelay == -1){
anim.setStartOffset(anim.getDuration());
}
else{
anim.setStartOffset(pin.animationDelay);
}
}
if(pin.action == DevicePin.PinAction.STATIONARY){
anim.setDuration(0);
}
if(pin.animListener != null){
anim.setAnimationListener(pin.animListener);
}
if(anim != null){
signalTextView.setAnimation(anim);
}
}
public boolean isHorizontal(){
return isHorizontal;
}
}
活动XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<view
class="org.ricts.abstractmachine.ui.device.RightAngleTriangleView"
android:id="@+id/edge"
android:layout_width="122px"
android:layout_height="150px"
custom:diagonal = "topLeftToBottomRight"
custom:fillPosition = "right"
custom:fillColour="@color/mux_fill_color"
custom:pinOrientation = "vertical"
/>
<view
class="org.ricts.abstractmachine.ui.device.RightAngleTriangleView"
android:id="@+id/edge2"
android:layout_width="122px"
android:layout_height="150px"
android:layout_toRightOf="@+id/edge"
custom:diagonal = "topLeftToBottomRight"
custom:fillPosition = "right"
custom:fillColour="@color/mux_fill_color"
custom:pinOrientation = "vertical"
/>
<view
class="org.ricts.abstractmachine.ui.device.RightAngleTriangleView"
android:id="@+id/edge3"
android:layout_width="122px"
android:layout_height="150px"
android:layout_below="@+id/edge"
custom:diagonal = "topLeftToBottomRight"
custom:fillPosition = "right"
custom:fillColour="@color/mux_fill_color"
custom:pinOrientation = "vertical"
/>
<view
class="org.ricts.abstractmachine.ui.device.PinView"
android:id="@+id/pin"
android:layout_width="58px"
android:layout_height="64px"
android:layout_below="@+id/edge"
android:layout_toRightOf="@+id/edge3"
custom:orientation="vertical"
custom:position="top"
/>
</RelativeLayout>
上面的代码是用于突出显示问题的Activity的XML。我还截了问题的截图(下图)。最顶部角落的三角形很好,并显示内部PinView。但是,另外两个(在第一个的下方和右侧)不显示PinView,即使代码完全相同,我已经通过Log确认预期的PinView位置似乎没问题。
我做错了什么?
答案 0 :(得分:0)
事实证明,在调用View.layout(l,t,r,b)而不是相对的时,我使用的是绝对值。我现在使用相对值,它可以工作!