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

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.

Related

Small EditText have a setError with a lot of lines

I have a small EditText and I want to display errors (using editText.setError()) in it. In Android API 10 the message is displayed in a lot of lines and it's unreadable. In Android 15 works relatively fine. I attach screenshots to illustrate the problem at the end of the question.
How I can display the error messages in a appropriate mode?
I wrote a little example to reproduce the problem:
The Activity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((EditText) findViewById(R.id.b)).setError("A error description and bla bla bla bla bla.");
}
The layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<EditText
android:id="#+id/a"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1" />
<EditText
android:id="#+id/b"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1" />
<EditText
android:id="#+id/c"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1" />
<EditText
android:id="#+id/d"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1" />
<EditText
android:id="#+id/e"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1" />
<EditText
android:id="#+id/f"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
Device with Android API 10:
Tablet with Android API 15:
Related question. But the answer doesn't work for me.
UPDATE
I executed the same code on two equals simulators except the API level. The results can be seen on the screens. The API 15 still does not fix the error completely. The text is legible but the popup is not in the correct position.
So looking at the source for 2.3.3 the width of the error text is set to slightly less than the width of the TextView it is related to.
They've jimmied around with that for 4.0.3 so that, in your example, the width of the pop-up is correct - but the nature of your layout is such that the pointer is in the wrong place.
I think you've a reasonable example for a bug report against 4.0.3 as I don't think you've got that unusual a use-case.
In order to sort this out though I'd recommend using a TextView that you hide and reveal as necessary. You can set an error image on the edit text as below.
Drawable errorImage = getContext().getResources().getDrawable( R.drawable.your_error_image);
theEditTextInQuestion.setCompoundDrawableWithIntrinsicBounds(errorImage, null, null, null);”
on api 14, TextView use this class;
private static class ErrorPopup extends PopupWindow {
private boolean mAbove = false;
private final TextView mView;
private int mPopupInlineErrorBackgroundId = 0;
private int mPopupInlineErrorAboveBackgroundId = 0;
ErrorPopup(TextView v, int width, int height) {
super(v, width, height);
mView = v;
// Make sure the TextView has a background set as it will be used the first time it is
// shown and positionned. Initialized with below background, which should have
// dimensions identical to the above version for this to work (and is more likely).
mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
com.android.internal.R.styleable.Theme_errorMessageBackground);
mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
}
void fixDirection(boolean above) {
mAbove = above;
if (above) {
mPopupInlineErrorAboveBackgroundId =
getResourceId(mPopupInlineErrorAboveBackgroundId,
com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
} else {
mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
com.android.internal.R.styleable.Theme_errorMessageBackground);
}
mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
mPopupInlineErrorBackgroundId);
}
private int getResourceId(int currentId, int index) {
if (currentId == 0) {
TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
R.styleable.Theme);
currentId = styledAttributes.getResourceId(index, 0);
styledAttributes.recycle();
}
return currentId;
}
#Override
public void update(int x, int y, int w, int h, boolean force) {
super.update(x, y, w, h, force);
boolean above = isAboveAnchor();
if (above != mAbove) {
fixDirection(above);
}
}
}
And call like this;
final float scale = getResources().getDisplayMetrics().density;
mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
mPopup.setFocusable(false);
So Android TextView use your phone density. I tried for change density but i couldn't work because it need root. If you can this, maybe its work. But i guess it is not possible.
I think you should set the width of the edit text as fill parents so that it will take the proper place, otherwise give the width like 200 or 250 dp so that in that particular width your error message will be shown to you.
does those two emulator have the same screen sizes?.. if so, i think it would be appropriate to set the layout_widths and the heights to wrap content for the popup and if the error still persist then define specifically the width and height of the popup
I think a problem might be that the LinearLayout in your XML has no weightSum attribute. I do not know if you can use layout_weight in child views if LinearLayout does not have a weightSum declared. Also, try changing your layout_widths to "wrap_content" and get rid of the layout_weights if that doesnt work.

Android: how to create a button with image and text that are both centred

I got stuck in an odd issue with Android - I want to have a button that looks like this:
|-----------------------------------------------------------------------|
| [icon] <5px> [text text text] |
|-----------------------------------------------------------------------|
and the group ([icon] <5px> [text text text]) should be centred. Note that 5px is used just as a placeholder for any padding you want to have between the icon and the text
I found some answers here that were more or less graviting around either setting a background (which I don't want to do because I have another background) or using the android:drawableLeft property to set the icon.
However looks like the documentation of the setCompoundDrawablesWithIntrinsicBounds method is a bit misleading (see here). It states that the image is placed on the left/right/top/bottom side of the TEXT wich is not true. The icon is placed on the corresponding side of the BUTTON. For example:
Setting the android:drawableLeft property puts the icon on the most left position and gets me this (with gravity CENTER):
|-----------------------------------------------------------------------|
| [icon] [text text text] |
|-----------------------------------------------------------------------|
or this (with gravity LEFT):
|-----------------------------------------------------------------------|
| [icon] [text text text] |
|-----------------------------------------------------------------------|
Both are ugly as hell :(
I found a workaround that looks like this:
public static void applyTextOffset(Button button, int buttonWidth) {
int textWidth = (int) button.getPaint().measureText(button.getText().toString());
int padding = (buttonWidth / 2) - ((textWidth / 2) + Constants.ICON_WIDTH + Constants.ICON_TO_TEXT_PADDING);
button.setPadding(padding, 0, 0, 0);
button.setCompoundDrawablePadding(-padding);
}
And it works more or less but I don't find it to my liking for following reasons:
it requires to know the button width. With auto-sized buttons it will not be known until the actual layout is done. Google recommend using a listener to learn the actual width after the rendering is done but this immensely complicates the code.
I feel like I'm taking over the layout responsibility from the Android layout engine
Isn't there a more elegant solution?
You can use the following Button subclass to achieve this effect.
Paste this class into your project and tweak the package name if desired.
package com.phillipcalvin.iconbutton;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.Button;
public class IconButton extends Button {
protected int drawableWidth;
protected DrawablePositions drawablePosition;
protected int iconPadding;
// Cached to prevent allocation during onLayout
Rect bounds;
private enum DrawablePositions {
NONE,
LEFT,
RIGHT
}
public IconButton(Context context) {
super(context);
bounds = new Rect();
}
public IconButton(Context context, AttributeSet attrs) {
super(context, attrs);
bounds = new Rect();
}
public IconButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
bounds = new Rect();
}
public void setIconPadding(int padding) {
iconPadding = padding;
requestLayout();
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Paint textPaint = getPaint();
String text = getText().toString();
textPaint.getTextBounds(text, 0, text.length(), bounds);
int textWidth = bounds.width();
int contentWidth = drawableWidth + iconPadding + textWidth;
int contentLeft = (int)((getWidth() / 2.0) - (contentWidth / 2.0));
setCompoundDrawablePadding(-contentLeft + iconPadding);
switch (drawablePosition) {
case LEFT:
setPadding(contentLeft, 0, 0, 0);
break;
case RIGHT:
setPadding(0, 0, contentLeft, 0);
break;
default:
setPadding(0, 0, 0, 0);
}
}
#Override
public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom) {
super.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);
if (null != left) {
drawableWidth = left.getIntrinsicWidth();
drawablePosition = DrawablePositions.LEFT;
} else if (null != right) {
drawableWidth = right.getIntrinsicWidth();
drawablePosition = DrawablePositions.RIGHT;
} else {
drawablePosition = DrawablePositions.NONE;
}
requestLayout();
}
}
2. Modify your layout to use this new subclass instead of plain Button:
<com.phillipcalvin.iconbutton.IconButton
android:id="#+id/search"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawableLeft="#drawable/search"
android:text="#string/search" />
3. If you want to add padding between the drawable and the text, add the following to your activity's onCreate:
// Anywhere after setContentView(...)
IconButton button = (IconButton)findViewById(R.id.search);
button.setIconPadding(10);
This subclass also supports drawableRight. It does not support more than one drawable.
If you want more features, such as the ability to specify the iconPadding directly in your layout XML, I have a library that supports this.
You may also consider using a custom view to create a custom button.
This could be anywhere from easy to extremely complex.
It would be easy if all you need to do is override onDraw(). It will be more complex if you need to lay out multiple views.
Simply way is to write Button in LinearLayout. Something like this
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="#drawable/ic_left"
android:drawablePadding="5dp"
android:duplicateParentState="true"
android:text="Button text"/>
</LinearLayout>
I tried this in eclipse and it spouted no errors:
<Button android:id="#+id/test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent" >
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:gravity="center"
android:background="#android:color/transparent" >
</LinearLayout>
</Button>
Therefore, I suppose you could add an image and text views in a layout, itself inside a button, and center the layout.
Best regards.

Choose wrapping view when layout is wider than screen

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.

Android : Implementing custom view with buttons

I am creating a custom view which will have a bitmap so that user can draw in it and some normal Android buttons in the bottom for user interaction.
To size my bitmap (height of drawing areas should be 50% ) I am overriding
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(widthMeasureSpec, (int)(parentHeight * 0.50));
super.onMeasure(widthMeasureSpec, (int)(parentHeight * 0.50));
}
This gives me exception that
"java.lang.IllegalArgumentException: width and height must be > 0"
If I set super.onMeasure(widthMeasureSpec, heightMeasureSpec);
I cannot see my buttons places in the bottom.
If I dont write super.onMeasure() anything I draw is not seen once I release the mouse.
I am using a xml file for layout :
<view class="com.my.CustomView" android:id="#+id/myView"
android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal">
<Button android:layout_height="wrap_content"
android:text="B1" android:layout_width="wrap_content"/>
<Button android:layout_height="wrap_content"
android:layout_width="wrap_content" android:text="B2"/>
</LinearLayout>
What else should I do ?
Wouldn't be easier to give your custom view a size in dp. You then can see your layout in design mode. Then use onSizeChanged to find out the size of your canvas.
onMeasure is usually used when the size of the canvas depends on runtime values, like the result of a game or number of loaded items etc.
Here is an example that works:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
int newH = (int) (parentHeight / 1.5f);
this.setMeasuredDimension(parentWidth, newH );
}
Also the constructor for your custom vuew must include attribute set:
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
Your onMeasure() is incomplete, it should take into account also the mode (UNSPECIFIED, EXACTLY, AT_MOST). Docs say that onMeasure() might get called several times, even with UNSPECIFIED 0-0 sizes, to see what size the view would like to be. See View, section Layout. There's no need to call super.onMeasure().
I don't get if your custom view includes only the drawing area or you're inflating the buttons too in it (my guess is the first case), but try Hierarchy Viewer, it will help you to understand how views are being laid out (and hence why in some cases you see drawings and sometimes not).

Software keyboard resizes background image on Android

Whenever the software keyboard appears, it resizes the background image. Refer to the screenshot below:
As you can see, the background is sort of squeezed. Anyone can shed a light on why the background resizes?
My Layout is as follows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/page_bg"
android:isScrollContainer="false"
>
<LinearLayout android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_width="fill_parent"
>
<EditText android:id="#+id/CatName"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="textCapSentences"
android:lines="1"
/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/save"
android:onClick="saveCat"
/>
</LinearLayout>
<ImageButton
android:id="#+id/add_totalk"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="#null"
android:src="#drawable/add_small"
android:scaleType="center"
android:onClick="createToTalk"
android:layout_marginTop="5dp"
/>
</LinearLayout>
Ok I fixed it by using
android:windowSoftInputMode="stateVisible|adjustPan"
entry inside <Activity > tag in manifest file. I think it was caused by having ScrollView inside the Activity.
I faced the same problem while developing a chat app, chat screen with a background image. android:windowSoftInputMode="adjustResize" squeezed my background image to fit the available space after the soft keyboard was displayed and "adjustPan" shifted the whole layout up to adjust the soft keyboard.
The solution to this problem was setting the window background instead of a layout background inside an activity XML. Use getWindow().setBackgroundDrawable() in your activity.
Here is the best solution to avoid such kind of problem.
Step 1: Create a style
<style name="ChatBackground" parent="AppBaseTheme">
<item name="android:windowBackground">#drawable/bg_chat</item>
</style>
Step 2: Set your activity style in the AndroidManifest file
<activity
android:name=".Chat"
android:screenOrientation="portrait"
android:theme="#style/ChatBackground" >
Through android:windowSoftInputMode="adjustPan" giving bad user experience because through that entire screen goes on top (shift to top ) So, following is one of the best answeres.
I have same Problem but after that , i Found Awesome answeres from the #Gem
In Manifest
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
In xml
Dont Set any background here and keep your view under ScrollView
In Java
You need to set the background to window:
getWindow().setBackgroundDrawableResource(R.drawable.bg_wood) ;
Thanks to #Gem.
just for addition...
if u have a listview on your activity
u need to add this android:isScrollContainer="false" in your listview properties...
and don't forget to add android:windowSoftInputMode="adjustPan" in your manifest xml at your activity...
if you guys using android:windowSoftInputMode="adjustUnspecified" with scrollable view on your layout, then your background will still resized by the soft keyboard...
it would be better if you use "adjustPan" value to prevent your background from resizing...
In case if somebody need an adjustResize behavior and don't want his ImageView to be resized here is another workaround.
Just put ImageView inside ScrollView => RelativeLayout with ScrollView.fillViewport = true.
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="100dp"
android:layout_height="100dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="#drawable/gift_checked" />
</FrameLayout>
</RelativeLayout>
</ScrollView>
After studying and implementing all available answers, here I am adding a solution.
This answer is combination of code from:
https://stackoverflow.com/a/45620231/1164529
https://stackoverflow.com/a/27702210/1164529
Here is the custom AppCompatImageView class which showed no stretching or scrolling w.r.t. soft keyboard:-
public class TopCropImageView extends AppCompatImageView {
public TopCropImageView(Context context) {
super(context);
setScaleType(ImageView.ScaleType.MATRIX);
}
public TopCropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ImageView.ScaleType.MATRIX);
}
public TopCropImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setScaleType(ImageView.ScaleType.MATRIX);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
computeMatrix();
}
#Override
protected boolean setFrame(int l, int t, int r, int b) {
computeMatrix();
return super.setFrame(l, t, r, b);
}
private void computeMatrix() {
if (getDrawable() == null) return;
Matrix matrix = getImageMatrix();
float scaleFactor = getWidth() / (float) getDrawable().getIntrinsicWidth();
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
}
}
To use it as background to my Fragment class, I set it as first element to FrameLayout.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<complete.package.TopCropImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#mipmap/my_app_background" />
<!-- Enter other UI elements here to overlay them on the background image -->
<FrameLayout>
I encountered the main problem when working on my app. At first, I use the method provided by #parulb to solve the problem. Thank him a lot. But later I noticed that the background image is partially hided by actionbar (and statusbar I am sure). This small issue is already proposed by #zgc7009 who commented below the answer of #parulb one year and a half ago but no one replied.
I worked a whole day to find out a way and fortunately I can at least solve this problem perfectly on my cellphone now.
First we need a layer-list resource in drawable folder to add padding on the top to the background image:
<!-- my_background.xml -->
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="75dp">
<bitmap android:src="#drawable/bg" />
</item>
</layer-list>
Second we set this file as resource for background as mentioned above:
getWindow().setBackgroundDrawableResource(R.drawable.my_background);
I am using Nexus 5. I found a way to get height of actionbar in xml but not the statusbar, so I have to use a fixed height 75dp for top padding. Hope anyone can find the last piece of this puzzle.
I suffered similar issues, but it seems like using adjustPan with android:isScrollContainer="false" still didn't fix my layout (which was a RecyclerView below a LinearLayout). The RecyclerView was fine, but every time the virtual keyboard showed up, the LinearLayout re-adjusted.
To prevent this behavior (I simply wanted to have the keyboard go over my layout), I went with the following code:
<activity
android:name=".librarycartridge.MyLibraryActivity"
android:windowSoftInputMode="adjustNothing" />
This tells Android to basically leave your layout alone when the virtual keyboard is called.
More reading about possible options can be found here (though oddly enough, it doesn't seem like there's an entry for adjustNothing).
Add this line in AndroidManifest.xml file:
android:windowSoftInputMode=adjustUnspecified
refer to this link for more info.
I faced with this problem, when my background image was just a ImageView inside a Fragment, and it was resized by the keyboard.
My solution was: using custom ImageView from this SO Answer, edited to be compatible with androidx.
import android.content.Context;
import android.graphics.Matrix;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageView;
/**
* Created by chris on 7/27/16.
*/
public class TopCropImageView extends AppCompatImageView {
public TopCropImageView(Context context) {
super(context);
setScaleType(ScaleType.MATRIX);
}
public TopCropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.MATRIX);
}
public TopCropImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setScaleType(ScaleType.MATRIX);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
recomputeImgMatrix();
}
#Override
protected boolean setFrame(int l, int t, int r, int b) {
recomputeImgMatrix();
return super.setFrame(l, t, r, b);
}
private void recomputeImgMatrix() {
if (getDrawable() == null) return;
final Matrix matrix = getImageMatrix();
float scale;
final int viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
final int viewHeight = getHeight() - getPaddingTop() - getPaddingBottom();
final int drawableWidth = getDrawable().getIntrinsicWidth();
final int drawableHeight = getDrawable().getIntrinsicHeight();
if (drawableWidth * viewHeight > drawableHeight * viewWidth) {
scale = (float) viewHeight / (float) drawableHeight;
} else {
scale = (float) viewWidth / (float) drawableWidth;
}
matrix.setScale(scale, scale);
setImageMatrix(matrix);
}
}
My solution is to substitute the background of the window with the one of the layout, then set the layout background to null. In this way I keep the image in the XML preview window:
So keep the background in the layout and add an id for it.
Then in the activity onCreate() put this code:
ConstraintLayout mainLayout = (ConstraintLayout)findViewById(R.id.mainLayout);
getWindow().setBackgroundDrawable(mainLayout.getBackground());
mainLayout.setBackground(null);
Just add in your activity
getWindow().setBackgroundDrawable(R.drawable.your_image_name);
I faced this same problem before but no solution worked for me so long because i was using a Fragment, and also
getActivity().getWindow().setBackgroundDrawable() was not a solution for me.
Solution which worked for me is to override FrameLayout with a logic to handle keyboard which should appear and change the bitmap on the go.
Here is my FrameLayout code (Kotlin):
class FlexibleFrameLayout : FrameLayout {
var backgroundImage: Drawable? = null
set(bitmap) {
field = bitmap
invalidate()
}
private var keyboardHeight: Int = 0
private var isKbOpen = false
private var actualHeight = 0
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
init()
}
fun init() {
setWillNotDraw(false)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
if (actualHeight == 0) {
actualHeight = height
return
}
//kb detected
if (actualHeight - height > 100 && keyboardHeight == 0) {
keyboardHeight = actualHeight - height
isKbOpen = true
}
if (actualHeight - height < 50 && keyboardHeight != 0) {
isKbOpen = false
}
if (height != actualHeight) {
invalidate()
}
}
override fun onDraw(canvas: Canvas) {
if (backgroundImage != null) {
if (backgroundImage is ColorDrawable) {
backgroundImage!!.setBounds(0, 0, measuredWidth, measuredHeight)
backgroundImage!!.draw(canvas)
} else if (backgroundImage is BitmapDrawable) {
val scale = measuredWidth.toFloat() / backgroundImage!!.intrinsicWidth.toFloat()
val width = Math.ceil((backgroundImage!!.intrinsicWidth * scale).toDouble()).toInt()
val height = Math.ceil((backgroundImage!!.intrinsicHeight * scale).toDouble()).toInt()
val kb = if (isKbOpen) keyboardHeight else 0
backgroundImage!!.setBounds(0, 0, width, height)
backgroundImage!!.draw(canvas)
}
} else {
super.onDraw(canvas)
}
}
}
And I used it as like an usual FrameLayout's background.
frameLayout.backgroundImage = Drawable.createFromPath(path)
Hope it helps.
You can wrap your LinearLayout with FrameLayout and add ImageView with Background:
<FrameLayout>
<ImageView
android:background="#drawable/page_bg"
android:id="#+id/backgroundImage" />
<LinearLayout>
....
</LinearLayout>
</FrameLayout>
And You can set it height when you creating activity/fragment (to prevent from scaling when open keyboard). Some code in Kotlin from Fragment:
activity?.window?.decorView?.height?.let {
backgroundImage.setHeight(it)
}
if you are set image as windowbackground and ui going to stuck. then there may be possibility you are uses drawable which is in single drawable folder,
if yes then you have to paste it in drawable-nodpi or drawable-xxxhdpi.

Categories

Resources