I have a relative layout section. Within it, i have to programmatically add several clickable textviews that may vary in widths. I need them to appear one after another. If a textView has width such that it cannot fit in the remaining space of the current row, it should start populating below the row in a similar fashion.
I need something like this:
What i've managed so far is this code below:
public static void updateWordsListingOnUi(final ArrayList<String> wordsList) { //TODO: incomplete and may be inefficient
mUiHandler.post(new Runnable() {
#Override
public void run() {
TextView textView;
wordsListingContainer.removeAllViewsInLayout(); //TODO
for (final String word : wordsList)
{
textView = new TextView(context);
textView.setText(word);
textView.setClickable(true);
textView.setPadding(1,1,10,1);
if (wordsList.indexOf(word)%2 == 0) {
textView.setBackgroundColor(context.getResources().getColor(R.color.colorPrimary));
}
else {
textView.setBackgroundColor(context.getResources().getColor(R.color.colorAccent));
}
textView.setId(wordsList.indexOf(word)); //give IDs from 0 - (maxSize-1)
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context, word, Toast.LENGTH_SHORT).show();
}
});
if(wordsList.indexOf(word) > 0) {
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.RIGHT_OF, textView.getId()-1); //TODO
wordsListingContainer.addView(textView,layoutParams);
}
else {
wordsListingContainer.addView(textView);
}
}
}
});
}
This does the job partially. It puts textviews one after the other, but when there i insufficient space, it doesn't wrap to the next row. I cannot think of how i can do that.
Here is a tutorial, that explains how, you can do this.
In short, your class need to be something like:
public class TagLayout extends ViewGroup {
int deviceWidth;
public TagLayout(Context context) {
this(context, null, 0);
}
public TagLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
Point deviceDisplay = new Point();
display.getSize(deviceDisplay);
deviceWidth = deviceDisplay.x;
}
#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;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// Measurement will ultimately be computing these values.
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
int mLeftWidth = 0;
int rowCount = 0;
// Iterate through all children, measuring them and computing our dimensions
// from their size.
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE)
continue;
// Measure the child.
measureChild(child, widthMeasureSpec, heightMeasureSpec);
maxWidth += Math.max(maxWidth, child.getMeasuredWidth());
mLeftWidth += child.getMeasuredWidth();
if ((mLeftWidth / deviceWidth) > rowCount) {
maxHeight += child.getMeasuredHeight();
rowCount++;
} else {
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Report our final dimensions.
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
}
}
Why don't you try out libraries, here i am sharing some of the links
Chips EditText Library
MultiTextTagView
TokenAutoComplete
Android Chips
These libraries will help you to achieve your output.
Related
I have the following GridLayout which stores a number of 6 maximum columns :
<androidx.gridlayout.widget.GridLayout
android:id="#+id/pokemonTeambuilderSpritesLayout"
android:layout_width="match_parent"
android:layout_height="100dp"
app:columnCount="6"
app:rowCount="1"
app:useDefaultMargins="true" />
And here I populate it :
List<Pokemon> pokemonList = pokemonTeam.getPokemonList();
for (Pokemon pokemon : pokemonList) {
CircularImageView ivPokemonSprite = new CircularImageView(mContext);
String pokemonId = pokemon.get_id();
int pokemonImageId = PokemonUtils.getPokemonSugimoriImageById(pokemonId, mContext);
Picasso.get().load(pokemonImageId).into(ivPokemonSprite);
GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(
GridLayout.spec(GridLayout.UNDEFINED, 1f),
GridLayout.spec(GridLayout.UNDEFINED, 1f)
);
layoutParams.width = 0;
ivPokemonSprite.setLayoutParams(layoutParams);
holder.teamSpritesGridLayout.addView(ivPokemonSprite);
}
My current output with 6 images is this :
With 3 images :
With 2 images:
My desired output would be :
For 6 images:
For 2 images:
For 4 images:
I want it to add them starting from left to right uniform and If the image doesn't have enough space to fit on its own I want it to "collide" with the others instead of letting margin between them (as you can see in the examples). Thats how I see that the GridLayoutManager works . How can I achieve this ?
you can create custom layout for handling your different cases. something like below:
public class PokemonLayout extends FrameLayout {
private int height;
private int width;
private int childWidth;
public PokemonLayout(Context context) {
this(context, null);
}
public PokemonLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PokemonLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public PokemonLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
width = MeasureSpec.getSize(widthMeasureSpec);
height = Integer.MIN_VALUE;
childWidth = Integer.MIN_VALUE;
measureChildren(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
height = Math.max(child.getMeasuredHeight(), height);
childWidth = Math.max(child.getMeasuredWidth(), childWidth);
}
setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
#Override
protected void onLayout(boolean changed, int leftParent, int topParent, int rightParent, int bottomParent) {
int count = getChildCount();
boolean overlap = count * childWidth > width;
int widthOffset = childWidth;
if(overlap) {
widthOffset = childWidth - (Math.abs(width - (count * childWidth)) / (count-1));
}
for (int i = 0; i < getChildCount(); i++) {
View child = (View) getChildAt(i);
child.layout(i*widthOffset,
0,(i*widthOffset) + childWidth
, child.getMeasuredHeight());
}
}
}
For 3 images:
For 5 images:
UPDATE 1:
change onLayout method to positioning children at the center of layout
#Override
protected void onLayout(boolean changed, int leftParent, int topParent, int rightParent, int bottomParent) {
int count = getChildCount();
boolean overlap = count * childWidth > width;
int widthOffset = childWidth;
int startOffest = (width - (count * childWidth)) / 2;
if(overlap) {
startOffest = 0;
widthOffset = childWidth - (Math.abs(width - (count * childWidth)) / (count-1));
}
for (int i = 0; i < getChildCount(); i++) {
View child = (View) getChildAt(i);
child.layout(startOffest+ (i*widthOffset),
0,(i*widthOffset) + childWidth + startOffest
, child.getMeasuredHeight());
}
}
I want to create a view in android that holds labels like Trello does.
The problem that I am facing is, how to create a new line break when the label holder reaches the max width capacity. I want to achieve something like this.
Also have to consider that the labels can have different width length.
I come to the idea of inflate a new horizontal view any time the sum of the width surpass the max capacity, but I don't know if exists other approach or more efficient way to solve this problem.
Thank you for the help.
With this case, I think you should use this and replace RecyclerView.
Just custom a ViewGroup by overriding onLayout and onMeasure method:
String[] dataList;
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int curItemWidth, curItemHeight, curLeft, curTop, maxHeight;
//get the available size of child view
int viewLeft = this.getPaddingLeft();
int viewTop = this.getPaddingTop();
int viewRight = this.getMeasuredWidth() - this.getPaddingRight();
int viewBottom = this.getMeasuredHeight() - this.getPaddingBottom();
int viewWidth = viewRight - viewLeft;
int viewHeight = viewBottom - viewTop;
maxHeight = 0;
curLeft = viewLeft + getCurLeft(0, viewWidth, viewHeight, viewRight);
curTop = viewTop;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE)
continue;
//Get the maximum size of the child
child.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.AT_MOST));
curItemWidth = child.getMeasuredWidth();
curItemHeight = child.getMeasuredHeight();
//wrap is reach to the end
if (curLeft + curItemWidth >= viewRight) { //new row
curLeft = viewLeft + getCurLeft(i, viewWidth, viewHeight, viewRight);
curTop += maxHeight;
maxHeight = 0;
}
//do the layout
child.layout(curLeft, curTop, curLeft + curItemWidth, curTop + curItemHeight);
//store the max height
maxHeight = Math.max(maxHeight, curItemHeight);
curLeft += curItemWidth;
}
}
private int getCurLeft(int currentPosition, int viewWidth, int viewHeight, int viewRight) {
int childCount = getChildCount();
int currentMaxWidth = 0;
for (int i = currentPosition; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE)
continue;
child.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.AT_MOST));
currentMaxWidth += child.getMeasuredWidth();
//wrap is reach to the end
if (currentMaxWidth >= viewRight) { //new row
return 0;
}
}
return (viewWidth - currentMaxWidth) / 2;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measurement will ultimately be computing these values.
int childCount = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
int currentRowWidth = 0;
int parentWidth = resolveSizeAndState(maxWidth, widthMeasureSpec, childState);
// Iterate through all children, measuring them and computing our dimensions
// from their size.
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE)
continue;
// Measure the child.
measureChild(child, widthMeasureSpec, heightMeasureSpec);
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
currentRowWidth += child.getMeasuredWidth();
if (currentRowWidth >= parentWidth) { //create new row.
maxHeight += child.getMeasuredHeight();
currentRowWidth = 0;
}
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Report our final dimensions.
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
}
public void setData(String[] dataList) {
this.dataList = dataList;
for (int i = 0; i < dataList.length; i++) {
View tagView = layoutInflater.inflate(R.layout.item_tag_layout, null, false);
TextView tagTextView = tagView.findViewById(R.id.label);
tagTextView.setText(dataList[i]);
tagTextView.setTag(i);
tagTextView.setOnClickListener(onClickListener);
if (dataList[i].equalsIgnoreCase(defaultItem)) {
selectedItemView = tagTextView;
tagTextView.setTextColor(ContextCompat.getColor(getContext(), R.color.tag_layout_text_selected));
} else {
tagTextView.setTextColor(ContextCompat.getColor(getContext(), R.color.tag_layout_text_default));
}
if (i == dataList.length - 1) {
View indicator = tagView.findViewById(R.id.indicator);
indicator.setVisibility(GONE);
}
this.addView(tagView);
}
}
I want to remove image which was recently added if anyone click minus button.
I have added image one by one on plus button click.
can see in snapshot
On plus button click images are going to add one by one.
want to remove on click of minus button recently added image.
image.setOnClickListener(new View.OnClickListener() {
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public void onClick(View v) {
ImageView image = new ImageView(Water.this);
image.setBackgroundResource(R.drawable.glass);
predicate.addView(image);
}
});
ImageView minus=(ImageView)findViewById(R.id.minus);
minus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ImageView image = new ImageView(Water.this);
image.setImageBitmap(null);
predicate.removeView(image);
//image.setBackgroundResource(R.drawable.glass);
//((ViewGroup) image.getParent()).removeView(image);
//predicate.removeView(image);
}
});
xml
<TextView
android:id="#+id/waterdescription"
android:text="Water Intake"
android:textSize="16dp"
android:layout_weight="1"
android:layout_marginLeft="20dp"
android:layout_width="wrap_content"
android:textColor="#283D65"
android:textStyle="bold"
android:layout_height="wrap_content"
/>
<ImageView
android:id="#+id/minus"
android:layout_weight="1"
android:layout_toRightOf="#+id/waterdescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/minus"
/>
<ImageView
android:id="#+id/image"
android:layout_weight="1"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/plus"
/>
predicate Layout
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 = 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 + 8;
}
}
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 + 20);
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(2, 2); // 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 = child.getLayoutParams();
if (xpos + childw > width) {
xpos = getPaddingLeft();
ypos += line_height;
}
child.layout(xpos, ypos, xpos + childw, ypos + childh);
xpos += childw + lp.width + 8;
}
}
}
}
You're not referencing the imageView you added in plus button's onClick() method.
minus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ImageView image = new ImageView(Water.this);
image.setImageBitmap(null);
predicate.removeView(image);
//image.setBackgroundResource(R.drawable.glass);
//((ViewGroup) image.getParent()).removeView(image);
//predicate.removeView(image);
}
});
ImageView image = new ImageView(Water.this); in this line, you're creating a new ImageView with water and trying to remove it from parent layout. But you didn't even add this.
What you need to do is to keep a reference to the the views you are adding in plus button's onClick() method.
You can do something like:
public class PredicateLayout extends ViewGroup {
private LinkedList<ImageView> imageViews;
//other parts are omitted...
public PredicateLayout(Context context, AttributeSet attrs) {
super(context, attrs);
imageViews = new LinkedList();
}
//...some other code...
public LinkedList<ImageView> getImageViews(){
return imageViews;
}
}
and when adding:
Plus Button:
...onClick() {
//..
predicate.addView(image);
predicate.getImageViews().add(image);
}
Minus Button:
...onClick(){
//pollLast returns last element in the list
ImageView lastAddedImageView = predicate.getImageViews().pollLast()
predicate.removeView(lastAddedImageView);
}
Add view like:
ImageView image = new ImageView(Water.this);
image.setId(Integer.parseInt("1234"));
image.setBackgroundResource(R.drawable.glass);
predicate.addView(image);
And remove it:
View rView=(ImageView)view.findViewById(Integer.parseInt("1234"));
predicate.removeView(rView);
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.
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.
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
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:
Set android:layout_width="match_parent" and get a result like this:
Or event use android:layout_width="wrap_content and get a result like this:
Hope it helps.
I wrote the following ViewGroup
public class myViewGroup extends ViewGroup{
List<View> qResult;
List<Point> qLoc;
ImageView qImage;
public QueryViewLayout(Context context){
super(context);
}
public QueryViewLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QueryViewLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
qResult = new LinkedList<View>();
qLoc = new LinkedList<Point>();
qImage = null;
}
public void addMainView(ImageBorderView view){
qImage = view;
super.removeAllViews();
super.addView(view);
}
public void addResultView(View result, Point loc){
super.addView(result);
qResult.add(result);
qLoc.add(loc);
}
/**
* Any layout manager that doesn't scroll will want this.
*/
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// Measurement will ultimately be computing these values.
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// Only main view affects the layouts measure
if (qImage != null) {
if (qImage.getVisibility() != GONE) {
// Measure the child.
qImage.measure(widthMeasureSpec, heightMeasureSpec);
maxWidth = qImage.getMeasuredWidth();
maxHeight = qImage.getMeasuredHeight();
childState = qImage.getMeasuredState();
}
}
for (View child:qResult){
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED)
child.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
}
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Report our final dimensions.
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
int parentLeft = left + getPaddingLeft();
int parentRight = right - getPaddingRight();
final int parentTop = top + getPaddingTop();
final int parentBottom = bottom - getPaddingBottom();
if (qImage == null) return;
qImage.layout(parentLeft, parentTop, parentRight, parentBottom);
Iterator<Point> loc = qLoc.iterator();
for (View child:qResult) {
Point p = loc.next();
if (child.getVisibility() != GONE) {
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
Point locOnView = qImage.projectOnView(p);
width = (width < (int) Math.max(parentRight - (int) locOnView.x, locOnView.x - parentLeft)) ?
width : (parentLeft + parentRight)/2;
height = (height < (int) Math.max(parentBottom - (int) locOnView.y, locOnView.y - parentTop)) ?
height : (parentBottom + parentTop)/2;
int x = (width < (parentRight - (int) locOnView.x)) ? (int) locOnView.x : (parentRight - width);
int y = (height < parentBottom - (int) locOnView.y) ? (int) locOnView.y : (parentBottom - height);
// Place the child.
child.layout(x, y, x + width, y + height);
}
}
}
}
It is supposed to show some arbitrary view on top of an image, given a location for that view, when I use a GridView as the arbitrary view, even though I have defined a certain width for the GridView it is forced to have a width as large as the frame. In the measure phase I changed the mode to
MeasureSpec.AT_MOST
for both width and height of the overlay view, but this does not seem to work, can someone please help.
here is the xml where I, inflate the GridView from
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/result_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:columnWidth="#dimen/result_view_column_width"
android:numColumns="2"
android:verticalSpacing="2dp"
android:horizontalSpacing="2dp"
android:stretchMode="none"
android:gravity="center"
android:layout_margin = "2dp"
android:background="#drawable/solid_with_shadow" />
After a lot of trial and error, replacing
child.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
with
measureChild(child, MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
worked for me, I am not sure why, but a wild guess would be calling measure on a child does not read all the xml props, but measureChild(child, ...) does.