Scrollview vertical e horizontal no android

151

Estou realmente cansado de procurar uma solução para o Scrollview vertical e horizontal.

Eu li que não existem visualizações / layouts na estrutura que implementem esse recurso, mas preciso de algo como isto:

Eu preciso definir um layout dentro de outro, o layout filho deve implementar a rolagem vertical / horizontal para mover.

Inicialmente, implementei um código que movia o layout pixel por pixel, mas acho que não é o caminho certo. Eu tentei com ScrollView e Horizontal ScrollView, mas nada funciona como eu quero, porque implementa apenas a rolagem vertical ou horizontal.

A tela não é minha solução, pois preciso anexar ouvintes a elementos filho de alguém.

O que eu posso fazer?

Kronos
fonte
14
É absolutamente ridículo que isso não esteja disponível imediatamente com o Android. Trabalhamos com meia dúzia de outras tecnologias de interface do usuário e é inédito que não haja algo tão básico quanto um contêiner de rolagem horizontal / vertical.
Flexician.com
Ok, então finalmente escrevemos o nosso para o nosso datagrid: androidjetpack.com/Home/AndroidDataGrid . Faz muito mais do que apenas rolagem horizontal / vertical. Mas a ideia é basicamente envolver um HScroller dentro de um Scroller Vertical. Você também precisa substituir os métodos filho de adicionar / remover para atingir o scroller interno e algumas outras travessuras, mas funciona.
flexicious.com

Respostas:

90

Misturando algumas das sugestões acima, foi possível obter uma boa solução:

ScrollView personalizado:

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;
    }
}

HorizontalScrollView personalizado:

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;
    }
}

o 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;
    }

}

o layout:

<?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
fonte
Esta é uma ótima resposta, eu tinha outros elementos de rolagem na exibição que também precisavam mudar. Tudo o que eu precisava fazer era acionar o deslocamento junto com os outros.
T. Markle
1
Ótimo! Alguns ajustes para levar em conta a velocidade, e isso seria perfeito (também, você não precisa a inicial LinearLayoutpalpite I)
Romuald Brunet
Hey Mahdi, você pode me informar sobre qual controle / widget você reigster o ouvinte de toque na classe de atividade.
precisa saber é o seguinte
Desculpe pelo atraso @DeepakSharma. Não registrei nenhum ouvinte de toque na atividade, apenas substituí o onTouchEvent da atividade.
Mahdi Hijazi
@MahdiHijazi pode u por favor adicionar seu código de rolagem horizontal e vertical de rolagem em github.com/MikeOrtiz/TouchImageView este está zumbindo com sucesso imagem no clique de botão, mas falhou imagem de rolagem para
Erum
43

Como esse parece ser o primeiro resultado de pesquisa no Google para "Android Vertical + ScrollView horizontal", pensei em adicionar isso aqui. Matt Clark construiu uma exibição personalizada com base na fonte do Android e parece funcionar perfeitamente: Two Dimensional ScrollView

Lembre-se de que a classe nessa página possui um erro que calcula a largura horizontal da visualização. Uma correção de Manuel Hilty está nos comentários:

Solução: Substitua a instrução na linha 808 da seguinte maneira:

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


Edit: O link não funciona mais, mas aqui está um link para uma versão antiga do post do blog .

Cachapa
fonte
3
Obrigado, era o que eu estava procurando.
Andrey Agibalov
1
Isso funciona como um encanto após algumas pequenas modificações para mim. Reimplementei fillViewporte onMeasureexatamente como a fonte ScrollView (v2.1). Também mudei os dois primeiros construtores com: this( context, null );e this(context, attrs, R.attr.scrollViewStyle);. Com isso, eu posso usar a barra de rolagem usando setVerticalScrollBarEnablede setHorizontalScrollBarEnabledno código.
Olivier_sdg
Funciona muito bem para mim! Muito obrigado!
Avio
mas como para obter o tamanho do polegar de rolagem e posição de rolagem da parte inferior vertical e da direita no modo horizontal
Ravi
10
Parece que a postagem vinculada desapareceu.
24514 loeschg
28

Encontrei uma solução melhor.

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>

Código 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; 
  }
}

Isso funciona !!!

Se você deseja carregar outro layout ou controle, a estrutura é a mesma.

Kronos
fonte
Apenas tentei isso, e funciona muito bem! Ele não possui indicadores de rolagem ou arremesso, mas como essa é uma abordagem de nível inferior, essas coisas podem ser construídas facilmente com facilidade. Obrigado!
precisa
Também funcionou bem para mim ao escrever uma exibição rolável bidimensional que produzia saída em tempo real a partir do resultado de comandos remotos SSH na rede.
Pilcrowpipe
@Kronos: E se eu quiser apenas rolar horizontalmente? Qual deve ser o meu parâmetro y em container.scrollBy (currentX - x2, currentY - y2). Existe alguma maneira de fazê-lo rolar horizontalmente?
21412 Ashwin
@Kronos: rola demais. Existe alguma maneira de parar o pergaminho quando o fim for alcançado?
Ashwin
Os botões não podem ser rolados, por quê?
salih Kallai
25

Eu uso e funciona bem:

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

O link da fonte está aqui: Android-spa

Redax
fonte
2
Funciona "muito bem" para conteúdo estático, na melhor das hipóteses. Vários engenheiros do Google disseram que o que você está tentando usar terá problemas ( groups.google.com/group/android-developers/browse_frm/thread/… e groups.google.com/group/android-beginners/browse_thread/thread/ ... )
CommonsWare
4
@ CommmonsWare: com respeito pelos brilhantes engenheiros do Google, nesses tópicos eles pediram para você reescrever do zero o seu próprio componente: esse é o melhor caminho. Certamente é verdade. Mas eles não dizem que esta solução é ruim e, se funcionar ... Talvez para eles demore um minuto, para mim é melhor escrever um xml usando os componentes existentes. O que significa "conteúdo estático"? Eu uso botões e imagens.
Redax
3
Por "conteúdo estático", quero dizer coisas que não envolvem eventos de toque. Meu ponto de vista - para outras pessoas que leem esta resposta - é que, embora sua solução funcione para um único ImageViewconteúdo rolável, ela pode ou não funcionar para outros conteúdos arbitrários e que o Google acredita que haverá problemas com sua abordagem. A opinião do Google também é importante em relação ao desenvolvimento futuro - eles podem não estar tentando garantir que sua abordagem funcione a longo prazo, se estiverem aconselhando as pessoas a não usá-la em primeiro lugar.
CommonsWare
Isso está funcionando bem para mim ... Estou atualizando uma visualização de imagem neste scrollview usando um thread
sonu thomas
19

Minha solução baseada na resposta de Mahdi Hijazi , mas sem visualizações personalizadas:

Layout:

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

Código (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;
        }
    });

Você pode alterar a ordem das visualizações de rolagem. Basta alterar a ordem no layout e no código. E, obviamente, em vez de WateverViewYouWant, você coloca o layout / vistas que deseja rolar nas duas direções.

Yan.Yurkin
fonte
Eu sugiro fortemente retornar false em hScroll.setOnTouchListener. Quando mudei para false, a lista começou a rolar "suavemente automático" no dedo para cima em ambas as direções.
Greta Radisauskaite
1
Funciona ao rolar horizontalmente primeiro. Quando vertical é em primeiro lugar, vai apenas na vertical
Irhala
6

Opção 1: você pode criar um novo design de interface do usuário que não exija rolagem horizontal e vertical simultânea.

Opção 2: você pode obter o código-fonte ScrollViewe HorizontalScrollViewaprender como a equipe principal do Android os implementou e criar sua própria BiDirectionalScrollViewimplementação.

Opção nº 3: você pode se livrar das dependências que exigem o uso do sistema de widgets e desenhar diretamente no Canvas.

Opção nº 4: se você se deparar com um aplicativo de código aberto que parece implementar o que você procura, veja como eles o fizeram.

CommonsWare
fonte
6

Tente isto

<?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
fonte
6

como o link Two Dimensional Scrollview está inoperante, não consegui obtê-lo, então criei meu próprio componente. Ele lida com arremessar e funciona corretamente para mim. A única restrição é que wrap_content pode não funcionar corretamente para esse componente.

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

androidseb
fonte
2

Eu tenho uma solução para o seu problema. Você pode verificar o código ScrollView, que trata apenas da rolagem vertical, ignora a horizontal e modifica-a. Eu queria uma visualização como uma webview, então modifiquei o ScrollView e funcionou bem para mim. Mas isso pode não atender às suas necessidades.

Deixe-me saber que tipo de interface do usuário você está segmentando.

Saudações,

Ravi Pandit

Ravi Pandit
fonte
1

Jogando com o código, você pode colocar um HorizontalScrollView em um ScrollView. Dessa forma, você pode ter o método de duas rolagens na mesma exibição.

Fonte: http://androiddevblog.blogspot.com/2009/12/creating-two-dimensions-scroll-view.html

Espero que isso possa ajudá-lo.

Eriatolc
fonte
1
Esta não é uma boa solução. Se você notar, ao arrastar a vista, você está sempre arrastando na vertical ou na horizontal. Você não pode arrastar ao longo de uma diagonal usando visualizações de rolagem aninhadas.
Sky Kelsey
O que é pior do que a falta de rolagem diagonal é a falta de ambas as barras de rolagem restantes na borda da exibição porque uma está aninhada. Um ScrollView melhor é a resposta e o link da Cachapa com o código de Matt Clark é a melhor solução no momento devido à integridade e generalização.
Huperniketes