android - 自定义 Android View 在线性和相对布局中显示不同

标签 android android-layout

我创建了一个自定义 View (ArrowContainer) 来包裹其他元素,为它们提供箭头形状的背景。但是,当包含在相对布局中时,我的 View 显示方式与包含在线性布局中时的显示方式不同。

这就是问题所在,顶部的 ArrowContainer 包含在 LinearLayout 中并且行为正确,底部的 ArrowContainer 包含在 RelativeLayout 中并且行为不正确。

An image showing the problem

有没有人见过这样的东西?我在 ArrowContainer.java 中插入的调试代码表明问题是由 RelativeLayout 测量 View 两次引起的,但我不确定为什么这会导致问题...

代码如下:

ArrowContainer.java

package com.example.arrowcontainertest;


import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

public class ArrowContainer extends ViewGroup {

    private static final int ARROW_LEFT = 0;
    private static final int ARROW_RIGHT = 1;
    private static final int ARROW_BOTH = 2;

    private static final int DEFAULT_COLOUR = 0xFFFF0000;

    private static final int HORIZONTAL_PADDING = 150;

    private Path path;
    private Paint paint;
    private int arrowSide = ARROW_RIGHT;
    private int colour = DEFAULT_COLOUR;
    private int downColour;
    private Paint downPaint;
    private Boolean isButton = false;

    private View child;

    public ArrowContainer(Context context) {
        super(context);
        init();
    }


    public ArrowContainer(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);

        try {
            arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
            colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
            isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
        } finally {
            a.recycle();
        }

        init();
    }

    public ArrowContainer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);

        try {
            arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
            colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
            isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
        } finally {
            a.recycle();
        }

        init();
    }

    private void init() {
        paint = new Paint();
        paint.setColor(colour);
        paint.setStyle(Style.FILL);
        setWillNotDraw(false);

        if (isButton) {
            setFocusable(true);
            setClickable(true);

            downColour = 0xFF00FF00;
            downPaint = new Paint();
            downPaint.setColor(downColour);
            downPaint.setStyle(Style.FILL);
        }
    }

    @Override
    protected void onFinishInflate() {
        // Must have exactly 1 child
        assert getChildCount()==1;
        if (getChildCount() == 1) {
            child = getChildAt(0);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Debug
        Log.e("DEBUG", "Type:" + getParent().getClass());
        Log.e("DEBUG", "Width Mode: " + MeasureSpec.getMode(widthMeasureSpec));
        Log.e("DEBUG", "Height Mode: " + MeasureSpec.getMode(heightMeasureSpec));
        Log.e("DEBUG", "Width Size: " + MeasureSpec.getSize(widthMeasureSpec));
        Log.e("DEBUG", "Height Size: " + MeasureSpec.getSize(heightMeasureSpec));   


        // Restrict the childs width to at most this components size minus a fixed value (HORIZONTAL_PADDING*numArrows)
        int numArrows=0;
        switch (arrowSide) {
            case ARROW_RIGHT:
                numArrows = 1;
                break;
            case ARROW_LEFT:
                numArrows = 1;
                break;
            case ARROW_BOTH:
                numArrows = 2;
                break;
        }
        int widthSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec)-HORIZONTAL_PADDING*numArrows, MeasureSpec.AT_MOST);
        int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        child.measure(widthSpec, heightSpec);
        int width = child.getMeasuredWidth();
        int height = child.getMeasuredHeight();
        setMeasuredDimension(width + (int) (numArrows*height/2f), height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        int childWidth = child.getMeasuredWidth();
        int childHeight = child.getMeasuredHeight();
        switch (arrowSide) {
            case ARROW_RIGHT:
                // Hug left
                child.layout(0, height/2 - childHeight/2, width - height/2, height/2 + childHeight/2);
                break;
            case ARROW_LEFT:
                // Hug right
                child.layout(height/2, height/2 - childHeight/2, width, height/2 + childHeight/2);
                break;
            case ARROW_BOTH:
                // Center
                child.layout(width/2 - childWidth/2, height/2 - childHeight/2, width/2 + childWidth/2, height/2 + childHeight/2);
                break;
        }
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        path = new Path();
        switch (arrowSide) {
            case ARROW_RIGHT:
                path.lineTo(0, h);
                path.lineTo(w-h/2f, h);
                path.lineTo(w, h/2f);
                path.lineTo(w-h/2f, 0);
                break;
            case ARROW_LEFT:
                path.moveTo(h/2f, 0);
                path.lineTo(0, h/2f);
                path.lineTo(h/2f, h);
                path.lineTo(w, h);
                path.lineTo(w, 0);  
                break;
            case ARROW_BOTH:
                path.moveTo(h/2f, 0);
                path.lineTo(0, h/2f);
                path.lineTo(h/2f, h);
                path.lineTo(w-h/2f, h);
                path.lineTo(w, h/2f);
                path.lineTo(w-h/2f, 0);
                break;
        }
        path.close();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        invalidate();
        if (isPressed()) {
            canvas.drawPath(path, downPaint);
        } else {
            canvas.drawPath(path, paint);
        }
        super.onDraw(canvas);
    }

}

activity_main.xml

<LinearLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" xmlns:app="http://schemas.android.com/apk/res/com.example.arrowcontainertest">

<LinearLayout 
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.example.arrowcontainertest.ArrowContainer
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:arrowSide="right">
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Play"
            android:textSize="50sp"/>
    </com.example.arrowcontainertest.ArrowContainer>   

</LinearLayout>

<RelativeLayout 
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.example.arrowcontainertest.ArrowContainer
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:arrowSide="right">
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Play"
            android:textSize="50sp"/>
    </com.example.arrowcontainertest.ArrowContainer>

</RelativeLayout>

主 Activity .java

package com.example.arrowcontainertest;

import android.os.Bundle;
import android.app.Activity;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

}

最佳答案

更新:

我一直无法解决这个问题,而且这个组件在其他情况下也引起了问题。因此,我决定重写组件以尽可能使用一些自定义功能。

我的解决方案是创建一个包含嵌套 LinearLayout 的自定义 LinearLayout。外部布局负责绘制背景,并应用足够的填充以留出空间来绘制箭头。所有 child 都被传递到内部布局。此解决方案并不完美,因为通常会出现过多的填充并因此浪费空间,但它足以满足我的目的。

代码在这里:

package com.example.arrowcontainertest;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;

public class NewArrowContainer extends LinearLayout {

    private static final int ARROW_LEFT = 0;
    private static final int ARROW_RIGHT = 1;
    private static final int ARROW_BOTH = 2;
    private static final int DEFAULT_COLOUR = 0xFFFF0000;
    private static final int ARROW_MAX_WIDTH = 150;

    private LinearLayout childLayout;

    private Path path;
    private Paint paint;
    private int arrowSide = ARROW_RIGHT;
    private int colour = DEFAULT_COLOUR;
    private int downColour;
    private Paint downPaint;
    private Boolean isButton = false;

    public NewArrowContainer(Context context) {
        super(context);
        init();
    }

    public NewArrowContainer(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);

        try {
            arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
            colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
            isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
        } finally {
            a.recycle();
        }

        init();
    }

    public NewArrowContainer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);

        try {
            arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
            colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
            isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
        } finally {
            a.recycle();
        }

        init();
    }

    private void init() {
        paint = new Paint();
        paint.setColor(colour);
        paint.setStyle(Style.FILL);
        setWillNotDraw(false);

        if (isButton) {
            setFocusable(true);
            setClickable(true);

            downColour = 0xFF00FF00;
            downPaint = new Paint();
            downPaint.setColor(downColour);
            downPaint.setStyle(Style.FILL);
        }

        LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        layoutInflater.inflate(R.layout.arrow_container, this);
        childLayout = (LinearLayout) findViewById(R.id.child);

        // Pass properties to childLayout
        childLayout.setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
        childLayout.setOrientation(getOrientation());

        // Give the padding sufficient for arrows
        switch (arrowSide) {
            case ARROW_RIGHT:
                setPadding(0, 0, ARROW_MAX_WIDTH, 0);
                break;
            case ARROW_LEFT:
                setPadding(ARROW_MAX_WIDTH, 0, 0, 0);
                break;
            case ARROW_BOTH:
                setPadding(ARROW_MAX_WIDTH, 0, ARROW_MAX_WIDTH, 0);
                break;
        }
    }

    public void setColour(int colour) {
        paint.setColor(colour);
    }

    @Override
    public void onFinishInflate() {
        // Pass all children to the childLayout
        while (getChildCount() > 1) {
            View v = getChildAt(1);
            removeViewAt(1);
            childLayout.addView(v);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        path = new Path();
        switch (arrowSide) {
            case ARROW_RIGHT:
                path.lineTo(0, h);
                path.lineTo(w-ARROW_MAX_WIDTH, h);
                path.lineTo(w-ARROW_MAX_WIDTH+h/2f, h/2f);
                path.lineTo(w-ARROW_MAX_WIDTH, 0);
                break;
            case ARROW_LEFT:
                path.moveTo(ARROW_MAX_WIDTH-h/2f, h/2f);
                path.lineTo(ARROW_MAX_WIDTH, h);
                path.lineTo(w, h);
                path.lineTo(w, 0);
                path.lineTo(ARROW_MAX_WIDTH, 0);    
                break;
            case ARROW_BOTH:
                path.moveTo(ARROW_MAX_WIDTH-h/2f, h/2f);
                path.lineTo(ARROW_MAX_WIDTH, h);
                path.lineTo(w-ARROW_MAX_WIDTH, h);
                path.lineTo(w-ARROW_MAX_WIDTH+h/2f, h/2f);
                path.lineTo(w-ARROW_MAX_WIDTH, 0);
                path.lineTo(ARROW_MAX_WIDTH, 0);
                break;
        }
        path.close();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        invalidate();
        if (isPressed()) {
            canvas.drawPath(path, downPaint);
        } else {
            canvas.drawPath(path, paint);
        }
        super.onDraw(canvas);
    }

}

关于android - 自定义 Android View 在线性和相对布局中显示不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21801188/

相关文章:

java - 如何在android中检查拖放是否为空

java - 什么是 textview.setTextSize()?

android创建下拉列表

android - Android 中的自定义搜索?

java - 用 subview 网格填充屏幕,但 subview 的大小是预期大小的一半

android - 当 Android Studio 检测到手机时,logcat 中没有可调试的进程

android - ContentProvider 实例化失败 - NullPointerException

带有动画的 Android 调整容器大小

android - 通过使用计时器(或处理程序)更改 ImageView 中的图像来创建动画

android - 在水平 ScrollView 中的 ImageView 列表之间添加边框/分隔符