developer tip

순환 대기열 / 래핑으로 ViewPager

copycodes 2020. 11. 21. 10:30
반응형

순환 대기열 / 래핑으로 ViewPager


일부 조각 간 탐색을 허용하기 위해 ViewPager와 함께 사용하고 FragmentStatePagerAdapter있습니다.

하자 내가 세 조각을 가지고 있다고 : A, BC. ViewPager는 처음에 Fragment A를 표시하고 오른쪽에서 왼쪽으로 스 와이프하여 Fragment B로 이동 한 다음 다시 스 와이프하여 Fragment C로 이동할 수 있습니다. 이를 통해 다음 탐색 경로를 사용할 수 있습니다 A <--> B <--> C..

내가 원하는 것은 Fragment A에서 왼쪽에서 오른쪽으로 스 와이프하고 ViewPager에 Fragment C를 표시하도록하는 것입니다. 즉, 순환 대기열로 작동하고 ... --> C <--> A <--> B <--> C <--> A <-- ...

조각이 다른 위치에 복제되는 것을 원하지 않습니다 (즉, 3 개 이상의 인스턴스로 끝나는 경우).

이 래핑 기능이 ViewPager로 가능합니까?


의사 무한 페이징 동작을 허용 할 수있는 ViewPager / PagerAdapter를 구현했습니다. 실제 개수로 매우 큰 숫자를 지정하여 작동하지만 데이터 세트 / 페이지 세트의 실제 범위에 매핑합니다. 또한 '첫 번째'페이지에서 즉시 왼쪽으로 스크롤 할 수 있도록 시작 부분을 큰 숫자로 오프셋합니다.

1,000,000 번째 페이지에 도달하면 잘 작동하지 않지만 (스크롤 할 때 그래픽 결함이 표시됨) 일반적으로 실제 사용 사례는 아닙니다. 가끔씩 카운트를 더 낮은 숫자로 재설정하여이 문제를 해결할 수 있지만 지금은 그대로 두겠습니다.

InfinitePagerAdapter기존 ViewPager를 래핑하고 사용하므로 매우 투명합니다. InfiniteViewPager약간의 작업이 당신이 잠재적으로 좌우로 여러 번에 스크롤 할 수 있도록 않습니다 않습니다.

https://github.com/antonyt/InfiniteViewPager


이것은 가짜 페이지가없는 솔루션이며 매력처럼 작동합니다.

public class CircularViewPagerHandler implements ViewPager.OnPageChangeListener {
    private ViewPager   mViewPager;
    private int         mCurrentPosition;
    private int         mScrollState;

    public CircularViewPagerHandler(final ViewPager viewPager) {
        mViewPager = viewPager;
    }

    @Override
    public void onPageSelected(final int position) {
        mCurrentPosition = position;
    }

    @Override
    public void onPageScrollStateChanged(final int state) {
        handleScrollState(state);
        mScrollState = state;
    }

    private void handleScrollState(final int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            setNextItemIfNeeded();
        }
    }

    private void setNextItemIfNeeded() {
        if (!isScrollStateSettling()) {
            handleSetNextItem();
        }
    }

    private boolean isScrollStateSettling() {
        return mScrollState == ViewPager.SCROLL_STATE_SETTLING;
    }

    private void handleSetNextItem() {
        final int lastPosition = mViewPager.getAdapter().getCount() - 1;
        if(mCurrentPosition == 0) {
            mViewPager.setCurrentItem(lastPosition, false);
        } else if(mCurrentPosition == lastPosition) {
            mViewPager.setCurrentItem(0, false);
        }
    }

    @Override
    public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
    }
}

ViewPager에 onPageChangeListener로 설정하기 만하면됩니다. (** 이제 사용되지 않음 ** 편집 메모 확인)

viewPager.setOnPageChangeListener(new CircularViewPagerHandler(viewPager));

ViewPager의 "끝"에서이 파란색 빛을 피하려면 ViewPager가있는 xml에이 줄을 적용해야합니다.

android:overScrollMode="never"

위의 솔루션을 개선하고 github에 작은 라이브러리를 만들었습니다 . 그것을 확인하시기 바랍니다 :)

편집 :: @Bhavana 주석에 따라 나중에 더 이상 사용되지 않으므로 setOnPageChangeListener 대신 addOnPageChangeListener를 사용하십시오.

 viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager));

방금이 질문을보고 해결책을 찾았습니다. 솔루션 링크

onPageScrollStateChanged 함수를 다음과 같이 변경하십시오. 4 개의이 있다고 가정 합니다.

private int previousState, currentState;

public void onPageScrollStateChanged(int state) {              

            int currentPage = viewPager.getCurrentItem();       //ViewPager Type
            if (currentPage == 3 || currentPage == 0){
                previousState = currentState;
                currentState = state;
                if (previousState == 1 && currentState == 0){

                    viewPager.setCurrentItem(currentPage == 0 ? 3 : 0);

                }

            }

        }

경우] 상태 변수가 존재하는 탭에 있고, 그 값은 1이다.

첫 번째 탭 이전 또는 마지막 탭 이후 탐색을 시도하면 0이됩니다 . 따라서 코드에 표시된대로 이전 상태와 현재 상태를 비교하면 완료됩니다.

여기에서 onPageScrollStateChanged 함수를 참조 하십시오 .

도움이 되었기를 바랍니다.


이를 수행하는 또 다른 방법은보기 페이저의 첫 번째 요소의 가짜 사본을 어댑터 끝에 추가하고 마지막 요소의 가짜 사본을 시작 부분에 추가하는 것입니다. 그런 다음 뷰 페이저에서 첫 번째 / 마지막 요소를 볼 때 애니메이션없이 페이지를 변경합니다.

따라서 기본적으로 뷰 페이저의 첫 번째 / 마지막 요소는 가짜이며 올바른 애니메이션을 생성 한 다음 끝 / 시작으로 전환합니다. 예:

list.add(list.get(0)); //add the first item again as the last, to create the wrap effect
list.add(0, list.get(list.size()-2)); //insert a copy of the last item as the new first item, so we can scroll backwards too

viewPager.setAdapter(list);
viewPager.setCurrentItem(1, false); //default to the second element (because the first is now a fake)

viewPager.setOnPageChangeListener(new OnPageChangeListener() {
private void handleScrollState(final int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) { //this is triggered when the switch to a new page is complete
            final int lastPosition = viewPager.getAdapter().getCount() - 1;
            if (currentPosition == lastPosition) {
                viewPager.setCurrentItem(1, false); //false so we don't animate
            } else if (currentPosition == 0) {
                viewPager.setCurrentItem(lastPosition -1, false);
            }
        }

}

이것은 반의어가 원하는 것을 거의 달성하지만 A-> C에서 벗어나지 않습니다. (격렬하게 테스트하지는 않았지만 충분히 잘 작동하는 것 같습니다)

public class MyAdapter extends PagerAdapter {
    private List<Object> mItems;
    private int mFakeCount = 0;

    public MyAdapter(Context context, List<Object> items) {
        mItems = items;
        mFakeCount = mItems.size()+1;
    }

    @Override
    public int getCount() {
        return mFakeCount;
    }

    @Override
    public Object instantiateItem(View collection, int position) {
        // make the size larger, and change the position
        // to trick viewpager into paging forever
        if (position >= mItems.size()-1) {
            int newPosition = position%mItems.size();

            position = newPosition;
            mFakeCount++;
        }

        // do your layout inflating and what not here.
        // position will now refer to a correct index into your mItems list
        // even if the user has paged so many times that the view has wrapped
    }
}

나는 모든 제안, 솔루션, 라이브러리 등을 시도해 왔지만 순수한 원형이 아니며 대부분의 경우 3 페이지 만 지원하지 않습니다.

그래서 나는 ViewPagernew를 사용하여 순환 예제를 구현했고 ViewPager2, new ViewPager는 a RecyclerViewViewHolders를 사용하여 뷰 재활용을 처리하고 예상대로 작동합니다!

TLDR : GITHUB

이 예에서는 단일 활성 애플리케이션 구축한다 ViewPager2그리고 FragmentPagerAdapter3 장 이상의 사이에지지 원형 탐색.

라이브러리의 알파 버전을 사용하고 androidx.viewpager2:viewpager2있지만 버전 1.0.0-alpha06은 Google이 API를 동결하고 베타로 이동하기 전에 계획된 마지막 버전 입니다.

여기에 이미지 설명 입력

1. ViewPager2build.gradle의 종속성에 라이브러리를 추가합니다.

dependencies {
    implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha06'
}

2. ViewPager2프로젝트에보기를 추가합니다 .

<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/vwpHome"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3. FragmentStateAdapter어댑터를 만듭니다 .

getItemCount()huuuuge 번호를 반환해야합니다. (2147483647)

getCenterPage()huuuuge 번호를 기반으로 중앙 페이지를 반환합니다. 이 메서드는에서 설정할 초기 페이지의 위치를 ​​가져 오는 데 사용됩니다 ViewPager2.이 경우 사용자는에서 끝까지 도달하기 위해 ~ 1073741823 시간을 스 와이프해야합니다 ViewPager2.

class CircularPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) {

    override fun getItemCount() = Integer.MAX_VALUE

    /**
     * Create the fragment based on the position
     */
    override fun createFragment(position: Int) = HomePagerScreens.values()[position % HomePagerScreens.values().size].fragment.java.newInstance()

    /**
     * Returns the same id for the same Fragment.
     */
    override fun getItemId(position: Int): Long = (position % HomePagerScreens.values().size).toLong()

    fun getCenterPage(position: Int = 0) = Integer.MAX_VALUE / 2 + position
}

HomeScreens는 페이지 정보가 포함 된 ENUM입니다.

enum class HomePagerScreens(@StringRes val title: Int,
                            val fragment: KClass<out Fragment>) {

    HOME_1(R.string.home_1, FragmentHome::class),
    HOME_2(R.string.home_2, FragmentHome::class),
    HOME_3(R.string.home_3, FragmentHome::class)
}

4. 어댑터를 ViewPager로 설정합니다.

val circularAdapter = CircularPagerAdapter(supportFragmentManager, lifecycle)
vwpHome.apply {
    adapter = circularAdapter
    setCurrentItem(circularAdapter.getCenterPage(), false)
}

이것을 달성하는 방법에 대한 간단한 아이디어. 페이지가있는 배열이있을 때이 배열을 정확히 중간에있는 다른 배열에 추가하기 만하면됩니다.

예를 들어 15 개의 요소가있는 배열이 있습니다. 따라서 중간 (200 위치)에있는 400 요소 배열에 배치합니다.

다음으로 배열을 왼쪽과 오른쪽으로 채우면 이동할 수 있습니다.

샘플 이미지 :

여기에 이미지 설명 입력

어떻게 보입니까?

https://youtu.be/7up36ylTzXk

코드가 말하게하십시오 :)

https://gist.github.com/gelldur/051394d10f6f98e2c13d

public class CyclicPagesAdapter extends FragmentStatePagerAdapter {

    public CyclicPagesAdapter(FragmentManager fm) {
        super(fm);
    }

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

    @Override
    public int getCount() {
        return _fragments.size();
    }

    public void addFragment(FragmentCreator creator) {
        _fragments.add(creator);
    }

    public interface FragmentCreator {
        Fragment createInstance();
    }

    public void clear() {
        _fragments.clear();
    }

    public void add(FragmentCreator fragmentCreator) {
        _fragments.add(fragmentCreator);
    }

    public void finishAdding() {
        ArrayList<FragmentCreator> arrayList = new ArrayList<>(Arrays.asList(new FragmentCreator[MAX_PAGES]));
        arrayList.addAll(MAX_PAGES / 2, _fragments);

        int mrPointer = _fragments.size() - 1;
        for (int i = MAX_PAGES / 2 - 1; i > -1; --i) {
            arrayList.set(i, _fragments.get(mrPointer));
            --mrPointer;
            if (mrPointer < 0) {
                mrPointer = _fragments.size() - 1;
            }
        }

        mrPointer = 0;
        for (int i = MAX_PAGES / 2 + _fragments.size(); i < arrayList.size(); ++i) {
            arrayList.set(i, _fragments.get(mrPointer));
            ++mrPointer;
            if (mrPointer >= _fragments.size()) {
                mrPointer = 0;
            }
        }
        _fragmentsRaw = _fragments;
        _fragments = arrayList;
    }

    public int getStartIndex() {
        return MAX_PAGES / 2;
    }

    public int getRealPagesCount() {
        return _fragmentsRaw.size();
    }

    public int getRealPagePosition(int index) {
        final FragmentCreator fragmentCreator = _fragments.get(index);
        for (int i = 0; i < _fragmentsRaw.size(); ++i) {
            if (fragmentCreator == _fragmentsRaw.get(i)) {
                return i;
            }
        }
        //Fail...
        return 0;
    }

    private static final int MAX_PAGES = 500;
    public ArrayList<FragmentCreator> _fragments = new ArrayList<>();
    public ArrayList<FragmentCreator> _fragmentsRaw;
}

나는 효과가 있다고 믿는 해결책이 있습니다. 테스트되지는 않았지만 여기에 있습니다.

FragmentPagerAdapter를 수퍼 클래스로 둘러싼 래퍼 :

public abstract class AFlexibleFragmentPagerAdapter extends
        FragmentPagerAdapter
{
    public AFlexibleFragmentPagerAdapter(FragmentManager fm)
    {
        super(fm);
        // TODO Auto-generated constructor stub
    }

    public abstract int getRealCount();

    public abstract int getStartPosition();

    public abstract int transformPosition(int position);
};

그런 다음 표준 FragmentPagerAdapter 구현은이 새로운 추상 클래스를 하위 클래스로 만듭니다.

public class SomeFragmentPagerAdapter extends
        AFlexibleFragmentPagerAdapter
{

    private Collection<Fragment> someContainer;

    public SomeFragmentPagerAdapter(FragmentManager fm, Container<Fragment> fs)
    {
        super(fm);
        someContainer = fs;
    }

    @Override
    public int getRealCount() { return someContainer.size(); }

    @Override
    public int getStartPosition() { return 0; }

    @Override
    public int transformPosition(int position) { return position; }
};

그리고 Cyclic Pager Adapter 구현.

public class SomeCyclicFragmentPagerAdapter extends
        SomeFragmentPagerAdapter 
{
    private int realCount = 0;
    private int dummyCount = 0;
    private static final int MULTI = 3;

    public SomeFragmentPagerAdapter(FragmentManager fm, ...)
    {
        super(fm);
        realCount = super.getCount();
        dummyCount *= MULTI;
    }

    @Override
    public int getCount() { return dummyCount; }

    @Override
    public int getRealCount() { return realCount; }

    @Override
    public int getStartPosition() { return realCount; }

    @Override
    public int transformPosition(int position)
    {
        if (position < realCount) position += realCount;
        else if (position >= (2 * realCount)) position -= realCount;

        return position;
    }
};

ViewPager 초기화

    this.mViewPager.setCurrentItem(this.mPagerAdapter.getStartPosition());

마지막으로 콜백 구현 :

    @Override
    public void onPageSelected(int position)
    {
        this.mTabHost.setCurrentTab(position % mPagerAdapter.getRealCount());
        this.mViewPager.setCurrentItem(this.mPagerAdapter
                .transformPosition(position));
    }

    @Override
    public void onTabChanged(String tabId)
    {
        int pos = this.mTabHost.getCurrentTab();

        this.mViewPager.setCurrentItem(this.mPagerAdapter
                .transformPosition(pos));
    }

  1. 항목에 대해 설정하면 큰 값 (예 : 500000)이 계산됩니다.
  2. 어댑터에서이 값 * 실제 항목 수를 반환합니다.
  3. getItem (int i)에서 i를-> i = i % 실제 항목 수로 조정합니다.
  4. onCreate (Bundle savedInstanceState)에서 500000/2 페이지의 현재 항목을 설정합니다.

    public class MainActivity extends FragmentActivity {
    
        private final static int ITEMS_MAX_VALUE = 500000;
        private int actualNumCount = 10;
        private ViewPager mPager = null;
    
        private class OptionPageAdapter extends FragmentStatePagerAdapter {
            public OptionPageAdapter(FragmentManager fm) {
                super(fm);
            }
    
            @Override
            public Fragment getItem(int i) {
                try {
                    i = i % OptionType.values().length;
                    OptionPage optionPage = new OptionPage(i);
                    optionPage.id = i;
                    return optionPage;
                } catch (Exception e) {
                    VayoLog.log(Level.WARNING, "Unable to create OptionFragment for id: " + i, e);
                }
                return null;
            }
    
            @Override
            public int getCount() {
                return ITEMS_MAX_VALUE * actualNumCount;
            }
        }
    
        public static class OptionPage extends Fragment {
            private int id = 0
            @Nullable
            @Override
            public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
                View mainView = inflater.inflate(R.layout.main_page, container, false);
                return mainView;
            }
    
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            mPager = (ViewPager) findViewById(R.id.main_pager);
            mPager.setOffscreenPageLimit(3);
            mPager.setAdapter(new OptionPageAdapter(this.getSupportFragmentManager()));
            mPager.setCurrentItem(ITEMS_MAX_VALUE/2,false);
    
        }
    
    }
    

  1. 목록 시작 부분에 마지막 요소의 가짜 사본을 추가하십시오.
  2. 끝에 첫 번째 요소의 가짜 사본을 추가하십시오.
  3. 초기화 코드의 어딘가에서 실제 첫 번째 요소를 선택하십시오.

        mViewPager.setCurrentItem(1);
    
  4. setOnPageChangeListener 수정 :

        @Override
        public void onPageSelected(int state) {
            if (state == ITEMS_NUMBER + 1) {
                mViewPager.setCurrentItem(1, false);
            } else if (state == 0) {
                mViewPager.setCurrentItem(ITEMS_NUMBER, false);
            }
        }
    

이 접근 방식을 기반으로

참 루프, 호출기 제목 스트립, 참 새로 고침 스크롤 없음

private static final int ITEM_NUM = 5 ; // 5 for smooth pager title strip
private void addFragment(String title, String className) {
    if (fragments.size() < ITEM_NUM) {
        Bundle b = new Bundle();
        b.putInt("pos", fragments.size());
        fragments.add(Fragment.instantiate(this, className, b));

    }
    titles.add(title);
    itemList.add(className);
}

페이지 어댑터보기 :

public static class MyFragmentPageAdapter extends FragmentPagerAdapter {

    private HashMap<Long, Fragment> mItems = new HashMap<Long, Fragment>();

    public MyFragmentPageAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    @Override
    public long getItemId(int position) {
        int id = java.lang.System.identityHashCode(fragments.get(position));
        return id;
    }

    @Override
    public Fragment getItem(int position) {
        long id = getItemId(position);
        if (mItems.get(id) != null) {
            return mItems.get(id);
        }
        Fragment f = fragments.get(position);
        return f;
    }

    @Override
    public int getCount() {
        return ITEM_NUM;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        String t = titles.get(getItem(position).getArguments()
                .getInt("pos"));
        return t;
    }

    @Override
    public int getItemPosition(Object object) {
        for (int i = 0; i < ITEM_NUM; i++) {
            Fragment item = (Fragment) fragments.get(i);
            if (item.equals((Fragment) object)) {
                return i;
            }
        }
        return POSITION_NONE;
    }
}

사용 :

    itemList = new ArrayList<String>();
    fragments = new ArrayList<Fragment>();
    titles = new ArrayList<String>();

    addFragment("Title1", Fragment1.class.getName());
    ...
    addFragment("TitleN", FragmentN.class.getName());

    mAdapter = new MyFragmentPageAdapter(getSupportFragmentManager());
    mViewPager = (ViewPager) findViewById(R.id...);

    mViewPager.setAdapter(mAdapter);
    mViewPager.setCurrentItem(2);
    currPage = 2;
    visibleFragmentPos = 2;

    mViewPager.setOnPageChangeListener(new OnPageChangeListener() {

        @Override
        public void onPageSelected(int curr) {
            dir = curr - currPage;
            currPage = curr;
        }

        private void shiftRight() {
            fragments.remove(0);
            int pos = visibleFragmentPos + 3;
            if (pos > itemList.size() - 1)
                pos -= itemList.size();
            if (++visibleFragmentPos > itemList.size() - 1)
                visibleFragmentPos = 0;
            insertItem(4, pos);

        }

        private void shiftLeft() {
            fragments.remove(4);
            int pos = visibleFragmentPos - 3;
            if (pos < 0)
                pos += itemList.size();
            if (--visibleFragmentPos < 0)
                visibleFragmentPos = itemList.size() - 1;
            insertItem(0, pos);
        }

        private void insertItem(int datasetPos, int listPos) {
            Bundle b = new Bundle();
            b.putInt("pos", listPos);
            fragments.add(datasetPos,
                    Fragment.instantiate(ctx, itemList.get(listPos), b));
        }

        @Override
        public void onPageScrollStateChanged(int state) {

            if (state == ViewPager.SCROLL_STATE_IDLE) {
                if (dir == 1) {
                    shiftRight();
                } else if (dir == -1) {
                    shiftLeft();

                }
                mAdapter.notifyDataSetChanged();
                currPage = 2;
            }
        }
    });

https://github.com/pozitiffcat/cyclicview 에서 간단한보기를 사용할 수 있습니다
. 기능 중 하나는 다음과 같습니다.

첫 번째와 마지막 뷰를 복제하지 않고 대신 비트 맵 변형을 사용합니다.

예:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CyclicView cyclicView = (CyclicView) findViewById(R.id.cyclic_view);
        cyclicView.setAdapter(new CyclicAdapter() {
            @Override
            public int getItemsCount() {
                return 10;
            }

            @Override
            public View createView(int position) {
                TextView textView = new TextView(MainActivity.this);
                textView.setText(String.format("TextView #%d", position + 1));
                return textView;
            }

            @Override
            public void removeView(int position, View view) {
                // Do nothing
            }
        });
    }
}

늦어서 미안하지만 대답을 봤고 나는 참을 수 없었고 대답해야했습니다 :).

어댑터의 get count 메소드에서 다음과 같이 진행하십시오.

 @Override
    public int getCount() {
        return myList.size() % Integer.MAX_VALUE; 
  }

작동합니다!

참고 URL : https://stackoverflow.com/questions/7546224/viewpager-as-a-circular-queue-wrapping

반응형