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());
}
}
Related
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.
The custom View in my custom ViewGroup refuses to show the drawable given to it by calling setImageResource(). It is laid out as I need it, however, as you can see in this screenshot, it's empty:
Also, it won't react on an onClick event.
Here's the java code for my Activity
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BattleShipsGameBoard gb = (BattleShipsGameBoard) findViewById(R.id.gameboard);
Tile tile = new Tile(this);
tile.setImageResource(R.drawable.tile_hit);
tile.setGameObjectType(BattleShipsGameBoard.LayoutParams.LAYOUT_TYPE_TILE);
tile.setPosition(new Point(50, 50));
tile.setWidth(90);
tile.setHeight(90);
gb.addView(tile);
}
}
and my custom view
public class Tile extends ImageView {
#SuppressWarnings("unused")
private static final String TAG = "Tile";
public int tag;
public int gameObjectType;
public Point position = new Point(0, 0);
public int mWidth = 1;
public int mHeight = 1;
public boolean isSelected = false;
public Tile(Context context) {
super(context);
setLayoutParams(new BattleShipsGameBoard.LayoutParams(
BattleShipsGameBoard.LayoutParams.WRAP_CONTENT,
BattleShipsGameBoard.LayoutParams.WRAP_CONTENT));
}
public Tile(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Tile(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void confirmChangesInLayout() {
BattleShipsGameBoard.LayoutParams lp = (BattleShipsGameBoard.LayoutParams) this
.getLayoutParams();
lp.setPosition(this.position);
lp.setWidth(this.mWidth);
lp.setHeight(this.mHeight);
setLayoutParams(lp);
invalidate();
requestLayout();
}
//... getters and setters, the setters all call confirmChangesInLayout()
}
my simple custom ViewGroup:
public class BattleShipsGameBoard extends ViewGroup {
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public Point position = new Point(0, 0);
public int type = 0;
public int height = 0;
public int width = 0;
//getters and setters
}
public BattleShipsGameBoard(Context context) {
super(context);
}
public BattleShipsGameBoard(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BattleShipsGameBoard(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private float unitWidth;
private float unitHeight;
private int parentWidth;
private int parentHeight;
/**
* count of units the screen estate is divided by
*/
public static int unitCount = 100;
/**
* Rectangle in which the size of a child is temporarily stored
*/
private Rect mTmpChildRect = new Rect();
/**
* lays out children
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.d(TAG, "-------------STARTING LAYOUT, " + getChildCount() + " children -------------");
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
Point pos = lp.getPosition();
int height = lp.getHeight();
int width = lp.getWidth();
measureChild(child, parentWidth, parentHeight);
mTmpChildRect.left = (int) ((pos.x - (width / 2)) * unitWidth);
mTmpChildRect.right = (int) ((pos.x + (width / 2)) * unitWidth);
mTmpChildRect.top = (int) ((pos.y + (height / 2)) * unitHeight);
mTmpChildRect.bottom = (int) ((pos.y - (height / 2)) * unitHeight);
child.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right, mTmpChildRect.bottom);
Log.d(TAG,
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
parentHeight = MeasureSpec.getSize(heightMeasureSpec);
parentWidth = MeasureSpec.getSize(widthMeasureSpec);
unitHeight = parentHeight / unitCount;
unitWidth = parentWidth / unitCount;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
child.measure(widthMeasureSpec, heightMeasureSpec);
}
}
setMeasuredDimension(parentWidth, parentHeight);
}
/**
* Any layout manager that doesn't scroll will want this.
*/
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
}
I just found the problem.
In the onLayout() method I mixed up mTmpChildRect.top and mTmpChildRect.bottom which is why it looked like it was laid out correctly but nothing could be drawn.
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.
Please help! I tried everything! :(
I've got a schedule Class, which is simply a custom ViewGroup (with custom onMeasure() and onLayout()), which enables me to place childs(=events) with LayoutParams for column/row start and column/row end. The number of childs and their LayoutParams depend on database entries.
Now I'm trying to add childs (events) from my database. I'd have to use a Cursor Adapter, so my schedule Class has to extend ListView, right? I tried that but the newView() method of the adapter is never called. Why not??
My custom ListView doesn't ask the adapter for childs, no childs are added. I also can't add the childs by hand calling schedule.addView() if I extend from AdapterView.
I'd be really (really) happy if someone could help!
Regards,
cody
This is my custom ViewGroup:
public class Schedule extends ViewGroup {
private int columns;
private int rows;
private float preferredCellWidth;
private float preferredCellHeight;
private String[] rowTimes;
private Paint paint;
public Schedule(Context context, int columns, int rows, float preferredCellWidth, float preferredCellHeight, String[] rowTimes) {
super(context);
this.columns = columns;
this.rows = rows;
this.preferredCellWidth = preferredCellWidth;
this.preferredCellHeight = preferredCellHeight;
this.rowTimes = rowTimes;
init(context);
}
private void init(Context context) {
Log.i("Schedule", "initSchedule...");
setPaint();
setWillNotDraw(false);
}
private void setPaint() {
paint = new Paint();
paint.setTextSize(preferredCellHeight*2/3);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(getResources().getColor(R.color.white));
}
public Schedule(Context context, AttributeSet attrs) {
super(context, attrs);
readAttr(context, attrs);
init(context);
}
public Schedule(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
readAttr(context, attrs);
init(context);
}
private void readAttr(Context c, AttributeSet attrs) {
android.content.res.TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ScheduleLayout);
this.columns = a.getInt(R.styleable.ScheduleLayout_columns, 1);
this.rows = a.getInt(R.styleable.ScheduleLayout_rows, 1);
this.preferredCellWidth = a.getDimension(R.styleable.ScheduleLayout_preferredCellWidth, 1);
this.preferredCellHeight = a.getDimension(R.styleable.ScheduleLayout_preferredCellHeight, 1);
a.recycle();
}
#Override
protected void onDraw(Canvas canvas) {
//Log.i(this.toString(),"onDraw ..."+" this.getLeft()="+this.getLeft()+", this.getWidth()="+this.getWidth());
super.onDraw(canvas);
for (int i = 0; i < rows; i++) {
int line = (int) Math.round(this.getTop()+ (i+1) * preferredCellHeight);
canvas.drawText(this.rowtimes[i], this.getLeft()+5, line-3, paint);
canvas.drawLine(this.getLeft(), line, this.getWidth(), line, paint);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i("Schedule", "onMeasure...");
float width = (MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight()) / columns;
float height = (MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom()) / rows;
float cellWidth = preferredCellWidth;
float cellHeight = preferredCellHeight;
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
cellWidth = width;
} else if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
cellWidth = Math.min(preferredCellWidth, width);
}
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
cellHeight = height;
} else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
cellHeight = Math.min(preferredCellHeight, height);
}
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int cwidth = (int) Math.round(cellWidth * lp.getWidth());
int cheight = (int) Math.round(cellHeight * lp.getHeight());
child.measure(
MeasureSpec.makeMeasureSpec(cwidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(cheight, MeasureSpec.EXACTLY)
);
}
}
setMeasuredDimension(
(int) Math.round(cellWidth * columns + getPaddingLeft() + getPaddingRight()),
(int) Math.round(cellHeight * rows + getPaddingTop() + getPaddingBottom())
);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed)
return;
int cellWidth = ((r-l) - getPaddingLeft() - getPaddingRight()) / columns;
int cellHeight = ((b-t) - getPaddingTop() - getPaddingBottom()) / rows;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int cl = (int) Math.round(getPaddingLeft() + lp.columnStart * cellWidth);
int cr = (int) Math.round(getPaddingLeft() + lp.columnEnd * cellWidth);
int ct = (int) Math.round(getPaddingTop() + lp.rowStart * cellHeight);
int cb = (int) Math.round(getPaddingTop() + lp.rowEnd * cellHeight);
child.layout(cl, ct, cr, cb);
}
}
}
protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {
Log.i("Schedule", "checkLayoutParams...");
if (p instanceof LayoutParams) {
LayoutParams lp = (LayoutParams) p;
if (lp.columnEnd > columns || lp.columnStart < 0)
return false;
if (lp.rowEnd > rows || lp.rowStart < 0)
return false;
return lp.columnEnd > lp.columnStart && lp.rowEnd > lp.rowStart;
} else
return false;
}
public android.widget.AbsListView.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new android.widget.AbsListView.LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends android.view.ViewGroup.LayoutParams {
public int columnStart;
public int columnEnd;
public int rowStart;
public int rowEnd;
public LayoutParams(int columnStart, int rowStart, int columnEnd, int rowEnd) {
super(WRAP_CONTENT, WRAP_CONTENT);
this.columnStart = columnStart;
this.columnEnd = columnEnd;
this.rowStart = rowStart;
this.rowEnd = rowEnd;
}
public LayoutParams(Context c, AttributeSet attrs) {
super(WRAP_CONTENT, WRAP_CONTENT);
android.content.res.TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.EventLayout);
this.columnStart = a.getInt(R.styleable.EventLayout_event_columnStart, 0);
this.columnEnd = a.getInt(R.styleable.EventLayout_event_columnEnd, this.columnStart + 1);
this.rowStart = a.getInt(R.styleable.EventLayout_event_rowStart, 0);
this.rowEnd = a.getInt(R.styleable.EventLayout_event_rowEnd, this.rowStart + 1);
a.recycle();
}
public int getWidth() {
return columnEnd - columnStart;
}
public int getHeight() {
return rowEnd - rowStart;
}
}
And this is the event-layout - event.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical"
android:gravity="center" >
<TextView android:id="#+id/text_event_name"
style="#style/Event_TextView1" />
<TextView android:id="#+id/text_event_name2"
style="#style/Event_TextView2" />
</LinearLayout>
<TextView android:id="#+id/text_event_weeks"
style="#style/Event_TextView2"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true" />
<TextView android:id="#+id/text_event_room"
style="#style/Event_TextView2"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" />
In my Activity I've got that code:
Schedule schedule = new Schedule(this, 4, rowTimes.length, 15, 15, rowTimes);
Cursor cursor = dbManager.getEvents(day);
MySimpleCurserAdapter adapter = ... ??
// schedule.setAdapter not working...
How can I add events to the schedule with the data from the cursor?
You should not need to be extending ListView. You just want to add an instance of ListView to your layout.
It sounds like you might want to be using a SimpleCursorAdaptor, where you can map items in your custom view to the data model objects you want them to display.
See Binding to Data with Adapter and Hello ListView for some examples of the right ways to use adapters and ListViews.