Eu tenho um ScrollView que envolve todo o meu layout, para que a tela inteira seja rolável. O primeiro elemento que tenho neste ScrollView é um bloco HorizontalScrollView que possui recursos que podem ser rolados horizontalmente. Adicionei um ouvinte ontouch à visualização horizontal de rolagem para manipular eventos de toque e forçar a exibição a "encaixar" na imagem mais próxima do evento ACTION_UP.
Portanto, o efeito que pretendo é como a tela inicial do Android, onde você pode rolar de um para o outro e ele se encaixa em uma tela quando você levanta o dedo.
Tudo isso funciona muito bem, exceto por um problema: preciso deslizar da esquerda para a direita quase perfeitamente na horizontal para que um ACTION_UP se registre. Se eu deslizar verticalmente no mínimo (o que eu acho que muitas pessoas costumam fazer em seus telefones quando deslizam de um lado para o outro), receberei um ACTION_CANCEL em vez de um ACTION_UP. Minha teoria é que isso ocorre porque a visualização horizontal de rolagem está dentro de uma visualização de rolagem e a visualização de rolagem está sequestrando o toque vertical para permitir a rolagem vertical.
Como posso desativar os eventos de toque da visualização de rolagem apenas dentro da visualização de rolagem horizontal, mas ainda permitir a rolagem vertical normal em outro local da visualização de rolagem?
Aqui está uma amostra do meu código:
public class HomeFeatureLayout extends HorizontalScrollView {
private ArrayList<ListItem> items = null;
private GestureDetector gestureDetector;
View.OnTouchListener gestureListener;
private static final int SWIPE_MIN_DISTANCE = 5;
private static final int SWIPE_THRESHOLD_VELOCITY = 300;
private int activeFeature = 0;
public HomeFeatureLayout(Context context, ArrayList<ListItem> items){
super(context);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
setFadingEdgeLength(0);
this.setHorizontalScrollBarEnabled(false);
this.setVerticalScrollBarEnabled(false);
LinearLayout internalWrapper = new LinearLayout(context);
internalWrapper.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
internalWrapper.setOrientation(LinearLayout.HORIZONTAL);
addView(internalWrapper);
this.items = items;
for(int i = 0; i< items.size();i++){
LinearLayout featureLayout = (LinearLayout) View.inflate(this.getContext(),R.layout.homefeature,null);
TextView header = (TextView) featureLayout.findViewById(R.id.featureheader);
ImageView image = (ImageView) featureLayout.findViewById(R.id.featureimage);
TextView title = (TextView) featureLayout.findViewById(R.id.featuretitle);
title.setTag(items.get(i).GetLinkURL());
TextView date = (TextView) featureLayout.findViewById(R.id.featuredate);
header.setText("FEATURED");
Image cachedImage = new Image(this.getContext(), items.get(i).GetImageURL());
image.setImageDrawable(cachedImage.getImage());
title.setText(items.get(i).GetTitle());
date.setText(items.get(i).GetDate());
internalWrapper.addView(featureLayout);
}
gestureDetector = new GestureDetector(new MyGestureDetector());
setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
int scrollX = getScrollX();
int featureWidth = getMeasuredWidth();
activeFeature = ((scrollX + (featureWidth/2))/featureWidth);
int scrollTo = activeFeature*featureWidth;
smoothScrollTo(scrollTo, 0);
return true;
}
else{
return false;
}
}
});
}
class MyGestureDetector extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//right to left
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
activeFeature = (activeFeature < (items.size() - 1))? activeFeature + 1:items.size() -1;
smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
return true;
}
//left to right
else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
activeFeature = (activeFeature > 0)? activeFeature - 1:0;
smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
return true;
}
} catch (Exception e) {
// nothing
}
return false;
}
}
}
MeetMe's HorizontalListView
biblioteca.HomeFeatureLayout extends HorizontalScrollView
) aqui velir.com/blog/index.php/2010/11/17/… Existem alguns comentários adicionais sobre o que está acontecendo enquanto a classe de rolagem personalizada é composta.Respostas:
Atualização: Eu descobri isso. No meu ScrollView, eu precisava substituir o método onInterceptTouchEvent para interceptar apenas o evento de toque se o movimento Y for> o movimento X. Parece que o comportamento padrão de um ScrollView é interceptar o evento de toque sempre que houver QUALQUER movimento Y. Portanto, com a correção, o ScrollView só interceptará o evento se o usuário estiver rolando deliberadamente na direção Y e, nesse caso, passar o ACTION_CANCEL para os filhos.
Aqui está o código da minha classe Scroll View que contém o HorizontalScrollView:
fonte
mGestureDetector.onTouchEvent(ev)
elas serão chamadas. Como é agora, não será chamado sesuper.onInterceptTouchEvent(ev)
for falso. Acabei de encontrar um caso em que crianças clicáveis na visualização de rolagem podem capturar os eventos de toque e o onScroll não será chamado. Caso contrário, obrigado, ótima resposta!Obrigado Joel por me dar uma pista sobre como resolver esse problema.
Simplifiquei o código (sem a necessidade de um GestureDetector ) para obter o mesmo efeito:
fonte
Acho que encontrei uma solução mais simples, só que isso usa uma subclasse do ViewPager em vez do ScrollView (seu pai).
ATUALIZAÇÃO 16-07-2013 : também adicionei uma substituição
onTouchEvent
. Possivelmente, poderia ajudar com os problemas mencionados nos comentários, embora o YMMV.Isso é semelhante à técnica usada no onScroll () do android.widget.Gallery . É explicado ainda mais pela apresentação do Google I / O 2013, Writing Views personalizadas para Android .
Atualização 10/10/2013 : Uma abordagem semelhante também é descrita em uma postagem de Kirill Grouchnikov sobre o aplicativo (então) Android Market .
fonte
ScrollView
com umLinearLayout
no qual oUninterceptableViewPager
é colocado. Na verdade,ret
sempre é falso ... Alguma pista de como consertar isso?TableRow
que está dentro de umTableLayout
que está dentro de umScrollView
(sim, eu sei ...), e está funcionando conforme o esperado. Talvez você possa tentar substituir emonScroll
vez deonInterceptTouchEvent
, como o Google faz (linha 1010)Descobri que algumas vezes um ScrollView recupera o foco e o outro perde o foco. Você pode impedir isso, concedendo apenas um dos focos scrollView:
fonte
Não estava funcionando bem para mim. Eu mudei e agora funciona sem problemas. Se alguém estiver interessado.
fonte
Graças a Neevek, sua resposta funcionou para mim, mas não bloqueia a rolagem vertical quando o usuário começa a rolar a exibição horizontal (ViewPager) na direção horizontal e, sem levantar o dedo na vertical, ele começa a rolar a exibição do contêiner subjacente (ScrollView) . Corrigi-o fazendo uma pequena alteração no código de Neevak:
fonte
Isso finalmente se tornou parte da biblioteca de suporte v4, NestedScrollView . Portanto, não é mais necessário usar hacks locais na maioria dos casos.
fonte
A solução de Neevek funciona melhor que a de Joel em dispositivos executando 3.2 e acima. Há um erro no Android que causa o java.lang.IllegalArgumentException: pointerIndex fora do alcance, se um detector de gestos for usado dentro de uma exibição de scoll. Para duplicar o problema, implemente um scollview personalizado, conforme sugerido por Joel, e coloque um pager de exibição dentro. Se você arrastar (não levante a figura) para uma direção (esquerda / direita) e depois para o oposto, você verá a falha. Também na solução de Joel, se você arrastar o pager da visualização movendo o dedo na diagonal, assim que sair da área de visualização do conteúdo do pager da visualização, o pager retornará à sua posição anterior. Todas essas questões têm mais a ver com o design interno do Android ou a falta dele do que a implementação de Joel, que por si só é um código inteligente e conciso.
http://code.google.com/p/android/issues/detail?id=18990
fonte