2017-07-07 77 views
8

Próbuję przenieść niektóre funkcje systemu iOS na system Android.Elementy RecyclerView ItemTouchHelper w Swipe

Zamierzam utworzyć tabelę, w której po przesunięciu w lewo po lewej stronie wyświetlany jest przycisk 2: Edytuj i usuń.

enter image description here

Gram z nim i wiem, że jestem bardzo blisko. Sekret naprawdę leży w metodzie OnChildDraw.

Chciałbym narysować prostokąt, który pasuje do tekstu Usuń, a następnie narysuj tekst edycji oprócz niego z odpowiednim kolorem tła. Pozostała biała spacja po kliknięciu powinna przywrócić wiersz do jego początkowej pozycji.

Udało mi się namalować tło, gdy użytkownik przesuwa się po bokach, ale nie wiem, jak dodać słuchaczy, a po przesunięciu w bok funkcja przeciągania zaczyna źle działać.

Pracuję nad Xamarin, ale czyste rozwiązania java są również akceptowane, ponieważ mogę je łatwo przenieść do C#.

public class SavedPlacesItemTouchHelper : ItemTouchHelper.SimpleCallback 
    { 
     private SavedPlacesRecyclerviewAdapter adapter; 
     private Paint paint = new Paint(); 
     private Context context; 

     public SavedPlacesItemTouchHelper(Context context, SavedPlacesRecyclerviewAdapter adapter) : base(ItemTouchHelper.ActionStateIdle, ItemTouchHelper.Left) 
     { 
      this.context = context; 
      this.adapter = adapter; 
     } 

     public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) 
     { 
      return false; 
     } 

     public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction) 
     { 
     } 

     public override void OnChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, bool isCurrentlyActive) 
     { 
      float translationX = dX; 
      View itemView = viewHolder.ItemView; 
      float height = (float)itemView.Bottom - (float)itemView.Top; 

      if (actionState == ItemTouchHelper.ActionStateSwipe && dX <= 0) // Swiping Left 
      { 
       translationX = -Math.Min(-dX, height * 2); 
       paint.Color = Color.Red; 
       RectF background = new RectF((float)itemView.Right + translationX, (float)itemView.Top, (float)itemView.Right, (float)itemView.Bottom); 
       c.DrawRect(background, paint); 

       //viewHolder.ItemView.TranslationX = translationX; 
      } 
      else if (actionState == ItemTouchHelper.ActionStateSwipe && dX > 0) // Swiping Right 
      { 
       translationX = Math.Min(dX, height * 2); 
       paint.Color = Color.Red; 
       RectF background = new RectF((float)itemView.Right + translationX, (float)itemView.Top, (float)itemView.Right, (float)itemView.Bottom); 
       c.DrawRect(background, paint); 
      } 

      base.OnChildDraw(c, recyclerView, viewHolder, translationX, dY, actionState, isCurrentlyActive); 
     } 
    } 
} 

Oto, co obecnie mam.

Jeśli wiesz, jak dodać odbiorców lub sugestie, zostaw komentarz!

UPDATE:

zdałem sobie sprawę, że na podwójnym kranu na białym pozostałej przestrzeni rzędu już przywrócić wiersz do stanu początkowego. Ani jeden z kranu choć :(

Odpowiedz

0

Jeśli używasz RecyclerView, spróbuj użyć OnScrollListener. Czy coś takiego.

private class YourOnScrollListener extends RecyclerView.OnScrollListener { 

    private boolean directionLeft; 

    @Override 
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 
     if (newState == RecyclerView.SCROLL_STATE_IDLE) { 
      if (directionLeft) drawButtons(); 
      //Draw buttons here if you want them to be drawn after scroll is finished 
      //here you can play with states, to draw buttons or erase it whenever you want 
     } 
    } 

    @Override 
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 
     super.onScrolled(recyclerView, dx, dy); 
     if (dx < 0) directionLeft = true; 
     else directionLeft = false; 
    } 

} 
+0

Jeśli element pomocniczy dotyku nie powiedzie się, to na pewno spróbuję, dzięki! –

0

Chciałem użyć tego dotykowego gest w moim app też po pracy zbyt wiele z Itemtouchhelper postanowiłem napisać własny program obsługi dotykowego:

private class TouchHelper : Java.Lang.Object, View.IOnTouchListener 
    { 
     ViewHolder vh; 
     public TouchHelper(ViewHolder vh) 
     { this.vh = vh; } 

     float DownX, DownY; bool isSliding; 
     TimeSpan tsDown; 
     public bool OnTouch(View v, MotionEvent e) 
     { 
      switch (e.Action) 
      { 
       case MotionEventActions.Down: 
        DownX = e.GetX(); DownY = e.GetY(); 
        tsDown = TimeSpan.Now; 
        break; 
       case MotionEventActions.Move: 
        float deltaX = e.GetX() - DownX, deltaY = e.GetX() - DownY; 
        if (Math.Abs(deltaX) >= Values.ScreenWidth/20 || Math.Abs(deltaY) >= Values.ScreenWidth/20) 
         isSliding = Math.Abs(deltaX) > Math.Abs(deltaY); 

        //TextsPlace is the layout that moves with touch 
        if(isSliding) 
         vh.TextsPlace.TranslationX = deltaX/2; 

        break; 
       case MotionEventActions.Cancel: 
       case MotionEventActions.Up: 
        //handle if touch was for clicking 
        if (Math.Abs(deltaX) <= 50 && (TimeSpan.Now - tsDown).TotalMilliseconds <= 400) 
         vh.OnTextsPlaceClick(vh.TextsPlace, null); 
        break; 
      } 
      return true; 
     } 
    } 

. Uwaga: Ustaw jako ontouchlistener z treścią viewholder gdy tworzenia viewholder Możesz dodać animacje do retur n przedmiot na pierwsze miejsce.

Możesz również napisać swój własny układ zarządzania układami, aby zablokować przewijanie w pionie podczas przesuwania elementu.

+0

Wciąż gram z ItemTouchHelper, kiedy wyczerpam wszystkie możliwe wyniki, mogę wypróbować Twoją technikę. Dziękuję za udostępnienie. –

13

Zmagałem się z tym samym problemem i próbowałem znaleźć rozwiązanie online. Większość rozwiązań używa podejścia dwuwarstwowego (element widoku jednej warstwy, przyciski innej warstwy), ale chcę trzymać się tylko ItemTouchHelper. Na koniec wpadłem na wypracowane rozwiązanie. Proszę sprawdzić poniżej.

import android.content.Context; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.graphics.Point; 
import android.graphics.Rect; 
import android.graphics.RectF; 
import android.support.v7.widget.RecyclerView; 
import android.support.v7.widget.helper.ItemTouchHelper; 
import android.util.Log; 
import android.view.GestureDetector; 
import android.view.MotionEvent; 
import android.view.View; 

import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.LinkedList; 
import java.util.List; 
import java.util.Map; 
import java.util.Queue; 

public abstract class SwipeHelper extends ItemTouchHelper.SimpleCallback { 

    public static final int BUTTON_WIDTH = YOUR_WIDTH_IN_PIXEL_PER_BUTTON 
    private RecyclerView recyclerView; 
    private List<UnderlayButton> buttons; 
    private GestureDetector gestureDetector; 
    private int swipedPos = -1; 
    private float swipeThreshold = 0.5f; 
    private Map<Integer, List<UnderlayButton>> buttonsBuffer; 
    private Queue<Integer> recoverQueue; 

    private GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener(){ 
     @Override 
     public boolean onSingleTapConfirmed(MotionEvent e) { 
      for (UnderlayButton button : buttons){ 
       if(button.onClick(e.getX(), e.getY())) 
        break; 
      } 

      return true; 
     } 
    }; 

    private View.OnTouchListener onTouchListener = new View.OnTouchListener() { 
     @Override 
     public boolean onTouch(View view, MotionEvent e) { 
      if (swipedPos < 0) return false; 
      Point point = new Point((int) e.getRawX(), (int) e.getRawY()); 

      RecyclerView.ViewHolder swipedViewHolder = recyclerView.findViewHolderForAdapterPosition(swipedPos); 
      View swipedItem = swipedViewHolder.itemView; 
      Rect rect = new Rect(); 
      swipedItem.getGlobalVisibleRect(rect); 

      if (e.getAction() == MotionEvent.ACTION_DOWN || e.getAction() == MotionEvent.ACTION_UP ||e.getAction() == MotionEvent.ACTION_MOVE) { 
       if (rect.top < point.y && rect.bottom > point.y) 
        gestureDetector.onTouchEvent(e); 
       else { 
        recoverQueue.add(swipedPos); 
        swipedPos = -1; 
        recoverSwipedItem(); 
       } 
      } 
      return false; 
     } 
    }; 

    public SwipeHelper(Context context, RecyclerView recyclerView) { 
     super(0, ItemTouchHelper.LEFT); 
     this.recyclerView = recyclerView; 
     this.buttons = new ArrayList<>(); 
     this.gestureDetector = new GestureDetector(context, gestureListener); 
     this.recyclerView.setOnTouchListener(onTouchListener); 
     buttonsBuffer = new HashMap<>(); 
     recoverQueue = new LinkedList<Integer>(){ 
      @Override 
      public boolean add(Integer o) { 
       if (contains(o)) 
        return false; 
       else 
        return super.add(o); 
      } 
     }; 

     attachSwipe(); 
    } 


    @Override 
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { 
     return false; 
    } 

    @Override 
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { 
     int pos = viewHolder.getAdapterPosition(); 

     if (swipedPos != pos) 
      recoverQueue.add(swipedPos); 

     swipedPos = pos; 

     if (buttonsBuffer.containsKey(swipedPos)) 
      buttons = buttonsBuffer.get(swipedPos); 
     else 
      buttons.clear(); 

     buttonsBuffer.clear(); 
     swipeThreshold = 0.5f * buttons.size() * BUTTON_WIDTH; 
     recoverSwipedItem(); 
    } 

    @Override 
    public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { 
     return swipeThreshold; 
    } 

    @Override 
    public float getSwipeEscapeVelocity(float defaultValue) { 
     return 0.1f * defaultValue; 
    } 

    @Override 
    public float getSwipeVelocityThreshold(float defaultValue) { 
     return 5.0f * defaultValue; 
    } 

    @Override 
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { 
     int pos = viewHolder.getAdapterPosition(); 
     float translationX = dX; 
     View itemView = viewHolder.itemView; 

     if (pos < 0){ 
      swipedPos = pos; 
      return; 
     } 

     if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){ 
      if(dX < 0) { 
       List<UnderlayButton> buffer = new ArrayList<>(); 

       if (!buttonsBuffer.containsKey(pos)){ 
        instantiateUnderlayButton(viewHolder, buffer); 
        buttonsBuffer.put(pos, buffer); 
       } 
       else { 
        buffer = buttonsBuffer.get(pos); 
       } 

       translationX = dX * buffer.size() * BUTTON_WIDTH/itemView.getWidth(); 
       drawButtons(c, itemView, buffer, pos, translationX); 
      } 
     } 

     super.onChildDraw(c, recyclerView, viewHolder, translationX, dY, actionState, isCurrentlyActive); 
    } 

    private synchronized void recoverSwipedItem(){ 
     while (!recoverQueue.isEmpty()){ 
      int pos = recoverQueue.poll(); 
      if (pos > -1) { 
       recyclerView.getAdapter().notifyItemChanged(pos); 
      } 
     } 
    } 

    private void drawButtons(Canvas c, View itemView, List<UnderlayButton> buffer, int pos, float dX){ 
     float right = itemView.getRight(); 
     float dButtonWidth = (-1) * dX/buffer.size(); 

     for (UnderlayButton button : buffer) { 
      float left = right - dButtonWidth; 
      button.onDraw(
        c, 
        new RectF(
          left, 
          itemView.getTop(), 
          right, 
          itemView.getBottom() 
        ), 
        pos 
      ); 

      right = left; 
     } 
    } 

    public void attachSwipe(){ 
     ItemTouchHelper itemTouchHelper = new ItemTouchHelper(this); 
     itemTouchHelper.attachToRecyclerView(recyclerView); 
    } 

    public abstract void instantiateUnderlayButton(RecyclerView.ViewHolder viewHolder, List<UnderlayButton> underlayButtons); 

    public static class UnderlayButton { 
     private String text; 
     private int imageResId; 
     private int color; 
     private int pos; 
     private RectF clickRegion; 
     private UnderlayButtonClickListener clickListener; 

     public UnderlayButton(String text, int imageResId, int color, UnderlayButtonClickListener clickListener) { 
      this.text = text; 
      this.imageResId = imageResId; 
      this.color = color; 
      this.clickListener = clickListener; 
     } 

     public boolean onClick(float x, float y){ 
      if (clickRegion != null && clickRegion.contains(x, y)){ 
       clickListener.onClick(pos); 
       return true; 
      } 

      return false; 
     } 

     public void onDraw(Canvas c, RectF rect, int pos){ 
      Paint p = new Paint(); 

      // Draw background 
      p.setColor(color); 
      c.drawRect(rect, p); 

      // Draw Text 
      p.setColor(Color.WHITE); 
      p.setTextSize(LayoutHelper.getPx(MyApplication.getAppContext(), 12)); 

      Rect r = new Rect(); 
      float cHeight = rect.height(); 
      float cWidth = rect.width(); 
      p.setTextAlign(Paint.Align.LEFT); 
      p.getTextBounds(text, 0, text.length(), r); 
      float x = cWidth/2f - r.width()/2f - r.left; 
      float y = cHeight/2f + r.height()/2f - r.bottom; 
      c.drawText(text, rect.left + x, rect.top + y, p); 

      clickRegion = rect; 
      this.pos = pos; 
     } 
    } 

    public interface UnderlayButtonClickListener { 
     void onClick(int pos); 
    } 
} 

Zastosowanie:

SwipeHelper swipeHelper = new SwipeHelper(this, recyclerView) { 
    @Override 
    public void instantiateUnderlayButton(RecyclerView.ViewHolder viewHolder, List<UnderlayButton> underlayButtons) { 
     underlayButtons.add(new SwipeHelper.UnderlayButton(
       "Delete", 
       0, 
       Color.parseColor("#FF3C30"), 
       new SwipeHelper.UnderlayButtonClickListener() { 
        @Override 
        public void onClick(int pos) { 
         // TODO: onDelete 
        } 
       } 
     )); 

     underlayButtons.add(new SwipeHelper.UnderlayButton(
       "Transfer", 
       0, 
       Color.parseColor("#FF9502"), 
       new SwipeHelper.UnderlayButtonClickListener() { 
        @Override 
        public void onClick(int pos) { 
         // TODO: OnTransfer 
        } 
       } 
     )); 
     underlayButtons.add(new SwipeHelper.UnderlayButton(
       "Unshare", 
       0, 
       Color.parseColor("#C7C7CB"), 
       new SwipeHelper.UnderlayButtonClickListener() { 
        @Override 
        public void onClick(int pos) { 
         // TODO: OnUnshare 
        } 
       } 
     )); 
    } 
}; 

Uwaga tej klasy pomocnika przeznaczony jest do lewej bezstykowa. Można zmienić kierunek przesuwania w konstruktorze SwipeHelpera i odpowiednio zmienić metodę opartą na dX w metodzie onChildDraw.

Jeśli chcesz wyświetlić obraz w przycisku, po prostu sprawiają, że korzystanie z imageResId w UnderlayButton i ponownie wdrożyć metodę OnDraw.

Znany błąd występuje po przesunięciu przedmiotu po przekątnej z jednego elementu do drugiego, a pierwszy dotknięty element będzie migał trochę. Można temu zaradzić, zmniejszając wartość getSwipeVelocityThreshold, ale to utrudnia użytkownikowi przesunięcie elementu. Możesz także dostosować odczucie przesuwania, zmieniając dwie inne wartości w getSwipeThreshold i getSwipeEscapeVelocity. Sprawdź kod źródłowy ItemTouchHelper, komentarze są bardzo pomocne.

Uważam, że istnieje wiele możliwości optymalizacji. To rozwiązanie daje pomysł, jeśli chcesz trzymać się ItemTouchHelper. Daj mi znać, jeśli masz problem z jego użyciem. Poniżej zrzut ekranu.

enter image description here

Podziękowanie: to rozwiązanie jest najczęściej inspirowane z odpowiedzią AdamWei w tym post

+0

Muszę zrezygnować z tego rozwiązania. Skończyło się usuwaniem po lewej stronie i edytowanie po prawej stronie. Ale chciałbym użyć przycisków. Twoje rozwiązanie wydaje się uzasadnione, wypróbuję je w przyszłym tygodniu, jeśli będę miał problemy, dam ci znać, jeśli to zadziała, zaznaczę to jako zaakceptowaną odpowiedź: D –

+0

@GustavoBaiocchiCosta Hej, powodzenia z tym rozwiązaniem? Jeśli to zadziałało, zaznacz to jako zaakceptowane. : D –

+0

Udało mi się sprawić, żeby pojawił się z itemTouchHelper, ale nie mogłem go kliknąć ... Zmieniłem więc implementację do przesuwania w lewo lub w prawo. Przepraszamy, ale moje pytanie wymaga rozwiązania itemTouchHelper: D –

0

Po Wenxi Zeng za odpowiedź here, jeśli chcemy mieć tekst przycisków na wielu liniach, wymienić UnderlayButton użytkownika Metoda onDraw z tym:

public void onDraw(Canvas canvas, RectF rect, int pos){ 
     Paint p = new Paint(); 

     // Draw background 
     p.setColor(color); 
     canvas.drawRect(rect, p); 

     // Draw Text 
     TextPaint textPaint = new TextPaint(); 
     textPaint.setTextSize(UtilitiesOperations.convertDpToPx(getContext(), 15)); 
     textPaint.setColor(Color.WHITE); 
     StaticLayout sl = new StaticLayout(text, textPaint, (int)rect.width(), 
       Layout.Alignment.ALIGN_CENTER, 1, 1, false); 

     canvas.save(); 
     Rect r = new Rect(); 
     float y = (rect.height()/2f) + (r.height()/2f) - r.bottom - (sl.getHeight() /2); 
     canvas.translate(rect.left, rect.top + y); 
     sl.draw(canvas); 
     canvas.restore(); 

     clickRegion = rect; 
     this.pos = pos; 
    } 
+0

Dzięki za wpis Castaldi. Kiedy zadanie wróci do tego problemu, z pewnością zaznaczę jedną z odpowiedzi jako zaakceptowaną, dzięki! –