2014-02-15 57 views
1

我创建了一个自定义视图(ArrowContainer)来环绕其他元素,给它们一个箭头形状的背景。但是,当包含在相对布局中时,我的视图与包含在线性布局中时的显示方式有所不同。在线性和相对布局中显示不同的自定义Android视图

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

An image showing the problem

有没有人见过这样的事情之前?我已经插入ArrowContainer.java调试代码表明,从问题产生的RelativeLayout两次测量的观点,但我不知道为什么,这将导致一个问题...

下面是代码:

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> 

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

} 
+1

这是件好事,你已经找到解决办法,但我想说的是,你应该使用父视图,而不是测量值提供的视图尺寸的实际值。我的意思是在'onLayout'中有'l','t','r'和'b'。如果您查看度量自身,比如说100x100,但您想扩展它以填充父级的宽度,那么如果您使用测量值而不是父级提供的值,则不会得到任何结果。 – nekavally

+0

谢谢,未来会这样做! – WMycroft

回答

1

更新:

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

我的解决方案是创建一个包含嵌套LinearLayout的自定义LinearLayout。外部布局负责绘制背景,并应用足够的填充以允许空间绘制箭头。所有的孩子都会被传递到内部布局。这个解决方案并不完美,因为经常会出现过多的填充和浪费的空间,但这对我的目的来说已经足够了。

代码是在这里:

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

} 
0
MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec)-HORIZONTAL_PADDING*numArrows, MeasureSpec.AT_MOST); 

我关注这条线,你为什么如果宽度,提供给makeMeasureSpec不查,是不是消极的?这种方法不执行范围检查,所以它是你的责任。负宽度=无效measureSpec =未定义的行为。

此外,当我实施一些自定义布局时,我已使用super.onMeasure来确定最大可用尺寸,然后通过getMeasuredWidth(),getMeasuredHeight()使用它们。

+0

感谢您的评论,我没有想过检查一下,我会在将来做!然而,在这种情况下,这似乎不成问题,因为在有问题的情况下,这是积极的。 – WMycroft

相关问题