Stop ScrollView from scrolling when TextView with selectable text becomes focused - android

I have a ScrollView with multiple TextView children nested inside its layout. I want the text in these children to be selectable (android:textIsSelectable="true"), so the user can use the copy, share and select all actions. But when one of the children receives focus (from touch or long press), it causes the parent ScrollView to scroll to the focused child. I suppose this is a feature, but it introduces three problems for me.
When text is selected with a long press, it causes the text to scroll and as a result the children above the focused child will scroll outside of visible bounds.
The position of the selected text will change and will not appear on the screen where the user made the long press, which is unintuitive.
My app responds to double-tap gestures. The first touch (or tap) of a double-tap will cause the child that received the touch event to receive focus and thus cause a scroll. Apparently, the scroll will cause the gesture to fail; the child is touched, the parent scrolls, the child is touched again, but the double-tap gesture is not detected.
Making the children unfocusable prevents the scroll, but then the text cannot be selected. So how can I have TextView views nested inside a ScrollView with selectable text, but prevent scrolling when one of the views receives focus?

While searching for possible solutions to this problem, I found this article https://programmersought.com/article/8487993014/. It is not directly related to this problem, but one specific part where the article shows some source code from ScrollView caught my attention:
public void requestChildFocus(View child, View focused) {
if (focused != null && focused.getRevealOnFocusHint()) {
If (!mIsLayoutDirty) {//This boolean value marks whether the layout has been completed. If it is completed, it is false.
scrollToChild(focused);
} else {
// The child may not be laid out yet, we can't compute the scroll yet
mChildToScrollTo = focused;
}
}
//super method [handling FOCUS_BLOCK_DESCENDANTS case] is called after scrolling on top.
//So setting the android:descendantFocusability="blocksDescendants" attribute to the ScrollView is invalid.
super.requestChildFocus(child, focused);
}
Here you can see that the ScrollView will attempt to scroll to the focused child if focused != null. So to disable this behaviour, create a subclass of ScrollView and override this method like so:
package com.test
// imports here...
public class MyScrollView extends ScrollView {
// constructors here...
#Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, null);
}
}
By ignoring the focused parameter and passing null to the super implementation, the scroll will never occur, but the parent will still request the child to receive focus and therefore text can still be selected.
The only thing left to do is replace the ScrollView parent in the layout file with the custom implementation defined above.
EDIT
The solution was tested and worked fine on API 30, but when I tested it on API 25 a NPE is thrown and the app crashes. The Android Docs recommend to use the NestedScrollView view instead for vertical scrolling, but the app still crashes. I looked at the source code for requestChildFocus(View, View) and it is slightly different:
#Override
public void requestChildFocus(View child, View focused) {
if (!mIsLayoutDirty) {
scrollToChild(focused);
} else {
// The child may not be laid out yet, we can't compute the scroll yet
mChildToScrollTo = focused;
}
super.requestChildFocus(child, focused);
}
private void scrollToChild(View child) {
child.getDrawingRect(mTempRect);
/* Offset from child's local coordinates to ScrollView coordinates */
offsetDescendantRectToMyCoords(child, mTempRect);
int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
if (scrollDelta != 0) {
scrollBy(0, scrollDelta);
}
}
The method scrollToChild(View) is made private and so we cannot override the default implementation (because why would anyone ever want to do that, right?), but scrollBy(int, int) is public. So instead of overriding requestChildFocus(View, View) in the MyScrollView class, override scrollBy(int, int) and make it do nothing. This was tested on both API 30 and 25 and worked as intended without crashing. I later tried to revert back to extending ScrollView and it still works. So you just have to override the method, the supertype does not matter.

Related

ScrollView scrolls to the same view after using of scrollTo()

I have a bunch of view in ScrollView.
Some of them are TextInputEditText.
When an error occurs I use setError for particular TextInputEditText.
And I have such effect.
But my layout is long, and I need to scroll to view with an error.
Code:
protected void showInputError(TextInputEditText edtText, #StringRes int resId) {
edtText.setError(getString(resId));
scrollView.post(() -> {
scrollView.scrollTo(0, edtText.getTop());
});
}
All works fine first time. But the app does the same scrolling after any other scrolling.
I mean,
set an error to view
scroll programmatically to this view
scroll manually to another place. Application scrolls again to view from step 2.
The main problem: Application scrolls back to view when user scrolls manually (after applying of scrollTo)
Please, give a hint how to overcome this

How can I tell when a Layout (and all its child Views) has fully finished inflating?

I have a situation where I have a custom Layout which contains a bunch of children and which I inflate into a ViewFlipper.
My problem is that the ViewFlipper badly stutters its animation as it tries to animate the Layout in, even as the Layout is still loading all its child Views.
I tried using onLayout, but that gets called the moment the parent Layout is done inflating (it doesn't wait for the child views to inflate, so the stutter persists)
I also tried onMeasure, but that's called dozens of times and keeps getting called every time anything in the Layout changes (such as the EditText getting focus, or changing value).
So, I'm stumped... does *ANYTHING happen, that I can listen for, when the Layout is fully inflated, so that I can THEN tell the Flipper to perform the animation?
You can override onFinishInflate() in your custom layout. This is called as the last phase of inflation, after all child views have been added.
You could use an OnPreDrawListener. This will be called immediately before the View is going to draw its first frame, at which point all of the children will be laid out.
CustomView myCustomView = (CustomView) findViewById(R.id.my_custom_view);
final ViewTreeObserver obs = myCustomView.getViewTreeObserver();
obs.addOnPreDrawListener(new OnPreDrawListener () {
#Override
public boolean onPreDraw () {
//We only care the first time it happens, so remove it
obs.removeOnPreDrawListener(this);
//Post your animation here, then return true
return true;
}
});

Create a linearview on every activity to perfom action

I am trying to create view that sites underneath all others that will perform an action when 3 fingers are touched. These activities all have their own touches and swipes.
I have got this to kind of work by creating a class that extends linearlayout and intercepting touches. and adding this in xml.
But this is only being called when an item like button or view is a clickable or touchable surface and isn't becoming a touchable surface itself.
in
public class MyCatcher extends LinearLayout {
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
// multitouch!! - touch down
int count = event.getPointerCount(); // Number of 'fingers' in this
if (count > 3) {
doThis(mContext);
}
}
return super.onInterceptTouchEvent(event);
}
And in xml
<packagename.MyCatcher
....>
my normal layout
</packagename.MyCatcher>
So this code is working when an item that is touchable is touched but not becoming its own touch layer.
I have tried setontouchlister in the constructor with no luck.
Thanks for any help
EXTRA: When setting the touchlistener via onAttach I can get it to catch touches but I can't use event.getPointerCount() like normal as it's returning 1.
I'm worried this may take up lots of processing?/
Instead of a Layout, create a normal View and add it to your existing layout so that it spans the whole screen and lays "above" all other views (in z-order). Now it should get all touch events prior to all other views. Your onTouch... implementation should make sure to pass all touch events along to the other views (except if you want e.g. buttons not to work under certain circumstances, then do not pass the events along).
onintercepttouchevent()
Is called only whenever a touch event is occured. ie; touch event occurs only when a view item like button,textbox,etc having onclicklistener enabled is pressed . For your question you need to set the onclicklistener for the entire view.

TextView inside ViewPager intercepts touch events

I have a ViewPager that contains two fragments. In any of the fragments I can touch a place, swipe to switch to another fragment. One of the fragments contains a list. Items in the list contains one TextView and one ImageView. The issue is, if you dragging has been started from tapping the ImageView, it's OK. But if it's been from the TextView, the drag was never known to the ViewPager, as a result the 'smooth switching' never happens.
Any clue on this?
EDIT
This picture is to show how my GUI is. If the drag has been started from TextViewE, it doesn't begin.
This thing bothered me too, but I've managed to find the answer.
Basically, the case is: if the view can scroll horizontally, it intercepts the horizontal motion event and ViewPager is not able to process it anymore.
Since API Level 14 TextViews have android:scrollHorizontally property (and setHorizontallyScrolling(boolean) method), which, if set to true, causes the TextView to intercept horizontal scroll motion events.
You may set it to false either in XML or right in the code, but watch out: android:singleLine property forces android:scrollHorizontally to be set to true! Very tricky point! But fortunately, you usually able to safely replace single line property with android:maxLines="1" and necessary ellipsize value.
Good luck!
you can override onTouchEvent() of the TextView:
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
return false;
}
return super.onTouchEvent(event);
}

Catch onTouch event by parent, handle it and pass to children

I tried to understand how Android handle touch event and got a little bit confused. From what I understand touch event are send to the root view and pass down to the children.
I have a FrameLayout that is a container for Fragment.
First fragment view is a ScrollView, second one is some kind of Gallery (HorizontalListView) and the last one is also FrameLayout. Only one fragment in the layout each time.
What I want to do is to identify user swipes on the screen, for the app use. I want to count the swipes and do something after some number of swipes.
I tried to put a OnTouchListener on the top FrameLayout but it doesn't get called when the child is the ScrollView or the Gallery. I tried to return false and also true in the end of onTouch, but I get same result - it's never being called.
How can I do it?
I just want to "transparently" handle the touch events and passing them on like I didn't even touch them.
My understanding is that it actually goes the other direction. The Child views get their event triggered first (sort of). The root view get's it's dispatchTouchEvent() called, which propagates the event down to the children's onTouchEvent(), and then, depending on whether they return true or false, the parent's onTouchEvent() is called.
The normal solution for intercepting things like this is to override dispatchTouchEvent(MotionEvent ev) in one's activity like so:
#Override
public boolean dispatchTouchEvent (MotionEvent ev) {
// Do your calcluations
return super.dispatchTouchEvent(ev);
}
The documentation for this one is here. Note that you can also override that method in any ViewGroup (such as a FrameLayout, etc)

Categories

Resources