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.
Related
I'm having trouble with a BottomSheetDialogFragment I implemented some days back in a project I'm in.
What happens is that I have a BottomSheet which contains a SearchView and a Recyclerview. The dialog fragment shows correctly and stuff, all good there.
The problem starts when I use the SearchView to filter the Recyclerview's results since when there's 5 or less results, the keyboard is overlapping the now small Recyclerview.
I want to know if it's possible to keep the BottomSheet height as match_parent or something to fill the window or keep the Recyclerview big enough to avoid the keyboard "messing up" with the results. I use the following method to make the fragment expanded when it opens:
private fun expandBottomSheet() {
view?.viewTreeObserver?.addOnGlobalLayoutListener {
val dialog = dialog as BottomSheetDialog
val bottomSheet = dialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
val behavior = BottomSheetBehavior.from<View>(bottomSheet)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
And my XML for the sheet is this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
app:layout_behavior="#string/bottom_sheet_behavior">
<View
android:layout_width="52dp"
android:layout_height="7dp"
android:layout_gravity="center"
android:layout_marginTop="#dimen/size_small_4"
android:layout_marginBottom="#dimen/size_small_4"
android:background="#drawable/border_top_swipe_indicator" />
<androidx.appcompat.widget.SearchView
android:id="#+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:iconifiedByDefault="false"
app:queryHint="#string/text_type_your_query" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/border_top_white"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="#layout/list_item" />
</LinearLayout>
Thanks in advance for the help!
Edit:
The bottom sheet containing the Recyclerview and stuff is a child fragment (a fragment instantiated from another fragment.)
For anyone who is searching for this question, if you want to force your BottomSheetDialogFragment have full screen height, just wrap your bottom sheet content layout inside of this custom FrameLayout:
public class MatchParentFrameLayout extends FrameLayout {
public MatchParentFrameLayout(#NonNull Context context) {
super(context);
}
public MatchParentFrameLayout(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public MatchParentFrameLayout(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MatchParentFrameLayout(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
In the manifest.xml file you have your activities declared there for your application. Inside of the <activity> block where this bottom sheet is hosted you can declare a window soft input mode so that the keyboard does not overlap the view - instead it pushes it up.
<activity
...
android:windowSoftInputMode="adjustResize|stateVisible"> ... </activity>
stateVisible: "The soft keyboard is visible when that's normally appropriate (when the user is navigating forward to the activity's main window)."
adjustResize: "The activity's main window is always resized to make room for the soft keyboard on screen."
Docs
That should work for you. It could be possible, depending on your views, for that to result in a poor UI and UX. If that is true, you could set a focus listener on the search view and, when it gains focus, programmatically set the state of the bottom sheet to expanded. See the answerhere.
Even though I'm not satisfied with the fix I came up to, I have to say it's working smoothly.
Basically I wrapped my bottomsheet with a ViewPager and it's not resizing.
I admit this is a hack and I'm hoping someone can provide a more decent answer to this. In the meantime, ViewPager with a single bottomsheet is the way to go.
You can set the height with the below code in On Activity created
view?.viewTreeObserver?.addOnGlobalLayoutListener {
val rect = Rect()
view?.getWindowVisibleDisplayFrame(rect)
val screenHeight = view?.rootView?.height
val keyPadHeight = screenHeight?.minus(rect.bottom)
if (screenHeight != null) {
if (keyPadHeight != 0) {
if (view?.paddingBottom != keyPadHeight) {
view?.setPadding(0, 0, 0, keyPadHeight!!)
}
} else {
if (view?.paddingBottom != 0) {
view?.setPadding(0, 0, 0, 0)
}
}
Hope this will work for you.
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.
I have problems with the translucent actionbar/navbar in the new Android KitKat (4.4) and the windowSoftInputMode="adjustResize".
Normaly, changing the InputMode to adjustResize, the app should resize itself when keyboard is shown, but here it won't! If I delete the lines for the transparent effect, the resize is working.
So if the keyboard is visible, my ListView is under it and I can't access the last few items (only by hiding the keyboard manually).
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="XYZ"
android:versionCode="23"
android:versionName="0.1" >
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="19" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/Theme.XYZStyle" >
<activity
android:name="XYZ"
android:label="#string/app_name"
android:windowSoftInputMode="adjustResize" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
values-v19/styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.XYZStyle" parent="#style/Theme.AppCompat.Light">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
</resources>
fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/listView_contacts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:divider="#null"
android:dividerHeight="0dp"
android:drawSelectorOnTop="true"
android:fastScrollAlwaysVisible="true"
android:fastScrollEnabled="true"
android:paddingBottom="#dimen/navigationbar__height" >
</ListView>
</RelativeLayout>
Any ideas for fixing this?
You are missing the following property:
android:fitsSystemWindows="true"
in the root RelativeLayout of the fragment .xml layout.
Update:
Last year there was an interesting talk by Chris Bane that explains in good detail how this works:
https://www.youtube.com/watch?v=_mGDMVRO3iE
There's a related bug report here. I've found a workaround that, from limited testing, seems to do the trick with no repercussions. Add a custom implementation of your root ViewGroup (I almost always am using FrameLayout, so this is what I've tested with) with the logic below. Then, use this custom layout in place of your root layout, and ensure you set android:fitsSystemWindows="true". You can then just call getInsets() any time after layout (e.g. add an OnPreDrawListener) to adjust the rest of your layout to account for the system insets, if desired.
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import org.jetbrains.annotations.NotNull;
/**
* #author Kevin
* Date Created: 3/7/14
*
* https://code.google.com/p/android/issues/detail?id=63777
*
* When using a translucent status bar on API 19+, the window will not
* resize to make room for input methods (i.e.
* {#link android.view.WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} and
* {#link android.view.WindowManager.LayoutParams#SOFT_INPUT_ADJUST_PAN} are
* ignored).
*
* To work around this; override {#link #fitSystemWindows(android.graphics.Rect)},
* capture and override the system insets, and then call through to FrameLayout's
* implementation.
*
* For reasons yet unknown, modifying the bottom inset causes this workaround to
* fail. Modifying the top, left, and right insets works as expected.
*/
public final class CustomInsetsFrameLayout extends FrameLayout {
private int[] mInsets = new int[4];
public CustomInsetsFrameLayout(Context context) {
super(context);
}
public CustomInsetsFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public final int[] getInsets() {
return mInsets;
}
#Override
protected final boolean fitSystemWindows(#NotNull Rect insets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Intentionally do not modify the bottom inset. For some reason,
// if the bottom inset is modified, window resizing stops working.
// TODO: Figure out why.
mInsets[0] = insets.left;
mInsets[1] = insets.top;
mInsets[2] = insets.right;
insets.left = 0;
insets.top = 0;
insets.right = 0;
}
return super.fitSystemWindows(insets);
}
}
Since fitSystemWindows was deprecated, please refer to the answer below to complete the workaround.
#kcoppock answer is really helpful, but fitSystemWindows was deprecated in API level 20
So since API 20 (KITKAT_WATCH) you should override onApplyWindowInsets
#Override
public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0,
insets.getSystemWindowInsetBottom()));
} else {
return insets;
}
}
If you want to customize the insets and you are targeting API level >=21 you can accomplish this without having to create a custom view group. By just setting fitsSystemWindows padding will be applied to your container view by default, which you may not want.
The version checks are built into this method and only devices >= 21 will execute the code inside the lambda. Kotlin example:
ViewCompat.setOnApplyWindowInsetsListener(container) { view, insets ->
insets.replaceSystemWindowInsets(0, 0, 0, insets.systemWindowInsetBottom).apply {
ViewCompat.onApplyWindowInsets(view, this)
}
}
Make sure your layout still sets the fitsSystemWindows flag otherwise the window insets listener will not be called.
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
/>
These sources are helpful:
https://medium.com/google-developers/why-would-i-want-to-fitssystemwindows-4e26d9ce1eec
https://medium.com/#azizbekian/windowinsets-24e241d4afb9
This worked for me to have translucent status bar and adjustResize in fragment:
Make a custom RelativeLayout as #Victor91 and #kcoppock said.
Use CustomRelativeLayout as parent layout for your fragment.
Declare theme with android:windowTranslucentStatus = true
The container Activity must be declared in Manifest with
android:windowSoftInputMode="adjustResize" and use the declared
theme
Please Use fitsSystemWindows on fragment root layout!
public class CustomRelativeLayout extends RelativeLayout {
private int[] mInsets = new int[4];
public CustomRelativeLayout(Context context) {
super(context);
}
public CustomRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
mInsets[0] = insets.getSystemWindowInsetLeft();
mInsets[1] = insets.getSystemWindowInsetTop();
mInsets[2] = insets.getSystemWindowInsetRight();
return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0,
insets.getSystemWindowInsetBottom()));
} else {
return insets;
}
}
}
Then in xml,
<com.blah.blah.CustomRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
</com.blah.blah.CustomRelativeLayout>
A small update on the helpful #Victor Rendina's answer caused by the replaceSystemWindowInsets and systemWindowInsetBottom methods deprecation.
Prerequisites:
API >= 21
implementation 'androidx.core:core-ktx:1.5.0-alpha02' at least
Kotlin extension:
fun View?.fitSystemWindowsAndAdjustResize() = this?.let { view ->
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
view.fitsSystemWindows = true
val bottom = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
WindowInsetsCompat
.Builder()
.setInsets(
WindowInsetsCompat.Type.systemBars(),
Insets.of(0, 0, 0, bottom)
)
.build()
.apply {
ViewCompat.onApplyWindowInsets(v, this)
}
}
}
Usage:
rootView.fitSystemWindowsAndAdjustResize()
where rootView is literally the root view of the layout :)
Note: if the extension does not work for your root view (I ran into this when having ConstraintLayout as the rootView) wrap the entire layout with a FrameLayout so that the FrameLayout becomes the new root view.
I had the same problem,
My Activity had a ScrollView as root view and with translucent statusbar activated it didn't resize correctly when keyboard showed... and conseguently the screen didn't scrolled hiding the input views.
Solution:
Moved everything (layout and activity logic) inside a new Fragment.
Then changed the Activity to only include this Fragment. Now everything works as expected!
This is the layout of the activity:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true" />
Add this first at your root layout.
android:fitsSystemWindows="true"
When you use this approach, it becomes your responsibility to ensure that critical parts of your app's UI (for example, the built-in controls in a Maps application) don't end up getting covered by system bars. This could make your app unusable. In most cases you can handle this by adding the android:fitsSystemWindows attribute to your XML layout file, set to true. This adjusts the padding of the parent ViewGroup to leave space for the system windows. This is sufficient for most applications.
In some cases, however, you may need to modify the default padding to get the desired layout for your app. To directly manipulate how your content lays out relative to the system bars (which occupy a space known as the window's "content insets"), override fitSystemWindows(Rect insets). The fitSystemWindows() method is called by the view hierarchy when the content insets for a window have changed, to allow the window to adjust its content accordingly. By overriding this method you can handle the insets (and hence your app's layout) however you want.
https://developer.android.com/training/system-ui/status#behind
If you want to become a master window fitter, please watch the video from the android developer.
https://www.youtube.com/watch?v=_mGDMVRO3iE
Based on Joseph Johnson's workaround in Android How to adjust layout in Full Screen Mode when softkeyboard is visible
call this in onCreate() after setContentView() in your activity.
AndroidBug5497Workaround.assistActivity(this);
a litte different from original replace return (r.bottom - r.top); with return r.bottom; in computeUsableHeight()
for some reason, i must set my activity fitsSystemWindows attribute to false.
this workaround saved me. it's works well for me. hope can help you.
the implementation class is:
public class AndroidBug5497Workaround {
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
public static void assistActivity (Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private AndroidBug5497Workaround(Activity activity) {
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent();
}
});
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return r.bottom;
}
}
It shouldn't work with the translucent status bar; that setting forces the window into fullscreen mode which does not work with adjustResize.
You can either use adjustPan or use the fitsSystemWindows properties. I would suggest reading about the feature though, it has significant side effects:
https://medium.com/google-developers/why-would-i-want-to-fitssystemwindows-4e26d9ce1eec
I had like a problem.
I set windowDrawsSystemBarBackgrounds to 'true' and my app should show under status bar.
It's my activity theme.
<item name="android:windowTranslucentStatus" tools:targetApi="KITKAT">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:statusBarColor">#android:color/transparent</item>
and I got help from jianshu's blog.
you can read code but text like me.
I add few code more.
public final class ZeroInsetsFrameLayout extends FrameLayout {
private int[] mInsets = new int[4];
public ZeroInsetsFrameLayout(Context context) {
super(context);
}
public ZeroInsetsFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ZeroInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public final int[] getInsets() {
return mInsets;
}
#Override
public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
outLocalInsets.left = 0;
outLocalInsets.top = 0;
outLocalInsets.right = 0;
return super.computeSystemWindowInsets(in, outLocalInsets);
}
#Override
protected final boolean fitSystemWindows(#NonNull Rect insets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Intentionally do not modify the bottom inset. For some reason,
// if the bottom inset is modified, window resizing stops working.
// TODO: Figure out why.
mInsets[0] = insets.left;
mInsets[1] = insets.top;
mInsets[2] = insets.right;
insets.left = 0;
insets.top = 0;
insets.right = 0;
}
return super.fitSystemWindows(insets);
}
}
This is my fragment layout.
<com.dhna.widget.ZeroInsetsFrameLayout 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"
android:fitsSystemWindows="true"
android:background="#color/white">
<!-- your xml code -->
</ZeroInsetsFrameLayout>
I want it to be helpful to you.
good luck!
AndroidBug5497Workaround.java take care memory leak. need below code
getViewTreeObserver().removeOnGlobalLayoutListener(listener);
My sample using RxJava that automatically call removeOnGlobalLayoutListener() when onPause() in Activity's lifecycle
public class MyActivity extends RxAppCompatActivity {
// ...
protected void onStart(){
super.onStart();
TRSoftKeyboardVisibility
.changes(this) // activity
.compose(this.<TRSoftKeyboardVisibility.ChangeEvent>bindUntilEvent(ActivityEvent.PAUSE))
.subscribe(keyboardEvent -> {
FrameLayout content = (FrameLayout) findViewById(android.R.id.content);
View firstChildView = content.getChildAt(0);
firstChildView.getLayoutParams().height = keyboardEvent.viewHeight();
firstChildView.requestLayout();
// keyboardEvent.isVisible = keyboard visible or not
// keyboardEvent.keyboardHeight = keyboard height
// keyboardEvent.viewHeight = fullWindowHeight - keyboardHeight
});
//...
}
package commonlib.rxjava.keyboard;
import android.app.Activity;
import android.view.View;
import android.widget.FrameLayout;
import kr.ohlab.android.util.Assert;
import rx.Observable;
public class TRSoftKeyboardVisibility {
public static Observable<ChangeEvent> changes(Activity activity) {
Assert.notNull(activity, "activity == null");
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
View childOfContent = content.getChildAt(0);
return Observable.create(
new TRSoftKeyboardVisibilityEventOnSubscribe(childOfContent));
}
public static final class ChangeEvent {
private final int keyboardHeight;
private final boolean visible;
private final int viewHeight;
public static ChangeEvent create(boolean visible, int keyboardHeight,
int windowDisplayHeight) {
return new ChangeEvent(visible, keyboardHeight, windowDisplayHeight);
}
private ChangeEvent(boolean visible, int keyboardHeight, int viewHeight) {
this.keyboardHeight = keyboardHeight;
this.visible = visible;
this.viewHeight = viewHeight;
}
public int keyboardHeight() {
return keyboardHeight;
}
public boolean isVisible() {
return this.visible;
}
public int viewHeight() {
return viewHeight;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ChangeEvent)) return false;
ChangeEvent that = (ChangeEvent) o;
if (keyboardHeight != that.keyboardHeight) return false;
if (visible != that.visible) return false;
return viewHeight == that.viewHeight;
}
#Override
public int hashCode() {
int result = keyboardHeight;
result = 31 * result + (visible ? 1 : 0);
result = 31 * result + viewHeight;
return result;
}
#Override
public String toString() {
return "ChangeEvent{" +
"keyboardHeight=" + keyboardHeight +
", visible=" + visible +
", viewHeight=" + viewHeight +
'}';
}
}
}
package commonlib.rxjava.keyboard;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;
import kr.ohlab.android.util.Assert;
import rx.Observable;
import rx.Subscriber;
import rx.android.MainThreadSubscription;
import timber.log.Timber;
public class TRSoftKeyboardVisibilityEventOnSubscribe
implements Observable.OnSubscribe<TRSoftKeyboardVisibility.ChangeEvent> {
private final View mTopView;
private int mLastVisibleDecorViewHeight;
private final Rect mWindowVisibleDisplayFrame = new Rect();
public TRSoftKeyboardVisibilityEventOnSubscribe(View topView) {
mTopView = topView;
}
private int computeWindowFrameHeight() {
mTopView.getWindowVisibleDisplayFrame(mWindowVisibleDisplayFrame);
return (mWindowVisibleDisplayFrame.bottom - mWindowVisibleDisplayFrame.top);
}
private TRSoftKeyboardVisibility.ChangeEvent checkKeyboardVisibility() {
int windowFrameHeightNow = computeWindowFrameHeight();
TRSoftKeyboardVisibility.ChangeEvent event = null;
if (windowFrameHeightNow != mLastVisibleDecorViewHeight) {
int mTopViewHeight = mTopView.getHeight();
int heightDiff = mTopViewHeight - windowFrameHeightNow;
Timber.e("XXX heightDiff=" + heightDiff);
if (heightDiff > (mTopViewHeight / 4)) {
event = TRSoftKeyboardVisibility.ChangeEvent.create(true, heightDiff, windowFrameHeightNow);
} else {
event = TRSoftKeyboardVisibility.ChangeEvent.create(false, 0, windowFrameHeightNow);
}
mLastVisibleDecorViewHeight = windowFrameHeightNow;
return event;
}
return null;
}
public void call(final Subscriber<? super TRSoftKeyboardVisibility.ChangeEvent> subscriber) {
Assert.checkUiThread();
final ViewTreeObserver.OnGlobalLayoutListener listener =
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
TRSoftKeyboardVisibility.ChangeEvent event = checkKeyboardVisibility();
if( event == null)
return;
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(event);
}
}
};
mTopView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
subscriber.add(new MainThreadSubscription() {
#Override
protected void onUnsubscribe() {
mTopView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
});
}
}
After I had researched on all forum. thoese ways can not help find point out. Lucky when i tried doing this way. It helps me resolved problem
XML
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- Your xml -->
</RelativeLayout>
Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView("Your Activity");
setAdjustScreen();
}
Created Func
protected void setAdjustScreen(){
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
/*android:windowSoftInputMode="adjustPan|adjustResize"*/
}
Finally adding some lines to your mainifest
<activity
android:name="Your Activity"
android:windowSoftInputMode="adjustPan|adjustResize"
android:screenOrientation="portrait"></activity>
I had the same problem. I have solved using coordinatorlayout
activity.main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:layout_height="match_parent" android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:popupTheme="#style/AppTheme.PopupOverlay"
android:background="?attr/colorPrimary"
android:id="#+id/toolbar"/>
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main2"/>
</android.support.design.widget.CoordinatorLayout>
content_main2.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.RecyclerView
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_marginTop="30dp"
android:layout_marginBottom="30dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:id="#+id/post_msg_recyclerview">
</android.support.v7.widget.RecyclerView>
<EditText
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_constraintBottom_toBottomOf="parent"
android:background="#color/colorPrimary"
/>
</android.support.constraint.ConstraintLayout>
MainActivity.java
now add this line linearLayoutManager.setStackFromEnd(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);
Adapter adapter1=new Adapter(arrayList);
recyclerView.setAdapter(adapter1);
<androidx.constraintlayout.widget.ConstraintLayout
android:fitsSystemWindows="true">
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.appbar.CollapsingToolbarLayout/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView>
<Editext/>
<androidx.core.widget.NestedScrollView/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
here is what i use
in the main view in the xml file you will add this
android:animateLayoutChanges="true"
then in the "onCreate" function you will before every thing get the status bar size like this
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0)
{
status_bar=getResources().getDimensionPixelSize(resourceId);
}
then finally in "onCreate" you will add this to update the size
main_view= findViewById(R.id.the_main);
main_view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
Rect r = new Rect();
View view = getWindow().getDecorView();
view.getWindowVisibleDisplayFrame(r);
if (Math.abs(old_size - r.height()) > 100)
{
ViewGroup.LayoutParams params = main_view.getLayoutParams();
params.height = r.height()+ status_bar ;
main_view.setLayoutParams(params);
}
old_size = r.height();
}
});
I don't why but option adjustResize doesn't work with fullscreen. I've just added titleBar and works ( android:theme="#style/AppTheme"). Insted that I use in code " getSupportActionBar().hide();"
<activity
android:name=".ChatActivity"
android:launchMode="singleInstance"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize"
android:theme="#style/AppTheme"
/>
The best practice allows user scroll content when the keyboard is shown.
So to add this functionality you need put your root layout inside the ScrollView and use windowSoftInputMode="adjustResize" activity method.
But if you want to use this functionality with <item name="android:windowTranslucentStatus">true</item>
flag on Android 5 content won't be scrollable and will overlaps with keyboard.
To solve this issue check this answer
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.
I am trying to display a GridView in a Dialog. Despite all my efforts, the GridView width grows to the entire screen, instead of wrapping to the columns. The layout and an image depicting the issue are below (I set a background color for the GridView to illustrate the issue).
<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/colorgridview"
android:background="#FF00CCBB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numColumns="4"
android:verticalSpacing="5dp"
android:horizontalSpacing="5dp"
android:columnWidth="70dp"
android:stretchMode="none"
/>
I know that this post is a bit outdated. But if someone needs a solution to this, this answer may come in handy.
It is possible to set the view's width after screen measurement.
To do this:
Let your class implement the OnGlobalLayoutListener.
The screen is measured when the onGlobalLayout method is called. We can do our magic here.
GridView.getLayoutParams().width = ....
edit:
I wasn't very clear on how to add the onGlobalLayoutListener. See plugmind's post, he shows how to add it.
Can't figure out how to get view/layout width/height
Kind regards,
Bram
I think you should use android:layout_width="fill_parent" instead of android:layout_width="wrap_content" because wrap content use the minimum place it needs. On the other hand, fill_parent use all space needed. More over you should get rid of "android:columnWidth="70dp".
It's certainly possible to set a fixed layout_width (in dp). Since the number of your columns is also fixed, could this be a workaround for you?
Had the same problem...
I solved it with overridden onMeasure()
public class GridViewEx extends GridView {
private int mRequestedNumColumns = 0;
public GridViewEx(Context context) {
super(context);
}
public GridViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
}
public GridViewEx(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void setNumColumns(int numColumns) {
super.setNumColumns(numColumns);
if (numColumns != mRequestedNumColumns) {
mRequestedNumColumns = numColumns;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mRequestedNumColumns > 0) {
int width = (mRequestedNumColumns * getColumnWidth())
+ ((mRequestedNumColumns-1) * getHorizontalSpacing())
+ getListPaddingLeft() + getListPaddingRight();
setMeasuredDimension(width, getMeasuredHeight());
}
}
}