Czy istnieje sposób, aby ViewPager
przewijać nie w poziomie, ale w pionie ?!
android
android-viewpager
user1709805
źródło
źródło
Odpowiedzi:
Możesz użyć ViewPager.PageTransformer, aby stworzyć iluzję pionu
ViewPager
. Aby uzyskać przewijanie w pionie zamiast w poziomie, będziesz musiał zastąpićViewPager
domyślne zdarzenia dotyku i zamienić współrzędneMotionEvent
s przed ich obsługą, np .:/** * Uses a combination of a PageTransformer and swapping X & Y coordinates * of touch events to create the illusion of a vertically scrolling ViewPager. * * Requires API 11+ * */ public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { super(context); init(); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { // The majority of the magic happens here setPageTransformer(true, new VerticalPageTransformer()); // The easiest way to get rid of the overscroll drawing that happens on the left and right setOverScrollMode(OVER_SCROLL_NEVER); } private class VerticalPageTransformer implements ViewPager.PageTransformer { @Override public void transformPage(View view, float position) { if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 1) { // [-1,1] view.setAlpha(1); // Counteract the default slide transition view.setTranslationX(view.getWidth() * -position); //set Y position to swipe in from top float yPosition = position * view.getHeight(); view.setTranslationY(yPosition); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } } /** * Swaps the X and Y coordinates of your touch event. */ private MotionEvent swapXY(MotionEvent ev) { float width = getWidth(); float height = getHeight(); float newX = (ev.getY() / height) * width; float newY = (ev.getX() / width) * height; ev.setLocation(newX, newY); return ev; } @Override public boolean onInterceptTouchEvent(MotionEvent ev){ boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); swapXY(ev); // return touch coordinates to original reference frame for any child views return intercepted; } @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(swapXY(ev)); } }
Oczywiście możesz dostosować te ustawienia według własnego uznania. Kończy się tak:
źródło
ViewPager w pionie
Wszystkie inne odpowiedzi wydawały się mieć z nimi pewne problemy. Nawet zalecane projekty open source nie łączyły ostatnich żądań ściągnięcia z poprawkami błędów. Potem znalazłem
VerticalViewPager
od Google, że używali jakiejś aplikacji DeskClock. Ufam implementacji Google znacznie bardziej niż innym odpowiedziom krążącym tutaj. (Wersja Google jest podobna do aktualnie najczęściej głosowanej odpowiedzi, ale jest dokładniejsza).Pełny przykład
Ten przykład jest prawie dokładnie taki sam, jak mój przykład Basic ViewPager , z kilkoma drobnymi zmianami w celu wykorzystania tej
VerticalViewPager
klasy.XML
Dodaj układy xml dla głównego działania i dla każdej strony (fragmentu). Zwróć uwagę, że używamy
VerticalViewPager
zamiast standarduViewPager
. Dołączę do tego kod poniżej.activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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" tools:context="com.example.verticalviewpager.MainActivity"> <com.example.myapp.VerticalViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
fragment_one.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/textview" android:textSize="30sp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </RelativeLayout>
Kod
Kod
VerticalViewPager
nie jest mój. To tylko okrojona wersja (bez komentarzy) z kodu źródłowego Google. Sprawdź ten, aby uzyskać najbardziej aktualną wersję. Komentarze są również pomocne w zrozumieniu tego, co się dzieje.VerticalViewPager.java
import android.support.v4.view.ViewPager; public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { this(context, null); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } @Override public boolean canScrollHorizontally(int direction) { return false; } @Override public boolean canScrollVertically(int direction) { return super.canScrollHorizontally(direction); } private void init() { setPageTransformer(true, new VerticalPageTransformer()); setOverScrollMode(View.OVER_SCROLL_NEVER); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final boolean toIntercept = super.onInterceptTouchEvent(flipXY(ev)); flipXY(ev); return toIntercept; } @Override public boolean onTouchEvent(MotionEvent ev) { final boolean toHandle = super.onTouchEvent(flipXY(ev)); flipXY(ev); return toHandle; } private MotionEvent flipXY(MotionEvent ev) { final float width = getWidth(); final float height = getHeight(); final float x = (ev.getY() / height) * width; final float y = (ev.getX() / width) * height; ev.setLocation(x, y); return ev; } private static final class VerticalPageTransformer implements ViewPager.PageTransformer { @Override public void transformPage(View view, float position) { final int pageWidth = view.getWidth(); final int pageHeight = view.getHeight(); if (position < -1) { view.setAlpha(0); } else if (position <= 1) { view.setAlpha(1); view.setTranslationX(pageWidth * -position); float yPosition = position * pageHeight; view.setTranslationY(yPosition); } else { view.setAlpha(0); } } } }
MainActivity.java
Zamieniłem się tylko
VerticalViewPager
naViewPager
.import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; public class MainActivity extends AppCompatActivity { static final int NUMBER_OF_PAGES = 2; MyAdapter mAdapter; VerticalViewPager mPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mAdapter = new MyAdapter(getSupportFragmentManager()); mPager = findViewById(R.id.viewpager); mPager.setAdapter(mAdapter); } public static class MyAdapter extends FragmentPagerAdapter { public MyAdapter(FragmentManager fm) { super(fm); } @Override public int getCount() { return NUMBER_OF_PAGES; } @Override public Fragment getItem(int position) { switch (position) { case 0: return FragmentOne.newInstance(0, Color.WHITE); case 1: // return a different Fragment class here // if you want want a completely different layout return FragmentOne.newInstance(1, Color.CYAN); default: return null; } } } public static class FragmentOne extends Fragment { private static final String MY_NUM_KEY = "num"; private static final String MY_COLOR_KEY = "color"; private int mNum; private int mColor; // You can modify the parameters to pass in whatever you want static FragmentOne newInstance(int num, int color) { FragmentOne f = new FragmentOne(); Bundle args = new Bundle(); args.putInt(MY_NUM_KEY, num); args.putInt(MY_COLOR_KEY, color); f.setArguments(args); return f; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mNum = getArguments() != null ? getArguments().getInt(MY_NUM_KEY) : 0; mColor = getArguments() != null ? getArguments().getInt(MY_COLOR_KEY) : Color.BLACK; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_one, container, false); v.setBackgroundColor(mColor); TextView textView = v.findViewById(R.id.textview); textView.setText("Page " + mNum); return v; } } }
Skończone
Powinieneś móc uruchomić aplikację i zobaczyć wynik na powyższej animacji.
Zobacz też
źródło
Mam rozwiązanie, które działa dla mnie w dwóch krokach.
onInstantiateItem()
zPagerAdapter
, utwórz widok i obróć go o -90:view.setRotation(-90f)
Jeśli używasz
FragmentPagerAdapter
, to:objFragment.getView().setRotation(-90)
Obróć
ViewPager
widok o 90 stopni:objViewPager.setRotation(90)
Działa jak urok, przynajmniej na moje wymagania.
źródło
Dla kogoś, kto boryka się z tym, jak sprawić, by ViewPager działał w pionie w poziomie, wykonaj następujące czynności:
ViewPager w pionie
public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { super(context); init(); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { setPageTransformer(true, new VerticalPageTransformer()); setOverScrollMode(OVER_SCROLL_NEVER); } private class VerticalPageTransformer implements ViewPager.PageTransformer { @Override public void transformPage(View view, float position) { if (position < -1) { view.setAlpha(0); } else if (position <= 1) { view.setAlpha(1); view.setTranslationX(view.getWidth() * -position); float yPosition = position * view.getHeight(); view.setTranslationY(yPosition); } else { view.setAlpha(0); } } } private MotionEvent swapXY(MotionEvent ev) { float width = getWidth(); float height = getHeight(); float newX = (ev.getY() / height) * width; float newY = (ev.getX() / width) * height; ev.setLocation(newX, newY); return ev; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); swapXY(ev); return intercepted; } @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(swapXY(ev)); } }
Poziomy ViewPager
public class HorizontalViewPager extends ViewPager { private GestureDetector xScrollDetector; public HorizontalViewPager(Context context) { super(context); } public HorizontalViewPager(Context context, AttributeSet attrs) { super(context, attrs); xScrollDetector = new GestureDetector(getContext(), new XScrollDetector()); } class XScrollDetector extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return Math.abs(distanceX) > Math.abs(distanceY); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (xScrollDetector.onTouchEvent(ev)) { super.onInterceptTouchEvent(ev); return true; } return super.onInterceptTouchEvent(ev); } }
źródło
Niewielkie rozszerzenie tej odpowiedzi pomogło mi spełnić moje wymagania (Pager z widokiem pionowym do animacji podobnej jak w Inshorts - Wiadomości w 60 słowach )
public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { super(context); init(); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { // The majority of the magic happens here setPageTransformer(true, new VerticalPageTransformer()); // The easiest way to get rid of the overscroll drawing that happens on the left and right setOverScrollMode(OVER_SCROLL_NEVER); } private class VerticalPageTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.75f; @Override public void transformPage(View view, float position) { if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 0) { // [-1,0] // Use the default slide transition when moving to the left page view.setAlpha(1); // Counteract the default slide transition view.setTranslationX(view.getWidth() * -position); //set Y position to swipe in from top float yPosition = position * view.getHeight(); view.setTranslationY(yPosition); view.setScaleX(1); view.setScaleY(1); } else if (position <= 1) { // [0,1] view.setAlpha(1); // Counteract the default slide transition view.setTranslationX(view.getWidth() * -position); // Scale the page down (between MIN_SCALE and 1) float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)); view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } } /** * Swaps the X and Y coordinates of your touch event. */ private MotionEvent swapXY(MotionEvent ev) { float width = getWidth(); float height = getHeight(); float newX = (ev.getY() / height) * width; float newY = (ev.getX() / width) * height; ev.setLocation(newX, newY); return ev; } @Override public boolean onInterceptTouchEvent(MotionEvent ev){ boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); swapXY(ev); // return touch coordinates to original reference frame for any child views return intercepted; } @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(swapXY(ev)); } }
Mam nadzieję, że to może być pomocne dla kogoś innego.
źródło
Istnieje kilka projektów open source, które twierdzą, że to robią. Tutaj są:
Zostały one oznaczone przez ich autorów jako przestarzałe:
I jeszcze jedno:
deskclock
aplikacji, ale nie wydaje się, aby była oficjalnie obsługiwana, ponieważ nie mogę znaleźć dokumentacji.źródło
Sprawdź to: https://github.com/JakeWharton/Android-DirectionalViewPager
Lub poniższe pytanie może Ci pomóc: Pionowy „Widok siatki ze stronami” lub „Viewpager”
źródło
DirectionalViewPager
nie był on aktualizowany od dłuższego czasu i dlatego brakuje w nim niektórych nowszych funkcjiViewPager
w bibliotece wsparcia; tjsetPageMargin(int)
. Generalnie jednak powinien wystarczyć. A jeśli nie, pobranie kodu źródłowegoViewPager
i zamiana całej logiki x / y i szerokości / wysokości nie jest zbyt trudne .DirectionalViewPager
ale, jak powiedział MH, nie jest aktualizowany (jego twórca uważa to za DEPRECATED) !! To niesamowite, że programiści Androida nie zapewnili społeczności pionuViewPager
, a jedynie poziom. Jestem nowicjuszem na Androidzie, rozwijanie mojej pionowejViewPager
zamiany x / y nie jest dla mnie takie łatwe .. wolałbym już zbudowane rozwiązanie .. w każdym razie, dziękujęTo tylko ulepszenie odpowiedzi z @Brett i @ Salman666, aby poprawnie przekształcić współrzędne (X, Y) na (Y, X), ponieważ wyświetlacze urządzeń są prostokątne:
...
/** * Swaps the X and Y coordinates of your touch event */ @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(swapXY(ev)); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(swapXY(ev)); } private MotionEvent swapXY(MotionEvent ev) { //Get display dimensions float displayWidth=this.getWidth(); float displayHeight=this.getHeight(); //Get current touch position float posX=ev.getX(); float posY=ev.getY(); //Transform (X,Y) into (Y,X) taking display dimensions into account float newPosX=(posY/displayHeight)*displayWidth; float newPosY=(1-posX/displayWidth)*displayHeight; //swap the x and y coords of the touch event ev.setLocation(newPosX, newPosY); return ev; }
...
Jednak nadal trzeba coś zrobić, aby poprawić reakcję ekranu dotykowego. Problem może być związany z tym, co @msdark skomentował odpowiedź @ Salman666.
źródło
true
wonInterceptTouchEvent
, a ja również zmodyfikowała klawiszy, dzięki czemu za pomocą D-pada wywołuje przewijania z górę / w dół zamiast w lewo / w prawo: gist.github.com/zevektor/fbacf4f4f6c39fd6e409import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { super(context); init(); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { setPageTransformer(true, new VerticalPageTransformer()); setOverScrollMode(OVER_SCROLL_NEVER); } private class VerticalPageTransformer implements PageTransformer { @Override public void transformPage(View view, float position) { int pageWidth = view.getWidth(); int pageHeight = view.getHeight(); if (position < -1) { view.setAlpha(0); } else if (position <= 1) { view.setAlpha(1); view.setTranslationX(pageWidth * -position); float yPosition = position * pageHeight; view.setTranslationY(yPosition); } else { view.setAlpha(0); } } } @Override public boolean onTouchEvent(MotionEvent ev) { ev.setLocation(ev.getY(), ev.getX()); return super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { ev.setLocation(ev.getY(), ev.getX()); return super.onInterceptTouchEvent(ev); } }
źródło
Skończyło się na utworzeniu nowego DirectionalViewPager, który może przewijać w pionie lub w poziomie, ponieważ wszystkie rozwiązania, które tu widziałem, mają wady:
Istniejące implementacje VerticalViewPager (autorstwa Castorflex i LambergaR)
Sztuczka transformacji z zamianą współrzędnych
VelocityTracker.computeCurrentVelocity
nadal oblicza prędkość z osią X, prawdopodobnie dlatego, że wewnętrznie używa się wywołania natywnego, które ignoruje zamianę współrzędnych.Zobacz obrót
źródło
Właściwie jest już jeden w źródle Androida . Zmodyfikowałem go, aby działał lepiej:
package com.crnlgcs.sdk.widgets; import android.content.Context; import android.support.v4.view.ViewConfigurationCompat; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; public class VerticalViewPager extends ViewPager { private static final String TAG = "VerticalViewPager"; private static final boolean DEBUG = true; private float mLastMotionX; private float mLastMotionY; private float mTouchSlop; private boolean mVerticalDrag; private boolean mHorizontalDrag; // Vertical transit page transformer private final ViewPager.PageTransformer mPageTransformer = new ViewPager.PageTransformer() { @Override public void transformPage(View view, float position) { final int pageWidth = view.getWidth(); final int pageHeight = view.getHeight(); if (position < -1) { // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 1) { view.setAlpha(1); // Counteract the default slide transition view.setTranslationX(pageWidth * -position); // set Y position to swipe in from top float yPosition = position * pageHeight; view.setTranslationY(yPosition); } else { // This page is way off-screen to the right. view.setAlpha(0); } } }; public VerticalViewPager(Context context) { super(context, null); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); init(); } private void init() { // Make page transit vertical setPageTransformer(true, mPageTransformer); // Get rid of the overscroll drawing that happens on the left and right (the ripple) setOverScrollMode(View.OVER_SCROLL_NEVER); } @Override public boolean onTouchEvent(MotionEvent ev) { final float x = ev.getX(); final float y = ev.getY(); if (DEBUG) Log.v(TAG, "onTouchEvent " + x + ", " + y); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { mLastMotionX = x; mLastMotionY = y; if (!super.onTouchEvent(ev)) return false; return verticalDrag(ev); } case MotionEvent.ACTION_MOVE: { final float xDiff = Math.abs(x - mLastMotionX); final float yDiff = Math.abs(y - mLastMotionY); if (!mHorizontalDrag && !mVerticalDrag) { if (xDiff > mTouchSlop && xDiff > yDiff) { // Swiping left and right mHorizontalDrag = true; } else if (yDiff > mTouchSlop && yDiff > xDiff) { //Swiping up and down mVerticalDrag = true; } } if (mHorizontalDrag) { return super.onTouchEvent(ev); } else if (mVerticalDrag) { return verticalDrag(ev); } } case MotionEvent.ACTION_UP: { if (mHorizontalDrag) { mHorizontalDrag = false; return super.onTouchEvent(ev); } if (mVerticalDrag) { mVerticalDrag = false; return verticalDrag(ev); } } } // Set both flags to false in case user lifted finger in the parent view pager mHorizontalDrag = false; mVerticalDrag = false; return false; } private boolean verticalDrag(MotionEvent ev) { final float x = ev.getX(); final float y = ev.getY(); ev.setLocation(y, x); return super.onTouchEvent(ev); } }
źródło
Obecnie bardziej aktywnym repozytorium jest lsjwzh / RecyclerViewPager .
RecyclerView
źródło
Jest to pionowa, przewijalna implementacja ViewPagera, która dobrze współpracuje z RecyclerView i ListView. guochong / VerticalViewPager .
użyj ViewPager.PageTransformer, aby ViewPager przewijał się w pionie, a także zapewnij View.OnTouchListener, aby poradzić sobie z konfliktem przewijania.
/** * 1.dispatch ACTION_DOWN,ACTION_UP,ACTION_CANCEL to child<br> * 2.hack ACTION_MOVE * * @param v * @param e * @return */ @Override public boolean onTouch(View v, MotionEvent e) { Log.i(TAG, "onTouchEvent " + ", action " + e.getAction() + ", e.rawY " + e.getRawY() + ",lastMotionY " + lastMotionY + ",downY " + downY); switch (e.getAction()) { case MotionEvent.ACTION_DOWN: downY = e.getRawY(); break; case MotionEvent.ACTION_MOVE: if (downY == Float.MIN_VALUE && lastMotionY == Float.MIN_VALUE) { //not start from MOTION_DOWN, the child dispatch this first motion downY = e.getRawY(); break; } float diff = e.getRawY() - (lastMotionY == Float.MIN_VALUE ? downY : lastMotionY); lastMotionY = e.getRawY(); diff = diff / 2; //slow down viewpager scroll Log.e(TAG, "scrollX " + dummyViewPager.getScrollX() + ",basescrollX " + dummyViewPager.getBaseScrollX()); if (dummyViewPager.getScrollX() != dummyViewPager.getBaseScrollX()) { if (fakeDragVp(v, e, diff)) return true; } else { if (ViewCompat.canScrollVertically(v, (-diff) > 0 ? 1 : -1)) { Log.e(TAG, "scroll vertically " + diff + ", move.lastMotionY " + e.getY()); break; } else { dummyViewPager.beginFakeDrag(); fakeDragVp(v, e, diff); adjustDownMotion(v, e); return true; } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (dummyViewPager.isFakeDragging()) { try { dummyViewPager.endFakeDrag(); } catch (Exception e1) { Log.e(TAG, "", e1); } } reset(); break; } return false; }
źródło
Nawet ja miałem ten sam problem z ustawieniem ViewPagera w pionie przez zamianę X i Y. Nie było to wcale gładkie.
Dzieje się tak, ponieważ kiedyś działało tylko wtedy, gdy kąt przesuwania był mniejszy niż 7,125 stopnia = arctan (1/8) podczas przecięcia i 14 stopni = arctan (1/4) podczas dotyku; tak jak oryginalny ViewPager poziomy działa, gdy kąt przesuwania jest mniejszy niż 26,565 stopni = arctan (1/2) podczas przecięcia i 45 stopni = arctan (1) podczas dotykania.
Dlatego skopiowałem kod Android support-core-ui i przeniosłem wartości do zmiennych, które pomnożyłem za pomocą odbicia.
Kod i plik README można znaleźć pod adresem https://github.com/deepakmishra/verticalviewpager i VerticalViewPager pod adresem https://github.com/deepakmishra/verticalviewpager/blob/master/app/src/main/java/com/viewpager/VerticalViewPager .Jawa
źródło
lepszym sposobem jest obrócenie podglądu o 90 stopni i użycie constraintLayout do dostosowania położenia.
źródło