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"/>
Related
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 have a CustomViewPager inside an ObservableScrollView which looks like this:
It seems to measure the fragment but does not measure the height of the fragment which is off the screen. So I can't actually scroll up.
This is the code for the CustomViewPager:
public class CustomViewPager extends ViewPager {
private View view;
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
View tab = getChildAt(getCurrentItem());
int width = getMeasuredWidth();
int tabHeight = tab.getMeasuredHeight();
if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
int fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(tabHeight + fragmentHeight + (int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()), MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public int measureFragment(View view) {
if (view == null)
return 0;
view.measure(0, 0);
return view.getMeasuredHeight();
}
}
NOTE: If I add say + 1000 to heightMeasureSpec in super.onMeasure(widthMeasureSpec, heightMeasureSpec); then I can scroll the size of the fragment as well as any extra space. But obviously this is not a preferred solution.
Here is my XML file:
<FrameLayout 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:orientation="vertical">
<LinearLayout
android:id="#+id/infoBox"
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="240dp">
<!-- Code for other views -->
</LinearLayout>
<com.github.ksoichiro.android.observablescrollview.ObservableScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="266dp"
android:orientation="vertical">
<com.ui.customviewpager.CustomViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</com.github.ksoichiro.android.observablescrollview.ObservableScrollView>
What seems to be the issue here? It seems like I am not the only one with this issue.
I implemented some of this design from this link here
ScrollView child should not have margin for the scrollView to display properly.
It seems that the margin is not taken into account when the scrollView measure its child.
Try removing margin of LinearLayout (you can use padding to reproduce the effect).
Why dont you use ObservableScrollView inside the fragment and put the viewpager inside a TouchInterceptionFrameLayout (from the same lib), then set setTouchInterceptionViewGroup of the ObservableScrollView to be the one containing your viewpager and do the logic on the TouchInterceptionListener in your activity to either move it up and down with with animation in order to mimic a smooth scrolling effect.
I hope that is that your are looking for to achieve and hopefully it will help you.
Ps: See the guy example app and source (Link), it is a bit complex but im sure you will find something.
Hello i am not much aware of ObservableScrollView but according to your problem you are not able to find out height of fragment. After searching a lot i am able to find out tutorial to get the height of fragments.
here is the link
http://adanware.blogspot.in/2012/06/android-getting-measuring-fragment.html
Hope, this will help you.
i am using SwipeRefreshLayout in my below layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#color/homePageBackground"
android:orientation="vertical" >
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipeRefreshLayout_listView"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<fragment
android:id="#+id/announcementHomefragment"
android:name="in.test.app.AnnouncementFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/homePageBackground" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:background="#color/homePageBackground" >
<TextView
android:id="#+id/newsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="15dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:text="#string/new_list"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#color/white"
android:textStyle="bold" />
<fragment
android:id="#+id/newshomefragment"
android:name="in.test.app.NewsFragment"
android:layout_width="wrap_content"
android:layout_height="190dp"
android:layout_below="#id/newsTitle"
android:layout_marginTop="-15dp" />
<TextView
android:id="#+id/productTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/newshomefragment"
android:layout_gravity="center"
android:layout_marginLeft="15dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:text="#string/product_in_home"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#color/white"
android:textStyle="bold" />
<fragment
android:id="#+id/proCategoryhomefragment"
android:name="in.test.app.CategoryFragment"
android:layout_width="wrap_content"
android:layout_height="170dp"
android:layout_below="#id/productTitle"
android:layout_marginTop="-15dp" />
<TextView
android:id="#+id/trainingTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/proCategoryhomefragment"
android:layout_gravity="center"
android:layout_marginLeft="15dp"
android:layout_marginTop="2dp"
android:gravity="center"
android:text="#string/trainings_in_home"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#color/white"
android:textStyle="bold" />
<fragment
android:id="#+id/trainingfragment"
android:name="in.test.app.TrainingFragment"
android:layout_width="match_parent"
android:layout_height="180dp"
android:layout_below="#id/trainingTitle"
android:layout_marginBottom="10dp"
android:layout_marginTop="-15dp" />
</RelativeLayout>
</ScrollView>
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
When I pull down my SwipeRefreshLayout it is working, but as you can see in the above code I have a scroll view inside that. So when I am pulling down my scroll view, it goes down and half the images are not showing because it came down. When I am trying to pull up again my scroll view is not going up. Instead, SwipeRefreshLayout is getting call. What should i do?
Please help me out.
I would say it's better to have an extended SwipeRefreshLayout with listener to be able to add various conditions from the classes that display this layout.
Something like the following:
GeneralSwipeRefreshLayout.java
public class GeneralSwipeRefreshLayout extends SwipeRefreshLayout {
private OnChildScrollUpListener mScrollListenerNeeded;
public interface OnChildScrollUpListener {
boolean canChildScrollUp();
}
public GeneralSwipeRefreshLayout(Context context) {
super(context);
}
public GeneralSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Listener that controls if scrolling up is allowed to child views or not
*/
public void setOnChildScrollUpListener(OnChildScrollUpListener listener) {
mScrollListenerNeeded = listener;
}
#Override
public boolean canChildScrollUp() {
if (mScrollListenerNeeded == null) {
Log.e(GeneralSwipeRefreshLayout.class.getSimpleName(), "listener is not defined!");
}
return mScrollListenerNeeded != null && mScrollListenerNeeded.canChildScrollUp();
}
}
And then inside your class that displays SwipeRefreshLayout containing ListView or GridView layout, you can do something like this:
mSwipeLayout.setOnChildScrollUpListener(new OnChildScrollUpListener() {
#Override
public boolean canChildScrollUp() {
return mListView.getFirstVisiblePosition() > 0 ||
mListView.getChildAt(0) == null ||
mListView.getChildAt(0).getTop() < 0;
}
});
Just create a class which extends SwipeRefreshLayout and override the method canChildScrollUp(). Return true when you want scroll down for your control.
For example for scrollview you may try this,
#override.
boolean canChildScrollUp()
{
//your condition to check scrollview reached at top while scrolling
if(scrollview.getScrollY() == 0.0)
return true;
else
return false;
}
As others have already stated, if you don't have your scrollable view (ie listview) as the direct child of the SwipeRefreshLayout, the stock canChildScrollUp will not work.
Has to do with the simple logic SwipeRefreshLayout uses in checking the ability of the child view to scroll.
I was using a ListView inside an ActionbarActivity, and wanted to include an empty view whenever my listview was empty. This caused problems, since the SwipeRefreshLayout class can only have a single child. Note it also checks this child's ability to scrollUp to determine if a pull down causes the child to scrollUp, or if it causes the childs content to refresh.
So if you want to use the same logic as SwipeRefreshLayout, just extend the class, and create a method to allow you to pass in the handle to your scrollable view. Note the stock implementation uses canScrollVertically() which does exactly what we want, but only appears in SDK >= 14.
Also don't forget to include the constructor that contains the param "AttributeSet", when you extend the class, otherwise you will have problems using the class in your layout files.
So, in the onCreate method of your Activity (in my case it was an ActionBarActivity) that includes the list view, just call setMyScrollableView passing in your ListView or whatever view you use that scrolls.
/*Constructor*/
public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
View mMyScrollableView = null; //The content that get's pulled down.
/*Method used to pass in my scrollable view*/
public void setMyScrollableView(View view){
mMyScrollableView = view;
}
/**
* #return Whether it is possible for the child view of this layout to
* scroll up. This was taken for the most part directly from SwipeRefreshLayout
*/
public boolean canChildScrollUp() {
if(mMyScrollableView == null)
return false;
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mMyScrollableView instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mMyScrollableView;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return mMyScrollableView.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mMyScrollableView, -1);
}
}
The solution from #User22791 works perfectly and, based on that, I created a library available on github that you can use (and modify) for make the usage of swipeRefreshLayout easier for developers. It's here: https://github.com/dagova/referencedSwipeRefreshLayout
Basically you just have to reference in your layout the view to be checked in the method canChildScrollUp. I hope it will be useful.
I also found the other answers didn't quite work.
Took me a while of head scratching to figure out that they are using the method getScrollY() which as this answer explains, is a View method describing how far it's been scroll within a container, not a method to describe how much your Scroll container has been scrolled.
If you use the same technique as in the other answers (overriding the canChildScrollUp() method) you can easily check if the Scrollable is at it's highest point:
#Override
public boolean canChildScrollUp() {
return !isListAtTop();
}
private boolean isListAtTop() {
if(mGridView.getChildCount() == 0) return true;
return mGridView.getChildAt(0).getTop() == 0;
}
(As you can see, I'm using a GridView, but you can use a ListView too)
Easier solution is to use onScrollListener and check if user can see firstElement.
someView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
if (isViewAtTop()) {
swipeLayout.setEnabled(true);
} else {
swipeLayout.setEnabled(false);
}
}
}
#Override
public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == 0) {
swipeLayout.setEnabled(true);
} else {
swipeLayout.setEnabled(false);
}
}
});
Where method isViewAtTop() is some other method, that checks this View is scrolled to the top
Ok I have got it working. If the SwipeRefreshLayout is the root of the layout and the ScrollView resides deep into the hierarchy (I had put the ScrollView inside a RelativeLayout) and not the direct child of the SwipeRefreshLayout, it won’t detect a swipe up on the ScrollView properly.
You should create a custom class that extends SwipeRefreshLayout and override canChildScrollUp() method in SwipRefreshLayout
Here is a example :
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {
private ScrollView scrollview;
public CustomSwipeRefreshLayout(Context context) {
super(context);
}
public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setView(ScrollView view) {
this.scrollview = view;
}
#Override
public boolean canChildScrollUp() {
return scrollview.getScrollY() != 0;
}
}
What I have:
Right now I have a Scroll view as a parent. Inside this scroll view, I am using a WebView that loads a URL and then shows text in it.
Here is my xml:
<ScrollView
android:id="#+id/parentScroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/Heading" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp" >
<WebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="#drawable/webview" />
</RelativeLayout>
</ScrollView>
What I want:
I want to scroll webView inside it. When I touch the webView, unfortunately the parent scroll view gets called.
I have to keep Parent scroll view also but besides this I want to scroll WebView content inside it when I touch on webView.
How can I do this?
Create a Custom Touch Intercepting Webview
CustomWebview.java
package com.mypackage.common.custom.android.widgets
public class CustomWebview extends WebView {
public CustomWebview(Context context) {
super(context);
}
public CustomWebview(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomWebview(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent event){
requestDisallowInterceptTouchEvent(true);
return super.onTouchEvent(event);
}
}
in layout.xml
<com.package.custom.widgets.CustomWebview
android:id="#+id/view_extra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
According to Android design documents you should never put a scrolling container inside a scrolling container if they scroll the same direction. It's not meant to handle such a thing.
I had the same problem. You should set your webView Height equal as its content. for this do this:
add this lines to your onCreate method in Activity:
webView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
webView.loadUrl("javascript:MyApp.resize(document.body.getBoundingClientRect().height)");
super.onPageFinished(view, url);
}
});
webView.addJavascriptInterface(this, "MyApp");
and add this method to your activity:
#JavascriptInterface
public void resize(final float height) {
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
webView.setLayoutParams(new LinearLayout.LayoutParams(getResources().getDisplayMetrics().widthPixels, (int) (height * getResources().getDisplayMetrics().density)));
}
});
}
Nested scrollable widgets are generally discouraged. It is a confusing user experience because it's easy to scroll the wrong thing. Say I intend to scroll the outer scroll region but I touched the inner region first and flinged really hard. Then the inner one will scroll and I'll be like huh? Why didn't it scroll?
Even if one scrolls horizontal and the other is vertical the gesture recognizers might confuse one for another so you get the same effect. It's a valid use case but it's still iffy and I'd avoid it. (IE: Humans don't perfectly swipe vertically and horizontally properly, it's usually with an angle.)
I would push to change the design to break out the scrollable areas. Ideally 1 scrollable item per page. Even propose one yourself and provide to the designer both experiences and see which one they choose.
To express how this will suck. Look at this example. This isn't a solution but just to show the experience.
<ScrollView
android:id="#+id/parentScroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/Heading" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent" android:orientation="vertical"
android:padding="5dp" >
<TextView
android:id="#+id/topText"
android:layout_width="match_parent"
android:layout_height="1000dp"
android:text="#string/lotsoftext" />
<WebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="#drawable/webview" />
<TextView
android:id="#+id/topText"
android:layout_width="match_parent"
android:layout_height="1000dp"
android:text="#string/lotsoftext" />
</RelativeLayout>
got myself into a pickle trying to squeeze two ListViews in the same activity. It works, using two separate ListFragments contained in a standard (vertical) LinearLayout.
The problem is, the two lists together are longer than the screen and the second list is therefore partially hidden. Visually, the user expects to drag the whole screen up and unveil the second list. But the two lists have their own internal scrolling and they do not allow for the whole screen to scroll as one piece.
Luckily the lists actually contain very few items (5 each on average). So, theoretically, I could populate a couple of LinearLayouts containers instead. The problem is, the data being displayed by the lists comes from a Cursor and is dynamic. While I am aware of the newView() and bindView() methods of the CursorAdapter, I don't quite understand how I can connect the adapter to the LinearLayout containers instead of ListViews. I.e. how does the CursorAdapter know that it must create 5 row items out of the 5 items it finds in its cursor? Where do I create the loop that iterates over the cursor item and creates the items in the LinearLayout container? And how do I refresh the content of the LinearLayout when the data in the Cursor changes? All the examples I'm finding neatly wrap these issues into the ListView provided by the ListActivity, but I can't use ListViews!
I'm confused!
Manu
EDIT : Here is the xml layout of the (Fragment)Activity when following breceivemail suggestion. Commented out is the original LinearLayout container, prior to breceivemail's suggestion. It should also be noted the the whole activity is in turn contained by a TabHost, but I don't know if that make any difference for the problem at hand.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!--
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
-->
<TextView android:id="#+id/title"
android:textSize="36sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/SelectPlayer"/>
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/Playing"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#000000"
android:background="#999999"/>
<fragment android:name="com.myDomain.myApp.PlayerListFragment"
android:id="#+id/playing"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/Reserve"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#000000"
android:background="#999999"/>
<fragment android:name="com.myDomain.myApp.PlayerListFragment"
android:id="#+id/reserve"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
Put your listViews in a vertical scroll. You can have scrollable listView inside of a vertical scroll by the following trick. use the following code and enjoy!
private int listViewTouchAction;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
setListViewScrollable(myListView1);
setListViewScrollable(myListView2);
}
private void setListViewScrollable(final ListView list) {
list.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
listViewTouchAction = event.getAction();
if (listViewTouchAction == MotionEvent.ACTION_MOVE)
{
list.scrollBy(0, 1);
}
return false;
}
});
list.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view,
int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE)
{
list.scrollBy(0, -1);
}
}
});
}
listViewTouchAction is a global integer value.