FlowLayout
Android에서 어떻게 할 수 있습니까?
답변
Devoxx 대학의 날 ( parleys.com에서 제공 ) 에서 제가 한 강연 을 보면 직접하는 방법을 배울 수 있습니다. 강연 중에 나는FlowLayout
사용자 지정 레이아웃을 작성하는 것이 얼마나 간단한 지 보여주기 위해 무대에서 구현을 작성했습니다.
답변
속성 FlexboxLayout
과 함께 사용해야 flexWrap="wrap"
합니다.
<com.google.android.flexbox.FlexboxLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flexWrap="wrap">
<!-- contents go here -->
</com.google.android.flexbox.FlexboxLayout>
빌드 지침 은 github repo를 참조하세요 .
이에 대한 추가 정보-https: //android-developers.googleblog.com/2017/02/build-flexible-layouts-with.html
답변
Romain Guy의 답변에 댓글을 달 수있을만큼 평판이 좋지는 않지만이 답변이 있어야합니다 (편집을 공유하기 위해 계정을 만들었습니다).
어쨌든, 다른 사람들이 그의 멋진 FlowLayout 솔루션에 몇 가지 문제가 있음을 알게 된 것을 봅니다. 나는 직접 하나를 찾을 수 있었고 다른 아이들처럼 일부 아이들이 잘리는 것을 보았습니다. 알고리즘을 자세히 살펴보면 높이 계산에서 매우 간단한 실수 인 것 같습니다. 마지막 아이가 새 줄에 놓일 때 높이가 제대로 계산되지 않았습니다. 계산을 조금 정리했습니다 ( “height”대 currentHeight의 이상한 사용이있었습니다).
다음 변경 사항은 “새 줄에있는 경우 마지막 자식이 잘림”문제를 해결합니다.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int widthLimit = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight();
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
boolean growHeight = widthMode != MeasureSpec.UNSPECIFIED;
int width = 0;
int currentWidth = getPaddingLeft();
int currentHeight = getPaddingTop();
int maxChildHeight = 0;
boolean breakLine = false;
boolean newLine = false;
int spacing = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++)
{
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
spacing = mHorizontalSpacing;
if (lp.horizontalSpacing >= 0)
{
spacing = lp.horizontalSpacing;
}
if (growHeight && (breakLine || ((currentWidth + child.getMeasuredWidth()) > widthLimit)))
{
newLine = true;
currentHeight += maxChildHeight + mVerticalSpacing;
width = Math.max(width, currentWidth - spacing);
currentWidth = getPaddingLeft();
maxChildHeight = 0;
}
else
{
newLine = false;
}
maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
lp.x = currentWidth;
lp.y = currentHeight;
currentWidth += child.getMeasuredWidth() + spacing;
breakLine = lp.breakLine;
}
if (newLine == false)
{
width = Math.max(width, currentWidth - spacing);
}
width += getPaddingRight();
int height = currentHeight + maxChildHeight + getPaddingBottom();
setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
}
답변
“flexbox-layout” 이라는 Google 라이브러리가 있습니다 . 당신은 그것을 확인해야합니다.
RecyclerView에서 사용하려면 다음과 같이 사용할 수 있습니다.
val layoutManager = FlexboxLayoutManager(activity)
layoutManager.flexDirection = FlexDirection.ROW
layoutManager.flexWrap = FlexWrap.WRAP
layoutManager.justifyContent = JustifyContent.FLEX_START
layoutManager.alignItems = AlignItems.FLEX_START
recyclerView.layoutManager=layoutManager
답변
이전 답변 중 하나와 마찬가지로 여기에서 솔루션으로 시작했습니다. http://hzqtc.github.io/2013/12/android-custom-layout-flowlayout.html .
나는 아래와 같이 아이들의 다양한 키를 설명하기 위해 그것을 확장했습니다.
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
// Custom layout that wraps child views to a new line
public class FlowLayout extends ViewGroup {
private int marginHorizontal;
private int marginVertical;
public FlowLayout(Context context) {
super(context);
init();
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() { // Specify the margins for the children
marginHorizontal = getResources().getDimensionPixelSize(R.dimen.activity_half_horizontal_margin);
marginVertical = getResources().getDimensionPixelSize(R.dimen.activity_half_vertical_margin);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childLeft = getPaddingLeft();
int childTop = getPaddingTop();
int lowestBottom = 0;
int lineHeight = 0;
int myWidth = resolveSize(100, widthMeasureSpec);
int wantedHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
child.measure(getChildMeasureSpec(widthMeasureSpec, 0, child.getLayoutParams().width),
getChildMeasureSpec(heightMeasureSpec, 0, child.getLayoutParams().height));
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
lineHeight = Math.max(childHeight, lineHeight);
if (childWidth + childLeft + getPaddingRight() > myWidth) { // Wrap this line
childLeft = getPaddingLeft();
childTop = marginVertical + lowestBottom; // Spaced below the previous lowest point
lineHeight = childHeight;
}
childLeft += childWidth + marginHorizontal;
if (childHeight + childTop > lowestBottom) { // New lowest point
lowestBottom = childHeight + childTop;
}
}
wantedHeight += childTop + lineHeight + getPaddingBottom();
setMeasuredDimension(myWidth, resolveSize(wantedHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int childLeft = getPaddingLeft();
int childTop = getPaddingTop();
int lowestBottom = 0;
int myWidth = right - left;
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
if (childWidth + childLeft + getPaddingRight() > myWidth) { // Wrap this line
childLeft = getPaddingLeft();
childTop = marginVertical + lowestBottom; // Spaced below the previous lowest point
}
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
childLeft += childWidth + marginHorizontal;
if (childHeight + childTop > lowestBottom) { // New lowest point
lowestBottom = childHeight + childTop;
}
}
}
}
여러 줄 텍스트 편집기를 래핑하는 솔루션으로 이것을 사용했습니다. 도움이 되었기를 바랍니다.
답변
다음은 동적 뷰 (FlowLayout이라고도 함)를 추가하여 다음과 같은 레이아웃을 얻을 수있는 사용자 정의 클래스입니다.
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/*
Created By Dhavalkumar Solanki
* */
public class FlowLayout extends ViewGroup {
private int line_height_space;
public static class LayoutParams extends ViewGroup.LayoutParams {
public int horizontal_spacing;
public 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) {
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_space = 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_space = Math.max(line_height_space, child.getMeasuredHeight() + lp.vertical_spacing);
if (xpos + childw > width) {
xpos = getPaddingLeft();
ypos += line_height_space;
}
xpos += childw + lp.horizontal_spacing;
}
}
this.line_height_space = line_height_space;
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
height = ypos + line_height_space;
} else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
if (ypos + line_height_space < height) {
height = ypos + line_height_space;
}
}
setMeasuredDimension(width, height);
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(1, 1); // default of 1px spacing
}
@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();
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_space;
}
child.layout(xpos, ypos, xpos + childw, ypos + childh);
xpos += childw + lp.horizontal_spacing;
}
}
}
}
예 :
text_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tool="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:id="@+id/tvText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="19sp"
android:background="@drawable/unselected_tag"
android:textColor="@color/colorPrimary"
tool:text="Temp" />
</RelativeLayout>
activity_flow_layou_demo.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="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitleBusiness"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Business Interest "
android:textColor="@color/colorPrimary"
android:textSize="25sp" />
<com.example.tristateandroid2.radardemo.FlowLayout
android:id="@+id/flowBusiness"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.tristateandroid2.radardemo.FlowLayout>
</LinearLayout>
<LinearLayout
android:layout_marginTop="@dimen/activity_horizontal_margin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitlePrivate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Private Interest "
android:textColor="@color/colorPrimary"
android:textSize="25sp" />
<com.example.tristateandroid2.radardemo.FlowLayout
android:id="@+id/flowPrivate"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.tristateandroid2.radardemo.FlowLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</RelativeLayout>
FlowLayouDemo.java
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
public class FlowLayouDemo extends AppCompatActivity {
private TextView tvTitleBusiness;
private FlowLayout flowBusiness;
private TextView tvTitlePrivate;
private FlowLayout flowPrivate;
private ArrayList<TagModel> arrayList;
private void findViews() {
tvTitleBusiness = (TextView) findViewById(R.id.tvTitleBusiness);
flowBusiness = (FlowLayout) findViewById(R.id.flowBusiness);
tvTitlePrivate = (TextView) findViewById(R.id.tvTitlePrivate);
flowPrivate = (FlowLayout) findViewById(R.id.flowPrivate);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flow_layou_demo);
findViews();
addLayouts();
}
private void addLayouts() {
if (arrayList == null) {
arrayList = new ArrayList<>();
}
flowBusiness.removeAllViews();
flowPrivate.removeAllViews();
for (int i = 0; i < 75; i++) {
final boolean[] selected = {false};
View view = this.getLayoutInflater().inflate(R.layout.text_view, null);
final TextView textView = (TextView) view.findViewById(R.id.tvText);
if (i % 5 == 0) {
arrayList.add(new TagModel(i, false, "Business VIEW : " + i));
textView.setText("Busi VIEW To IS : " + i);
} else {
arrayList.add(new TagModel(i, false, "TEXT IS : " + i));
textView.setText("Busi IS : " + i);
}
textView.setBackgroundResource(R.drawable.unselected_tag);
textView.setTextColor(Color.parseColor("#3F51B5"));
textView.setTag(i);
if(i<=50){
flowBusiness.addView(view);
}else {
textView.setText("Priv View : "+i);
flowPrivate.addView(view);
}
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (selected[0]) {
selected[0] = false;
textView.setBackgroundResource(R.drawable.unselected_tag);
textView.setTextColor(Color.parseColor("#3F51B5"));
} else {
selected[0] = true;
textView.setBackgroundResource(R.drawable.selected_tag);
textView.setTextColor(Color.parseColor("#FFFFFF"));
}
}
});
}
}
}
답변
@MattNotEquals () FlowLayout에 대한 개정 를 지원 입니다.
이것은 왼쪽, 오른쪽, 위쪽 및 아래쪽 여백을 지원하기위한 MarginLayoutParms의 최소 구현 일뿐입니다.
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* Original version courtesy of MattNotEquals() at http://stackoverflow.com/a/34169798/4515489 - 4/13/17.
* 7/15/17 Revised to support MarginLayoutParams.
*/
public class FlowLayout extends ViewGroup {
// Custom layout that wraps child views to a new line.
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childLeft = getPaddingLeft();
int childTop = getPaddingTop();
int lowestBottom = 0;
int lineHeight = 0;
int myWidth = resolveSize(100, widthMeasureSpec);
int wantedHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
child.measure(getChildMeasureSpec(widthMeasureSpec, 0, child.getLayoutParams().width),
getChildMeasureSpec(heightMeasureSpec, 0, child.getLayoutParams().height));
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
lineHeight = Math.max(childHeight, lineHeight);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
childLeft += lp.leftMargin;
childTop += lp.topMargin;
if (childLeft + childWidth + lp.rightMargin + getPaddingRight() > myWidth) { // Wrap this line
childLeft = getPaddingLeft() + lp.leftMargin;
childTop = lowestBottom + lp.topMargin; // Spaced below the previous lowest point
lineHeight = childHeight;
}
childLeft += childWidth + lp.rightMargin;
if (childTop + childHeight + lp.bottomMargin > lowestBottom) { // New lowest point
lowestBottom = childTop + childHeight + lp.bottomMargin;
}
}
wantedHeight += lowestBottom + getPaddingBottom(); // childTop + lineHeight + getPaddingBottom();
setMeasuredDimension(myWidth, resolveSize(wantedHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int childLeft = getPaddingLeft();
int childTop = getPaddingTop();
int lowestBottom = 0;
int myWidth = right - left;
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
childLeft += lp.leftMargin;
childTop += lp.topMargin;
if (childLeft + childWidth + lp.rightMargin + getPaddingRight() > myWidth) { // Wrap this line
childLeft = getPaddingLeft() + lp.leftMargin;
childTop = lowestBottom + lp.topMargin; // Spaced below the previous lowest point
}
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
childLeft += childWidth + lp.rightMargin;
if (childTop + childHeight + lp.bottomMargin > lowestBottom) { // New lowest point
lowestBottom = childTop + childHeight + lp.bottomMargin;
}
}
}
@Override
public boolean shouldDelayChildPressedState() {
return false;
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new FlowLayout.LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (lp instanceof LayoutParams) {
return new LayoutParams((LayoutParams) lp);
}
else if (lp instanceof MarginLayoutParams) {
return new LayoutParams((MarginLayoutParams) lp);
}
else
return super.generateLayoutParams(lp);
}
/**
* Per-child layout information for layouts that support margins.
*/
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
super(source);
}
public LayoutParams(@NonNull LayoutParams source) {
super(source);
}
}
}