wake-up-neo.com

Hinzufügen eines farbigen Hintergrunds mit Text / Symbol unter der überstrichenen Zeile, wenn RecyclerView von Android verwendet wird

EDIT: Das eigentliche Problem war, dass mein LinearLayout in ein anderes Layout eingebunden wurde, was das falsche Verhalten verursachte. Die von Sanvywell akzeptierte Antwort enthält ein besseres und vollständigeres Beispiel für das Zeichnen einer Farbe in der Wischansicht als das in der Frage angegebene Code-Snippet.

Nun, da das Widget RecyclerView natives Wischen von Zeilen mithilfe der Klasse ItemTouchHelper unterstützt, versuche ich, es in einer App zu verwenden, in der sich Zeilen ähnlich wie in der Inbox-App von Google verhalten . Wenn Sie also nach links wischen, wird eine Aktion ausgeführt, und wenn Sie nach rechts wischen, wird eine andere ausgeführt.

Das Implementieren der Aktionen selbst war mit der onSwiped -Methode von ItemTouchHelper.SimpleCallback einfach. Es war mir jedoch nicht möglich, auf einfache Weise die Farbe und das Symbol festzulegen, die in der Ansicht angezeigt werden sollen, die gerade gewischt wird (wie in der Inbox-App von Google).

Dazu versuche ich, die onChildDraw -Methode von ItemTouchHelper.SimpleCallback wie folgt zu überschreiben:

@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView,
                        RecyclerView.ViewHolder viewHolder, float dX, float dY,
                        int actionState, boolean isCurrentlyActive) {
    RecyclerViewAdapter.ViewHolder vh = (RecyclerViewAdapter.ViewHolder) viewHolder;
    LinearLayout ll = vh.linearLayout;

    Paint p = new Paint();
    if(dX > 0) {
        p.setARGB(255, 255, 0, 0);
    } else {
        p.setARGB(255, 0, 255, 0);
    }

    c.drawRect(ll.getLeft(), ll.getTop(), ll.getRight(), ll.getBottom(), p);

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

Das Bestimmen der Wischrichtung aus dX und das Festlegen der entsprechenden Farbe funktioniert wie beabsichtigt, aber die Koordinaten, die ich vom ViewHolder erhalte, entsprechen immer der Stelle, an der das erste LinearLayout aufgeblasen wurde.

Wie erhalte ich die korrekten Koordinaten für das LinearLayout, das sich in der aktuell überstrichenen Zeile befindet? Gibt es eine einfachere Möglichkeit (die kein Überschreiben von onChildDraw erfordert), um die Hintergrundfarbe und das Symbol festzulegen?

27
Manvis

Ich hatte Mühe, diese Funktion ebenfalls zu implementieren, aber Sie haben mich in die richtige Richtung gelenkt.

@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
    if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        // Get RecyclerView item from the ViewHolder
        View itemView = viewHolder.itemView;

        Paint p = new Paint();
        if (dX > 0) {
            /* Set your color for positive displacement */

            // Draw Rect with varying right side, equal to displacement dX
            c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
                    (float) itemView.getBottom(), p);
        } else {
            /* Set your color for negative displacement */

            // Draw Rect with varying left side, equal to the item's right side plus negative displacement dX
            c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),
                    (float) itemView.getRight(), (float) itemView.getBottom(), p);
        }

        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
    }
}
49
Sanvywell

Die akzeptierte Antwort färbt den Hintergrund sehr gut, hat jedoch nicht das Zeichnen des Symbols angesprochen.

Dies funktionierte für mich, da sowohl die Hintergrundfarbe festgelegt als auch das Symbol gezeichnet wurde, ohne dass das Symbol beim Streichen gedehnt wurde oder nach dem Streichen eine Lücke zwischen dem vorherigen und dem nächsten Element verbleibt.

public static final float ALPHA_FULL = 1.0f;

public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
    if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        // Get RecyclerView item from the ViewHolder
        View itemView = viewHolder.itemView;

        Paint p = new Paint();
        Bitmap icon;

        if (dX > 0) {
            /* Note, ApplicationManager is a helper class I created 
               myself to get a context outside an Activity class - 
               feel free to use your own method */

            icon = BitmapFactory.decodeResource(
                    ApplicationManager.getContext().getResources(), R.drawable.myleftdrawable);

            /* Set your color for positive displacement */
            p.setARGB(255, 255, 0, 0);

            // Draw Rect with varying right side, equal to displacement dX
            c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
                    (float) itemView.getBottom(), p);

            // Set the image icon for Right swipe
            c.drawBitmap(icon,
                    (float) itemView.getLeft() + convertDpToPx(16),
                    (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2,
                    p);
        } else {
            icon = BitmapFactory.decodeResource(
                    ApplicationManager.getContext().getResources(), R.drawable.myrightdrawable);

            /* Set your color for negative displacement */
            p.setARGB(255, 0, 255, 0);

            // Draw Rect with varying left side, equal to the item's right side
            // plus negative displacement dX
            c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),
                    (float) itemView.getRight(), (float) itemView.getBottom(), p);

            //Set the image icon for Left swipe
            c.drawBitmap(icon,
                    (float) itemView.getRight() - convertDpToPx(16) - icon.getWidth(),
                    (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2,
                    p);
        }

        // Fade out the view as it is swiped out of the parent's bounds
        final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
        viewHolder.itemView.setAlpha(alpha);
        viewHolder.itemView.setTranslationX(dX);

    } else {
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
    }
}

private int convertDpToPx(int dp){
    return Math.round(dp * (getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
26
HappyKatz

Ich bin mir nicht sicher, wie diese Lösungen (von @Sanvywell, @HappyKatz und @ user2410066) für euch funktionieren, aber in meinem Fall brauchte ich eine weitere Überprüfung in der onChildDraw -Methode.

Es sieht so aus, als ob ItemTouchHelperViewHolders der entfernten Zeilen behält, falls sie wiederhergestellt werden müssen. Es ruft auch onChildDraw für diese VHs auf, zusätzlich zu dem VH, das geklaut wird. Ich bin mir nicht sicher, welche Auswirkungen dieses Verhalten auf die Speicherverwaltung hat, benötigte jedoch eine zusätzliche Überprüfung am Anfang von onChildDraw, um das Zeichnen für "fantome" Zeilen zu vermeiden.

if (viewHolder.getAdapterPosition() == -1) {
    return;
}

BONUS TEIL:

Ich wollte auch mit dem Zeichnen fortfahren, da andere Zeilen nach dem Löschen einer Zeile an ihren neuen Positionen animiert werden. Dies konnte in ItemTouchHelper und onChildDraw nicht durchgeführt werden. Am Ende musste ich noch einen Item Decorator hinzufügen. Es geht in diese Richtung:

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (parent.getItemAnimator().isRunning()) {
        // find first child with translationY > 0
        // draw from it's top to translationY whatever you want

        int top = 0;
        int bottom = 0;

        int childCount = parent.getLayoutManager().getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getLayoutManager().getChildAt(i);
            if (child.getTranslationY() != 0) {
                top = child.getTop();
                bottom = top + (int) child.getTranslationY();                    
                break;
            }
        }

        // draw whatever you want

        super.onDraw(c, parent, state);
    }
}

UPDATE: Ich habe einen Blog-Beitrag über das Löschen von Funktionen in der Ansicht "Wischen" des Recyclers geschrieben. Jemand könnte es nützlich finden. Keine 3rd Party Bibliothek notwendig.

Blog PostGit Repo

7

Die HappyKatz-Lösung hat einen kniffligen Fehler. Gibt es einen Grund für das Zeichnen einer Bitmap, wenn dX == 0 ist? In einigen Fällen führt dies zu einer dauerhaften Symbolsichtbarkeit über dem Listenelement. Auch Symbole werden über dem Listenelement sichtbar, wenn Sie das Listenelement berühren und dX == 1. So beheben Sie diese:

        if (dX > rectOffset) {
            c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
                    (float) itemView.getBottom(), leftPaint);
            if (dX > iconOffset) {
                c.drawBitmap(leftBitmap,
                        (float) itemView.getLeft() + padding,
                        (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - leftBitmap.getHeight()) / 2,
                        leftPaint);
            }
        } else if (dX < -rectOffset) {
            c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),
                    (float) itemView.getRight(), (float) itemView.getBottom(), rightPaint);
            if (dX < -iconOffset) {
                c.drawBitmap(rightBitmap,
                        (float) itemView.getRight() - padding - rightBitmap.getWidth(),
                        (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - rightBitmap.getHeight()) / 2,
                        rightPaint);
            }
        }
5
user2410066

Für Leute, die diese Standardeinstellung immer noch finden, ist dies der einfachste Weg.

Eine einfache Utility-Klasse, um einem RecyclerView-Objekt einen Hintergrund, ein Symbol und eine Beschriftung hinzuzufügen, während Sie es nach links oder rechts wischen.

enter image description hereenter image description here

in Gradle einfügen

implementation 'it.xabaras.Android:recyclerview-swipedecorator:1.1'

Überschreiben Sie die onChildDraw-Methode der ItemTouchHelper-Klasse

@Override
public void onChildDraw (Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,float dX, float dY,int actionState, boolean isCurrentlyActive){
    new RecyclerViewSwipeDecorator.Builder(MainActivity.this, c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
            .addBackgroundColor(ContextCompat.getColor(MainActivity.this, R.color.my_background))
            .addActionIcon(R.drawable.my_icon)
            .create()
            .decorate();

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

weitere Informationen -> https://github.com/xabaras/RecyclerViewSwipeDecorator

3
kelvin andre

Für die Implementierung habe ich den von Marcin Kitowicz erstellten Beispielcode verwendet hier .

Vorteile dieser Lösung:

  1. Verwendet die Hintergrundansicht mit Layoutgrenzen, anstatt ein Rechteck zu erstellen, das über jeder Bitmap oder Drawable angezeigt wird.
  2. Verwendet Drawable Image im Gegensatz zu Bitmap, das einfacher zu implementieren ist, als ein Drawable in ein Bitmap zu konvertieren.

Den ursprünglichen Implementierungscode finden Sie hier . Um das Wischen nach links zu implementieren, habe ich die inverse Positionierungslogik nach links und rechts verwendet.

override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
    var icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
    var iconLeft = 0
    var iconRight = 0

    val background: ColorDrawable
    val itemView = viewHolder.itemView
    val margin = convertDpToPx(32)
    val iconWidth = icon!!.intrinsicWidth
    val iconHeight = icon.intrinsicHeight
    val cellHeight = itemView.bottom - itemView.top
    val iconTop = itemView.top + (cellHeight - iconHeight) / 2
    val iconBottom = iconTop + iconHeight

    // Right swipe.
    if (dX > 0) {
        icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
        background = ColorDrawable(Color.RED)
        background.setBounds(0, itemView.getTop(), (itemView.getLeft() + dX).toInt(), itemView.getBottom())
        iconLeft = margin
        iconRight = margin + iconWidth
    } /*Left swipe.*/ else {
        icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
        background = ColorDrawable(Color.BLUE)
        background.setBounds((itemView.right - dX).toInt(), itemView.getTop(), 0, itemView.getBottom())
        iconLeft = itemView.right - margin - iconWidth
        iconRight = itemView.right - margin
    }
    background.draw(c)
    icon?.setBounds(iconLeft, iconTop, iconRight, iconBottom)
    icon?.draw(c)
    super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}
3
Adam Hurwitz

So mache ich es ohne Bibliothek von Drittanbietern.

Die Vordergrundansicht ist in der Recycler-Ansicht immer sichtbar, und wenn ein Wischen ausgeführt wird, bleibt der Hintergrund in einer statischen Position sichtbar.

enter image description here

Erstellen Sie Ihr benutzerdefiniertes RecyclerView-Objekt und fügen Sie dem Hintergrundlayout des Objekts Ihr benutzerdefiniertes Symbol, Ihren Text und Ihre Hintergrundfarbe hinzu. Beachten Sie, dass ich RelativeLayout mit id=foreground Und id=background Eine ID hinzufüge.

Hier ist meins recylerview_item.xml.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:orientation="vertical">

    <RelativeLayout
        Android:id="@+id/background"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:background="@color/colorPrimary"> <!--Add your background color here-->

        <ImageView
            Android:id="@+id/delete_icon"
            Android:layout_width="30dp"
            Android:layout_height="30dp"
            Android:layout_alignParentRight="true"
            Android:layout_centerVertical="true"
            Android:layout_marginRight="10dp"
            app:srcCompat="@drawable/ic_delete"/>

        <TextView
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:layout_centerVertical="true"
            Android:layout_marginRight="10dp"
            Android:layout_toLeftOf="@id/delete_icon"
            Android:text="Swipe to delete"
            Android:textColor="#fff"
            Android:textSize="13dp" />
    </RelativeLayout>

    <RelativeLayout
        Android:padding="20dp"
        Android:id="@+id/foreground"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:background="@color/colorWhite">

            <TextView
                Android:id="@+id/textView"
                Android:text="HelloWorld"
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content" />

    </RelativeLayout>
</FrameLayout>

und aus deinem ViewHolder definiere deinen RelativeLayout foreground und background view und mache es öffentlich. Erstellen Sie auch eine Methode, mit der das Element entfernt wird. In meinem Fall befindet sich mein ViewHolder unter meinem RecyclerViewAdapter.class, Also ...

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {

    List<Object> listItem;

    public RecyclerViewAdapter(...) {
        ...
    } 

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.recyclerview_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        ....
    }

    @Override
    public int getItemCount() {
        ...
    }

    public class ViewHolder extends RecyclerView.ViewHolder{

        public RelativeLayout foreground, background;

        public ViewHolder(View itemView) {
            super(itemView);

            /** define your foreground and background **/

            foreground = itemView.findViewById(R.id.foreground);
            background = itemView.findViewById(R.id.background);

        }

    }

    /**Call this later to remove the item on swipe**/
    public void removeItem(int position){
        //remove the item here
        listItem.remove(position);
        notifyItemRemoved(position);
    }
}

Und erstelle eine Klasse und nenne sie RecyclerItemTouchHelper.class, Hier passiert das Wischen.

public class RecyclerItemTouchHelper extends ItemTouchHelper.SimpleCallback {

    private RecyclerItemTouchHelperListener listener;

    public RecyclerItemTouchHelper(int dragDirs, int swipeDirs, RecyclerItemTouchHelperListener listener) {
        super(dragDirs, swipeDirs);
        this.listener = listener;
    }

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

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (viewHolder != null) {
            final View foregroundView = ((RecyclerViewAdapter.ViewHolder) viewHolder).foreground;
            getDefaultUIUtil().onSelected(foregroundView);
        }
    }

    @Override
    public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
                                RecyclerView.ViewHolder viewHolder, float dX, float dY,
                                int actionState, boolean isCurrentlyActive) {
        final View foregroundView = ((RecyclerViewAdapter.ViewHolder) viewHolder).foreground;
        getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY,
                actionState, isCurrentlyActive);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        final View foregroundView = ((RecyclerViewAdapter.ViewHolder) viewHolder).foreground;
        getDefaultUIUtil().clearView(foregroundView);
    }

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView,
                            RecyclerView.ViewHolder viewHolder, float dX, float dY,
                            int actionState, boolean isCurrentlyActive) {
        final View foregroundView = ((RecyclerViewAdapter.ViewHolder) viewHolder).foreground;

        getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY,
                actionState, isCurrentlyActive);
    }

    @Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
        listener.onSwiped(viewHolder, direction, viewHolder.getAdapterPosition());
    }

    @Override
    public int convertToAbsoluteDirection(int flags, int layoutDirection) {
        return super.convertToAbsoluteDirection(flags, layoutDirection);
    }

    public interface RecyclerItemTouchHelperListener {
        void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position);
    }
}

Fügen Sie nun von Ihrem MainActivity.class Oder wo immer sich Ihr RecyclerView befindet, das RecyclerItemTouchHelper hinzu. In meinem Fall ist die RecyclerView in MainActivity.class, Also habe ich RecyclerItemTouchHelper.RecyclerItemTouchHelperListener In sie implementiert und die Methode onSwiped()... überschrieben.

public class MainActivity extends AppCompatActivity implements RecyclerItemTouchHelper.RecyclerItemTouchHelperListener {

    RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        //Configure RecyclerView

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);  
        RecyclerView.LayoutManager mLyoutManager = new LinearLayoutManager(getApplicationContext());
        recyclerView.setLayoutManager(mLyoutManager);
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        adapter = new RecyclerViewAdapter(this);
        adapter.setClickListener(this);
        recyclerView.setAdapter(adapter);
        recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));

        //Attached the ItemTouchHelper
        ItemTouchHelper.SimpleCallback itemTouchHelperCallback = new RecyclerItemTouchHelper(0, ItemTouchHelper.LEFT, this);
        new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerView);
    }

    //define the method onSwiped()
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position) {
        if (viewHolder instanceof RecyclerViewAdapter.ViewHolder) {
            adapter.removeItem(viewHolder.getAdapterPosition()); //remove the item from the adapter
        }
    }

}

Für weitere Informationen und Erläuterungen hier ist der Blog dafür.

0
Polar