Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swipe Gesture applied at Fragment level along with ViewPager with its default swipe disabled

I found the question below: Android: FragmentActivity inside FragmentActivity (ScrollView in NavigationBar) . However, this question of mine is about how to use Transactions to show fragments as well as enabling swipe gesture detection in a safe way. Note: There is a link in the answer that I have posted (This also has an effective way of showing fragments in a container using transactions, including two scenarios). Please see that. I have tried a few things in regards, but by using a fragment supporting ViewPager, not nested:

Details: https://moqups.com/[email protected]/lc0ZOERO/p:a5d7b55eb

  1. Disabled default swipe using onTouchEvent and onInterceptTouchEvent using custom ViewPager.
  2. Declared FragmentStatePagerAdapter provided with a list of fragments, each of which is displayed in each tab.
  3. Declare Fragments in the main_layout of the main_activity which has this ViewPager alongside. Idea is to show the activity_fragments when a button on a ViewPager_fragment is clicked and when the user presses the return button, then the default ViewPager_fragment is shown again using the back stack transactions on add and popping them on BackPressed of the activity. I am therefore also maintaining my custom back-stack to show/hide the activity_fragments when the back stack pops a transaction.

Now what I want to achieve is to do the third point above using swipe-gestures for the right [left to right] swipes only.

I have used GestureListener and SimpleOnGestureListener and activity's OnTouchEvent for this.

The problem that I face is:

This gesture works on the portion of the activity screen which is below the fragment and the portion of the activity. I want the gesture work

  1. On the Fragment area which has multiple views in its layout.
  2. In only the left-right direction.
  3. Only from activity_fragment to ViewPager's tab fragment to behave like history navigation as already done in onBakPressed/onKeyDown implementation using back stack popups and my custom back stack.

I tried the following class and change in my activity fragment in layouts like this.

My Gesture and Touch listener extended with frame layout:

        public class OnSwipeTouchListener extends FrameLayout {

        private final GestureDetector gestureDetector;
        private static final String TAG = "OnSwipeTouchListener";
    private Context context;
        public OnSwipeTouchListener(Context context, AttributeSet attrs) {
            super(context, attrs);
            this.context=context;
            gestureDetector = new GestureDetector(context, new GestureListener());
            setClickable(true);

        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return super.onInterceptTouchEvent(ev);
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
 gestureDetector.onTouchEvent(motionEvent);
            return super.onTouchEvent(event);
        }


        private final class GestureListener extends SimpleOnGestureListener {

            private static final int SWIPE_THRESHOLD = 100;
            private static final int SWIPE_VELOCITY_THRESHOLD = 100;

            @Override
            public boolean onDown(MotionEvent e) {

                return true;
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

                boolean result = false;
                try {
                    float diffY = e2.getY() - e1.getY();
                    float diffX = e2.getX() - e1.getX();
                    if (Math.abs(diffX) > Math.abs(diffY)) {
                        if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
                            if (diffX > 0) {
                                onSwipeRight();
                            } else {
                                onSwipeLeft();
                            }
                        }
                    } else {
                        if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) >           SWIPE_VELOCITY_THRESHOLD) {
                            if (diffY > 0) {
                                onSwipeBottom();
                            } else {
                                onSwipeTop();
                            }
                        }
                    }
                } catch (Exception exception) {
                    //see that e1 is null
                }
                return result;
            }
        }

        public void onSwipeRight() {
            ///manage my back stack history here.
        }

        public void onSwipeLeft() {
        }

        public void onSwipeTop() {
        }

        public void onSwipeBottom() {
        }
    }

and then change frame layouts to this class reference:

<path.to.my.package.OnSwipeTouchListener
            android:id="@+id/activity_frag_frame_layout1"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:visibility="invisible" >

            <fragment
                android:id="@+id/activity_frag1"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                class="path.to.my.package.fragments.Activity_Frag1"
                android:tag="act_frag1" />
        </path.to.my.package.OnSwipeTouchListener>

It helped me little, so should it help you in finding solution. There is a searchView in one of the fragments that stopped working, and no click works. If you get it what I want to achieve, please help in this regards.

Update 1: Returning true in both onTouchEvent and onInterceptTouchEvent has the desired effect, but it blocks the clicks in fragments where SearchView is there, no clicks work in the clickable views present. Only the swipe is working. Update: In my latest: I have also discarded the use of transaction-back-stacks completely because I rely on my custom back-stack where I maintain the navigation history for all the tabs. This I do using setCurrentTab and onTabSelected methods given by ViewPager and FragmentStatePagerAdapter.

Update 2: This one is difficult to implement: Check which events need to be intercepted and which are to be passed to the child views: http://developer.android.com/training/gestures/viewgroup.html

Update 3:

  1. In my layout for activity, there is a view pager, and in each frame layouts below it, there is one fragment.

  2. When you start the app, the ViewPager shows the fragment in the first tab.

  3. When a button on this fragment is clicked, one of the fragments in the frame layouts below view pager is shown and added to my custom back stack.

  4. When the return key is pressed, this fragment is hidden again to show the tab fragment in the view pager. Every of the above is done. Only I want the swipe gesture for the left to right to work, and I found that I can do it by intercepting touch events. But it does not do that at all When I return true, only the swipe gesture works, otherwise, this action is canceled and the clicks on this fragment work.

like image 964
Abhinav Saxena Avatar asked Sep 16 '13 06:09

Abhinav Saxena


1 Answers

I have mentioned this answer in http://mobi-app-dev.blogspot.in/2014/01/android-transactions.html.

Check which events needs to be intercepted and which are to be passed to the child views: http://developer.android.com/training/gestures/viewgroup.html

Instead of using my swipe gesture listener in frame-layout on which fragments are attached, now I am using it on the relative-layouts which are the root-views of the fragments.

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = super.onInterceptTouchEvent(event);

        // In general, we don't want to intercept touch events. They should be
        // handled by the child view.

        gestureDetector.onTouchEvent(event);
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean touched = super.onTouchEvent(event);
        gestureDetector.onTouchEvent(event);
        return touched;
    }

Here are some tips that I follow now while using fragments in my activities:

Effective management of transactions for fragment:

[Note: In case you are using FragmentStatePagerAdapter, you can play with replacing a fragment with another fragment at a particular index in the list of fragment that you are using as a data for this adapter, in a way that you are in the same tab but its content is changed without changing the tab. Then use notifyDataSetChanged(). Here you do not use transactions, but otherwise you do].

  1. Use Fragment.instantiate() to initialize your fragments instead of constructor.
  2. Use listeners to update/delete the reference of instance (from the collection used to refer them) of fragment onCreateView and onDestroy.
  3. Use getView() to get the root view of fragment every where and on update functionality.
  4. Do not keep track of instances, infact avoid static pointers where ever possible.
  5. Use getSupportFragment instead of keeping reference of FragmentManager in view pager adapter or elsewhere.
  6. Use attach detatch on transaction hide view, to get rid of child views on Fragment's root view and re initialize. This is because only one fragment needs to be visible at a time, the others need to detach. Therefore re-attach on demand.
  7. getActivity() or getApplicationContext() whatever applicable, instead of keeping a global variable to keep references to context.
  8. onConfiguration change (orientation, keyboard, resize): do in activity and pass it to active fragment which manages it. Important: If you do want to use fragments for transactions, do not declare them in layouts. Provide container (layout) id in add fragment transaction.
  9. A Fragment transaction should be done in an event call, not anywhere else, and such an event should not be repetitive. This prevents Illegal Argument Exception.
  10. There is a ViewPager method where you do not have to use Fragments.Instead, you can use Views, or ViewGroups. This replaces the nesting of fragments.
  11. Detect the level of fragments, that means if you want a fragment 'B' to replace existing fragment 'A', and when you want to go back to show fragment 'A' when pressing back on 'B', put that transaction in back stack.

Tip : Carefull while adding swipe feature because there are other clickable views. Swipe with intercept touch on RootView returning false but analysing touches.


One more thing: you should observe that having one fragment in one container at a time resolves another issue: If there are two fragments , one on the top of the other, the clicks from one fragment goes to the other fragment that is beneath. So hide or replace to have just one fragment at a time. Back-Stack helps in maintaining the navigation levels, and on configuration change or update, try to update the same instance of the target active fragment, instead of doing transactions because that changes the order of fragments navigated.

like image 165
Abhinav Saxena Avatar answered Oct 10 '22 19:10

Abhinav Saxena