【问题标题】:Custom Draw Line Between Two Views Not Working Properly两个视图之间的自定义绘制线无法正常工作
【发布时间】:2017-11-01 02:10:41
【问题描述】:

我正在尝试在 android 中的按钮之间画线。我创建了一个自定义类,它在相对布局内的按钮之间画线(相对布局是父布局)。

这是我的 MatchTheColoumnDrawView.java 类,它接受上下文、startView、endView、lineColour、endCircleColour、厚度(浮点数)、方向(LEFT_TO_RIGHT 或 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);
    }


}

我编写了一个类 MatchTheFollowingAttempted,它扩展了相对布局,它包含用于在它们之间绘制按钮和线条的逻辑。这是 MatchTheFollowingAttempted.java 类。

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);
        }



    }

}

当我在 LinearLayout(Inside Scrollview) 中直接使用 MatchTheFollowingAttempted 时,它将正确显示视图,其中包含按钮和它们之间的线条。如附图所示(Image_One)。这是它的xml。

<?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>

但是当我在线性布局中添加第二个 MatchTheFollowingAttempted 时,线条将不会显示(如附加图像 Image_Two 中所示)。这是线性布局中带有两个 MatchTheFollowingAttempted 的 xml。

<?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>

当我真正运行它时,它不会显示第二个布局。

【问题讨论】:

    标签: android android-canvas android-custom-view


    【解决方案1】:

    目前,您以编程方式将MatchTheColumnDrawViews 添加到MatchTheFollowingAttempted,而不设置LayoutParamsLayoutParams 有默认值,所以只有 MatchTheFollowingAttempted 的一个实例你很幸运。

    所以要解决这个问题,你必须将LayoutParams 设置为MatchTheColumnDrawViews,例如这样:

    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);
    }
    

    如果您的左侧和右侧Buttons 的高度差异很大,那么您将不得不找到一个更好的公式,但想法是请求尽可能多的空间而不是整个屏幕(否则下一个自定义 @987654334 @ 根本不会显示),因此请尝试将MatchTheColumnDrawView 底部与最低Button 底部对齐。

    附:我是怎么知道的?

    在您的 View 上使用 Android Studio 的布局检查器(顺便说一句,感谢您发布完整代码!)显示“缺失”的 Views 实际上的高度为 0。

    MatchTheColumnDrawView 中覆盖onMeasure() 以记录测量的宽度和高度证实了这一点:RelativeLayout 进行了两次布局传递:在第一次传递之后高度为 0,但在第二次传递之后,View应该有一些高度 > 0 否则将不会显示。

    编辑:

    MatchTheFollowingAttempted 中,您为每个按钮设置了一个填充 如果在计算中间高度值时减去填充,绿色圆圈将精确绘制在View 边缘的中间,例如

    float endViewMidHeightY = endView.getY() + endView.getHeight() / 2 - 10;
    

    因为Button 有自己的填充(看起来比实际要小),所以圆圈仍然不与可见的Button 边缘重叠。要实现这一点,要么使用您自己的 Button 背景,要么(有点骇人听闻,因为在未来的 Android 版本中填充可能会发生变化)从 x 坐标(左侧)中减去填充,分别将填充添加到 x 坐标(右侧)。

    下面的屏幕截图显示了一些自定义Views 和一些Buttons 的示例:

    【讨论】:

    • 我会尽力让你知道的。
    • 感谢您的解释。
    • @Abhishek - 不客气!解决这个问题需要一些时间,我非常喜欢,尤其是因为您的详细问题使我能够运行测试,而不仅仅是猜测。
    • 如何从边缘的中点开始选择线?有什么解决办法吗?
    • @Abhishek - 你的意思是像绿点应该以中点为中心点吗?
    【解决方案2】:

    你好试试下面的代码

    <?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>
    

    【讨论】:

    • 我只想通过 MatchTheFollowingAttempted 添加它。因为我想动态地使用画线。
    • 那么你必须使用List Adapter添加视图基于你可以隐藏和显示线的场景。
    猜你喜欢
    • 2023-03-27
    • 2019-10-24
    • 1970-01-01
    • 1970-01-01
    • 2021-04-06
    • 1970-01-01
    • 2021-05-09
    • 2020-03-06
    • 1970-01-01
    相关资源
    最近更新 更多