Axbor Axrorov
Axbor Axrorov

Reputation: 2806

Android ViewPager like instagram photo activity

I need to use same fragment for Photos and Videos tab and still have three tabs. I have two fragments in viewpager and 3 tabs in tablayout. I want to achieve viewpager changing effect like instagram. What I've done: I created custom viewpager below:

public class SwipableViewPager extends ViewPager {
    private float downX;
    private float downY;
    private float mStartDragX;
    private float upX;
    private float upY;
    private boolean dragDisabled;

    private SwipeListener mListener;

    public void setSwipeListener(SwipeListener listener) {
        mListener = listener;
    }

    public SwipableViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        float x = ev.getX();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = ev.getX();
                downY = ev.getY();
                mStartDragX = x;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                upX = ev.getX();
                upY = ev.getY();

                float deltaX = downX - upX;
                if(deltaX>-getWidth() && deltaX< -getWidth()/4 && getCurrentItem() == getAdapter().getCount() - 1){
                    mListener.setPageAndEnableDrag();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mStartDragX - 30 > x && getCurrentItem() == getAdapter().getCount() - 1) {
                    mListener.onSwipeOutAtEnd();
                }
                break;
        }
        if(dragDisabled) {
            return false;
        }
        else{
            return super.onInterceptTouchEvent(ev);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(dragDisabled) return false;
        return super.onTouchEvent(ev);
    }

    public void disableDrag(boolean disabled) {
        dragDisabled = disabled;
    }



    public interface SwipeListener {
        void setPageAndEnableDrag();
        void onSwipeOutAtEnd();
    }
}

It will notify swipe right when current item is last item in adapter (this need for when you are in photo tab and you want to go video tab with swiping). In activity I set up viewpager like this:

private void setupViewPager() {
        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
        adapter.addFragment(GalleryFragment.newInstance(), getResources().getString(R.string.gallery_toolbar_title));
        TakePhotoFragment fragment = new TakePhotoFragment();
        adapter.addFragment(fragment, getResources().getString(R.string.photo));
        viewPager.setAdapter(adapter);
        viewPager.setSwipeListener(this);
        tabLayout.setupWithViewPager(viewPager);
        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {

                switch (tab.getPosition()){
                    case 1:  //selected photo tab
                        viewPager.setCurrentItem(1);
                        toolbar.setTitle(R.string.photo);
                        viewPager.disableDrag(false);    //switch on default swipe
                        break;
                    case 2: // selected video tab
                        toolbar.setTitle(R.string.video);
                        viewPager.disableDrag(true);    // switch off default swipe
                        break;
                    default: // selected gallery tab
                        viewPager.setCurrentItem(tab.getPosition());
                        toolbar.setTitle(R.string.gallery_toolbar_title);
                        viewPager.disableDrag(false);    //switch on default swipe
                        break;
                }
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
    }

    @Override
    public void setPageAndEnableDrag() {
        tabLayout.getTabAt(1).select();
        viewPager.disableDrag(false);
    }
    public void setPagerEnabledDrag(boolean enabled) {
        viewPager.disableDrag(!enabled);
    }
    @Override
    public void onSwipeOutAtEnd() {
        viewPager.disableDrag(true);
        tabLayout.getTabAt(2).select();
    }

Everything okay, but when you are in gallery tab and if you want to go video tab, you can not done it by clicking video tab. When you click video tab first it makes active photo tab and after that if you click video tab secondly it will active video tab. Also changing from photo tab and video tab by swiping working ugly. I need to achieve when click video tab it must go to video tab not to photo tab like now.

enter image description here

also see video: https://youtu.be/tbT7mzkmydQ

  1. Why I have two fragments only, but three tabs? Because Camera object can be created only one in one Activity, thats why I have to use one fragment for photo and video tabs.
  2. Why I have to deal with unabling drag? When User goes to third tab I must disable default swiping effect of viewpager (if I don't do so, when swiping from thrid tab to second viewpager will be go to the first tab)
  3. I'm not wanting an infinite viewpager.
  4. I don't want simply to get from the right (third) fragment to the left (first) fragment.

PLEASE SEE THE VIDEO: Video of my activity in action

Upvotes: 2

Views: 2855

Answers (2)

random
random

Reputation: 10309

As I understand you need to use same fragment for Photos and Videos tab and still have three tabs.

Swiping from photo to video tab - Add a blank fragment at third tab keeping its width to zero in pager adapter. Photo fragment will keep displaying when swiping from photo to video tab.

private void setupViewPager(){
        SectionsPagerAdapter adapter =  new SectionsPagerAdapter(getSupportFragmentManager());

        adapter.addFragment(new GalleryFragment());
        adapter.addFragment(new PhotoFragment());
        adapter.addFragment(new Fragment()); //blank fragment

        mViewPager = (ViewPager) findViewById(R.id.viewpager_container);
        mViewPager.setAdapter(adapter);

        mTabLayout = (TabLayout) findViewById(R.id.tabsBottom);
        mTabLayout.setupWithViewPager(mViewPager);

        mTabLayout.getTabAt(0).setText(getString(R.string.gallery));
        mTabLayout.getTabAt(1).setText(getString(R.string.photo));
        mTabLayout.getTabAt(2).setText(getString(R.string.video));
        ...
    }

Set width of blank tab to 0

public class SectionsPagerAdapter extends FragmentPagerAdapter {
        ...
        @Override
        public float getPageWidth(int position) {
            if (position == 2) {
                return 0;
            }
            return super.getPageWidth(position);
        }
    }

Swiping from video to photo tab - Increase fling distance for Video tab so that swipe stops at photo tab. Reset fling distance to default for other tabs. You'll need Java Reflection API to make fling distance field accessible as it is a private field in ViewPager.class

final float density = getResources().getDisplayMetrics().density;
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        Field mFlingDistance;
        try {
            mFlingDistance = ViewPager.class.getDeclaredField("mFlingDistance");
            mFlingDistance.setAccessible(true);
            switch (tab.getPosition()){
                case 2: mFlingDistance.set(mViewPager, (int)(200 * density));
                        break;
                default: mFlingDistance.set(mViewPager, (int)(25 * density));
                         break;
            }
         } catch (NoSuchFieldException e) {
             e.printStackTrace();
         } catch (IllegalAccessException e) {
             e.printStackTrace();
         }
     }

     @Override
     public void onTabUnselected(TabLayout.Tab tab) {}

     @Override
     public void onTabReselected(TabLayout.Tab tab) {}
 });

Link to video: https://i.sstatic.net/pxmXf.jpg

This is a quick-and-dirty solution in the direction. You might need to fine tune it.

Upvotes: 5

kalabalik
kalabalik

Reputation: 3832

It is thoroughly unclear what you want to achieve. Why do have two fragments only, but three tabs? Why do implement a swiper (swiping, or rather paging, is built into the viewpager by default). Why do have to deal with unabling drag? Is this, or rather will this be, a feature of the viewpager? Or will it be a feature of one of your fragments? (In the latter, you should leave it with that fragment.)

You seem to be wanting an infinite viewpager (swiping to the left (!) on the far right fragment). Is this correct? Or do you simply want to get from the right (third) fragment to the left (first) fragment? You can do so with a simple implementation of ViewPager and TabLayout: the pages (fragments) will be swipable/pagable (swipe twice for switching from the third to the first fragment) and the tabs will be clickable (click once for doing the same switch).

Since nowhere do you define the order of your fragments, please don't speak of "Video" or "Gallery", but of the first and second (and possibly third) fragments.

For the sake of clarity, I post an activity with a standard viewpager-tablayout-toolbar setup - no swipe detection necessary.

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ArrayList<Fragment> fragments = new ArrayList<>();
        // make sure to instantiate and add all your fragments
        // your fragments will probably be of different types
        fragments.add(YourFragment.newInstance("Gallery"));
        fragments.add(YourFragment.newInstance("Video"));
        fragments.add(YourFragment.newInstance("Photo"));

        final Toolbar toolbar = findViewById(R.id.toolbar);

        final ViewPager viewPager = findViewById(R.id.viewpager);
        FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public int getCount() {
                return fragments.size();
            }

            @Override
            public Fragment getItem(int position) {
                return fragments.get(position);
            }

            public CharSequence getPageTitle(int position) {
                return ((YourFragment) fragments.get(position)).getTitle();
            }

        };
        viewPager.setAdapter(adapter);

        TabLayout tabLayout = findViewById(R.id.sliding_tabs);
        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                // tab.getPosition() returns index for viewPager.setCurrentItem()
                viewPager.setCurrentItem(tab.getPosition());
                toolbar.setTitle(((YourFragment) fragments.get(tab.getPosition())).getTitle());
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
            }
        });
        tabLayout.setupWithViewPager(viewPager);
    }

    // make your fragments implement this interface
    interface FragmentTitle {
        CharSequence getTitle();
    }
}

Upvotes: 3

Related Questions