two directional scroll view - android
I would like to have a linearlayout with a header section on top and a webview below. The header will be short and the webview may be longer and wider than the screen.
What is the best way to get horizontal and vertical scrolling? Is a ScrollView nested inside a HorizontalScrollView a good idea?
Is a ScrollView nested inside a HorizontalScrollView a good idea?
Yes, and no.
Yes, my understanding is that ScrollView and HorizontalScrollView can be nested.
No, AFAIK, neither ScrollView nor HorizontalScrollView work with WebView.
I suggest that you have your WebView fit on the screen.
there is another way. moddified HorizontalScrollView as a wrapper for ScrollView. normal HorizontalScrollView when catch touch events don't forward them to ScrollView and you only can scroll one way at time. here is solution:
package your.package;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.view.MotionEvent;
import android.content.Context;
import android.util.AttributeSet;
public class WScrollView extends HorizontalScrollView
{
public ScrollView sv;
public WScrollView(Context context)
{
super(context);
}
public WScrollView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public WScrollView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override public boolean onTouchEvent(MotionEvent event)
{
boolean ret = super.onTouchEvent(event);
ret = ret | sv.onTouchEvent(event);
return ret;
}
#Override public boolean onInterceptTouchEvent(MotionEvent event)
{
boolean ret = super.onInterceptTouchEvent(event);
ret = ret | sv.onInterceptTouchEvent(event);
return ret;
}
}
using:
#Override public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
/*BIDIRECTIONAL SCROLLVIEW*/
ScrollView sv = new ScrollView(this);
WScrollView hsv = new WScrollView(this);
hsv.sv = sv;
/*END OF BIDIRECTIONAL SCROLLVIEW*/
RelativeLayout rl = new RelativeLayout(this);
rl.setBackgroundColor(0xFF0000FF);
sv.addView(rl, new LayoutParams(500, 500));
hsv.addView(sv, new LayoutParams(WRAP_CONTENT, MATCH_PARENT /*or FILL_PARENT if API < 8*/));
setContentView(hsv);
}
Two years further down the line I think the open source community might have to your rescue:
2D Scroll View.
Edit: The Link doesn't work anymore but here is a link to an old version of the blogpost;
I searched really long to make this work and finally found this thread here. wasikuss' answer came quite close to the solution, but still it did not work properly. Here is how it works very well (at least for me (Android 2.3.7)). I hope, it works on any other Android version as well.
Create a class called VScrollView:
package your.package.name;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
public class VScrollView extends ScrollView {
public HorizontalScrollView sv;
public VScrollView(Context context) {
super(context);
}
public VScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
sv.dispatchTouchEvent(event);
return true;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
super.onInterceptTouchEvent(event);
sv.onInterceptTouchEvent(event);
return true;
}
}
Your layout should look like:
<your.package.name.VScrollView
android:id="#+id/scrollVertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<HorizontalScrollView
android:id="#+id/scrollHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TableLayout
android:id="#+id/table"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:stretchColumns="*" >
</TableLayout>
</HorizontalScrollView>
</your.package.name.VScrollView>
In your activity, you should do something like:
hScroll = (HorizontalScrollView) findViewById(R.id.scrollHorizontal);
vScroll = (VScrollView) findViewById(R.id.scrollVertical);
vScroll.sv = hScroll;
... and that's how it works. At least for me.
There is an easy workaround:
In you activity get a reference to the outer scrollView (I'm going to assume a vertical scrollview) and a reference to the first child of that scroll view.
Scrollview scrollY = (ScrollView)findViewById(R.id.scrollY);
LinearLayout scrollYChild = (LinearLayout)findViewById(R.id.scrollYChild);
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
scrollYChild.dispatchTouchEvent(event);
scrollY.onTouchEvent(event);
return true;
}
One could argue that this solution is a bit hacky. But it has worked great for me in several applications!
Late to answer, but hopefully might be helpful to someone.
You can check out droid-uiscrollview. This is heavily based on #MrCeeJ's answer, but I seemed to have a lot of trouble getting the actual content to be rendered. Hence I pulled in the latest source from HorizontalScrollView & ScrollView to create droid-uiscrollview. There are a few todo's left which I haven't gotten around to finish, but it does suffice to get content to scroll both horizontally & vertically at the same time
I've try both wasikuss and user1684030 solutions and I had to adapt them because of one warning log: HorizontalScrollView: Invalid pointerId=-1 in onTouchEvent, and because I wasn't fan of this need of creating 2 scroll views.
So here is my class:
public class ScrollView2D extends ScrollView {
private HorizontalScrollView innerScrollView;
public ScrollView2D(Context context) {
super(context);
addInnerScrollView(context);
}
public ScrollView2D(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() == 1) {
View subView = getChildAt(0);
removeViewAt(0);
addInnerScrollView(getContext());
this.innerScrollView.addView(subView);
} else {
addInnerScrollView(getContext());
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean handled = super.onTouchEvent(event);
handled |= this.innerScrollView.dispatchTouchEvent(event);
return handled;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
super.onInterceptTouchEvent(event);
return true;
}
public void setContent(View content) {
if (content != null) {
this.innerScrollView.addView(content);
}
}
private void addInnerScrollView(Context context) {
this.innerScrollView = new HorizontalScrollView(context);
this.innerScrollView.setHorizontalScrollBarEnabled(false);
addView(this.innerScrollView);
}
}
And when using it in XML, you have nothing to do if the content of this scroll view is set in here. Otherwise, you just need to call the method setContent(View content) in order to let this ScrollView2D knows what is its content.
For instance:
// Get or create a ScrollView2D.
ScrollView2D scrollView2D = new ScrollView2D(getContext());
scrollView2D.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(scrollView2D);
// Set the content of scrollView2D.
RelativeLayout testView = new RelativeLayout(getContext());
testView.setBackgroundColor(0xff0000ff);
testView.setLayoutParams(new ViewGroup.LayoutParams(2000, 2000));
scrollView2D.setContent(testView);
For a while I've been trying solutions from here, but the one that worked best still had one problem: It ate all events, none were making it through to elements within the scroller.
So I've got ... yet another answer, in Github and well-commented at least hopefully: https://github.com/Wilm0r/giggity/blob/master/app/src/main/java/net/gaast/giggity/NestedScroller.java
Like all solutions, it's a nested HorizontalScrollview (outer) + ScrollView (inner), with the outer receiving touch events from Android, and the inner receiving them only internally from the outer view.
Yet I'm relying on the ScrollViews to decide whether a touch event is interesting and until they accept it, do nothing so touches (i.e. taps to open links/etc) can still make it to child elements.
(Also the view supports pinch to zoom which I needed.)
In the outer scroller:
#Override
public boolean onInterceptTouchEvent(MotionEvent event)
{
if (super.onInterceptTouchEvent(event) || vscroll.onInterceptTouchEventInt(event)) {
onTouchEvent(event);
return true;
}
return false;
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
super.onTouchEvent(event);
/* Beware: One ugliness of passing on events like this is that normally a ScrollView will
do transformation of the event coordinates which we're not doing here, mostly because
things work well enough without doing that.
For events that we pass through to the child view, transformation *will* happen (because
we're completely ignoring those and let the (H)ScrollView do the transformation for us).
*/
vscroll.onTouchEventInt(event);
return true;
}
vscroll here is the "InnerScroller", subclassed from ScrollView, with a few changes to event handling: I've done some terrible things to ensure incoming touch events directly from Android are discarded, and instead it will only take them from the outer class - and only then pass those on to the superclass:
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
/* All touch events should come in via the outer horizontal scroller (using the Int
functions below). If Android tries to send them here directly, reject. */
return false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
/* It will still try to send them anyway if it can't find any interested child elements.
Reject it harder (but pretend that we took it). */
return true;
}
public boolean onInterceptTouchEventInt(MotionEvent event) {
return super.onInterceptTouchEvent(event);
}
public boolean onTouchEventInt(MotionEvent event) {
super.onTouchEvent(event);
}
I know you have accepted your answer but may be this could give you some idea.
<?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:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<HorizontalScrollView
android:layout_alignParentBottom="true"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ImageView
android:src="#drawable/device_wall"
android:scaleType="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</HorizontalScrollView>
</RelativeLayout>
</LinearLayout>
</ScrollView>
Related
How to zoom/pan image while inside a scrollview
I am using a ViewPager with a TouchImageView inside it and it works great, (I have used this solution in many of my Android apps). However I have an app for which there are many other controls on the same screen so they are all inside a scrollview control. In this scenario I see the scrollview does not play nice and I am not able to pan within the zoomed image. When I use my finger to pan upward or downward the entire page scrolls instead of the image panning. So here is what I am trying to do.... Inside the TouchImageView I detect Zoom Begin and Zoom End and have created an interface to make a callback to my Activity onZoomBegin() and onZoomEnd() methods. In the onZoomBegin() method I want to disable the scrollview from responding to any touch events and in onZoomEnd() I can re-enable it. So far here are the things I have tried doing in the onZoomBegin() method for which none are working.... scrollView.setEnabled(false); scrollView.requestDisallowInterceptTouchEvent(true); also I have tried the answer to a similar question which was to takeover the onTouchListener like such: scrollView.setOnTouchListener(new View.OnTouchListener() { #Override public boolean onTouch(View v, MotionEvent event) { return true; } }); This does stop the scrollview from scrolling but the scrollview is still intercepting the touch events cause the image still will not pan up or down. I've tried checking nestedScrollingEnabled in the layout designer, no joy.... I just want to know is there a way to totally disable a scrollview and then re-enable it from responding to touch events?
I found this answer on another question somewhere but by the time I realized it was the solution to my problem (answer to my question) then I lost reference to it. I will keep looking so I can edit this post to give credit where credit is due. public class CustomScrollView extends ScrollView { // true if we can scroll the ScrollView // false if we cannot scroll private boolean scrollable = true; public CustomScrollView(Context context) { super(context); } public CustomScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public void setScrollingEnabled(boolean scrollable) { this.scrollable = scrollable; } public boolean isScrollable() { return scrollable; } #Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // if we can scroll pass the event to the superclass if (scrollable) return super.onTouchEvent(ev); // only continue to handle the touch event if scrolling enabled return false; // scrollable is always false at this point default: return super.onTouchEvent(ev); } } #Override public boolean onInterceptTouchEvent(MotionEvent ev) { // Don't do anything with intercepted touch events if // we are not scrollable if (!scrollable) return false; else return super.onInterceptTouchEvent(ev); } } This part I just figured out for myself.... In the TouchImageView I added a callback interface which is called when a zoom begins and ends so in my Activity I only had to do this: private class OnZoomListener implements TouchImageView.OnZoomListener { #Override public void onZoomBegin() { isZoomed = true; scrollView.scrollTo(0, 0); scrollView.setScrollingEnabled(false); // <-- disables scrollview hideImageControls(); sizeViewPager(); } #Override public void onZoomEnd() { scrollView.setScrollingEnabled(true); // <-- enables scrollview showImageControls(); isZoomed = false; } }
Cannot select right button on ListView with FastScroll enabled
I have a ListView with image on left, title & subtitle in the centre and an ImageButton on the right (this button doesn't have any margin on the right). <ListView android:id="#+id/contacts" android:layout_width="match_parent" android:layout_height="match_parent" android:cacheColorHint="#android:color/transparent" android:fastScrollEnabled="true" android:scrollbarStyle="outsideInset"/> I have enabled fast scrolling for this ListView. When I try to click the right ImageButton the scrollbar comes in focus and ListView starts scrolling. I am not able to select the button on the right. Please help me out.
You need to override ListView class and its onInterceptTouchEvent method. public class CustomListView extends ListView { public CustomListView(Context context, AttributeSet attrs) { super(context, attrs); } #Override public boolean onInterceptTouchEvent(MotionEvent ev) { setFastScrollEnabled(false); boolean possibleResult = super.onInterceptTouchEvent(ev); setFastScrollEnabled(true); boolean actualResult = super.onInterceptTouchEvent(ev); if (ev.getAction() == MotionEvent.ACTION_DOWN) { return possibleResult && actualResult; } return super.onInterceptTouchEvent(ev); } } And it will look like: However, the issue which you observe is an expected behaviour. The proper fix would be to add padding to the end of your row. Take a look on the Google's PhoneBook app, for example: As you can see here, the cell-size is smaller, than 100% of screen's width. I hope, it helps
How do I disable the Tab click event for a PagerTabStrip
I would like to disable the tab click / touch event on my PagerTabStrip. Basically I only want users to be able to swipe. I want the appearance of the tabs, but no touching/clicking events. I'm not sure if there is something configurable in the xml below. Or if there is something in code that I can do. I've tried overriding the strip's ontouch and onclick listeners. but no luck. <android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="#+id/myId"> <android.support.v4.view.PagerTabStrip android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:id="#+id/tabStripID" android:background="#color/dark_blue" android:textColor="#color/text"/> </android.support.v4.view.ViewPager>
Overriding the onClick listener on PagerTabStrip does nothing because the onClick listeners are actually on two TextViews (the text for the previous and next tabs) contained within the PagerTabStrip class, and there is currently no API on PagerTabStrip to directly access/override those listeners. The following is solution that gets around this problem (and also doesn't get into the weeds of the internal PagerTabStrip implementation). I verified that the following works: Create your own PagerTabStrip and consume the touch event in onInterceptTouchEvent() by returning true. This will prevent either of the PagerTabStrip's internal onClick listeners from receiving touch event and doing the tab switch. public class MyPagerTabStrip extends PagerTabStrip { private boolean isTabSwitchEnabled; public MyPagerTabStrip(Context context, AttributeSet attrs) { super(context, attrs); this.isTabSwitchEnabled = true; } #Override public boolean onInterceptTouchEvent(MotionEvent event) { if (this.isTabSwitchEnabled) { return super.onInterceptTouchEvent(event); } else { return true; } } public void setTabSwitchEnabled(boolean isSwipeEnabled) { this.isTabSwitchEnabled = isSwipeEnabled; } } I assume that you'll also want to disable the ViewPager swiping that would also result in a tab switch. The following code does that (here, you have to return false in onTouch() and onInterceptTouch() instead of true to allow normal touch events to reach your current tab fragment): public class MyViewPager extends ViewPager { private boolean isSwipeEnabled; public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); this.isSwipeEnabled = true; } #Override public boolean onTouchEvent(MotionEvent event) { if (this.isSwipeEnabled) { return super.onTouchEvent(event); } return false; } #Override public boolean onInterceptTouchEvent(MotionEvent event) { if (this.isSwipeEnabled) { return super.onInterceptTouchEvent(event); } return false; } public void setPagingEnabled(boolean isSwipeEnabled) { this.isSwipeEnabled = isSwipeEnabled; } } Remember to change the XML file to reference these new classes: <com.mypackage.MyViewPager ... <com.mypackage.MyPagerTabStrip ...
Answer of Steve B is almost working, only the onTouchEvent() override is missing from the TabStrip (the same as for the viewpager). Without it it still receives the clicks and change page (at least on Android L).
Two dimensional scrolling... a suggestion that needs feedback
While spending a copious amount of time googling for a relatively simple solution to my problem I found this as a solution for two-dimensional scrolling. I have a a horizontalscrollview nested in a scrollview. I fiddled with this in a few ways and was unsuccessful in making anything functional. Does anyone have any ideas on how to make a concept like this work? Scrollview scrollY = (ScrollView)findViewById(R.id.scrollY); LinearLayout scrollYChild = (LinearLayout)findViewById(R.id.scrollYChild); #Override public boolean dispatchTouchEvent(MotionEvent event) { scrollYChild.dispatchTouchEvent(event); scrollY.onTouchEvent(event); return true; } I have also found this: http://blog.gorges.us/2010/06/android-two-dimensional-scrollview/ but I'm don't understand at all how to implement such a long piece of code properly. It doesn't make much sense to me that two-dimensional scrolling is inherent in a webview but nonexistent elsewhere... Any and all help is appreciated. Edit: How exactly does this work when zoomed in on an image in the gallery. Surely there has to be a way to implement that same functionality here.
Im not sure about the blog you have posted, this was my solution: /** * This class disables Y-motion on touch event. * It should only be used as parent class of HorizontalScrollView */ public class ParentScrollView extends ScrollView { private GestureDetector mGestureDetector; View.OnTouchListener mGestureListener; #SuppressWarnings("deprecation") public ParentScrollView(Context context, AttributeSet attrs) { super(context, attrs); mGestureDetector = new GestureDetector(new YScrollDetector()); setFadingEdgeLength(0); } #Override public boolean onInterceptTouchEvent(MotionEvent ev) { if( mGestureDetector.onTouchEvent(ev)&super.onInterceptTouchEvent(ev)){ return true; }else{ return false; } } // Return false if we're scrolling in the x direction class YScrollDetector extends SimpleOnGestureListener { #Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if(Math.abs(distanceY) > Math.abs(distanceX)) { return true; } return false; } } } XML: <com.example.Views.ParentScrollView android:layout_width="match_parent" android:layout_height="match_parent" > <HorizontalScrollView android:id="#+id/tlDBtable" android:layout_width="wrap_content" android:layout_height="wrap_content" > </HorizontalScrollView> </com.example.Views.ParentScrollView> Basically the parent scrollview which only scrolls veritcal will be disabled because you will use a new custom class. Then you put a HScrollview within the scroll view. The Parentscroll view will pass the touch even if its not vertical to the horiszontalscroll view which makes it 2D scrolling effect.
Stop ScrollView from auto-scrolling to an EditText
Seems to be a common problem without a great solution that I have found. Goal is to stop a ScrollView from auto-scrolling to an EditText (or any view for that matter) that has focus. You have a bunch of views (Buttons, TextViews, etc) in an ScrollView, one of which is an EditText. Upon clicking say a Button within the ScrollView, the ScrollView scrolls down to the EditText (its off screen). This is not desired, as there are other elements that you don't want scrolled off the screen. Now I can stop this from happening when the screen first shows by having other focusable elements in the ScrollView. However, the general problem still exists. The user scrolls down manually to the EditText, enters some numbers, then scrolls up to the top (EditText off screen now), they click a button in the ScrollView, and guess what? The ScrollView scrolls down to that darn EditText. I'm thinking about extending the ScrollView and overriding some of the methods there like findFocusableViewInBounds, but I have a feeling I'll just be getting myself into more trouble. Please help if you can. I've played around with things like having an 0 height EditText at the top of my ScrollView, adding Next Focusable element properties to the other items in the ScrollView, etc. I suppose one "hack" might be to get the EditText to lose focus when the virtual or manual keyboard gets hidden or something.
After struggling with that problem for quite some time, I've found a solution that seems to work without being too ugly. First, make sure that whatever ViewGroup (directly) contains your EditText has descendantFocusability set to "Before Descendants," focusable set to "true" and focusableInTouchMode set to "true." This will not be the ScrollView itself, but the layout inside where you have your various views. Next add an onTouchListener to your ScrollView that removes focus from the EditText whenever it is touched, like so: ScrollView scroll = (ScrollView)findViewById(R.id.scrollView1); scroll.setOnTouchListener(new OnTouchListener() { #Override public boolean onTouch(View v, MotionEvent event) { if (myEditText.hasFocus()) { myEditText.clearFocus(); } return false; } }); Tell me if that doesn't fix it. What should happen is that the Layout gets focus instead of the EditText, so no scrolling should happen.
Just create an empty view at the top of linearlayout <View android:layout_width="fill_parent" android:id="#+id/focus_view" android:layout_height="0dp" android:focusable="true" android:focusableInTouchMode="true"><requestFocus/></View> Single line solves the problem
I had the same problem. There's one trick that I'm using to deal with this problem: public void onClick(View v) { button.requestFocusFromTouch(); //prevents from loosing focus and scrolling view down .... }
The issue is not on the java code, but on the manifest code. In your AndroidManifest.xml add an attribute to the Activity: <activity android:name=".MyActivity" android:windowSoftInputMode="adjustPan"> </activity>
By adding 2 parameters in: android:focusable="true" android:focusableInTouchMode="true" In which Main layout is there. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="#+id/layMain" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#color/background" android:focusable="true" android:focusableInTouchMode="true"> By this EditText will not be auto focused.
Here is what I did <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" style="#style/measurementTableRowStyle" android:focusable="true" android:layout_height="fill_parent"> <requestFocus></requestFocus> <LinearLayout android:id="#+id/linearLayout1" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:id="#+id/desc_text" android:text="Value : " style="#style/attributeNameTextStyle" android:layout_width="wrap_content" android:focusable="true" android:layout_height="wrap_content"> <requestFocus></requestFocus> </TextView> <TextView style="#style/attributeValueStyle" android:id="#+id/value_text" android:text="TextView" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView> </LinearLayout> The reason is in such cases you have to make all other views focus-able inside the scrollview by an explicit android:focusable="true" and then <requestFocus></requestFocus> . This should work everytime IMO
thomas88wp answer, https://stackoverflow.com/a/6486348/528746 worked for me. But I had two problems: 1. When scrolling, I wanted to hide the keyboard 2. I had lots of EditText views and didn't want to write it for each one of them (I do getActivity() since I'm writing this inside a Fragment and not an activity) ScrollView scroll = (ScrollView)view.findViewById(R.id.layout_scroll); scroll.setOnTouchListener(new OnTouchListener() { #Override public boolean onTouch(View v, MotionEvent event) { // Check if the view with focus is EditText if (getActivity().getCurrentFocus() instanceof EditText) { EditText ed = (EditText)getActivity().getCurrentFocus(); if (ed.hasFocus()) { // Hide the keyboard InputMethodManager inputManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); inputManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); // Clear the focus ed.clearFocus(); } } return false; } });
My fix to this most horrific bug, (worth noting that this is pre API11 only where they modified the fling method not to be stupid). The old fling method finds the next focus that it will get to.. which isn't really that helpful. Other versions of this class don't really work as they stop focus working when the user genuinely traverses the form from the keyboard. public class NonFocusingScrollView extends ScrollView { private boolean mBlockRequestFocusOnFling = false; public NonFocusingScrollView(Context context) { super(context); } public NonFocusingScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public NonFocusingScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } #Override public ArrayList<View> getFocusables(int direction) { if(mBlockRequestFocusOnFling) return new ArrayList<View>(); return super.getFocusables(direction); } #Override public void requestChildFocus(View child, View focused) { if(!mBlockRequestFocusOnFling) super.requestChildFocus(child, focused); } #Override public void fling(int velocityY) { mBlockRequestFocusOnFling = true; super.fling(velocityY); mBlockRequestFocusOnFling = false; } }
I was having a similar problem and finally got it to work. My scroll view contains a series of customized buttons, followed by an EditText (which normally has focus, but I don't want it to be losing focus). Any time the buttons were clicked, the scroll view auto-scrolled to the focused EditText. Overriding public boolean requestChildRectangleOnScreen(final View child, final Rect rectangle, final boolean immediate) and always returning false (default behavior of a ViewGroup) did the trick. Hope it helps with your situation too.
We can write a custom ScrollView and override the onScrollChanged method and clear the focus from the focused view and optionally hide the keyboard. #Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { View v = getFocusedChild(); if (v != null) { InputMethodManager imm = (InputMethodManager) getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(v.getWindowToken(), 0); v.clearFocus(); } super.onScrollChanged(l, t, oldl, oldt); }
I often has this problem when my apps handle orientation change. In that case I use the following kind of code: #Override protected void onCreate(Bundle savedInstanceState) { ... // to avoid the scrollview to scroll to this element automatically mEditTextSearch.setFocusable(false); // Get the saved scroll position final int scrolly = savedInstanceState.getInt("scrolly"); mScrollView.post(new Runnable() { #Override public void run() { mScrollView.scrollTo(0, scrolly); // Restore the initial state of the EditText mEditTextSearch.setFocusable(true); mEditTextSearch.setFocusableInTouchMode(true); mEditTextSearch.setClickable(true); } }); ... }
Another version of thomas88wp's code: ScrollView scroll = (ScrollView)getActivity().findViewById(R.id.scrollView_addNewBill); scroll.setOnTouchListener(new OnTouchListener() { #Override public boolean onTouch(View arg0, MotionEvent arg1) { View focussedView = getCurrentFocus(); if( focussedView != null ) focussedView.clearFocus(); return false; } });
I made a test project to experiment with the various solutions if anyone wants to play with it. https://github.com/marchold/EditText-ErrorPopup-Scroll-View
Create a custom ScrollView (create a class and have it extend HorizontalScrollView) and make a getter setter for scrollable. Then override computeScrollDeltaToGetChildRectOnScreen. How it works: Every time android has an edit text or something in focus that is off screen it calls method computeScrollDeltaToGetChildRectOnScreen to bring it into view. If you Override it and return 0 when it is disabled than it will not scroll... So you will have A custom scroll view like this: public class TrackableHorizontalScrollView extends HorizontalScrollView { // true if we can scroll (not locked) // false if we cannot scroll (locked) private boolean mScrollable = true; public TrackableHorizontalScrollView(Context context) { super(context); } public TrackableHorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public TrackableHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setScrollingEnabled(boolean enabled) { mScrollable = enabled; } public boolean isScrollable() { return mScrollable; } #Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // if we can scroll pass the event to the superclass if (mScrollable) return super.onTouchEvent(ev); // only continue to handle the touch event if scrolling enabled return mScrollable; // mScrollable is always false at this point default: return super.onTouchEvent(ev); } } #Override public boolean onInterceptTouchEvent(MotionEvent ev) { // Don't do anything with intercepted touch events if // we are not scrollable if (!mScrollable) return false; else return super.onInterceptTouchEvent(ev); } #Override public void scrollTo(int x, int y){ if (!mScrollable) return; super.scrollTo(x, y); } //Custom smooth scroll method since norm is final and cannot be overridden public final void smooothScrollToIfEnabled(int x, int y){ if (!mScrollable) return; smoothScrollTo(x, y); } #Override protected int computeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect rect){ if (!mScrollable) return 0; return super.computeScrollDeltaToGetChildRectOnScreen(rect); } } You can use this inside your XML like this: <com.your.package.ui.widget.TrackableHorizontalScrollView android:id="#+id/wi_et_credit_scroller" android:layout_toRightOf="#id/wi_et_credit_iv" android:layout_width="fill_parent" android:scrollbars="none" android:layout_height="wrap_content" android:paddingRight="5dp" android:layout_gravity="center_vertical"> <!--Whatever you have inside the scrollview--> </com.your.package.ui.widget.TrackableHorizontalScrollView>
The best Solution is to add focus options for the child of your scrollview : android:descendantFocusability="beforeDescendants" android:focusable="true" android:focusableInTouchMode="true" Then your xml file will look like : <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="#+id/scrollView" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="50dp" android:descendantFocusability="beforeDescendants" android:focusable="true" android:focusableInTouchMode="true" android:orientation="vertical" > <EditText android:id="#+id/editText_one" android:layout_width="match_parent" android:layout_height="100dp" android:text="TestApp 1" /> <EditText android:id="#+id/editText_two" android:layout_width="match_parent" android:layout_height="100dp" android:text="TestApp 2" /> <EditText android:id="#+id/editText_three" android:layout_width="match_parent" android:layout_height="100dp" android:text="TestApp 3" /> </LinearLayout> </ScrollView>
For me, it didn't work to override ScrollView onTouch. Also did not work android:descendantFocusability="beforeDescendants" android:focusableInTouchMode="true" This and another mentioned solutions only worked for the first time - only when EditText is not selected, but once you select it, scrollview autoscrolls again. Because I was already written a code to hide a keyboard when touching other views, I just added two lines of code and it worked like a champ: public static void setupUI(final Activity activity, final View view) { //view is the parent view in your layout OnTouchListener mTouchListener = new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { try { View vFocused = null; vFocused = activity.getCurrentFocus(); if (vFocused != null) { hideSoftKeyboard(activity, v); if (vFocused instanceof EditText) { vFocused.clearFocus();//this is the trick to avoid ScrollView autoscroll } } } catch (Exception e) { } return false; } }; // Set up touch listener for non-text box views to hide keyboard. if (!(view instanceof EditText) && !(view instanceof ViewGroup)) { view.setOnTouchListener(mTouchListener); } // If a layout container, iterate over children and seed recursion. if (view instanceof ViewGroup) { view.setOnTouchListener(mTouchListener); for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { View innerView = ((ViewGroup) view).getChildAt(i); setupUI(activity, innerView); } } } public static void hideSoftKeyboard(Context context, View v) { InputMethodManager inputMethodManager = (InputMethodManager) context .getSystemService(Activity.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0); } also added this in root view: android:descendantFocusability="beforeDescendants" android:focusableInTouchMode="true" Maybe its not really nice solution, but its working.
My solution is below, to trace the source code and override some function to stop auto scrolling by focused item. You can check if the focusedView is TextView or its child is TextView, by using focusedView.findViewById(R.id.textview_id_you_defined) != null or focusedView instanceof TextView == true. public class StopAutoFocusScrollView extends ScrollView { private View focusedView; private ScrollMonitorListener listener; public interface ScrollMonitorListener { public boolean enableScroll(View view); } public StopAutoFocusScrollView(Context context) { super(context); } public StopAutoFocusScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public StopAutoFocusScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setScrollMonitorListener(ScrollMonitorListener listener) { this.listener = listener; } #Override public void requestChildFocus(View child, View focused) { focusedView = focused super.requestChildFocus(child, focused); } //flow : requestChildFocus -> scrollToChild -> scrollBy //Therefore, you can give listener to determine you want scroll to or not #Override public void scrollBy(int x, int y) { if (listener == null || listener.enableScroll(focusedView)) { super.scrollBy(x, y); } } }
I had a slightly different objection to this infuriating deficiency. Whenever I tapped one of a number of RadioButtons below the EditTexts, the scroll position jumped to accommodate what Android determined to be the visible and focused EditText. All attempts to retain the current desired scroll position via a Runnable that issued ScrollView.scrollTo(x,y) were dutifully IGNORED by Android! I share my solution in the hope that it may save someone else 8 (eight) wasted hours. /* This interesting little 'hack' prevents unwanted scroll 'jump' occurring when user touches a RadioButton for example [ Causes focus to change - but maybe this is a lesser evil! ] */ mScrollView.setOnTouchListener(new View.OnTouchListener() { #Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() != MotionEvent.ACTION_UP) return false; mScrollView.clearFocus(); return false; } });
Only this code works for me: public static void preventScrollViewFromScrollingToEdiText(ScrollView view) { view.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); view.setFocusable(true); view.setFocusableInTouchMode(true); view.setOnTouchListener(new View.OnTouchListener() { #Override public boolean onTouch(View v, MotionEvent event) { v.requestFocusFromTouch(); return false; } }); } All credits go to this original answer.
Try this one :) public class CustomHorizontalScrollView extends HorizontalScrollView { public CustomHorizontalScrollView(Context context) { super(context); } public CustomHorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public CustomHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } #Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(ev); } #Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev); } #Override public void scrollTo(int x, int y) { super.scrollTo(x, y); } //Custom smooth scroll method since norm is final and cannot be overridden public final void smooothScrollToIfEnabled(int x, int y) { smoothScrollTo(x, y); } #Override protected int computeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect rect) { /* if (getContext() != null && getContext() instanceof Activity) { Activity activity = (Activity) getContext(); if (!activity.isFinishing()) { View view = activity.getCurrentFocus(); if (view != null) { if (view instanceof EditText) { return 0; } } } } return super.computeScrollDeltaToGetChildRectOnScreen(rect); */ return 0; } }
I solved this problem adding the descendantFocusability attribute to the ScrollView's containing LinearLayout, with the value blocksDescendants. For example: <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:descendantFocusability="blocksDescendants" >
None of these answers worked for me. What I did was, I added: android:overScrollMode="never" in my ScrollView and set the height to wrap_content. My view was very complex as it was legacy code with LinearLayout inside LinearLayout inside LinearLayout. This helped me, hope it will help someone else too!