I have been stuck into this for quite some time. I am trying to develop a chat module. I have been stuck into this part where when the SoftInputKeyboard overlays the content of the RecyclerView. I have tried almost every combination of adjustResize and adjustPan with stateHidden and stateVisible with no success at all.
On Using adjustPan the ActionBar gets hidden along with 2-3 recyclerview items.
I have attached screenshots. Any help with be appreciated.
XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/light_grey"
android:fitsSystemWindows="true"
android:focusable="true"
android:focusableInTouchMode="true">
<LinearLayout
android:id="#+id/listFooter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<EditText
android:id="#+id/messageInput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#android:color/white"
android:hint="Write a message"
android:inputType="textMultiLine"
android:padding="16dp"
android:textSize="14sp" />
<ImageView
android:id="#+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#color/colorPrimary"
android:contentDescription="#null"
android:padding="12dp"
android:src="#drawable/ic_send" />
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview_chat_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/listFooter"
android:layout_marginTop="8dp" />
</RelativeLayout>
A ) AdjustResize
B ) With AdjustPan
The android:windowSoftInputMode does not scroll your content for you when the keyboard is shown/hidden.
The documentation says:
adjustResize - The activity's main window is always resized to make room for the soft keyboard on screen.
adjustPan - The activity's main window is not resized to make room for the soft keyboard. Rather, the contents of the window are
automatically panned so that the current focus is never obscured by
the keyboard and users can always see what they are typing. This is
generally less desirable than resizing, because the user may need to
close the soft keyboard to get at and interact with obscured parts of
the window.
Basically this means that adjustResize makes your rootview smaller and puts the softKeyboard below it. And adjustPan pushes the top half of the rootview out of the screen to make room for the the softKeyboard.
I would suggest using adjustResize because it wont push your Toolbar out of the screen. Then you would have to scroll the content yourself. Its easier said than done obviously, but there are methods built in to do this.
First you would have to get the last visible item position in the recyclerview.
//class variable
private int lastVisiblePosition = 0;
#Override
protected void onCreate(Bundle savedInstanceState)
{
//...
recyclerview_chat_main.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
super.onScrolled(recyclerView, dx, dy);
lastVisiblePosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findLastVisibleItemPosition();
}
});
//...
}
Then you have to do is scroll to that item when the SoftKeyboard is shown, the issue with that is there is no built in way to get when the keyboard is shown, fortunately someone has already addressed that here: https://stackoverflow.com/a/25681196/2027232
using Jaap's answer we could add something like this:
//class variables
private ViewGroup rootLayout = null;
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = null;
#Override
protected void onCreate(Bundle savedInstanceState)
{
//...
ViewGroup rootLayout = (ViewGroup)findViewById(android.R.id.content);
keyboardLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
int heightDiff = rootLayout.getRootView().getHeight() - rootLayout.getHeight();
int contentViewTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getHeight();
if(heightDiff > contentViewTop)
{
recyclerview_chat_main.getLayoutManager().scrollToPosition(lastVisiblePosition);
}
}
};
rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(keyboardLayoutListener);
//...
}
And lastly dont forget to remove the global listener when the activity gets destroyed:
#Override
protected void onDestroy()
{
super.onDestroy();
if(rootLayout != null && keyboardLayoutListener != null)
rootLayout.getViewTreeObserver().removeOnGlobalLayoutListener(keyboardLayoutListener);
}
The accepted answer didn't work for me.
This works:
In your activity tag in manifest, add android:windowSoftInputMode="adjustResize".
In onCreate method of your activity add this:
View.OnLayoutChangeListener layoutChangeListener = new View.OnLayoutChangeListener()
{
#Override
public void onLayoutChange(View v, int left, final int top, int right, final int bottom,
int oldLeft, final int oldTop, int oldRight, final int oldBottom)
{
if (oldBottom != 0)
{
//when softkeyboard opens, the height of layout get's small, and when softkeyboa
//-rd closes the height grows back(gets larger).We can find the change of height
// by doing oldBotton - bottom, and the result of subtraction is how much we nee
//-d to scroll. Change of height is positive if keyboard is opened, and negative
//if it's closed.
int pixelsToScrollVertically = oldBottom - bottom;
recyclerView.scrollBy(0, pixelsToScrollVertically);
}
}
};
myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);
recyclerView.addOnLayoutChangeListener(layoutChangeListener);
Related
TL;DR: TextView in Bottomsheet not showing wrapped multi-line text the first time Bottomsheet is expanded, but adjust itself after it collapsed.
So I am using the Bottomsheet from design-23.2.1 library.
My layout file looks like this:
<android.support.design.widget.CoordinatorLayout>
......
<LinearLayout
android:id="#+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_peekHeight="#dimen/bottom_sheet_peek_height"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"/>
</android.support.design.widget.CoordinatorLayout>
The content of the Bottomsheet is basically a list:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false" />
...
</LinearLayout>
The issue is whenever the Bottomsheet is set to STATE_EXPANDED the first time, the TextView is single line and text is wrapped, and there is no ellipsis … at line end.
Then after it's set to STATE_COLLAPSED, TextView's height is fine and multi-lined properly.
I know height re-layout happened after set to STATE_COLLAPSED because I slide it from collapse and the multi-line is already there.
A work around is provided here. I followed it and added this:
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetBehavior.onLayoutChild(coordinatorLayout,
bottomSheetView,
ViewCompat.LAYOUT_DIRECTION_LTR);
}
}
........
}
It did actually make the height re-adjust when Bottomsheet is expanded the first time. However it occurred abruptly right after the expanding animation is done.
Is there any way to adjust the height ahead of the expanding animation just like how Google Map does?
Update
I found that this issue is because I have set Bottomsheet to STATE_COLLAPSED before it was expanded. If that was not set then the problem is gone and height is adjusted properly the first time.
Now my question is: why set it to STATE_COLLAPSED before expanding will cause that issue?
if for some reason you still have to use old support library, here is the workaround for this.
mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull final View bottomSheet, int newState) {
bottomSheet.post(new Runnable() {
#Override
public void run() {
//workaround for the bottomsheet bug
bottomSheet.requestLayout();
bottomSheet.invalidate();
}
});
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
}
});
After switching to design library 24.0.0, the issue cannot be reproduced anymore.
Thanks for the efforts from the Android team to make our life easier and easier.
I have a web page for testing purposes ( https://storage.googleapis.com/htmltestingbucket/nested_scroll_helper.html ) that just prints a counter of the scroll event the html has caught in a fixed header
When the Android WebView is the only scroll-able element in the fragment everything is fine and the WebView sends the scroll events to the page
If I want to add native elements above and below the WebView then things get much more complex.
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="SOMETHING ABOVE THE WEBVIEW" />
<WebView
android:id="#+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="SOMETHING BELOW THE WEBVIEW" />
</LinearLayout>
</ScrollView>
I know it's not good to have a WebView inside a ScrollView but I have to provide a single scrolling experience with hybrid content and proper scrolling events in the html document.
I found plenty of questions on the matter but I was able to create a full end-to-end solution
Also, I know lint has an Offical check for that:
NestedScrolling
--------------- Summary: Nested scrolling widgets
Priority: 7 / 10 Severity: Warning Category: Correctness
A scrolling widget such as a ScrollView should not contain any nested
scrolling widgets since this has various usability issues
And yet, I can't implement the web view content in native so I need an alternative way to do that
To Keep Webview inside scrollview here you need to measure height of the webview and set it in layout params.
Here i have tried to give answer for the scrollable webview.
<ScrollView
android:layout_width="fill_parent" android:background="#FF744931"
android:layout_height="fill_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="WebViewLayout">
<TextView
android:id="#+id/txtVoiceSeachQuery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0000"
android:textSize="26sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="SOMETHING ABOVE THE WEBVIEW" />
<com.example.ScrollableWebView
android:id="#+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:isWebViewInsideScroll="true" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="SOMETHING BELOW THE WEBVIEW" />
</LinearLayout>
</ScrollView>
res/values/attrs.xml
To add attribute for the Custom Control
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollableWebView">
<attr name="isWebViewInsideScroll" format="boolean"></attr>
</declare-styleable>
</resources>
ScrollableWebView
public class ScrollableWebView extends WebView {
private boolean webViewInsideScroll = true;
public static final String RESOURCE_NAMESPACE = "http://schemas.android.com/apk/res-auto";
public ScrollableWebView(Context context) {
super(context);
}
public ScrollableWebView(Context context, AttributeSet attrs) {
super(context, attrs);
setWebViewInsideScroll(attrs.getAttributeBooleanValue
(RESOURCE_NAMESPACE, "isWebViewInsideScroll", true));
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isWebViewInsideScroll()) {
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
setLayoutParams(params);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public boolean isWebViewInsideScroll() {
return webViewInsideScroll;
}
public void setWebViewInsideScroll(boolean webViewInsideScroll) {
this.webViewInsideScroll = webViewInsideScroll;
}
}
To fetch attribute value you can also use Stylable but here i have done without using it.
ScrollableWebView webview = (ScrollableWebView) findViewById(R.id.webview);
webview.loadUrl("https://storage.googleapis.com/htmltestingbucket/nested_scroll_helper.html");
Below is link of output
Shows Textview(any view) on top inside scrollview with webview
Show TextView(any view) on bottom inside scrollview with webview
If you dont want to create attribute file & add Custom attributes in res/values/attrs.xml than you can ignore that file & check this pastebin here i gave without any custom attribute like isWebViewInsideScroll. you can remove it from xml layout too.
Let me know if anything.
if you place you webview inside scrollview you will not get html scrolling effect, because your webview content will not scroll ( it will be placed full lengtth inside scrollview.)
To face your need to place elements above and below you can listen to webview scroll and use DragViewHelper or nineoldandroids to move header and footer, so user will think, they are single element (you dont need scrollview).
webView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
ViewHelper.setTranslationY(headerTextView, -event.getY());
}
});
public class ObservableWebView extends WebView {
private OnScrollChangeListener onScrollChangeListener;
public ObservableWebView(Context context) {
super(context);
}
public ObservableWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObservableWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (onScrollChangeListener != null) {
onScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
}
}
public void setOnScrollChangeListener(OnScrollChangeListener onScrollChangeListener) {
this.onScrollChangeListener = onScrollChangeListener;
}
public OnScrollChangeListener getOnScrollChangeListener() {
return onScrollChangeListener;
}
public interface OnScrollChangeListener {
/**
* Called when the scroll position of a view changes.
*
* #param v The view whose scroll position has changed.
* #param scrollX Current horizontal scroll origin.
* #param scrollY Current vertical scroll origin.
* #param oldScrollX Previous horizontal scroll origin.
* #param oldScrollY Previous vertical scroll origin.
*/
void onScrollChange(WebView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY);
}
}
This example should help you to hide header, i used nineoldandroid for it
It seems the most elegant way I could find to handle this is as following:
- Listen to the SrollView scrolls:You can use an ObservableScrollView or call setOnScrollChangeListener() from API level 23
- Calculate the scroll Y offset in pixels
- Call the WebView.evaluateJavascript()
- Pass it all the details of the scroll event
So the general concepts is passing:
"$(document).trigger('scroll');" as the first param evaluateJavascript
I'm still testing the details and working out the kinks but it Seems like the better way to go, I will try to edit this answer with more info as I solve this
If anyway has a better solution for I would like to hear it
I have the same issue recently and I found your posts here :)
I have a WebView nested in a ScrollVIew. And the page which I loaded into WebView need to call a JS function when it scroll to the end, but in scroll view , web page's window.onscroll = functionXXX() {} never get called.
Finally, I have to set a OnScrollListener to the ScrollView, and call my JS function manually by the code below
#Override
public void onScroll(int scrollY) {
if (!scrollView.canScrollVertically(scrollY)) {
mWebView.loadUrl("javascript:functionXXX();");
}
}
Maybe our situations are different, but I hope it will give u some inspiration :)
in fact it is not so good put an scrollable view into another. Try to use this:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
And
<WebView
android:id="#+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
I'm using TextInputLayout from Android Design Library to show label on EditText.
The problem is when I start activity with that EditText hint (label) text overlaps the actual text (for a second) and only then returns to its own place (at the top of the EditText).
To illustrate this issue I recorded a short sample video: https://youtu.be/gy0CzcYggxU
Here is my activity.xml:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:id="#+id/firstNameTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<EditText
android:id="#+id/firstNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/first_name"
android:inputType="textCapWords"
android:textColor="#color/textPrimary"
android:textColorHint="#color/textSecondary"
android:textSize="16sp"
android:theme="#style/CustomEditText"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<EditText
android:id="#+id/lastNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/last_name"
android:inputType="textCapWords"
android:textColor="#color/textPrimary"
android:textColorHint="#color/textSecondary"
android:textSize="16sp"
android:theme="#style/CustomEditText"/>
</android.support.design.widget.TextInputLayout>
I came up with a cheap workaround for this and another bug.
Subclass the TextInputLayout
See code for addView()
If you have text set in the text view when it is inflated it will set the hint to collapsed and prevent an animation. This code performs a workaround that will temporarily set text until the state is set during setup. As a bonus there is code that makes sure the hint gets drawn just in case there is only one layout pass.
public class TextInputLayout extends android.support.design.widget.TextInputLayout {
public TextInputLayout(Context context) {
super(context);
}
public TextInputLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#SuppressLint("DrawAllocation")
#Override
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
if (ViewCompat.isLaidOut(this)) {
super.onLayout(changed, left, top, right, bottom);
} else {
// Workaround for this terrible logic where onLayout gets called before the view is flagged as laid out.
// The normal TextInputLayout is depending on isLaidOut when onLayout is called and failing the check which prevents initial drawing
// If there are multiple layout passes this doesn't get broken
post(new Runnable() {
#SuppressLint("WrongCall")
#Override
public void run() {
TextInputLayout.super.onLayout(changed, left, top, right, bottom);
}
});
}
}
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof EditText) {
EditText editText = (EditText) child;
if (StringUtils.isEmpty(editText.getText().toString())) {
editText.setText(" "); // Set filler text so the initial state of the floating title is to be collapsed
super.addView(child, index, params);
editText.setText(""); // Set back to blank to cause the hint to animate in just in case the user sets text
// This prevents the hint from being drawn over text that is set programmatically before the state is determined
return;
}
}
super.addView(child, index, params);
}
}
The workaround that worked for me was to update activity like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
textInputLayout.setHintAnimationEnabled(false);
textInput.setText("sample");
textInputLayout.setHintAnimationEnabled(true);
...
}
Finally found the adequate explanation of the issue:
Well it turns out that there was a performance optimization added to
the framework in Android 4.0 which allowed your view hierarchy only
one single draw pass before the Activity animation was started. Once
the Activity animation has ended, your view hierarchy is drawn every
~16ms as you expect.
Read more: https://medium.com/#chrisbanes
TLDR: it is platform limitation and this behavior will occur on older versions (Marshmallow and lower).
On Nougat animation will run as expected without the lag.
You can set the hints programmatically with a small delay. This is not a ideal solution, but at least it looks better than overlapping hints.
new Handler().postDelayed(
new Runnable() {
#Override
public void run () {
textInputLayout.setHint("My hint");
}
}, 100
);
I think this may be fixed for compile 'com.android.support:design:23.0.1'
Daniel Ochoa noted a workaround in the comments which worked for me - set the initial state for the EditText with some text content (an empty string should do it). That'll force the hint's initial state to be up.
<android.support.design.widget.TextInputLayout
android:id="#+id/firstNameTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<EditText
android:id="#+id/firstNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/first_name"
android:inputType="textCapWords"
android:textColor="#color/textPrimary"
android:textColorHint="#color/textSecondary"
android:textSize="16sp"
android:theme="#style/CustomEditText"
android:text=" "/>
</android.support.design.widget.TextInputLayout>
I'm trying to figure out how the expand/collapse animation of the toolbar is done. If you have a look at the Telegram app settings, you will see that there is a listview and the toolbar. When you scroll down, the toolbar collapse, and when you scroll up it expands. There is also the animation of the profile pic and the FAB. Does anyone have any clue on that? Do you think they built all the animations on top of it? Maybe I'm missing something from the new APIs or the support library.
I noticed the same behaviour on the Google calendar app, when you open the Spinner (I don't think it's a spinner, but it looks like): The toolbar expands and when you scroll up, it collapse.
Just to clearify: I don't need the QuickReturn method. I know that probably Telegram app is using something similar. The exact method that I need is the Google Calendar app effect. I've tried with
android:animateLayoutChanges="true"
and the expand method works pretty well. But obviously, If I scroll up the ListView, the toolbar doesn't collapse.
I've also thought about adding a GestureListener but I want to know if there are any APIs or simpler methods of achieving this.
If there are none, I think I will go with the GestureListener. Hopefully to have a smooth effect of the Animation.
Thanks!
Edit :
Since the release of the Android Design support library, there's an easier solution. Check joaquin's answer
--
Here's how I did it, there probably are many other solutions but this one worked for me.
First of all, you have to use a Toolbar with a transparent background. The expanding & collapsing Toolbar is actually a fake one that's under the transparent Toolbar. (you can see on the first screenshot below - the one with the margins - that this is also how they did it in Telegram).
We only keep the actual Toolbar for the NavigationIcon and the overflow MenuItem.
Everything that's in the red rectangle on the second screenshot (ie the fake Toolbar and the FloatingActionButton) is actually a header that you add to the settings ListView (or ScrollView).
So you have to create a layout for this header in a separate file that could look like this :
<!-- The headerView layout. Includes the fake Toolbar & the FloatingActionButton -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="#+id/header_container"
android:layout_width="match_parent"
android:layout_height="#dimen/header_height"
android:layout_marginBottom="3dp"
android:background="#android:color/holo_blue_dark">
<RelativeLayout
android:id="#+id/header_infos_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:padding="16dp">
<ImageView
android:id="#+id/header_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="8dp"
android:src="#android:drawable/ic_dialog_info" />
<TextView
android:id="#+id/header_title"
style="#style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/header_picture"
android:text="Toolbar Title"
android:textColor="#android:color/white" />
<TextView
android:id="#+id/header_subtitle"
style="#style/TextAppearance.AppCompat.Subhead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/header_title"
android:layout_toRightOf="#+id/header_picture"
android:text="Toolbar Subtitle"
android:textColor="#android:color/white" />
</RelativeLayout>
</RelativeLayout>
<FloatingActionButton
android:id="#+id/header_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="10dp"
android:src="#drawable/ic_open_in_browser"/>
</FrameLayout>
(Note that you can use negative margins/padding for the fab to be straddling on 2 Views)
Now comes the interesting part. In order to animate the expansion of our fake Toolbar, we implement the ListView onScrollListener.
// The height of your fully expanded header view (same than in the xml layout)
int headerHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
// The height of your fully collapsed header view. Actually the Toolbar height (56dp)
int minHeaderHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height);
// The left margin of the Toolbar title (according to specs, 72dp)
int toolbarTitleLeftMargin = getResources().getDimensionPixelSize(R.dimen.toolbar_left_margin);
// Added after edit
int minHeaderTranslation;
private ListView listView;
// Header views
private View headerView;
private RelativeLayout headerContainer;
private TextView headerTitle;
private TextView headerSubtitle;
private FloatingActionButton headerFab;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.listview_fragment, container, false);
listView = rootView.findViewById(R.id.listview);
// Init the headerHeight and minHeaderTranslation values
headerHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
minHeaderTranslation = -headerHeight +
getResources().getDimensionPixelOffset(R.dimen.action_bar_height);
// Inflate your header view
headerView = inflater.inflate(R.layout.header_view, listview, false);
// Retrieve the header views
headerContainer = (RelativeLayout) headerView.findViewById(R.id.header_container);
headerTitle = (TextView) headerView.findViewById(R.id.header_title);
headerSubtitle = (TextView) headerView.findViewById(R.id.header_subtitle);
headerFab = (TextView) headerView.findViewById(R.id.header_fab);;
// Add the headerView to your listView
listView.addHeaderView(headerView, null, false);
// Set the onScrollListener
listView.setOnScrollListener(this);
// ...
return rootView;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState)
{
// Do nothing
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
Integer scrollY = getScrollY(view);
// This will collapse the header when scrolling, until its height reaches
// the toolbar height
headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation));
// Scroll ratio (0 <= ratio <= 1).
// The ratio value is 0 when the header is completely expanded,
// 1 when it is completely collapsed
float offset = 1 - Math.max(
(float) (-minHeaderTranslation - scrollY) / -minHeaderTranslation, 0f);
// Now that we have this ratio, we only have to apply translations, scales,
// alpha, etc. to the header views
// For instance, this will move the toolbar title & subtitle on the X axis
// from its original position when the ListView will be completely scrolled
// down, to the Toolbar title position when it will be scrolled up.
headerTitle.setTranslationX(toolbarTitleLeftMargin * offset);
headerSubtitle.setTranslationX(toolbarTitleLeftMargin * offset);
// Or we can make the FAB disappear when the ListView is scrolled
headerFab.setAlpha(1 - offset);
}
// Method that allows us to get the scroll Y position of the ListView
public int getScrollY(AbsListView view)
{
View c = view.getChildAt(0);
if (c == null)
return 0;
int firstVisiblePosition = view.getFirstVisiblePosition();
int top = c.getTop();
int headerHeight = 0;
if (firstVisiblePosition >= 1)
headerHeight = this.headerHeight;
return -top + firstVisiblePosition * c.getHeight() + headerHeight;
}
Note that there are some parts of this code I didn't test, so feel free to highlight mistakes. But overall, I'm know that this solution works, even though I'm sure it can be improved.
EDIT 2:
There were some mistakes in the code above (that I didn't test until today...), so I changed a few lines to make it work :
I introduced another variable, minHeaderTranslation, which replaced minHeaderHeight;
I changed the Y translation value applied to the header View from :
headerView.setTranslationY(Math.max(-scrollY, minHeaderTranslation));
to :
headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation));
Previous expression wasn't working at all, I'm sorry about that...
The ratio calculation also changed, so that it now evolves from the bottom the toolbar (instead of the top of the screen) to the full expanded header.
Also check out CollapsingTitleLayout written by Chris Banes in Android team:
https://plus.google.com/+ChrisBanes/posts/J9Fwbc15BHN
Code: https://gist.github.com/chrisbanes/91ac8a20acfbdc410a68
Use design support library http://android-developers.blogspot.in/2015/05/android-design-support-library.html
include this in build.gradle
compile 'com.android.support:design:22.2.0'
compile 'com.android.support:appcompat-v7:22.2.+'
for recycler view include this also
compile 'com.android.support:recyclerview-v7:22.2.0'
<!-- AppBarLayout allows your Toolbar and other views (such as tabs provided by TabLayout)
to react to scroll events in a sibling view marked with a ScrollingViewBehavior.-->
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<!-- specify tag app:layout_scrollFlags -->
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"/>
<!-- specify tag app:layout_scrollFlags -->
<android.support.design.widget.TabLayout
android:id="#+id/tabLayout"
android:scrollbars="horizontal"
android:layout_below="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"/>
<!-- app:layout_collapseMode="pin" will help to pin this view at top when scroll -->
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="Title"
android:gravity="center"
app:layout_collapseMode="pin" />
</android.support.design.widget.AppBarLayout>
<!-- This will be your scrolling view.
app:layout_behavior="#string/appbar_scrolling_view_behavior" tag connects this features -->
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</android.support.design.widget.CoordinatorLayout>
Your activity should extend AppCompatActivity
public class YourActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
//set toolbar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
}
Your app theme should be like this
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
</style>
</resources>
This is my implementation:
collapsedHeaderHeight and expandedHeaderHeight are defined somewhere else, with the function getAnimationProgress I can get the Expand/Collapse progress, base on this value I do my animation and show/hide the real header.
listForumPosts.setOnScrollListener(new AbsListView.OnScrollListener() {
/**
* #return [0,1], 0 means header expanded, 1 means header collapsed
*/
private float getAnimationProgress(AbsListView view, int firstVisibleItem) {
if (firstVisibleItem > 0)
return 1;
// should not exceed 1
return Math.min(
-view.getChildAt(0).getTop() / (float) (expandedHeaderHeight - collapsedHeaderHeight), 1);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// at render beginning, the view could be empty!
if (view.getChildCount() > 0) {
float animationProgress = getAnimationProgress(view, firstVisibleItem);
imgForumHeaderAvatar.setAlpha(1-animationProgress);
if (animationProgress == 1) {
layoutForumHeader.setVisibility(View.VISIBLE);
} else {
layoutForumHeader.setVisibility(View.GONE);
}
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// do nothing
}
}
I'm disabling the normal top action bar by using:
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
& I want to use a bottom action bar to have the done/cancel actions like this calendar app:
but when I try to write something to the editTexts available in the scrollView, the bottom action bar hides the fields, & I want it to be visible like also the calendar app below:
So, how can I achieve similar behavior? (so the bottom action bar won't hide any field when opening the soft keyboard),
I'm using code like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="true"
android:orientation="vertical"
android:weightSum="1">
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:fillViewport="false"
android:id="#+id/formScrollView">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- all form fields goes here -->
</LinearLayout>
</ScrollView>
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/white"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:padding="#dimen/done_button_padding"
android:id="#+id/happeningDoneLayout">
<Button
android:id="#+id/doneButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="#string/done"
android:layout_alignParentBottom="true"/>
<Button
android:id="#+id/cancelButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="#string/cancel"
android:layout_alignParentBottom="true"/>
</FrameLayout>
</RelativeLayout>
Simplest thing to do is to prevent your layout from being resized when the virtual keyboard comes up:
<activity
android:name=".ShareFromDriveActivity_"
android:hardwareAccelerated="true"
android:label="#string/titleSharingCalendar"
android:launchMode="standard"
android:parentActivityName=".AppWidgetConfigure_"
android:screenOrientation="sensor"
android:theme="#style/Theme.Materialamberpurple"
android:windowSoftInputMode="stateHidden|adjustPan" >
<intent-filter>
<action android:name="de.kashban.android.picturecalendar.INTENT_ACTION_SHARE_FROM_DRIVE" />
</intent-filter>
</activity>
The important line is android:windowSoftInputMode="stateHidden|adjustPan". stateHidden ensures the keyboard is not opened up when starting the activity even if an EditText has focus.
adjustPan is what you are looking for: The Layout will no longer be resized (including your lower buttons), but the keyboad will overlay the layout. It is still possible to scroll them into the visible part, but when the keyboard comes up, they are not visible.
Source: Android Guides
Perhaps this setting alone will help your case.
If that's not enough and you require the Buttons to be really gone, try using this:
// Detect soft keyboard visibility changes
final SoftKeyboardStateHelper softKeyboardStateHelper =
new SoftKeyboardStateHelper(lyt_share_from_drive_main);
softKeyboardStateHelper.addSoftKeyboardStateListener(this);
SoftKeyboardStateHelper is a class from Artem Zinnatullin to detect state changes of the Softkeyboard:
/**
*
*/
package de.kashban.android.picturecalendar.util.local;
/**
* #author Artem Zinnatullin
* http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android/9108219#9108219
* Usage: final SoftKeyboardStateHelper softKeyboardStateHelper = new SoftKeyboardStateHelper(findViewById(R.id.activity_main_layout);
* softKeyboardStateHelper.addSoftKeyboardStateListener(...);
*/
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;
import java.util.LinkedList;
import java.util.List;
public class SoftKeyboardStateHelper implements ViewTreeObserver.OnGlobalLayoutListener {
public interface SoftKeyboardStateListener {
void onSoftKeyboardOpened(int keyboardHeightInPx);
void onSoftKeyboardClosed();
}
private final List<SoftKeyboardStateListener> listeners = new LinkedList<SoftKeyboardStateListener>();
private final View activityRootView;
private int lastSoftKeyboardHeightInPx;
private boolean isSoftKeyboardOpened;
public SoftKeyboardStateHelper(View activityRootView) {
this(activityRootView, false);
}
public SoftKeyboardStateHelper(View activityRootView, boolean isSoftKeyboardOpened) {
this.activityRootView = activityRootView;
this.isSoftKeyboardOpened = isSoftKeyboardOpened;
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
#Override
public void onGlobalLayout() {
final Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
activityRootView.getWindowVisibleDisplayFrame(r);
final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
if (!isSoftKeyboardOpened && heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
isSoftKeyboardOpened = true;
notifyOnSoftKeyboardOpened(heightDiff);
} else if (isSoftKeyboardOpened && heightDiff < 100) {
isSoftKeyboardOpened = false;
notifyOnSoftKeyboardClosed();
}
}
public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) {
this.isSoftKeyboardOpened = isSoftKeyboardOpened;
}
public boolean isSoftKeyboardOpened() {
return isSoftKeyboardOpened;
}
/**
* Default value is zero (0)
* #return last saved keyboard height in px
*/
public int getLastSoftKeyboardHeightInPx() {
return lastSoftKeyboardHeightInPx;
}
public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
listeners.add(listener);
}
public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
listeners.remove(listener);
}
private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) {
this.lastSoftKeyboardHeightInPx = keyboardHeightInPx;
for (SoftKeyboardStateListener listener : listeners) {
if (listener != null) {
listener.onSoftKeyboardOpened(keyboardHeightInPx);
}
}
}
private void notifyOnSoftKeyboardClosed() {
for (SoftKeyboardStateListener listener : listeners) {
if (listener != null) {
listener.onSoftKeyboardClosed();
}
}
}
}
In your activity implement the Interface SoftKeyboardStateListener and override these methods:
#Override
public void onSoftKeyboardOpened(int keyboardHeightInPx) {
if (D.DEBUG_APP) Log.d(TAG, "onSoftKeyboardOpened() called with keyboard height " + keyboardHeightInPx);
rdgVisibility.setVisibility(View.GONE);
if (tvPermissionLabel != null)
tvPermissionLabel.setVisibility(View.GONE);
lyt_ShareDriveOkCancel.setVisibility(View.GONE);
cbShareWithDev.setVisibility(View.GONE);
}
#Override
public void onSoftKeyboardClosed() {
if (D.DEBUG_APP) Log.d(TAG, "onSoftKeyboardClosed() called.");
rdgVisibility.setVisibility(View.VISIBLE);
if (tvPermissionLabel != null)
tvPermissionLabel.setVisibility(View.VISIBLE);
lyt_ShareDriveOkCancel.setVisibility(View.VISIBLE);
cbShareWithDev.setVisibility(View.VISIBLE);
}
In these two methods change the Visibility of your lower Buttons accordingly. Done.
Here's how it looks in my app:
Keyboard is closed, full layout visible
Keyboard is open, all controls but EditText gone. Reason is that the EditText could span several lines and on small screens it was too cluttered with the full layout in place.
To make sure the bottom action bar will not hide any other controls, the ScrollView and the bar can be stacked in a vertical linear layout. This allows the ScrollView to shrink/expand with the focused control visible when the keyboard appears/disappears, while keeping the bottom action bar always visible at the bottom of the screen below the ScrollView.
adjustPan should not be used with this solution.
The weights are distributed such that the ScrollView is the part that would change its height dynamically.
Here's a minimized sample of the code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/formScrollView"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:layout_weight="1"
... >
...
</ScrollView>
<FrameLayout
android:id="#+id/bottomBar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
... >
...
</FrameLayout>
</LinearLayout>
Android Keyboard hides EditText
android:windowSoftInputMode="adjustPan|adjustResize"
That should give you the effect your looking for. Put that in the manifest of the relevant activity.