创建标签云

Create Tag Cloud

我需要创建一个基本的 标签云。我的目标是添加多个 TextView(Tags),如果数字超过设备宽度,它们会自动适应新行。 (喜欢 Instagram)。

现在 布局的 宽度 包含 (TextView)tags 超出(设备宽度)末尾的 TextView 包裹自身

我尝试使用 RelativeLayout 但我无法弄清楚其中的逻辑。 我查看了 this post 但如果有更好或更简单的解决方案。

感谢您的宝贵时间。

结果不一定要像 Instagram。

更简洁的解决方案是编写您自己的自定义 ViewGroup class。找到下面的示例。

如需完整的描述性解释,请访问 How to Create Custom Layout in Android by Extending ViewGroup Class

public class TagLayout extends ViewGroup {

    public TagLayout(Context context) {
        super(context);
    }

    public TagLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        int curWidth, curHeight, curLeft, curTop, maxHeight;

        //get the available size of child view
        final int childLeft = this.getPaddingLeft();
        final int childTop = this.getPaddingTop();

        final int childRight = this.getMeasuredWidth() - this.getPaddingRight();
        final int childBottom = this.getMeasuredHeight() - this.getPaddingBottom();

        final int childWidth = childRight - childLeft;
        final int childHeight = childBottom - childTop;

        maxHeight = 0;
        curLeft = childLeft;
        curTop = childTop;
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE)
                return;

            //Get the maximum size of the child
            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
            curWidth = child.getMeasuredWidth();
            curHeight = child.getMeasuredHeight();
            //wrap is reach to the end
            if (curLeft + curWidth >= childRight) {
                curLeft = childLeft;
                curTop += maxHeight;
                maxHeight = 0;
            }
            //do the layout
            child.layout(curLeft, curTop, curLeft + curWidth, curTop + curHeight);
            //store the max height
            if (maxHeight < curHeight)
                maxHeight = curHeight;
            curLeft += curWidth;
        }
    }
}

要使用 TagLayout,您可以将其添加到 activity/fragment 布局声明中。 main_activity.xml

<com.javatechig.taglayout.TagLayout
    android:id="@+id/tagLayout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

现在我们可以有一个自定义视图,允许对每个标签项布局进行一定程度的自定义。

tag_layout.xml

<TextView
    android:id="@+id/tagTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:background="#a000"
    android:padding="10dp"
    android:textColor="#fff" />

MainActivity.java 最后,从activityclass可以添加标签项如下。

public class MainActivity extends AppCompatActivity {

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

        TagLayout tagLayout = (TagLayout) findViewById(R.id.tagLayout);
        LayoutInflater layoutInflater = getLayoutInflater();
        String tag;
        for (int i = 0; i <= 20; i++) {
            tag = "#tag" + i;
            View tagView = layoutInflater.inflate(R.layout.tag_layout, null, false);

            TextView tagTextView = (TextView) tagView.findViewById(R.id.tagTextView);
            tagTextView.setText(tag);
            tagLayout.addView(tagView);
        }
    }
}

结果

我相信上面的解决方案总是考虑整个屏幕宽度,所以如果开发人员设置 android:layout_width 参数,例如它不会尊重它的值甚至 parent 的边距和填充值.

我已将其修复如下:

private int mScreenWidth = 0;
private int mAvailableWidth = -1;

public TagLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

private void init(Context context) {

    Display display = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
    Point deviceSize = new Point();
    display.getSize(deviceSize);

    mScreenWidth = deviceSize.x;
}

private void calculateAvailableWidth() {

    if(getLayoutParams() != null && getLayoutParams().width > 0) {
        mAvailableWidth = getLayoutParams().width;
        return;
    }

    mAvailableWidth = mScreenWidth;

    ViewGroup parent = this;

    while(parent != null) {

        mAvailableWidth -= parent.getPaddingLeft() + parent.getPaddingRight();

        if(parent.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
            ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)parent.getLayoutParams();
            mAvailableWidth -= layoutParams.leftMargin + layoutParams.rightMargin;
        }

        if(parent.getParent() instanceof ViewGroup)
            parent = (ViewGroup)parent.getParent();
        else
            parent = null;
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    int currentRowWidth = 0;
    int currentRowHeight = 0;
    int maxItemWidth = 0;
    int maxWidth = 0;
    int maxHeight = 0;

    if(mAvailableWidth == -1)
        calculateAvailableWidth();

    for(int i = 0; i < count; i++) {
        View child = getChildAt(i);

        if(child.getVisibility() == GONE)
            continue;

        try {
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }
        catch(Exception e) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }

        int childWidth = child.getMeasuredWidth() + child.getPaddingRight() + child.getPaddingLeft();
        int childHeight = child.getMeasuredHeight() + child.getPaddingTop() + child.getPaddingBottom();

        maxItemWidth = Math.max(maxItemWidth, childWidth);

        if(currentRowWidth + childWidth < mAvailableWidth) {
            currentRowWidth += childWidth;
            maxWidth = Math.max(maxWidth, currentRowWidth);
            currentRowHeight = Math.max(currentRowHeight, childHeight);
        }
        else {
            currentRowWidth = childWidth;
            maxHeight += currentRowHeight;
        }
    }

    if(getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
        mAvailableWidth = maxItemWidth;
        maxWidth = maxItemWidth;
    }

    maxHeight += currentRowHeight + getPaddingTop() + getPaddingBottom();
    setMeasuredDimension(maxWidth, maxHeight);
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    int count = getChildCount();

    int currentLeft = getPaddingLeft();
    int currentTop = getPaddingTop();
    int currentRight;
    int currentBottom;

    int parentWidth = this.getPaddingRight() - this.getPaddingLeft() + right;

    for(int i = 0; i < count; i++) {

        View child = getChildAt(i);

        if(child.getVisibility() == View.GONE)
            return;

        int currentWidth = child.getMeasuredWidth() + child.getPaddingRight() + child.getPaddingLeft();
        int currentHeight = child.getMeasuredHeight() + child.getPaddingBottom() + child.getPaddingTop();

        if(currentLeft + currentWidth > parentWidth) {
            currentLeft = getPaddingLeft();
            currentTop += currentHeight;
        }

        currentBottom = currentTop + currentHeight;
        currentRight = currentLeft + currentWidth;

        child.layout(currentLeft, currentTop, currentRight, currentBottom);

        currentLeft += currentWidth;
    }
}

这样您就可以设置 android:layout_width="250dp" 并得到如下结果:

设置android:layout_width="match_parent"并得到如下结果:

或者事件使用 android:layout_width="wrap_content 并得到这样的结果:

希望对您有所帮助。