Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create Tag Cloud

I need to create a basic Tag Cloud. My aim is to add multiple TextView(Tags) that automatically fits themselves in a new line if the number exceeds the device width. (Like Instagram).

Right now when the width of the layout containing (TextView)tags exceeds the (device width) the TextView(s) at the end wraps itself. the TextView at the last gets distorted

I tried using RelativeLayout but i am unable to figure out the logic. I viewed this post but if there is a better or simply a cleaner solution.

Thanks for your precious time.

It is not necessary that the result looks like Instagram. what i want to achieve

like image 556
harsh_v Avatar asked Dec 08 '22 02:12

harsh_v


2 Answers

I believe the solution above always consider the whole screen width, so if the developer sets android:layout_width parameter for example it won't respect its value or even parent's margin and padding values.

I've fixed it as follows:

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

This way you are able to set android:layout_width="250dp" and get a result like this:

enter image description here

Set android:layout_width="match_parent" and get a result like this:

enter image description here

Or event use android:layout_width="wrap_content and get a result like this:

enter image description here

Hope it helps.

like image 24
Lucas Santos Avatar answered Dec 11 '22 10:12

Lucas Santos


The cleaner solution is to write your own custom ViewGroup class. Find the sample below.

For complete descriptive explanation visit, 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;
        }
    }
}

To use the TagLayout, you can add it to your activity/fragment layout declaration. main_activity.xml

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

Now we can have a custom view to allow some level of customization for each tag item layout.

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 Finally, from activity class you can add the tag items as follows.

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

Result

enter image description here

like image 50
Nilanchala Panigrahy Avatar answered Dec 11 '22 09:12

Nilanchala Panigrahy