How do I disable the Tab click event for a PagerTabStrip - android

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).

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;
}
}

Make swipes on one viewpager be set to another one

I have 2 view pagers in a linear layout both taking up same amount of screen. I want to make it so that if a user swipes any where on the screen then the swipe should only be for the lower viewpager. I tried increasing the touch delegate but that didn't work.
Well you're trying to some trippy stuff! I'd suggest making a CustomViewPager class for the 2nd viewpager which can have an instance of the 1st viewPager.
public class CustomViewPager extends ViewPager {
private ViewPager firstViewPager;
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.enabled = true;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(firstViewPager!=null){
firstViewPager.onTouchEvent(event);
}
return super.onTouchEvent(event);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if(firstViewPager!=null){
firstViewPager.onInterceptTouchEvent(event);
}
return super.onInterceptTouchEvent(event);
}
public void setFirstViewPager(ViewPager firstViewPager) {
this.firstViewPager = firstViewPager;
}
}
In this you are passing the motion event that you get in the 2nd viewpager's onTouchEvent and onInterceptTouchEvent to the 1st viewpager. You'd also want to disable the swipe on the 1st viewpager that you could do as described here.

Android ViewPager scrolling issue with only one item when using getPageWidth from PagerAdapter

I have a ViewPager with a custom PagerAdapter that overwrites the method getPageWidth to return 0.3f, so I can show multiple pages in the screen, as suggested in the following blog:
http://commonsware.com/blog/2012/08/20/multiple-view-viewpager-options.html
In this case the ViewPager starts empty and the user starts filling the pages (images) as requested. The images are filled from left to right. All is working fine and the pages are dinamically created, and it is easy to swipe between them when there are multiple images.
However there is a problem when there are less than three pages instantiated. A swipe gesture from right (no content) to left produces a flickering with pages moving from left to right alternatively, as if the ViewPager were trying to move the first pages to right but later turning to its original position. This happens during the whole gesture, and stop when leaving the gesture. As I said this does not happen when there are pages enough to cover the whole screen width and a real scroll is necessary.
Any Ideas?
I know this is an old question, but I was just looking for a solution and came across this link (which oddly references this question). Anyway, I was able to figure out a solution based on their comments. The basic idea is to allow touch events based on the state of a boolean flag which you set.
Extend ViewPager on override onInterceptTouchEvent & onTouchEvent to only call super if you've set the flag. My class looks like this:
public class MyViewPager extends ViewPager {
private boolean isPagingEnabled = false;
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewPager(Context context) {
super(context);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (isPagingEnabled) {
return super.onInterceptTouchEvent(event);
}
return false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (isPagingEnabled) {
return super.onTouchEvent(event);
}
return false;
}
public void setPagingEnabled(boolean pagingEnabled) {
isPagingEnabled = pagingEnabled;
}
}
In your layout xml, replace your com.android.support.v4.ViewPager elements with com.yourpackage.MyViewPager elements.
Since you return 0.3f from getPageWidth() in your pager adapter, you would want scrolling enabled when the fourth item has been added to it. The tricky part is having this line of code everywhere when you define your pager adapter, and add or remove any objects from the adapter backing list.
mPager.setPagingEnabled(items.size() > 3);
Rahuls solution works, but if you want auto enabling of paging according to content, you can modify ViewPager like this:
public class MyViewPager extends ViewPager {
public MyViewPager(Context context,
AttributeSet attrs) {
super(context, attrs);
}
public MyViewPager(Context context) {
super(context);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (canScrollHorizontally(1)||canScrollHorizontally(-1)) {
return super.onInterceptTouchEvent(event);
}
return false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (canScrollHorizontally(1)||canScrollHorizontally(-1)) {
return super.onTouchEvent(event);
}
return false;
}
}
Now, if the ViewPager is scrollable, it will allow touches, otherwise disable.
I was able to solve it this way, in your getPageWidth() method,if there is only one image[you can get to know by checking size of array or arraylist] return 1, if the input size is greater than 1 return your decimal.
Here's the code to achieve this.
#override
public float getPageWidth(int position){
if(inpputsize==1){
return 1.0;
}
return 0.9;
}

Limited Swipe Area Android

I need to limit the swipe area inside a ViewPager. For example, if the user make the gesture to swipe on top half space of the screen it swipe to the next fragment, but if the user make the gesture on the bottom half of the screen it do nothing.
There is a way to do that?
This might be a what you need:
public class MyPager extends ViewPager {
public MyPager(Context context) {
super(context);
}
public MyPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(inNeutralArea(ev.getX(),ev.getY())){
//--events re-directed to this ViewPager's onTouch() and to its child views from there--
return false;
}else {
//--events intercepted by this ViewPager's default implementation, where it looks for swipe gestures--
return super.onInterceptTouchEvent(ev);
}
}
private boolean inNeutralArea(float x, float y) {
//--check if x,y inside non reactive area, return true/false accordingly--
return false;
}
}
Use this MyPager class in layout xml in place of ViewPager.
Don't forget to do to the same in overriding the onTouchEvent, otherwise the ViewPager will still scroll on Android 4.1 and later. Caught me ofguard and took me a while to figure it out.

two directional scroll view

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>

Categories

Resources