Choose wrapping view when layout is wider than screen - android

I have a horizontal linear layout with an image and a couple text views. In some languages (German...) the text is so long that to fit everything on one line, the layout would have to be wider than the screen. To prevent this, android automatically makes the text views wrap on to the next line.
Is there any way to choose which of the text views will end up wrapping? At the moment it appears that the last view added to the layout is the one that wraps. However I'd really like to have one of the earlier text views wrap and have the last text view always display on a single line. Is this possible? I've already subclassed most of the views involved so I can override protected methods.
Heres a rough outline of my code:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/some_icon" />
<TextView
android:id="#+id/can_wrap_if_neccessary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/some_really_long_text" />
<TextView
android:id="#+id/shouldnt_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/some_more_really_long_text" />
</LinearLayout

We can know which Text will wrap.
Logic:
First calculate width of Text If width of Text is greater than width of the screen, that text will be wrapped
The following method returns true if text will be wrapped else returns false
Source code
boolean isTextWrapped(String text) {
boolean isWrapped = false;
int widthOfText = 0;
int deviceWidth = 0;
// calculate widthOftext
TextView textView = new TextView(this);
textView.setVisibility(View.GONE);
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text, 0, text.length(), bounds);
widthOfText = bounds.width();
System.out.println("...text view width..."+widthOfText);
// calculate width of screen
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
deviceWidth = displaymetrics.widthPixels;
System.out.println("...text view width..."+widthOfText+"...screen width..."+deviceWidth);
isWrapped = widthOfText > deviceWidth ? true : false;
return isWrapped;
}

I figured it out: the solution is to set the layout_weight attribute on the view(s) that I want to wrap, and not set it at all on the views I don't want to wrap. Any view with a layout_weight will wrap in preference over those without.

Related

Create grid (n × n) in Android ConstraintLayout with variable number of n

I want to create a square grid inside ConstraintLayout. My first thought was to create a horizontal chain, give some margin value and set to all single view size attributes width = match_constraint, height = match_constraint and set the ratio to 1:1. It works and it looks like:
And it's easy when a size of the grid is 2×2 - there are 4 elements so it's easy. But what I should do when I had to create a grid 7×7? We have 49 views so setting all of these views could be tricky. I want to do this in constraint layout because I want to have a flexible layout. :)
Since you say that you have a variable number of squares, I assume that you are willing to create the n*n grid in code. Here is an approach to creating the grid. This is just one way and there are probably others.
First, create a layout with ConstraintLayout as the root view. In that layout, define a widget that has width and height of match_constraints and is constrained by the parent. This will give you a square widget regardless of the device orientation. (I use a View here so it can be seen, but it is better to use a Space widget although it probably doesn't really matter.)
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<View
android:id="#+id/gridFrame"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:background="#android:color/holo_blue_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Here is the code for the activity that creates a 7*7 grid. We will use the on-screen view from the layout as the "parent" view to contain the squares.
MainActivity.java
public class MainActivity extends AppCompatActivity {
int mRows = 7;
int mCols = 7;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ConstraintLayout layout = findViewById(R.id.layout);
int color1 = getResources().getColor(android.R.color.holo_red_light);
int color2 = getResources().getColor(android.R.color.holo_blue_light);
TextView textView;
ConstraintLayout.LayoutParams lp;
int id;
int idArray[][] = new int[mRows][mCols];
ConstraintSet cs = new ConstraintSet();
// Add our views to the ConstraintLayout.
for (int iRow = 0; iRow < mRows; iRow++) {
for (int iCol = 0; iCol < mCols; iCol++) {
textView = new TextView(this);
lp = new ConstraintLayout.LayoutParams(ConstraintSet.MATCH_CONSTRAINT,
ConstraintSet.MATCH_CONSTRAINT);
id = View.generateViewId();
idArray[iRow][iCol] = id;
textView.setId(id);
textView.setText(String.valueOf(id));
textView.setGravity(Gravity.CENTER);
textView.setBackgroundColor(((iRow + iCol) % 2 == 0) ? color1 : color2);
layout.addView(textView, lp);
}
}
// Create horizontal chain for each row and set the 1:1 dimensions.
// but first make sure the layout frame has the right ratio set.
cs.clone(layout);
cs.setDimensionRatio(R.id.gridFrame, mCols + ":" + mRows);
for (int iRow = 0; iRow < mRows; iRow++) {
for (int iCol = 0; iCol < mCols; iCol++) {
id = idArray[iRow][iCol];
cs.setDimensionRatio(id, "1:1");
if (iRow == 0) {
// Connect the top row to the top of the frame.
cs.connect(id, ConstraintSet.TOP, R.id.gridFrame, ConstraintSet.TOP);
} else {
// Connect top to bottom of row above.
cs.connect(id, ConstraintSet.TOP, idArray[iRow - 1][0], ConstraintSet.BOTTOM);
}
}
// Create a horiontal chain that will determine the dimensions of our squares.
// Could also be createHorizontalChainRtl() with START/END.
cs.createHorizontalChain(R.id.gridFrame, ConstraintSet.LEFT,
R.id.gridFrame, ConstraintSet.RIGHT,
idArray[iRow], null, ConstraintSet.CHAIN_PACKED);
}
cs.applyTo(layout);
}
}
Just change mRows and mCols and the grid will adjust itself. If your grid will always be square, you will not need to set the ratio of the grid container in the code. You can also place your grid within a more complicated layout. Just make sure that the grid container has the right dimensions and you are good to go.
Best idea is to create two views linear layouts, one that has horizontalAlignment and Another that has vertical alignment.
Group with vertical alignment is one that you call in your layout and pass to it as an attribute a number(7).
This group will add horizontal group 7 times to itself. Each horizontal layout will in-turn take a number (7) again. And that will add 7 squares.
Trick is to see that each square will have same weight. And each horizontal row will have same weight. That way u will get grids of right size provides you insert Verical layout in square ViewGroup
If I got it right I think the best way is to use the Flow widget
androidx.constraintlayout.helper.widget.Flow
and put the id of all views which should be included in the grid in the following field:
app:constraint_referenced_ids
more info can be found here:
https://bignerdranch.com/blog/constraintlayout-flow-simple-grid-building-without-nested-layouts/

Set Android navigation drawer menu footer at bottom of screen

I'm trying to do something that from the beginning I already know it's quite difficult to achieve, and it's to place a footer for a navigation drawer menu at the bottom of the screen.
The fact is that I need the footer to be exactly at the bottom of the screen when the list view items of the drawer are all visible on the screen and the footer should be just below the last item when elements go off the screen and scrollbars appear (normal behaviour).
For that I'm using the addFooterView method in the next way
ViewGroup footer = (ViewGroup)inflater.inflate(R.layout.testme_drawer_footer, mDrawerList, false);
mDrawerList.addFooterView(footer, null, false);
Where testme_drawer_footer is the next layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/footer_menu_facebook"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start"
android:orientation="horizontal">
<TextView
android:id="#+id/drawer_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#8d3169"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:textColor="#fff"
android:textSize="12sp"/>
</LinearLayout>
Without doing anything the addFooterView just behaves the normal way and if elements are all visible in screen and there is much blank space left at bottom the footer just places below the last element (bad for what I'm trying to achieve).
I've tried many suggestions in different StackOverflow posts with no avail and after struggling my head for a while I was able to get something very close to what I need, and it's the next:
I have given all the list view elements a fixed height and the same for header so in the end I calculate footer height with screenHeight - statusBarHeight - actionBarHeight - drawerHeaderHeight - listViewElementHeight * numberOfElements in the next way:
ViewGroup footer = (ViewGroup)inflater.inflate(R.layout.testme_drawer_footer, mDrawerList, false);
int screenHeight = GeneralUtils.getScreenHeight(oActivity);
int statusBarHeight = GeneralUtils.getStatusBarHeight(oActivity);
int actionBarHeight = GeneralUtils.getActionBarHeight(oActivity);
int drawerHeaderHeight = GeneralUtils.dp2px(60, oActivity);
int menuItemHeight = drawerHeaderHeight;
int totalItemsHeight = menuItemHeight*(endItem-startItem+1);
int footerHeight = screenHeight - statusBarHeight - actionBarHeight - drawerHeaderHeight - totalItemsHeight;
footer.setMinimumHeight(footerHeight);
mDrawerList.setFooterDividersEnabled(true);
mDrawerList.addFooterView(footer, null, false);
But it's most likely some of the height measurement methods are not being quite exact and there is a difference of some pixels that it's not equal in all tested devices.
I know this is not the best way to do it, in fact I don't like it, I don't like to set a fixed height for drawer header and elements insted of wrap_content and I don't like calculating overall height this way but cannot find any other working way to achieve this.
Any help?
Thanks in advance!
This is the way I set the ListView in all activities:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/llMainMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="horizontal">
//MAIN CONTENT
<ListView
android:id="#+id/left_drawer"
android:layout_width="#dimen/navigation_drawer_width_phone"
android:layout_height="match_parent"
android:layout_gravity="left|start"
android:headerDividersEnabled="true"
android:background="#ffeeeeee"/>
</android.support.v4.widget.DrawerLayout>
After struggling my head for the whole day I've finally found a great and accurate solution and I'll answer my own question in order to help anyone who might be facing the same problem.
The key was to set fixed heights to ListView items (in my case I've set a fixed height of 60dp) and calculate ListView height before it gets drawn with a TreeObserver, then you just multiply fixed item height by number of items and substract this value to ListView height. In my case I had to add header height (also fixed) and item dividers size to the total ListView height, so in the end my piece of code looks like follows:
final int numberOfItems = endItem - startItem + 1;
final ViewTreeObserver treeObserver = mDrawerList.getViewTreeObserver();
treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mDrawerList.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int mDrawerListHeight = mDrawerList.getHeight();
int dividerHeight = mDrawerList.getDividerHeight() * numberOfItems;
int footerHeight = calculateFooterHeight(oActivity, mDrawerListHeight, numberOfItems, dividerHeight);
ViewGroup footer = (ViewGroup)inflater.inflate(R.layout.testme_drawer_footer, mDrawerList, false);
footer.setMinimumHeight(footerHeight);
mDrawerList.setFooterDividersEnabled(true);
mDrawerList.addFooterView(footer, null, false);
UserSession us = new UserSession(oActivity);
String footerText = us.getUserSession().getAlias();
TextView tvDrawerFooter = (TextView) oActivity.findViewById(R.id.drawer_footer);
tvDrawerFooter.setText(footerText);
// Set the list's click listener
DrawerItemClickListener drawer = new DrawerItemClickListener();
drawer.oActivity = oActivity;
mDrawerList.setOnItemClickListener(drawer);
}
});
private static int calculateFooterHeight(Activity oActivity, int listViewHeight, int numberOfItems, int dividerHeight){
int drawerHeaderHeight = GeneralUtils.dp2px(60, oActivity);
int menuItemHeight = drawerHeaderHeight;
int totalItemsHeight = menuItemHeight * numberOfItems;
return listViewHeight - drawerHeaderHeight - totalItemsHeight - dividerHeight;
}
I truly hope I can help anyone else with the same problem.

TextView works wrong with breakStrategy="balanced" and layout_width="wrap_content"

I have such layout (I've removed some attributes, cause they really doesn't matter, full demo project is here):
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content" (* this is important)
android:layout_height="wrap_content"
android:breakStrategy="balanced" (* this is important)
android:text="#string/long_text" />
</LinearLayout>
The text long_text is quite long, so it would be separated for a few lines.
Two important lines are android:layout_width="wrap_content" and android:breakStrategy="balanced".
I expect that TextView will calculate width according to the actual text width, but it doesn't.
Does anybody know how to fix this?
UPDATE
Attribute android:breakStrategy="balanced" works only for API 23 and higher. In my example text takes 3 lines, and balanced strategy makes each line approximately same width.
I expect that in this case width of view itself will be the same as the longest line.
I'm looking for any workaround. I need solution like in Hangouts chat. I can't figure out how it works.
UPDATE 2
Unfortunately, accepted answer doesn't work with RTL text.
This happens because when TextView calculates its width it measure all text as 1 line (in your case) and on this step it knows nothing about android:breakStrategy. So it is trying to utilize all available space to render as much text on first line as it can.
To get more details, please check method int desired(Layout layout) here
To fix it you can use this class:
public class MyTextView extends TextView {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Layout layout = getLayout();
if (layout != null) {
int width = (int) Math.ceil(getMaxLineWidth(layout))
+ getCompoundPaddingLeft() + getCompoundPaddingRight();
int height = getMeasuredHeight();
setMeasuredDimension(width, height);
}
}
private float getMaxLineWidth(Layout layout) {
float max_width = 0.0f;
int lines = layout.getLineCount();
for (int i = 0; i < lines; i++) {
if (layout.getLineWidth(i) > max_width) {
max_width = layout.getLineWidth(i);
}
}
return max_width;
}
}
which I took here - Why is wrap content in multiple line TextView filling parent?
You'll need to measure the width of the text in TextView programatically, like so:
Rect bounds = new Rect();
textView.getPaint().getTextBounds(textView.getText().toString(), 0, textView.getText().length(), bounds);
Now, you can place a colored rectangle behind the TextView, and set its width programatically after measuring the text (I'm using FrameLayout to put the Views one on top of the other, but you can use RelativeLayout as well):
XML:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="+#id/background"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#color/green" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:breakStrategy="balanced"
android:text="#string/long_text" />
</FrameLayout>
Code:
Rect bounds = new Rect();
textView.getPaint().getTextBounds(textView.getText().toString(), 0, textView.getText().length(), bounds);
View bg = findViewById(R.id.background);
gb.getLayoutParams().width = bounds.width();
Code is untested, but I'm sure you get the point.
UPDATE
It may be possible to do this without using a second background view, by setting the TextView width to match bounds.width(), but this trigger a change in how the text breaks, so need to be careful not to cause an infinite loop.

How to set `ImageView`'s height to match it's width while `layout_weight` is set?

Here is the deal:
I have a GridView with android:numColumns="2". The items in the GridView are ImageViews with android:layout_weight="1", so they are half the screen width. The problem is the heightproperty, it should be equal to the width. I tried to play with scaleype, so far without success.
How can I set the width of the GridView elements to fill half the screen and set their height to be equal to their width?
You have to get the ImageViews width programmatically and then set the height.
But if you just use img.getWidth(); eg. in OnCreate(), it will return 0 because it hasn't been drawn yet.
So you have to do something like this in OnCreate():
ImageView img = (ImageView) findViewById(R.id.img);
ViewTreeObserver vto = img.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
int x;
img.getViewTreeObserver().removeOnPreDrawListener(this);
x = img.getMeasuredWidth();
img.setLayoutParams(new LinearLayout.LayoutParams(x,x));
return true;
}
});
This will get your Imageview's width after its been drawn and set its height equal to it.
To set your ImageView equal to half the screen, you need to add the following to your XML for the ImageView:
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:scaleType="fitXY"
android:adjustViewBounds="true"/>
To then set the height equal to this width, you need to do it in code. In the getView method of your GridView adapter, set the ImageView height equal to its measured width:
int measuredImageWidth = mImageView.getMeasuredWidth();
mImageView.getLayoutParams().height = measuredImageWidth;
Hope this helps!

Place view inside FrameLayout in Android

I want to add a view inside a FrameLayout programmatically and to place it in a specific point within the layout with a specific width and height. Does FrameLayout support this? If not, should I use an intermediate ViewGroup to achieve this?
int x; // Can be negative?
int y; // Can be negative?
int width;
int height;
View v = new View(context);
// v.setLayoutParams(?); // What do I put here?
frameLayout.addView(v);
My initial idea was to add an AbsoluteLayout to the FrameLayout and place the view inside the AbsoluteLayout. Unfortunately I just found out that AbsoluteLayout is deprecated.
Any pointers will be much appreciated. Thanks.
The following example (working code) shows how to place a view (EditText) inside of a FrameLayout. Also it shows how to set the position of the EditText using the setPadding setter of the FrameLayout (everytime the user clicks on the FrameLayout, the position of the EditText is set to the position of the click):
public class TextToolTestActivity extends Activity{
FrameLayout frmLayout;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
frmLayout = (FrameLayout)findViewById(R.id.frameLayout1);
frmLayout.setFocusable(true);
EditText et = new EditText(this);
frmLayout.addView(et,100,100);
frmLayout.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("TESTING","touch x,y == " + event.getX() + "," + event.getY() );
frmLayout.setPadding(Math.round(event.getX()),Math.round(event.getY()) , 0, 0);
return true;
}
});
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:id="#+id/frameLayout1"
android:layout_height="fill_parent" android:layout_width="fill_parent">
</FrameLayout>
</LinearLayout>
You can also add a margin around the newly added view to position it inside the FrameLayout.
FrameLayout frameLayout = (FrameLayout) findViewById(R.id.main); // or some other R.id.xxx
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.setMargins(0, metrics.heightPixels - 20, 0, 0);
View v = new View(context);
v.setLayoutParams(params);
frameLayout.addView(v);
This will position the FrameLayout 20 pixels from the bottom of the screen.
Edit: completed the example so it stands by itself. And oh, yes it does work.
It's true that with FrameLayout all children are pegged to the top left of the screen, but you still have some control with setting their padding. If you set different padding values to different children, they will show up at different places in the FrameLayout.
From the link Quinn1000 provided:
You can add multiple children to a FrameLayout, but all children are pegged to the top left of the screen.
This means you can't put your View at a specific point inside the FrameLayout (except you want it to be at the top left corner :-)).
If you need the absolute positioning of the View, try the AbsoluteLayout:
A layout that lets you specify exact locations (x/y coordinates) of its children. Absolute layouts are less flexible and harder to maintain than other types of layouts without absolute positioning.
As for setting the width and height of the View, also like Quinn1000 said, you supply the v.setLayoutParams() method a LayoutParams object, depending on the container you chose (AbsoluteLayout, LinearLayout, etc.)
The thread here on stackOverflow at
How do you setLayoutParams() for an ImageView?
covers it somewhat.
For instance:
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(30, 30);
yourImageView.setLayoutParams(layoutParams);
implies that you need to be defining a LinearLayout.LayoutParams (or in your case a FrameLayout.layoutParams) object to pass to the setLayoutParams method of your v object.
At
http://developer.android.com/reference/android/widget/FrameLayout.html
it almost makes it looks like you could ask your v to:
generateDefaultLayoutParams () via this method if you have not defined the parameters specifically.
But it's late, and those links are making my eyes bleed a little. Let me know if they nhelp any :-)

Categories

Resources