사용자에게 일부 데이터를 제공하는 활동을 만들려고합니다. 데이터는 각각 위젯 인 ‘단어’로 나눌 수 있으며 ‘단어’의 시퀀스는 단어를 포함하는 ViewGroup 위젯 인 데이터 ( ‘sentence’?)를 형성합니다. ‘문장’의 모든 ‘단어’에 필요한 공간이 디스플레이의 사용 가능한 가로 공간을 초과하므로 일반적인 텍스트처럼 이러한 ‘문장’을 래핑하고 싶습니다.
다음 코드 :
public class WrapTest extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout l = new LinearLayout(this);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
LinearLayout.LayoutParams mlp = new LinearLayout.LayoutParams(
new ViewGroup.MarginLayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
mlp.setMargins(0, 0, 2, 0);
for (int i = 0; i < 10; i++) {
TextView t = new TextView(this);
t.setText("Hello");
t.setBackgroundColor(Color.RED);
t.setSingleLine(true);
l.addView(t, mlp);
}
setContentView(l, lp);
}
}
왼쪽 그림과 같은 결과가 나오지만 오른쪽 그림과 같은 위젯을 표시하는 레이아웃을 원합니다.
그러한 레이아웃이나 레이아웃과 매개 변수의 조합이 있습니까? 아니면이를 위해 자체 ViewGroup을 구현해야합니까?
답변
2016 년 5 월부터 Google의 FlexboxLayout이라는 새로운 레이아웃이 있습니다.이 레이아웃은 사용자가 원하는 용도로 고도로 구성 할 수 있습니다.
FlexboxLayout은 현재 https://github.com/google/flexbox-layout의 Google GitHub 저장소에 있습니다 .
build.gradle
파일에 종속성을 추가하여 프로젝트에서 사용할 수 있습니다 .
dependencies {
compile 'com.google.android:flexbox:0.3.2'
}
FlexboxLayout 사용법 및 저장소 readme 또는 Mark Allison 기사에서 찾을 수있는 모든 속성에 대한 자세한 내용은 다음을 참조하십시오.
https://blog.stylingandroid.com/flexboxlayout-part-1/
답변
원하는대로 레이아웃을 만들었지 만 현재로서는 상당히 제한적입니다. 의견 및 개선 제안은 물론 환영합니다.
활동:
package se.fnord.xmms2.predicate;
import se.fnord.android.layout.PredicateLayout;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.widget.TextView;
public class Predicate extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PredicateLayout l = new PredicateLayout(this);
for (int i = 0; i < 10; i++) {
TextView t = new TextView(this);
t.setText("Hello");
t.setBackgroundColor(Color.RED);
t.setSingleLine(true);
l.addView(t, new PredicateLayout.LayoutParams(2, 0));
}
setContentView(l);
}
}
또는 XML 레이아웃 :
<se.fnord.android.layout.PredicateLayout
android:id="@+id/predicate_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
그리고 레이아웃 :
package se.fnord.android.layout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* ViewGroup that arranges child views in a similar way to text, with them laid
* out one line at a time and "wrapping" to the next line as needed.
*
* Code licensed under CC-by-SA
*
* @author Henrik Gustafsson
* @see http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
* @license http://creativecommons.org/licenses/by-sa/2.5/
*
*/
public class PredicateLayout extends ViewGroup {
private int line_height;
public PredicateLayout(Context context) {
super(context);
}
public PredicateLayout(Context context, AttributeSet attrs){
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
assert(MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED);
final int width = MeasureSpec.getSize(widthMeasureSpec);
// The next line is WRONG!!! Doesn't take into account requested MeasureSpec mode!
int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
final int count = getChildCount();
int line_height = 0;
int xpos = getPaddingLeft();
int ypos = getPaddingTop();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
final int childw = child.getMeasuredWidth();
line_height = Math.max(line_height, child.getMeasuredHeight() + lp.height);
if (xpos + childw > width) {
xpos = getPaddingLeft();
ypos += line_height;
}
xpos += childw + lp.width;
}
}
this.line_height = line_height;
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED){
height = ypos + line_height;
} else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST){
if (ypos + line_height < height){
height = ypos + line_height;
}
}
setMeasuredDimension(width, height);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(1, 1); // default of 1px spacing
}
@Override
protected boolean checkLayoutParams(LayoutParams p) {
return (p instanceof LayoutParams);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
final int width = r - l;
int xpos = getPaddingLeft();
int ypos = getPaddingTop();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int childw = child.getMeasuredWidth();
final int childh = child.getMeasuredHeight();
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (xpos + childw > width) {
xpos = getPaddingLeft();
ypos += line_height;
}
child.layout(xpos, ypos, xpos + childw, ypos + childh);
xpos += childw + lp.width;
}
}
}
}
결과 :
답변
나는 이것과 매우 유사한 것을 구현했지만 내가 생각하는 것을 사용하여 간격과 패딩을 처리하는 좀 더 표준적인 방법을 사용했습니다. 여러분의 생각을 알려주세요. 귀속없이 자유롭게 재사용 할 수 있습니다.
package com.asolutions.widget;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.asolutions.widget.R;
public class RowLayout extends ViewGroup {
public static final int DEFAULT_HORIZONTAL_SPACING = 5;
public static final int DEFAULT_VERTICAL_SPACING = 5;
private final int horizontalSpacing;
private final int verticalSpacing;
private List<RowMeasurement> currentRows = Collections.emptyList();
public RowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.RowLayout);
horizontalSpacing = styledAttributes.getDimensionPixelSize(R.styleable.RowLayout_android_horizontalSpacing,
DEFAULT_HORIZONTAL_SPACING);
verticalSpacing = styledAttributes.getDimensionPixelSize(R.styleable.RowLayout_android_verticalSpacing,
DEFAULT_VERTICAL_SPACING);
styledAttributes.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int maxInternalWidth = MeasureSpec.getSize(widthMeasureSpec) - getHorizontalPadding();
final int maxInternalHeight = MeasureSpec.getSize(heightMeasureSpec) - getVerticalPadding();
List<RowMeasurement> rows = new ArrayList<RowMeasurement>();
RowMeasurement currentRow = new RowMeasurement(maxInternalWidth, widthMode);
rows.add(currentRow);
for (View child : getLayoutChildren()) {
LayoutParams childLayoutParams = child.getLayoutParams();
int childWidthSpec = createChildMeasureSpec(childLayoutParams.width, maxInternalWidth, widthMode);
int childHeightSpec = createChildMeasureSpec(childLayoutParams.height, maxInternalHeight, heightMode);
child.measure(childWidthSpec, childHeightSpec);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
if (currentRow.wouldExceedMax(childWidth)) {
currentRow = new RowMeasurement(maxInternalWidth, widthMode);
rows.add(currentRow);
}
currentRow.addChildDimensions(childWidth, childHeight);
}
int longestRowWidth = 0;
int totalRowHeight = 0;
for (int index = 0; index < rows.size(); index++) {
RowMeasurement row = rows.get(index);
totalRowHeight += row.getHeight();
if (index < rows.size() - 1) {
totalRowHeight += verticalSpacing;
}
longestRowWidth = Math.max(longestRowWidth, row.getWidth());
}
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : longestRowWidth
+ getHorizontalPadding(), heightMode == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec)
: totalRowHeight + getVerticalPadding());
currentRows = Collections.unmodifiableList(rows);
}
private int createChildMeasureSpec(int childLayoutParam, int max, int parentMode) {
int spec;
if (childLayoutParam == LayoutParams.FILL_PARENT) {
spec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY);
} else if (childLayoutParam == LayoutParams.WRAP_CONTENT) {
spec = MeasureSpec.makeMeasureSpec(max, parentMode == MeasureSpec.UNSPECIFIED ? MeasureSpec.UNSPECIFIED
: MeasureSpec.AT_MOST);
} else {
spec = MeasureSpec.makeMeasureSpec(childLayoutParam, MeasureSpec.EXACTLY);
}
return spec;
}
@Override
protected void onLayout(boolean changed, int leftPosition, int topPosition, int rightPosition, int bottomPosition) {
final int widthOffset = getMeasuredWidth() - getPaddingRight();
int x = getPaddingLeft();
int y = getPaddingTop();
Iterator<RowMeasurement> rowIterator = currentRows.iterator();
RowMeasurement currentRow = rowIterator.next();
for (View child : getLayoutChildren()) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
if (x + childWidth > widthOffset) {
x = getPaddingLeft();
y += currentRow.height + verticalSpacing;
if (rowIterator.hasNext()) {
currentRow = rowIterator.next();
}
}
child.layout(x, y, x + childWidth, y + childHeight);
x += childWidth + horizontalSpacing;
}
}
private List<View> getLayoutChildren() {
List<View> children = new ArrayList<View>();
for (int index = 0; index < getChildCount(); index++) {
View child = getChildAt(index);
if (child.getVisibility() != View.GONE) {
children.add(child);
}
}
return children;
}
protected int getVerticalPadding() {
return getPaddingTop() + getPaddingBottom();
}
protected int getHorizontalPadding() {
return getPaddingLeft() + getPaddingRight();
}
private final class RowMeasurement {
private final int maxWidth;
private final int widthMode;
private int width;
private int height;
public RowMeasurement(int maxWidth, int widthMode) {
this.maxWidth = maxWidth;
this.widthMode = widthMode;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public boolean wouldExceedMax(int childWidth) {
return widthMode == MeasureSpec.UNSPECIFIED ? false : getNewWidth(childWidth) > maxWidth;
}
public void addChildDimensions(int childWidth, int childHeight) {
width = getNewWidth(childWidth);
height = Math.max(height, childHeight);
}
private int getNewWidth(int childWidth) {
return width == 0 ? childWidth : width + horizontalSpacing + childWidth;
}
}
}
또한 /res/values/attrs.xml 아래에 항목이 필요하며, 아직없는 경우 만들 수 있습니다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RowLayout">
<attr name="android:verticalSpacing" />
<attr name="android:horizontalSpacing" />
</declare-styleable>
</resources>
xml 레이아웃에서의 사용법은 다음과 같습니다.
<?xml version="1.0" encoding="utf-8"?>
<com.asolutions.widget.RowLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="10dp"
android:horizontalSpacing="10dp"
android:verticalSpacing="20dp">
<FrameLayout android:layout_width="30px" android:layout_height="40px" android:background="#F00"/>
<FrameLayout android:layout_width="60px" android:layout_height="40px" android:background="#F00"/>
<FrameLayout android:layout_width="70px" android:layout_height="20px" android:background="#F00"/>
<FrameLayout android:layout_width="20px" android:layout_height="60px" android:background="#F00"/>
<FrameLayout android:layout_width="10px" android:layout_height="40px" android:background="#F00"/>
<FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/>
<FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/>
<FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/>
</com.asolutions.widget.RowLayout>
답변
ApmeM 의 android-flowlayout 프로젝트도 줄 바꿈을 지원합니다.
답변
다음은 단순화 된 코드 전용 버전입니다.
package com.superliminal.android.util;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* A view container with layout behavior like that of the Swing FlowLayout.
* Originally from http://nishantvnair.wordpress.com/2010/09/28/flowlayout-in-android/ itself derived from
* http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
*
* @author Melinda Green
*/
public class FlowLayout extends ViewGroup {
private final static int PAD_H = 2, PAD_V = 2; // Space between child views.
private int mHeight;
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
assert (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED);
final int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
final int count = getChildCount();
int xpos = getPaddingLeft();
int ypos = getPaddingTop();
int childHeightMeasureSpec;
if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST)
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
else
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
mHeight = 0;
for(int i = 0; i < count; i++) {
final View child = getChildAt(i);
if(child.getVisibility() != GONE) {
child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec);
final int childw = child.getMeasuredWidth();
mHeight = Math.max(mHeight, child.getMeasuredHeight() + PAD_V);
if(xpos + childw > width) {
xpos = getPaddingLeft();
ypos += mHeight;
}
xpos += childw + PAD_H;
}
}
if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
height = ypos + mHeight;
} else if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
if(ypos + mHeight < height) {
height = ypos + mHeight;
}
}
height += 5; // Fudge to avoid clipping bottom of last row.
setMeasuredDimension(width, height);
} // end onMeasure()
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
int xpos = getPaddingLeft();
int ypos = getPaddingTop();
for(int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if(child.getVisibility() != GONE) {
final int childw = child.getMeasuredWidth();
final int childh = child.getMeasuredHeight();
if(xpos + childw > width) {
xpos = getPaddingLeft();
ypos += mHeight;
}
child.layout(xpos, ypos, xpos + childw, ypos + childh);
xpos += childw + PAD_H;
}
}
} // end onLayout()
}
답변
첫 번째 답변에 문제가 있습니다.
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
예를 들어, ListView에서 목록 항목은 0의 heightMeasureSpec (UNSPECIFIED)으로 전달되므로 여기서 크기 0 (AT_MOST)의 MeasureSpec이 모든 하위 항목에 전달됩니다. 이것은 전체 PredicateLayout이 보이지 않음을 의미합니다 (높이 0).
빠른 수정으로 다음과 같이 자식 높이 MeasureSpec을 변경했습니다.
int childHeightMeasureSpec;
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
childHeightMeasureSpec =
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
}
else {
childHeightMeasureSpec =
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
그리고
child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
childHeightMeasureSpec);
훨씬 더 까다로운 EXACT 모드를 처리하지 않지만 나를 위해 작동하는 것 같습니다.
답변
이전에 게시 된 내용에 따라 약간 수정 된 버전 :
- Eclipse 레이아웃 편집기에서 작동합니다.
-
모든 항목을 수평으로 중앙에 배치합니다.
import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; public class FlowLayout extends ViewGroup { private int line_height; public static class LayoutParams extends ViewGroup.LayoutParams { public final int horizontal_spacing; public final int vertical_spacing; /** * @param horizontal_spacing Pixels between items, horizontally * @param vertical_spacing Pixels between items, vertically */ public LayoutParams(int horizontal_spacing, int vertical_spacing, ViewGroup.LayoutParams viewGroupLayout) { super(viewGroupLayout); this.horizontal_spacing = horizontal_spacing; this.vertical_spacing = vertical_spacing; } /** * @param horizontal_spacing Pixels between items, horizontally * @param vertical_spacing Pixels between items, vertically */ public LayoutParams(int horizontal_spacing, int vertical_spacing) { super(0, 0); this.horizontal_spacing = horizontal_spacing; this.vertical_spacing = vertical_spacing; } } public FlowLayout(Context context) { super(context); } public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { assert (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED); final int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); final int count = getChildCount(); int line_height = 0; int xpos = getPaddingLeft(); int ypos = getPaddingTop(); int childHeightMeasureSpec; if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); } else { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec); final int childw = child.getMeasuredWidth(); line_height = Math.max(line_height, child.getMeasuredHeight() + lp.vertical_spacing); if (xpos + childw > width) { xpos = getPaddingLeft(); ypos += line_height; } xpos += childw + lp.horizontal_spacing; } } this.line_height = line_height; if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { height = ypos + line_height; } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { if (ypos + line_height < height) { height = ypos + line_height; } } setMeasuredDimension(width, height); } @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(1, 1); // default of 1px spacing } @Override protected android.view.ViewGroup.LayoutParams generateLayoutParams( android.view.ViewGroup.LayoutParams p) { return new LayoutParams(1, 1, p); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { if (p instanceof LayoutParams) { return true; } return false; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); final int width = r - l; int xpos = getPaddingLeft(); int ypos = getPaddingTop(); int lastHorizontalSpacing = 0; int rowStartIdx = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final int childw = child.getMeasuredWidth(); final int childh = child.getMeasuredHeight(); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (xpos + childw > width) { final int freeSpace = width - xpos + lastHorizontalSpacing; xpos = getPaddingLeft() + freeSpace / 2; for (int j = rowStartIdx; j < i; ++j) { final View drawChild = getChildAt(j); drawChild.layout(xpos, ypos, xpos + drawChild.getMeasuredWidth(), ypos + drawChild.getMeasuredHeight()); xpos += drawChild.getMeasuredWidth() + ((LayoutParams)drawChild.getLayoutParams()).horizontal_spacing; } lastHorizontalSpacing = 0; xpos = getPaddingLeft(); ypos += line_height; rowStartIdx = i; } child.layout(xpos, ypos, xpos + childw, ypos + childh); xpos += childw + lp.horizontal_spacing; lastHorizontalSpacing = lp.horizontal_spacing; } } if (rowStartIdx < count) { final int freeSpace = width - xpos + lastHorizontalSpacing; xpos = getPaddingLeft() + freeSpace / 2; for (int j = rowStartIdx; j < count; ++j) { final View drawChild = getChildAt(j); drawChild.layout(xpos, ypos, xpos + drawChild.getMeasuredWidth(), ypos + drawChild.getMeasuredHeight()); xpos += drawChild.getMeasuredWidth() + ((LayoutParams)drawChild.getLayoutParams()).horizontal_spacing; } } } }