使用Android';时,在滑动行下添加带文本/图标的彩色背景;s回收视图
EDIT:真正的问题是我的使用Android';时,在滑动行下添加带文本/图标的彩色背景;s回收视图,android,android-layout,android-canvas,android-support-library,android-recyclerview,Android,Android Layout,Android Canvas,Android Support Library,Android Recyclerview,EDIT:真正的问题是我的LinearLayout被包装在另一个布局中,这导致了不正确的行为。Sanvywell接受的答案提供了一个更好、更完整的示例,说明了如何在滑动视图下绘制颜色,而不是我在问题中提供的代码片段。 现在这个小部件在类的帮助下对行扫描有了本机支持,我正尝试在一个应用程序中使用它,其中行的行为类似于谷歌的收件箱应用程序。也就是说,向左滑动执行一个动作,向右滑动执行另一个动作 使用的onsweep方法很容易实现操作本身。然而,我找不到一个简单的方法来设置应该出现在当前正在浏览的视图
LinearLayout
被包装在另一个布局中,这导致了不正确的行为。Sanvywell接受的答案提供了一个更好、更完整的示例,说明了如何在滑动视图下绘制颜色,而不是我在问题中提供的代码片段。
现在这个小部件在类的帮助下对行扫描有了本机支持,我正尝试在一个应用程序中使用它,其中行的行为类似于谷歌的收件箱应用程序。也就是说,向左滑动执行一个动作,向右滑动执行另一个动作
使用的onsweep
方法很容易实现操作本身。然而,我找不到一个简单的方法来设置应该出现在当前正在浏览的视图下的颜色和图标(比如在谷歌的收件箱应用程序中)
为此,我尝试重写的onchildraw
方法,如下所示:
@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确定滑动方向并设置适当的颜色可以按预期工作,但我从视窗夹
获得的坐标始终与第一个线性布局
充气的位置相对应
如何获取当前刷卡行中的
线性布局的正确坐标?有没有一种更简单的方法(不需要重写onchildraw)来设置背景颜色和图标?我也在努力实现这一功能,但你把我引向了正确的方向
@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);
}
}
公认的答案在给背景上色方面做得很好,但没有提到绘制图标
这对我来说很有效,因为它既设置了背景色,又绘制了图标,在滑动过程中图标不会被拉伸,也不会在滑动后的上一个和下一个项目之间留下间隙
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));
}
HappyKatz解决方案有一个棘手的bug。当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);
}
}
if(dX>rectcoffset){
c、 drawRect((float)itemView.getLeft(),(float)itemView.getTop(),dX,
(float)itemView.getBottom(),leftPaint);
如果(dX>iconOffset){
c、 drawBitmap(左位图,
(float)itemView.getLeft()+填充,
(float)itemView.getTop()+((float)itemView.getBottom()-(float)itemView.getTop()-leftBitmap.getHeight())/2,
左油漆);
}
}否则如果(dX<-rectfoset){
c、 drawRect((float)itemView.getRight()+dX,(float)itemView.getTop(),
(float)itemView.getRight(),(float)itemView.getBottom(),rightPaint);
如果(dX<-iconOffset){
c、 drawBitmap(右位图,
(float)itemView.getRight()-padding-rightBitmap.getWidth(),
(float)itemView.getTop()+((float)itemView.getBottom()-(float)itemView.getTop()-rightmap.getHeight())/2,
右油漆);
}
}
我不确定这些解决方案(由@Sanvywell、@HappyKatz和@user2410066编写)对你们是如何起作用的,但就我而言,我需要在onchildraw
方法中再检查一次
看起来像是ItemTouchHelper
保留了被删除行的ViewHolder
s,以防需要恢复它们。除了正在刷卡的VH之外,它还为这些VH调用onchildraw
。不确定这种行为对内存管理的影响,但我需要在onchildraw
的开头进行额外检查,以避免绘制“fantom”行
奖金部分:
我还想在滑动删除一行后,随着其他行动画移动到它们的新位置,继续绘图,但我无法在ItemTouchHelper
和onchildraw
中完成。最后,我不得不添加另一个项目装饰做这件事。大致是这样的:
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);
}
}
public void onDraw(画布c、RecyclerView父对象、RecyclerView.State){
if(parent.getItemAnimator().isRunning()){
//查找translationY>0的第一个子项
//从它的顶端画出你想要的任何东西
int-top=0;
int-bottom=0;
int childCount=parent.getLayoutManager().getChildCount();
for(int i=0;i
更新:我写了一篇关于“循环利用者视图”的博客文章,内容是“滑动以删除”功能。也许有人会觉得它有用。不需要第三方库
为了实现,我使用了Marcin Kitowicz创建的示例代码
此解决方案的好处:
使用具有布局边界的背景视图,而不是创建一个矩形,该矩形将显示在任何位图或可绘制的视图的顶部
使用可绘制图像而不是位图,这比将可绘制图像转换为位图更容易实现
可以找到原始的实现代码。为了实现左扫,我使用了反向左和右定位逻辑
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)
}
}
对于仍然发现这种默认情况的人来说,这是最简单的方法
一个简单的实用程序类,用于在向左或向右滑动RecyclerView项时向其添加背景、图标和标签
插入到Gradle
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.1'
重写ItemTouchHelper类的OnChildRaw方法
@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);
}
有关更多信息->以下是我在没有第三方库的情况下的做法
前景
@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);
}
<?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>
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);
}
}
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);
}
}
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
}
}
}