在我正在处理的项目中,我将视图定位在FrameLayout
WRAP_CONTENT
内:
<FrameLayout
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"
tools:context=".MainActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0400">
<TextView
android:background="#04FF00"
android:text="Test"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:layout_marginBottom="10dp"
android:layout_gravity="top|left"
/>
</FrameLayout>
</FrameLayout>
自Android 3.0(API11)以来,渲染的布局符合预期:
但在Android 2.3.3(API10)及以下版本中,绿色MarginBottom
的{{1}}似乎是从红色TextView
中提取的:
为什么布局在API10及以下版本中未按预期呈现?这是Android中的错误吗?如果是这样,是否有快速解决方法?
我知道我可以在FrameLayout
上应用底部填充而不是FrameLayout
的底部边距。但在我的情况下,我没有简单的方法来实现这一点,因为布局是以编程方式构建的。
修改(22-07)
在我寻找解决这个问题的方法的过程中,我查看了the changes between API10 and API11。但我在TextView
课程中找不到任何机会。这很奇怪,因为在API11中修复了以下错误(我认为可能与之相关):https://code.google.com/p/android/issues/detail?id=28057。
编辑(23-07)
保证金底部似乎不是问题;删除它没有任何区别。它似乎是导致API10及以下版本错误高度的上边距。
答案 0 :(得分:1)
FrameLayout
文档说明了以下内容:
FrameLayout旨在阻挡屏幕上要显示的区域 单个项目。通常,FrameLayout应该用于保存单个 子视图,因为它可能很难组织子视图 在没有孩子的情况下可以扩展到不同屏幕尺寸的方式 相互重叠。
这是我放弃FrameLayout
的原因。
然后我开始寻找一个不同的ViewGroup
,它支持x / y定位元素和提供重力的能力。
AbsoluteLayout
仅支持按x / y定位(也已弃用)。
RelativeLayout
进行额外测量(在我的情况下)意味着不良表现。
所以我最终创建了自己的ViewGroup
,它基本上是FrameLayout
的引力和AbsoluteLayout
的x / y的合并:
package com.example.common.widget;
import java.util.ArrayList;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
public class FixedLayout extends ViewGroup
{
private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP;
private final ArrayList<View> mMatchParentChildren = new ArrayList<View>();
public FixedLayout(Context context)
{
super(context);
}
public FixedLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public FixedLayout(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY
|| MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// Find rightmost and bottom-most child
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams)child.getLayoutParams();
maxWidth =
Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin);
maxHeight =
Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin
+ lp.bottomMargin);
childState = childState | getChildMeasuredState(child);
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT
|| lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeft() + getPaddingRight();
maxHeight += getPaddingTop() + getPaddingBottom();
// Check against minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(
getResolvedSizeAndState(maxWidth, widthMeasureSpec, childState),
getResolvedSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
for (int i = 0; i < mMatchParentChildren.size(); i++) {
final View child = mMatchParentChildren.get(i);
final LayoutParams lp = (LayoutParams)child.getLayoutParams();
int childWidthMeasureSpec;
int childHeightMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthMeasureSpec =
MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft()
- getPaddingRight() - lp.leftMargin - lp.rightMargin - lp.x,
MeasureSpec.EXACTLY);
}
else {
childWidthMeasureSpec =
getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight()
+ lp.leftMargin + lp.rightMargin + lp.x, lp.width);
}
if (lp.height == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec =
MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop()
- getPaddingBottom() - lp.topMargin - lp.bottomMargin - lp.y,
MeasureSpec.EXACTLY);
}
else {
childHeightMeasureSpec =
getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom()
+ lp.topMargin + lp.bottomMargin + lp.y, lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
/**
* Returns a set of layout parameters with a width of
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, a height of
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and with the coordinates (0, 0).
*/
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams()
{
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0);
}
/**
* Ask one of the children of this view to measure itself, taking into account both the
* MeasureSpec requirements for this view and its padding and margins. The child must have
* MarginLayoutParams The heavy lifting is done in getChildMeasureSpec.
*
* @param child
* The child to measure
* @param parentWidthMeasureSpec
* The width requirements for this view
* @param widthUsed
* Extra space that has been used up by the parent horizontally (possibly by other
* children of the parent)
* @param parentHeightMeasureSpec
* The height requirements for this view
* @param heightUsed
* Extra space that has been used up by the parent vertically (possibly by other
* children of the parent)
*/
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed)
{
final LayoutParams lp = (LayoutParams)child.getLayoutParams();
final int childWidthMeasureSpec =
getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft() + getPaddingRight()
+ lp.leftMargin + lp.rightMargin + lp.x + widthUsed, lp.width);
final int childHeightMeasureSpec =
getChildMeasureSpec(parentHeightMeasureSpec, getPaddingTop() + getPaddingBottom()
+ lp.topMargin + lp.bottomMargin + lp.y + heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
int count = getChildCount();
final int parentLeft = getPaddingLeft();
final int parentRight = right - left - getPaddingRight();
final int parentTop = getPaddingTop();
final int parentBottom = bottom - top - getPaddingBottom();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams)child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (horizontalGravity) {
case Gravity.LEFT:
childLeft = parentLeft + lp.x;
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.x;
break;
case Gravity.RIGHT:
childLeft = parentRight - width - lp.x;
break;
default:
childLeft = parentLeft + lp.x;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.y;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.y;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.y;
break;
default:
childTop = parentTop + lp.y;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)
{
return new FixedLayout.LayoutParams(getContext(), attrs);
}
// Override to allow type-checking of LayoutParams.
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p)
{
return p instanceof FixedLayout.LayoutParams;
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
{
return new LayoutParams(p);
}
@Override
public boolean shouldDelayChildPressedState()
{
return false;
}
/**
* Return only the state bits of {@link #getMeasuredWidthAndState()} and
* {@link #getMeasuredHeightAndState()} of a child view, combined into one integer. The width
* component is in the regular bits {@link #MEASURED_STATE_MASK} and the height component is at
* the shifted bits {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
*/
public final int getChildMeasuredState(View child)
{
return (child.getMeasuredWidth() & MEASURED_STATE_MASK)
| ((child.getMeasuredHeight() >> MEASURED_HEIGHT_STATE_SHIFT) & (MEASURED_STATE_MASK >> MEASURED_HEIGHT_STATE_SHIFT));
}
/**
* Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpec.
* Will take the desired size, unless a different size is imposed by the constraints. The
* returned value is a compound integer, with the resolved size in the
* {@link #MEASURED_SIZE_MASK} bits and optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set
* if the resulting size is smaller than the size the view wants to be.
*
* @param size
* How big the view wants to be
* @param measureSpec
* Constraints imposed by the parent
* @return Size information bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
public static int getResolvedSizeAndState(int size, int measureSpec, int childMeasuredState)
{
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
}
else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
/**
* Per-child layout information associated with AbsoluteLayout. See
* {@link android.R.styleable#AbsoluteLayout_Layout Absolute Layout Attributes} for a list of
* all child view attributes that this class supports.
*/
public static class LayoutParams extends ViewGroup.MarginLayoutParams
{
/**
* The horizontal, or X, location of the child within the view group.
*/
public int x;
/**
* The vertical, or Y, location of the child within the view group.
*/
public int y;
/**
* The gravity to apply with the View to which these layout parameters are associated.
*
* @see android.view.Gravity
*/
public int gravity = -1;
/**
* Creates a new set of layout parameters with the specified width, height and location.
*
* @param width
* the width, either {@link #MATCH_PARENT}, {@link #WRAP_CONTENT} or a fixed size
* in pixels
* @param height
* the height, either {@link #MATCH_PARENT}, {@link #WRAP_CONTENT} or a fixed
* size in pixels
*/
public LayoutParams(int width, int height)
{
super(width, height);
}
/**
* Creates a new set of layout parameters with the specified width, height and location.
*
* @param width
* the width, either {@link #MATCH_PARENT}, {@link #WRAP_CONTENT} or a fixed size
* in pixels
* @param height
* the height, either {@link #MATCH_PARENT}, {@link #WRAP_CONTENT} or a fixed
* size in pixels
* @param x
* the X location of the child
* @param y
* the Y location of the child
*/
public LayoutParams(int width, int height, int x, int y)
{
super(width, height);
this.x = x;
this.y = y;
}
/**
* Creates a new set of layout parameters. The values are extracted from the supplied
* attributes set and context. The XML attributes mapped to this set of layout parameters
* are:
*
* <ul>
* <li><code>layout_x</code>: the X location of the child</li>
* <li><code>layout_y</code>: the Y location of the child</li>
* <li>All the XML attributes from {@link android.view.ViewGroup.LayoutParams}</li>
* </ul>
*
* @param c
* the application environment
* @param attrs
* the set of attributes from which to extract the layout parameters values
*/
public LayoutParams(Context c, AttributeSet attrs)
{
super(c, attrs);
TypedArray a =
c.obtainStyledAttributes(attrs,
com.example.common.R.styleable.FixedLayout_Layout);
gravity =
a.getInt(com.example.common.R.styleable.FixedLayout_Layout_layout_gravity,
-1);
x =
a.getDimensionPixelOffset(
com.example.common.R.styleable.FixedLayout_Layout_layout_x, 0);
y =
a.getDimensionPixelOffset(
com.example.common.R.styleable.FixedLayout_Layout_layout_y, 0);
topMargin =
a.getDimensionPixelOffset(
com.example.common.R.styleable.FixedLayout_Layout_layout_marginTop,
0);
leftMargin =
a.getDimensionPixelOffset(
com.example.common.R.styleable.FixedLayout_Layout_layout_marginLeft,
0);
bottomMargin =
a.getDimensionPixelOffset(
com.example.common.R.styleable.FixedLayout_Layout_layout_marginBottom,
0);
rightMargin =
a.getDimensionPixelOffset(
com.example.common.R.styleable.FixedLayout_Layout_layout_marginRight,
0);
a.recycle();
}
/**
* {@inheritDoc}
*/
public LayoutParams(ViewGroup.LayoutParams source)
{
super(source);
}
}
}
答案 1 :(得分:0)
尝试从android:layout_gravity="top|left"
TextView
将填充添加到父布局
<FrameLayout 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"
tools:context=".MainActivity" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0400"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:paddingTop="10dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#04FF00"
android:text="Test" />
</FrameLayout>