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ć?



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>

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 .


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.


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


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.


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.


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>

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


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


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.


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.