我正在 Android Studio 中开发一个应用程序,可以让用户可视化家谱。
对于家庭成员的个人资料,我创建了自定义视图
PersonCard
:
public class PersonCard extends FrameLayout {
private ImageView imageView;
private LinearLayout textContainer;
private View topAnchor, bottomAnchor, leftAnchor, rightAnchor;
private int imageSize;
public PersonCard(Context context) {
super(context);
init(null);
}
public PersonCard(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public PersonCard(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
setBackgroundResource(R.drawable.person_card);
int padding = getResources().getDimensionPixelSize(R.dimen.person_card_padding);
setPadding(padding, padding, padding, padding);
if (attrs != null) {
TypedArray a = getContext().getTheme().obtainStyledAttributes(
attrs,
R.styleable.PersonCard,
0, 0);
try {
imageSize = a.getDimensionPixelSize(R.styleable.PersonCard_imageSize, 0);
} finally {
a.recycle();
}
}
imageView = new ImageView(getContext());
FrameLayout.LayoutParams imageParams = new FrameLayout.LayoutParams(
imageSize,
imageSize
);
imageParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
imageView.setLayoutParams(imageParams);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setImageResource(R.drawable.default_avatar);
textContainer = new LinearLayout(getContext());
FrameLayout.LayoutParams textContainerParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
textContainerParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
textContainerParams.topMargin = imageSize + padding;
textContainer.setLayoutParams(textContainerParams);
textContainer.setOrientation(LinearLayout.VERTICAL);
for (int i = 0; i < 3; i++) {
TextView textView = new TextView(getContext());
textView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
textView.setText("Text " + (i + 1));
textView.setTextColor(ContextCompat.getColor(getContext(), R.color.black));
textView.setPadding(16, 16, 16, 8);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
textContainer.addView(textView);
}
topAnchor = new View(getContext());
bottomAnchor = new View(getContext());
leftAnchor = new View(getContext());
rightAnchor = new View(getContext());
int anchorColor = ContextCompat.getColor(getContext(), R.color.tea_rose);
int anchorSize = 10;
topAnchor.setBackgroundColor(anchorColor);
bottomAnchor.setBackgroundColor(anchorColor);
leftAnchor.setBackgroundColor(anchorColor);
rightAnchor.setBackgroundColor(anchorColor);
FrameLayout.LayoutParams topAnchorParams = new FrameLayout.LayoutParams(
anchorSize,
anchorSize
);
topAnchorParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
topAnchorParams.topMargin = -padding;
topAnchor.setLayoutParams(topAnchorParams);
FrameLayout.LayoutParams bottomAnchorParams = new FrameLayout.LayoutParams(
anchorSize,
anchorSize
);
bottomAnchorParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
bottomAnchorParams.bottomMargin = -padding;
bottomAnchor.setLayoutParams(bottomAnchorParams);
FrameLayout.LayoutParams leftAnchorParams = new FrameLayout.LayoutParams(
anchorSize,
anchorSize
);
leftAnchorParams.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT;
leftAnchorParams.leftMargin = -padding;
leftAnchor.setLayoutParams(leftAnchorParams);
FrameLayout.LayoutParams rightAnchorParams = new FrameLayout.LayoutParams(
anchorSize,
anchorSize
);
rightAnchorParams.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT;
rightAnchorParams.rightMargin = -padding;
rightAnchor.setLayoutParams(rightAnchorParams);
addView(imageView);
addView(textContainer);
addView(topAnchor);
addView(bottomAnchor);
addView(leftAnchor);
addView(rightAnchor);
}
public int[] getTopAnchorPosition() {
int[] position = new int[2];
topAnchor.getLocationOnScreen(position);
return position;
}
public int[] getBottomAnchorPosition() {
int[] position = new int[2];
bottomAnchor.getLocationOnScreen(position);
return position;
}
public int[] getLeftAnchorPosition() {
int[] position = new int[2];
leftAnchor.getLocationOnScreen(position);
return position;
}
public int[] getRightAnchorPosition() {
int[] position = new int[2];
rightAnchor.getLocationOnScreen(position);
return position;
}
}
A
PersonCard
看起来像这样:
重要的是
PersonCard
两侧的玫瑰点,因为我计划连接不同的PersonCards
,我的计划是使用这些空视图(玫瑰点)作为每个PersonCards
的“锚点”
通过另一个自定义视图进行连接。
为了拥有连接到两个
PersonCards
的东西,我创建了 ConnectionLine
视图:
public class ConnectionLine extends View {
private Paint paint;
private float startX, startY, endX, endY;
public ConnectionLine(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ConnectionLine(Context context) {
super(context);
init();
}
private void init() {
paint = new Paint();
paint.setColor(getResources().getColor(android.R.color.black)); // Set the color to black
paint.setStrokeWidth(5);
}
public void setPoints(float startX, float startY, float endX, float endY) {
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLine(startX, startY, endX, endY, paint);
}
}
这相当简单,只允许我从 A 点到 B 点画一条线。
接下来,我在我的
PersonCards
中放置了两个ConnectionLine
和一个Fragment
,就像这样:
在我的
ConnectionsFragment.java
中,我有以下代码:
public class ConnectionsFragment extends Fragment {
private PersonCard personCard1, personCard2;
private ConnectionLine connectionLine;
public ConnectionsFragment() {
}
public static ConnectionsFragment newInstance(String param1, String param2) {
ConnectionsFragment fragment = new ConnectionsFragment();
return fragment;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_connections, container, false);
personCard1 = rootView.findViewById(R.id.personCard1);
personCard2 = rootView.findViewById(R.id.personCard2);
connectionLine = rootView.findViewById(R.id.connectionLine);
personCard1.post(new Runnable() {
@Override
public void run() {
int[] startLocation = personCard1.getRightAnchorPosition();
int[] endLocation = personCard2.getLeftAnchorPosition();
connectionLine.setPoints(startLocation[0], startLocation[1], endLocation[0], endLocation[1]);
}
});
return rootView;
}
}
我正在尝试使用
anchor
从 personCard1
获取右侧 personCard1.getRightAnchorPosition();
的位置,以便将它们作为我的 ConnectionLine
的起始坐标传递。与 anchor
的左侧 personCard2
相同,我使用 personCard2.getLeftAnchorPosition();
来获取左锚点的位置。
按照我的逻辑,通过这些步骤,
ConnectionLine
应该成功连接两个personCards
。但是,当我执行应用程序时,会发生这种情况:
按照我的逻辑,通过这些步骤,
ConnectionLine
应该成功连接两个personCards
。但是,当我执行应用程序时,会发生这种情况:
如您所见,ConnectionLine 离它应该在的锚点不远。
我还尝试连接其他锚点,但它们也没有完全连接到我想要的位置,而且我已经没有想法了。从我的角度来看,
ConnectionLine
不会错,因为它只是获取我给它的坐标并连接到它们。这意味着我的坐标在某种程度上一定是错误的。此外,x 坐标似乎总是正确的,但 y 坐标始终是错误的。
有人有这方面的经验吗?或者也许有解决方案?
事实证明,您必须用
anchor
视图的坐标减去所需的 parent
的坐标,因为视图始终相对于其父视图。你可以使用这样的东西:
public int[] getAnchorPosition(View anchor, View parent) {
int[] anchorPosition = new int[2];
int[] parentPosition = new int[2];
anchor.getLocationInWindow(anchorPosition);
parent.getLocationInWindow(parentPosition);
return new int[]{anchorPosition[0] - parentPosition[0] + anchor.getWidth() / 2,
anchorPosition[1] - parentPosition[1] + anchor.getHeight() / 2};
}
然后用你想要获取坐标的
anchor
调用这个方法:
public int[] getTopAnchorPosition(View parent) {
return getAnchorPosition(topAnchor, parent);
}