Przewiń widok w pionie i poziomie w systemie Android

152

Jestem naprawdę zmęczony szukaniem rozwiązania dla widoku Scrollview w pionie i poziomie.

Czytałem, że w frameworku nie ma żadnych widoków / układów implementujących tę funkcję, ale potrzebuję czegoś takiego:

Muszę zdefiniować układ w innym, układ podrzędny musi zaimplementować przewijanie w pionie / poziomie do przenoszenia.

Początkowo zaimplementowano kod, który przesuwał układ piksel po pikselu, ale myślę, że to nie jest właściwy sposób. Wypróbowałem to z ScrollView i Horizontal ScrollView, ale nic nie działa tak, jak chcę, ponieważ implementuje tylko przewijanie w pionie lub w poziomie.

Canvas nie jest moim rozwiązaniem, ponieważ muszę dołączyć słuchacze w czyichś elementach potomnych.

Co mogę zrobić?

Kronos
źródło
14
To absolutnie śmieszne, że nie jest to dostępne po wyjęciu z pudełka z Androidem. Pracowaliśmy z pół tuzinem innych technologii interfejsu użytkownika i jest czymś niezwykłym, aby nie mieć tak podstawowej rzeczy, jak kontener z przewijaniem poziomym / pionowym.
flexicious.com
Ok, więc w końcu napisaliśmy własny dla naszego datagrid: androidjetpack.com/Home/AndroidDataGrid . Robi znacznie więcej niż tylko przewijanie w poziomie / w pionie. Ale chodzi w zasadzie o zawinięcie HScrollera wewnątrz Vertical Scroller. Musisz także zastąpić metody add / remove child, aby kierować na wewnętrzny scroller i kilka innych sztuczek, ale to działa.
flexicious.com,

Odpowiedzi:

91

Łącząc niektóre z powyższych sugestii, udało się uzyskać dobre rozwiązanie:

Niestandardowy widok przewijania:

package com.scrollable.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ScrollView;

public class VScroll extends ScrollView {

    public VScroll(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

    public VScroll(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
}

Niestandardowy poziomy widok przewijania:

package com.scrollable.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;

public class HScroll extends HorizontalScrollView {

    public HScroll(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

    public HScroll(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
}

ScrollableImageActivity:

package com.scrollable.view;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;

public class ScrollableImageActivity extends Activity {

    private float mx, my;
    private float curX, curY;

    private ScrollView vScroll;
    private HorizontalScrollView hScroll;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        vScroll = (ScrollView) findViewById(R.id.vScroll);
        hScroll = (HorizontalScrollView) findViewById(R.id.hScroll);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float curX, curY;

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                mx = event.getX();
                my = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                curX = event.getX();
                curY = event.getY();
                vScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                hScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                mx = curX;
                my = curY;
                break;
            case MotionEvent.ACTION_UP:
                curX = event.getX();
                curY = event.getY();
                vScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                hScroll.scrollBy((int) (mx - curX), (int) (my - curY));
                break;
        }

        return true;
    }

}

Układ:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <com.scrollable.view.VScroll android:layout_height="fill_parent"
        android:layout_width="fill_parent" android:id="@+id/vScroll">
        <com.scrollable.view.HScroll android:id="@+id/hScroll"
            android:layout_width="fill_parent" android:layout_height="fill_parent">
            <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:src="@drawable/bg"></ImageView>
        </com.scrollable.view.HScroll>
    </com.scrollable.view.VScroll>

</LinearLayout>
Mahdi Hijazi
źródło
To świetna odpowiedź, miałem inne przewijane elementy w widoku, które również wymagały zmiany. Wszystko, co musiałem zrobić, to uruchomić scrollby razem z innymi.
T. Markle
1
Wspaniały! Kilka poprawek, aby wziąć pod uwagę prędkość, a to byłoby idealne (również, LinearLayoutmyślę , że nie potrzebujesz początkowego )
Romuald Brunet
Hej Mahdi, czy możesz dać mi znać, na której kontrolce / widżecie uruchomiłeś słuchacz dotyku w klasie aktywności.
Shachillies,
Przepraszamy za opóźnienie @DeepakSharma. Nie zarejestrowałem żadnego odbiornika dotyku w działaniu, właśnie nadpisałem zdarzenie onTouchEvent działania.
Mahdi Hijazi
@MahdiHijazi może u proszę dodać swój kod poziomego przewijania i przewijania pionowego w github.com/MikeOrtiz/TouchImageView ten jest z powodzeniem powiększanie obrazu na kliknięcia przycisku, ale nie do przewijania obrazu
Erum
43

Ponieważ wydaje się, że jest to pierwszy wynik wyszukiwania w Google dla „Android Vertical + Horizontal ScrollView”, pomyślałem, że powinienem dodać to tutaj. Matt Clark zbudował niestandardowy widok oparty na źródle Androida i wydaje się, że działa on doskonale: Two Dimensional ScrollView

Pamiętaj, że klasa na tej stronie ma błąd obliczający szerokość poziomą widoku. Poprawka autorstwa Manuela Hilty znajduje się w komentarzach:

Rozwiązanie: Zastąp instrukcję w wierszu 808 następującym:

final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);


Edycja: Link już nie działa, ale tutaj jest link do starej wersji posta na blogu .

Cachapa
źródło
3
Dzięki, właśnie tego szukałem.
Andrey Agibalov
1
To działa jak urok po drobnych modyfikacjach. Ponownie zaimplementowałem fillViewporti onMeasuredokładnie tak samo, jak źródło ScrollView (v2.1). Zmieniłem również dwa pierwsze konstruktory za pomocą: this( context, null );i this(context, attrs, R.attr.scrollViewStyle);. Dzięki temu mogę używać paska przewijania za pomocą setVerticalScrollBarEnabledi setHorizontalScrollBarEnabledw kodzie.
olivier_sdg
U mnie też działa świetnie! Dziękuję Ci bardzo!
Avio
ale jak uzyskać rozmiar kciuka przewijania i pozycję przewijania od dołu w pionie i od prawej w trybie poziomym
Ravi
10
Wygląda na to, że połączony post zniknął.
loeschg
28

Znalazłem lepsze rozwiązanie.

XML: (design.xml)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent">
  <FrameLayout android:layout_width="90px" android:layout_height="90px">
    <RelativeLayout android:id="@+id/container" android:layout_width="fill_parent" android:layout_height="fill_parent">        
    </RelativeLayout>
</FrameLayout>
</FrameLayout>

Kod Java:

public class Example extends Activity {
  private RelativeLayout container;
  private int currentX;
  private int currentY;

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.design);

    container = (RelativeLayout)findViewById(R.id.container);

    int top = 0;
    int left = 0;

    ImageView image1 = ...
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image1, layoutParams);

    ImageView image2 = ...
    left+= 100;
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image2, layoutParams);

    ImageView image3 = ...
    left= 0;
    top+= 100;
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image3, layoutParams);

    ImageView image4 = ...
    left+= 100;     
    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(left, top, 0, 0);               
    container.addView(image4, layoutParams);
  }     

  @Override 
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            currentX = (int) event.getRawX();
            currentY = (int) event.getRawY();
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            int x2 = (int) event.getRawX();
            int y2 = (int) event.getRawY();
            container.scrollBy(currentX - x2 , currentY - y2);
            currentX = x2;
            currentY = y2;
            break;
        }   
        case MotionEvent.ACTION_UP: {
            break;
        }
    }
      return true; 
  }
}

To działa !!!

Jeśli chcesz załadować inny układ lub formant, struktura jest taka sama.

Kronos
źródło
Właśnie tego próbowałem i działa bardzo dobrze! Nie ma wskaźników przewijania ani rzucania, ale ponieważ jest to podejście niższego poziomu, te rzeczy można dość łatwo zbudować na podstawie tego. Dzięki!
ubzack
Pomogło mi również napisanie dwuwymiarowego przewijanego widoku, który wyświetlał dane wyjściowe w czasie rzeczywistym w wyniku zdalnych poleceń SSH przez sieć.
pilcrowpipe
@Kronos: A co jeśli chcę przewijać tylko w poziomie? Jaki powinien być mój parametr y w container.scrollBy (currentX - x2, currentY - y2). Czy jest jakiś sposób, w jaki możemy sprawić, by przewijał się tylko w poziomie?
Ashwin
@Kronos: Przewija się za daleko. Czy istnieje sposób, aby zatrzymać przewijanie po osiągnięciu końca?
Ashwin
Przyciski nie są przewijalne, dlaczego?
salih kallai
25

Używam go i działa dobrze:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView android:id="@+id/ScrollView02" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content"
            xmlns:android="http://schemas.android.com/apk/res/android">
<HorizontalScrollView android:id="@+id/HorizontalScrollView01" 
                      android:layout_width="wrap_content" 
                      android:layout_height="wrap_content">
<ImageView android:id="@+id/ImageView01"
           android:src="@drawable/pic" 
           android:isScrollContainer="true" 
           android:layout_height="fill_parent" 
           android:layout_width="fill_parent" 
           android:adjustViewBounds="true">
</ImageView>
</HorizontalScrollView>
</ScrollView>

Link do źródła jest tutaj: Android-spa

Redax
źródło
2
W najlepszym przypadku działa „dobrze” w przypadku zawartości statycznej. Wielu inżynierów Google powiedziało, że to, czego próbujesz użyć, będzie miało problemy ( groups.google.com/group/android-developers/browse_frm/thread/… i groups.google.com/group/android-beginners/browse_thread/thread/ … ).
CommonsWare
4
@CommonsWare: z szacunkiem dla wielu genialnych inżynierów Google, w tych wątkach, w których kazali przepisać od podstaw własny komponent: to najlepszy sposób. Z pewnością to prawda. Ale nie mówią, że to rozwiązanie jest złe, a jeśli działa ... Może im to chwilę potrwa, dla mnie lepiej napisać jakiś xml z istniejących komponentów. Co to znaczy „treść statyczna”? Używam przycisku i obrazów.
Redax
3
Przez „treść statyczną” rozumiem rzeczy, które same w sobie nie obejmują zdarzeń dotykowych. Chodzi mi o to - dla innych czytających tę odpowiedź - że chociaż twoje rozwiązanie może działać w przypadku pojedynczej ImageViewzawartości jako przewijalnej zawartości, może działać lub nie w przypadku innych dowolnych treści, i że Google uważa, że ​​wystąpią problemy z Twoim podejściem. Opinia Google ma również znaczenie w odniesieniu do przyszłego rozwoju - mogą nie próbować upewnić się, że Twoje podejście działa na dłuższą metę, jeśli w pierwszej kolejności doradza ludziom, aby go nie stosowali.
CommonsWare
To działa dobrze dla mnie ... Odświeżam widok obrazu w tym przewijanym widoku za pomocą wątku
sonu thomas
19

Moje rozwiązanie oparte na odpowiedzi Mahdi Hijazi , ale bez żadnych niestandardowych widoków:

Układ:

<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/scrollHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ScrollView 
        android:id="@+id/scrollVertical"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" >

        <WateverViewYouWant/>

    </ScrollView>
</HorizontalScrollView>

Kod (onCreate / onCreateView):

    final HorizontalScrollView hScroll = (HorizontalScrollView) value.findViewById(R.id.scrollHorizontal);
    final ScrollView vScroll = (ScrollView) value.findViewById(R.id.scrollVertical);
    vScroll.setOnTouchListener(new View.OnTouchListener() { //inner scroll listener         
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return false;
        }
    });
    hScroll.setOnTouchListener(new View.OnTouchListener() { //outer scroll listener         
        private float mx, my, curX, curY;
        private boolean started = false;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            curX = event.getX();
            curY = event.getY();
            int dx = (int) (mx - curX);
            int dy = (int) (my - curY);
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    if (started) {
                        vScroll.scrollBy(0, dy);
                        hScroll.scrollBy(dx, 0);
                    } else {
                        started = true;
                    }
                    mx = curX;
                    my = curY;
                    break;
                case MotionEvent.ACTION_UP: 
                    vScroll.scrollBy(0, dy);
                    hScroll.scrollBy(dx, 0);
                    started = false;
                    break;
            }
            return true;
        }
    });

Możesz zmienić kolejność widoków przewijania. Po prostu zmień ich kolejność w układzie i w kodzie. I oczywiście zamiast WthingViewYouWant umieść układ / widoki, które chcesz przewijać w obu kierunkach.

Yan.Yurkin
źródło
Zdecydowanie sugeruję zwrócenie wartości false w hScroll.setOnTouchListener. Kiedy zmieniłem na fałsz, lista zaczęła się przewijać „płynnie automatycznie” palcem w górę w obu kierunkach.
Greta Radisauskaite
1
Działa podczas przewijania najpierw w poziomie. Kiedy pion jest pierwszy, idzie tylko pionowo
Irhala
6

Opcja nr 1: Możesz wymyślić nowy projekt interfejsu użytkownika, który nie wymaga jednoczesnego przewijania w poziomie i w pionie.

Opcja nr 2: Możesz uzyskać kod źródłowy do ScrollViewi HorizontalScrollView, dowiedzieć się, jak zaimplementował je główny zespół Androida, i stworzyć własną BiDirectionalScrollViewimplementację.

Opcja nr 3: Możesz pozbyć się zależności, które wymagają od ciebie korzystania z systemu widgetów i rysować bezpośrednio na kanwie.

Opcja nr 4: Jeśli natkniesz się na aplikację typu open source, która wydaje się implementować to, czego szukasz, zobacz, jak to zrobili.

CommonsWare
źródło
6

Spróbuj tego

<?xml version="1.0" encoding="utf-8"?>
<ScrollView android:id="@+id/Sview" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        xmlns:android="http://schemas.android.com/apk/res/android">
<HorizontalScrollView 
   android:id="@+id/hview" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content">
<ImageView .......
 [here xml code for image]
</ImageView>
</HorizontalScrollView>
</ScrollView>
MBMJ
źródło
6

ponieważ łącze Two Dimensional Scrollview jest nieaktywne, nie mogłem go pobrać, więc stworzyłem własny komponent. Obsługuje rzucanie i działa prawidłowo dla mnie. Jedynym ograniczeniem jest to, że wrap_content może nie działać poprawnie dla tego komponentu.

https://gist.github.com/androidseb/9902093

androidseb
źródło
2

Mam rozwiązanie twojego problemu. Możesz sprawdzić kod ScrollView, który obsługuje tylko przewijanie w pionie i ignoruje poziomy i zmodyfikuj to. Chciałem widoku przypominającego przeglądarkę internetową, więc zmodyfikowałem ScrollView i dobrze mi się to udało. Ale to może nie odpowiadać Twoim potrzebom.

Daj mi znać, do jakiego typu interfejsu użytkownika kierujesz reklamy.

Pozdrowienia,

Ravi Pandit

Ravi Pandit
źródło
1

Grając z kodem, możesz umieścić HorizontalScrollView w ScrollView. W ten sposób możesz mieć dwie metody przewijania w tym samym widoku.

Źródło: http://androiddevblog.blogspot.com/2009/12/creating-two-dimensions-scroll-view.html

Mam nadzieję, że to ci pomoże.

Eriatolc
źródło
1
To nie jest dobre rozwiązanie. Jeśli zauważysz, podczas przeciągania widoku zawsze przeciągasz w pionie lub w poziomie. Nie możesz przeciągać po przekątnej, używając zagnieżdżonych widoków przewijania.
Sky Kelsey,
Co gorsza niż brak przewijania po przekątnej, to brak obu pasków przewijania na krawędzi widoku, ponieważ jeden jest zagnieżdżony. Lepszy ScrollView jest odpowiedzią, a link Cachapy do kodu Matta Clarka jest obecnie najlepszym rozwiązaniem ze względu na kompletność i uogólnienie.
Huperniketes