web-dev-qa-db-ja.com

AndroidのRecyclerViewを使用しているときに、スワイプした行の下にテキスト/アイコンで色付きの背景を追加する

編集:本当の問題は、私のLinearLayoutが別のレイアウトにラップされていたため、不正な動作が発生したことです。 Sanvywellの受け入れられた答えには、質問で提供したコードスニペットよりも、スワイプビューで色を描画する方法のより完全な例があります。

RecyclerView ウィジェットは ItemTouchHelper クラスの助けを借りて行スワイプをネイティブにサポートしているので、行がGoogleの受信トレイアプリと同様に動作するアプリで使用しようとしています。つまり、左側にスワイプすると1つのアクションが実行され、右側にスワイプすると別のアクションが実行されます。

ItemTouchHelper.SimpleCallbackonSwipedメソッドを使用すると、アクション自体を簡単に実装できました。ただし、現在スワイプされているビューの下に表示される色とアイコンを設定する簡単な方法を見つけることができませんでした(GoogleのInboxアプリなど)。

そのために、 ItemTouchHelper.SimpleCallbackonChildDrawメソッドを次のようにオーバーライドしようとしています。

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

DXからのスワイプ方向の決定と適切な色の設定は意図したとおりに機能しますが、ViewHolderから取得する座標は常に最初のLinearLayoutが膨張した場所に対応します。

現在スワイプされている行にあるLinearLayoutの正しい座標を取得するにはどうすればよいですか?背景色とアイコンを設定する簡単な方法(onChildDrawをオーバーライドする必要がない)はありますか?

27
Manvis

私もこの機能を実装するのに苦労していましたが、あなたは私を正しい方向に導きました。

@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

受け入れられた答えは、背景を着色するという素晴らしい仕事をしますが、アイコンの描画については言及しませんでした。

これは、スワイプ中にアイコンが引き伸ばされたり、スワイプ後に前のアイテムと次のアイテムの間に隙間が生じたりすることなく、背景色を設定し、アイコンを描いたため、私にとってはうまくいきました。

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

HappyKatzソリューションには扱いにくいバグがあります。 dX == 0のときにビットマップを描画する理由はありますか?場合によっては、これによりリストアイテムの上に永続的なアイコンが表示されます。また、リストアイテムとdX == 1をタッチするだけで、リストアイテムの上にアイコンが表示されます。これらを修正するには:

        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

それでもこのデフォルトを見つけている人にとっては、これが最も簡単な方法です。

左右にスワイプしながら、RecyclerViewアイテムに背景、アイコン、ラベルを追加するためのシンプルなユーティリティクラス。

enter image description hereenter image description here

gradleに挿入

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

ItemTouchHelperクラスのonChildDrawメソッドをオーバーライドします

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

詳細情報-> https://github.com/xabaras/RecyclerViewSwipeDecorator

3
kelvin andre

実装するために、Marcin Kitowicz here によって作成されたサンプルコードを使用しました。

このソリューションの利点:

  1. BitmapまたはDrawableの上に表示されるRectangleを作成する代わりに、レイアウト境界のある背景ビューを使用します。
  2. DrawableをBitmapに変換する必要があるよりも実装が簡単な、Bitmapとは対照的なDrawableイメージを使用します。

元の実装コードは here にあります。左スワイプを実装するために、逆の左右の配置ロジックを使用しました。

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

サードパーティライブラリなしでそれを行う方法は次のとおりです。

前景ビューはリサイクラビューで常に表示され、スワイプが実行されると、静的な位置にとどまって背景が表示されます。

enter image description here

カスタムRecyclerViewアイテムを作成し、アイテムの背景レイアウトにカスタムアイコン、テキスト、および背景色を追加します。 _id=foreground_と_id=background_を使用してRelativeLayoutにidを設定していることに注意してください。

これが私の_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>
_

そして、ViewHolderから_RelativeLayout foreground_と_background view_を定義し、公開します。また、アイテムを削除するメソッドを作成します。私の場合、ViewHolderは_RecyclerViewAdapter.class_の下にあるため、...

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

そして、クラスを作成して_RecyclerItemTouchHelper.class_という名前を付けます。ここでスワイプが発生します。

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

ここで、_MainActivity.class_またはRecyclerViewがある場所から、RecyclerItemTouchHelperをアタッチします。私の場合、RecyclerViewは_MainActivity.class_にあるため、_RecyclerItemTouchHelper.RecyclerItemTouchHelperListener_を実装し、メソッドonSwiped()...をオーバーライドします。

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

}
_

詳細と説明については、 here を参照してください。

0
Polar