I am trying to draw lines between buttons in android. I created one custom class which draw line between buttons inside relative layout(Relative Layout is parent layout).
Here is my MatchTheColoumnDrawView.java class which accepts context, startView, endView, lineColour, endCircleColour, thickness(in float), direction(LEFT_TO_RIGHT OR RIGHT_TO_LEFT).
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.Log;
import android.view.View;
/**
* Created by Abhishek on 12/21/2016.
*
* Changes made as per new design on Date 9 Oct 2017
*
*/
public class MatchTheColumnDrawView extends View {
public static final int LEFT_TO_RIGHT = 1, RIGHT_TO_LEFT = 2;
private Paint mLinePaint, mCirclePaint;
private View startView, endView;
private int direction;
private Canvas canvas;
private float dashWidth = 15f;
private float dashGap = 8f;
float[] intervals = new float[]{dashWidth, dashGap};
float phase = 0;
private int LINE_COLOR = Color.parseColor("#BEBEBE");
private int END_CIRCLE_COLOR = Color.parseColor("#FF99CC00");
/**
*
* parametrised constructor draws line from
* @startView
* to
* @endView
* as per
* @direction
* and
* @lineColor
* and
* @endCircleColor
*
* when
* @lineColor == null
* default lineColor is gray
*
* when
* @endCircleColor == null
* default endCircleColor is green
* */
public MatchTheColumnDrawView(Context context,
View startView, View endView,
String lineColor, String endCircleColor,
float thickness, int direction) {
super(context);
mLinePaint = new Paint();
if (lineColor != null) LINE_COLOR = Color.parseColor(lineColor);
mLinePaint.setColor(LINE_COLOR);
mLinePaint.setStrokeWidth(thickness);
mLinePaint.setStyle(Paint.Style.STROKE);
mCirclePaint = new Paint();
if (endCircleColor != null) END_CIRCLE_COLOR = Color.parseColor(endCircleColor);
mCirclePaint.setColor(END_CIRCLE_COLOR);
mCirclePaint.setStrokeWidth(thickness);
this.startView = startView;
this.endView = endView;
this.direction = direction;
//setBackgroundColor To Transparent
super.setBackgroundColor(Color.TRANSPARENT);
}
public View getStartView() {
return startView;
}
public void setStartView(View startView) {
this.startView = startView;
}
public View getEndView() {
return endView;
}
public void setEndView(View endView) {
this.endView = endView;
}
public Canvas getCanvas() {
return canvas;
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
@Override
protected void onDraw(Canvas canvas) {
this.canvas = canvas;
Log.d("Direction", String.valueOf(direction));
Log.d("Start View Y:", String.valueOf(startView.getY()));
Log.d("Start View H:", String.valueOf(startView.getHeight()));
Log.d("End View Y:", String.valueOf(endView.getY()));
Log.d("End View H:", String.valueOf(endView.getHeight()));
//By default takes LEFT_TO_RIGHT
if (direction == RIGHT_TO_LEFT) {
//For RIGHT TO LEFT
//Calculating Left X And Mid Of Height Y
/*
* ______________
* | |
* This Point ==>> .| |
* | |
* |______________|
* */
float startViewLeftX = startView.getX();
float startViewMidHeightY = startView.getY() + startView.getHeight() / 2;
//Calculating Right X And Mid Of Height Y
/*
* ______________
* | |
* | |. <<== This Point
* | |
* |______________|
* */
float endViewRightX = endView.getX() + endView.getWidth(); //20 is just to remove unwanted padding on Right Side
float endViewMidHeightY = endView.getY() + endView.getHeight() / 2;
Path mPath = new Path();
mPath.moveTo(startViewLeftX, startViewMidHeightY);
mPath.lineTo(endViewRightX, endViewMidHeightY);
DashPathEffect dashPathEffect = new DashPathEffect(intervals, phase);
mLinePaint.setPathEffect(dashPathEffect);
canvas.drawPath(mPath, mLinePaint);
//canvas.drawLine(startViewLeftX, startViewMidHeightY, endViewRightX, endViewMidHeightY, mLinePaint);
canvas.drawCircle(startViewLeftX, startViewMidHeightY, 5, mCirclePaint);
canvas.drawCircle(endViewRightX, endViewMidHeightY, 5, mCirclePaint);
} else {
//FOR LEFT_TO_RIGHT
//Calculating Right X And Mid Of Height Y
/*
* ______________
* | |
* | |. <<== This Point
* | |
* |______________|
* */
float startViewRightX = startView.getX() + startView.getWidth(); //20 is just to remove unwanted padding on Right Side
float startViewMidHeightY = startView.getY() + startView.getHeight() / 2;
//Calculating Left X And Mid Of Height Y
/*
* ______________
* | |
* This Point ==>> .| |
* | |
* |______________|
* */
float endViewLeftX = endView.getX();
float endViewMidHeightY = endView.getY() + endView.getHeight() / 2;
Path mPath = new Path();
mPath.moveTo(startViewRightX, startViewMidHeightY);
mPath.lineTo(endViewLeftX, endViewMidHeightY);
DashPathEffect dashPathEffect = new DashPathEffect(intervals, phase);
mLinePaint.setPathEffect(dashPathEffect);
canvas.drawPath(mPath, mLinePaint);
//canvas.drawLine(startViewRightX, startViewMidHeightY, endViewLeftX, endViewMidHeightY, mLinePaint);
canvas.drawCircle(startViewRightX, startViewMidHeightY, 5, mCirclePaint);
canvas.drawCircle(endViewLeftX, endViewMidHeightY, 5, mCirclePaint);
}
}
@Override
public void setBackgroundColor(int color) {
super.setBackgroundColor(color);
}
}
I wrote one class MatchTheFollowingAttempted which extend Relative layout and it contains logic for drawing buttons and lines between them. Here is MatchTheFollowingAttempted.java class.
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.RelativeLayout;
import java.util.ArrayList;
/**
* Created by Abhishek on 24-10-2017.
*/
public class MatchTheFollowingAttempted extends RelativeLayout {
private Context mContext;
int numberOfOneSideButtons = 5;
public MatchTheFollowingAttempted(Context context) {
super(context);
mContext = context;
initialiseView();
}
public MatchTheFollowingAttempted(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
initialiseView();
}
public MatchTheFollowingAttempted(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initialiseView();
}
public MatchTheFollowingAttempted(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
initialiseView();
}
public void initialiseView() {
ArrayList<Button> leftSideButtons = new ArrayList<>();
ArrayList<Button> rightSideButtons = new ArrayList<>();
ArrayList<MatchTheColumnDrawView> matchTheColumnDrawViewArrayList = new ArrayList<>();
for (int i = 0; i < numberOfOneSideButtons; i++) {
Button mButton = new Button(mContext);
mButton.setId(View.generateViewId());
RelativeLayout.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if (i != 0) {
layoutParams.addRule(BELOW, leftSideButtons.get(i-1).getId());
}
layoutParams.setMargins(10, 10, 10, 10);
mButton.setLayoutParams(layoutParams);
leftSideButtons.add(mButton);
addView(mButton);
}
for (int i = 0; i < numberOfOneSideButtons; i++) {
Button mButton = new Button(mContext);
mButton.setId(View.generateViewId());
RelativeLayout.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(ALIGN_PARENT_RIGHT);
if (i != 0) {
layoutParams.addRule(BELOW, rightSideButtons.get(i-1).getId());
}
layoutParams.setMargins(10, 10, 10, 10);
mButton.setLayoutParams(layoutParams);
rightSideButtons.add(mButton);
addView(mButton);
}
for (int i = 0; i < numberOfOneSideButtons; i++) {
MatchTheColumnDrawView matchTheColumnDrawView = new MatchTheColumnDrawView(mContext, leftSideButtons.get(i), rightSideButtons.get(4-i), null, null, 2.0f, MatchTheColumnDrawView.LEFT_TO_RIGHT);
matchTheColumnDrawViewArrayList.add(matchTheColumnDrawView);
addView(matchTheColumnDrawView);
}
}
}
When I directly use MatchTheFollowingAttempted inside LinearLayout(Inside Scrollview) it will shows view properly with buttons and lines between them. As shown in image attached(Image_One). Here is the xml for it.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<MatchTheFollowingAttempted
android:layout_width="match_parent"
android:layout_height="wrap_content">
</MatchTheFollowingAttempted>
</LinearLayout>
</ScrollView>
</RelativeLayout>
But when I add second MatchTheFollowingAttempted in Linear Layout Then Lines will not shown(As in attached image Image_Two). Here is xml with two MatchTheFollowingAttempted in Linear Layout.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<MatchTheFollowingAttempted
android:layout_width="match_parent"
android:layout_height="wrap_content">
</MatchTheFollowingAttempted>
<MatchTheFollowingAttempted
android:layout_width="match_parent"
android:layout_height="wrap_content">
</MatchTheFollowingAttempted>
</LinearLayout>
</ScrollView>
</RelativeLayout>
When I run it real its not showing second layout.
Currently, you are adding MatchTheColumnDrawViews programmatically to MatchTheFollowingAttempted without setting LayoutParams. There are default values for LayoutParams, so with just one instance of MatchTheFollowingAttempted you were simply lucky.
So to fix the problem you have to set LayoutParams to the MatchTheColumnDrawViews, for example like this:
for (int i = 0; i < numberOfOneSideButtons; i++) {
MatchTheColumnDrawView matchTheColumnDrawView = new MatchTheColumnDrawView(
mContext, leftSideButtons.get(i),
rightSideButtons.get(numberOfOneSideButtons - 1 - i),
null, null,
2.0f, MatchTheColumnDrawView.LEFT_TO_RIGHT);
RelativeLayout.LayoutParams layoutParams = new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.addRule(ALIGN_TOP,
leftSideButtons.get(0).getId());
layoutParams.addRule(ALIGN_BOTTOM,
leftSideButtons.get(numberOfOneSideButtons - 1).getId());
matchTheColumnDrawView.setLayoutParams(layoutParams);
matchTheColumnDrawViewArrayList.add(matchTheColumnDrawView);
addView(matchTheColumnDrawView);
}
If your left side and right side Buttons have very varying heights then you will have to find a better formula but the idea is to request as much space as possible but not the whole screen (else the next custom View will not be shown at all), so try to align the MatchTheColumnDrawView bottom to the lowest Button bottom.
P.S. How did I find out?
Using Android Studio's Layout Inspector on your View (BTW thanks for posting the full code!) showed that the "missing" Views actually had a height of 0.
Overriding onMeasure() in MatchTheColumnDrawView to log the measured width and height confirmed this: RelativeLayout does two layout passes: having a height of 0 after the first pass may happen, but after the second pass the View should have some height > 0 or it will not be shown.
EDIT:
In MatchTheFollowingAttempted, you set a padding to each of the Buttons
The green circles will be drawn exactly in the middle of the View's edge if you subtract the padding when calculating the mid height values, e.g.
float endViewMidHeightY = endView.getY() + endView.getHeight() / 2 - 10;
Because a Button has its own padding (it looks smaller than it actually is), the circles still do not overlap the visible Button edge. To achieve this, either use your own Button background or (kind of hacky because the padding may change in future Android versions) subtract the padding from the x coordinate (left side) respectively add the padding to the x coordinate (right side) .
The screenshot below shows an example with some custom Views and some Buttons:
Hi try the following code
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="50dp">
<Button
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<View
android:layout_weight="5"
android:layout_width="match_parent"
android:background="#313131"
android:layout_gravity="center_vertical"
android:layout_height="3dp"/>
<Button
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" /></LinearLayout>
来源:https://stackoverflow.com/questions/46911682/custom-draw-line-between-two-views-not-working-properly



