欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

RecyclerView实现探探卡片滑动效果

程序员文章站 2022-03-23 13:47:13
这里是一个通过自定义view和自定义recyclerview的:layoutmanager,再结合itemtouchhelper实现的一个仿探探的卡片滑动的效果: 效...

这里是一个通过自定义view和自定义recyclerview的:layoutmanager,再结合itemtouchhelper实现的一个仿探探的卡片滑动的效果:

RecyclerView实现探探卡片滑动效果

效果图已经奉上,接下来是代码:

首先是每张图片的布局:item

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:layout_width="336dp"
 android:layout_height="426dp"
 android:background="@drawable/img_card_background"
 android:gravity="center"
 android:orientation="vertical">
 
 <relativelayout
  android:layout_width="match_parent"
  android:layout_height="0dp"
  android:layout_weight="1">
 
  <com.bwie.w.test1121.cardswipelayout.roundimageview
   android:id="@+id/iv_avatar"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:scaletype="centercrop"
   android:src="@drawable/img_avatar_01"
   app:radius="7.5dp" />
 
  <imageview
   android:id="@+id/iv_dislike"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_alignparentright="true"
   android:layout_marginright="15dp"
   android:layout_margintop="15dp"
   android:alpha="0"
   android:src="@drawable/img_dislike" />
 
  <imageview
   android:id="@+id/iv_like"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_marginleft="15dp"
   android:layout_margintop="15dp"
   android:alpha="0"
   android:src="@drawable/img_like" />
 
 </relativelayout>
 
 <relativelayout
  android:layout_width="match_parent"
  android:layout_height="100dp"
  android:paddingleft="14dp"
  android:paddingtop="15dp">
 
  <textview
   android:id="@+id/tv_name"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:gravity="center"
   android:text="小姐姐"
   android:textcolor="@android:color/black"
   android:textsize="16sp" />
 
  <textview
   android:id="@+id/tv_age"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_below="@id/tv_name"
   android:layout_margintop="5dp"
   android:background="@drawable/shape_age"
   android:gravity="center"
   android:text="♀ 23"
   android:textcolor="#ffffff"
   android:textsize="14sp" />
 
  <textview
   android:id="@+id/tv_constellation"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_below="@id/tv_name"
   android:layout_marginleft="4dp"
   android:layout_margintop="5dp"
   android:layout_torightof="@id/tv_age"
   android:background="@drawable/shape_constellation"
   android:gravity="center"
   android:text="狮子座"
   android:textcolor="#ffffff"
   android:textsize="14sp" />
 
  <textview
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_below="@id/tv_age"
   android:layout_margintop="5dp"
   android:gravity="center"
   android:text="it/互联网"
   android:textcolor="#cbcbcb" />
 
 </relativelayout>
 
 
</linearlayout>

activity_main:

<android.support.v7.widget.recyclerview
  android:id="@+id/recyclerview"
  android:layout_width="match_parent"
  android:layout_height="match_parent" />

一个常量参数类:cardconfig

/**
 * 常量参数
 */
 
public final class cardconfig {
 /**
  * 显示可见的卡片数量
  */
 public static final int default_show_item = 3;
 /**
  * 默认缩放的比例
  */
 public static final float default_scale = 0.1f;
 /**
  * 卡片y轴偏移量时按照14等分计算
  */
 public static final int default_translate_y = 14;
 /**
  * 卡片滑动时默认倾斜的角度
  */
 public static final float default_rotate_degree = 15f;
 /**
  * 卡片滑动时不偏左也不偏右
  */
 public static final int swiping_none = 1;
 /**
  * 卡片向左滑动时
  */
 public static final int swiping_left = 1 << 2;
 /**
  * 卡片向右滑动时
  */
 public static final int swiping_right = 1 << 3;
 /**
  * 卡片从左边滑出
  */
 public static final int swiped_left = 1;
 /**
  * 卡片从右边滑出
  */
 public static final int swiped_right = 1 << 2;
}

拖动item的回调类:carditemtouchhelpercallback

public class carditemtouchhelpercallback<t> extends itemtouchhelper.callback {
 
 private final recyclerview.adapter adapter;
 private list<t> datalist;
 private onswipelistener<t> mlistener;
 
 public carditemtouchhelpercallback(@nonnull recyclerview.adapter adapter, @nonnull list<t> datalist) {
  this.adapter = checkisnull(adapter);
  this.datalist = checkisnull(datalist);
 }
 
 public carditemtouchhelpercallback(@nonnull recyclerview.adapter adapter, @nonnull list<t> datalist, onswipelistener<t> listener) {
  this.adapter = checkisnull(adapter);
  this.datalist = checkisnull(datalist);
  this.mlistener = listener;
 }
 
 private <t> t checkisnull(t t) {
  if (t == null) {
   throw new nullpointerexception();
  }
  return t;
 }
 
 public void setonswipedlistener(onswipelistener<t> mlistener) {
  this.mlistener = mlistener;
 }
 
 
 /**
  * 设置滑动类型标记
  *
  * @param recyclerview
  * @param viewholder
  * @return
  *   返回一个整数类型的标识,用于判断item那种移动行为是允许的
  */
 @override
 public int getmovementflags(recyclerview recyclerview, recyclerview.viewholder viewholder) {
  int dragflags = 0;
  int swipeflags = 0;
  recyclerview.layoutmanager layoutmanager = recyclerview.getlayoutmanager();
  if (layoutmanager instanceof cardlayoutmanager) {
   swipeflags = itemtouchhelper.left | itemtouchhelper.right;
  }
  return makemovementflags(dragflags, swipeflags);
 }
 /**
  * 拖拽切换item的回调
  *
  * @param recyclerview
  * @param viewholder
  * @param target
  * @return
  *   如果item切换了位置,返回true;反之,返回false
  */
 
 @override
 public boolean onmove(recyclerview recyclerview, recyclerview.viewholder viewholder, recyclerview.viewholder target) {
  return false;
 }
 
 /**
  *
  * 划出时会执行
  * @param viewholder
  * @param direction:左侧划出为:4,右侧划出为:8
  */
 @override
 public void onswiped(recyclerview.viewholder viewholder, int direction) {
  log.d("mylog", "onswiped: " + direction);
  // 移除 ontouchlistener,否则触摸滑动会乱了
  viewholder.itemview.setontouchlistener(null);
  int layoutposition = viewholder.getlayoutposition();
  t remove = datalist.remove(layoutposition);
  adapter.notifydatasetchanged();
  if (mlistener != null) {
   mlistener.onswiped(viewholder, remove, direction == itemtouchhelper.left ? cardconfig.swiped_left : cardconfig.swiped_right);
  }
  // 当没有数据时回调 mlistener
  if (adapter.getitemcount() == 0) {
   if (mlistener != null) {
    mlistener.onswipedclear();
   }
  }
 }
 /**
  * item是否支持滑动
  *
  * @return
  *   true 支持滑动操作
  *   false 不支持滑动操作
  */
 
 @override
 public boolean isitemviewswipeenabled() {
  return false;
 }
 
 /**
  * 拖动时会执行的方法
  * @param c
  * @param recyclerview
  * @param viewholder
  * @param dx
  * @param dy
  * @param actionstate
  * @param iscurrentlyactive
  */
 @override
 public void onchilddraw(canvas c, recyclerview recyclerview, recyclerview.viewholder viewholder,
       float dx, float dy, int actionstate, boolean iscurrentlyactive) {
  super.onchilddraw(c, recyclerview, viewholder, dx, dy, actionstate, iscurrentlyactive);
  // log.d("mylog", "onchilddraw: 拖动");
  view itemview = viewholder.itemview;
  if (actionstate == itemtouchhelper.action_state_swipe) {
   float ratio = dx / getthreshold(recyclerview, viewholder);
   // ratio 最大为 1 或 -1
   if (ratio > 1) {
    ratio = 1;
   } else if (ratio < -1) {
    ratio = -1;
   }
   log.d("mylog", "onchilddraw: " + ratio);
   itemview.setrotation(ratio * cardconfig.default_rotate_degree);
   int childcount = recyclerview.getchildcount();
   // 当数据源个数大于最大显示数时
   if (childcount > cardconfig.default_show_item) {
    for (int position = 1; position < childcount - 1; position++) {
     int index = childcount - position - 1;
     view view = recyclerview.getchildat(position);
     view.setscalex(1 - index * cardconfig.default_scale + math.abs(ratio) * cardconfig.default_scale);
     view.setscaley(1 - index * cardconfig.default_scale + math.abs(ratio) * cardconfig.default_scale);
 
     /* view.setscalex(1 - index * cardconfig.default_scale );
     view.setscaley(1 - index * cardconfig.default_scale);*/
     view.settranslationy((index - math.abs(ratio)) * itemview.getmeasuredheight() / cardconfig.default_translate_y);
    }
   } else {
    // 当数据源个数小于或等于最大显示数时
    for (int position = 0; position < childcount - 1; position++) {
     int index = childcount - position - 1;
     view view = recyclerview.getchildat(position);
     view.setscalex(1 - index * cardconfig.default_scale + math.abs(ratio) * cardconfig.default_scale);
     view.setscaley(1 - index * cardconfig.default_scale + math.abs(ratio) * cardconfig.default_scale);
     view.settranslationy((index - math.abs(ratio)) * itemview.getmeasuredheight() / cardconfig.default_translate_y);
    }
   }
   if (mlistener != null) {
    if (ratio != 0) {
     // log.d("mylog", "onchilddraw: 不为零");
     mlistener.onswiping(viewholder, ratio, ratio < 0 ? cardconfig.swiping_left : cardconfig.swiping_right);
    } else {
     // log.d("mylog", "onchilddraw: 为零");
     mlistener.onswiping(viewholder, ratio, cardconfig.swiping_none);
    }
   }
  }
 }
 
 @override
 public void clearview(recyclerview recyclerview, recyclerview.viewholder viewholder) {
  super.clearview(recyclerview, viewholder);
  viewholder.itemview.setrotation(0f);
 }
 
 private float getthreshold(recyclerview recyclerview, recyclerview.viewholder viewholder) {
  return recyclerview.getwidth() * getswipethreshold(viewholder);
 }
 
}

自定义布局管理器:cardlayoutmanager:

/**
 * 自定义布局管理器
 */
 
public class cardlayoutmanager extends recyclerview.layoutmanager {
 
 private recyclerview mrecyclerview;
 private itemtouchhelper mitemtouchhelper;
 
 public cardlayoutmanager(@nonnull recyclerview recyclerview, @nonnull itemtouchhelper itemtouchhelper) {
  this.mrecyclerview = checkisnull(recyclerview);
  this.mitemtouchhelper = checkisnull(itemtouchhelper);
 }
 
 private <t> t checkisnull(t t) {
  if (t == null) {
   throw new nullpointerexception();
  }
  return t;
 }
 
 @override
 public recyclerview.layoutparams generatedefaultlayoutparams() {
  return new recyclerview.layoutparams(viewgroup.layoutparams.wrap_content, viewgroup.layoutparams.wrap_content);
 }
 
 @override
 public void onlayoutchildren(final recyclerview.recycler recycler, recyclerview.state state) {
  detachandscrapattachedviews(recycler);
  int itemcount = getitemcount();
  // 当数据源个数大于最大显示数时
  if (itemcount > cardconfig.default_show_item) {
   for (int position = cardconfig.default_show_item; position >= 0; position--) {
    final view view = recycler.getviewforposition(position);
    addview(view);
    measurechildwithmargins(view, 0, 0);
    int widthspace = getwidth() - getdecoratedmeasuredwidth(view);
    int heightspace = getheight() - getdecoratedmeasuredheight(view);
    // recyclerview 布局
    layoutdecoratedwithmargins(view, widthspace / 2, heightspace / 2,
      widthspace / 2 + getdecoratedmeasuredwidth(view),
      heightspace / 2 + getdecoratedmeasuredheight(view));
 
    if (position == cardconfig.default_show_item) {
     view.setscalex(1 - (position - 1) * cardconfig.default_scale);
     view.setscaley(1 - (position - 1) * cardconfig.default_scale);
     view.settranslationy((position - 1) * view.getmeasuredheight() / cardconfig.default_translate_y);
    } else if (position > 0) {
     view.setscalex(1 - position * cardconfig.default_scale);
     view.setscaley(1 - position * cardconfig.default_scale);
     view.settranslationy(position * view.getmeasuredheight() / cardconfig.default_translate_y);
    } else {
     view.setontouchlistener(montouchlistener);
    }
   }
  } else {
   // 当数据源个数小于或等于最大显示数时
   for (int position = itemcount - 1; position >= 0; position--) {
    final view view = recycler.getviewforposition(position);
    addview(view);
    measurechildwithmargins(view, 0, 0);
    int widthspace = getwidth() - getdecoratedmeasuredwidth(view);
    int heightspace = getheight() - getdecoratedmeasuredheight(view);
    // recyclerview 布局
    layoutdecoratedwithmargins(view, widthspace / 2, heightspace / 2,
      widthspace / 2 + getdecoratedmeasuredwidth(view),
      heightspace / 2 + getdecoratedmeasuredheight(view));
 
    if (position > 0) {
     view.setscalex(1 - position * cardconfig.default_scale);
     view.setscaley(1 - position * cardconfig.default_scale);
     view.settranslationy(position * view.getmeasuredheight() / cardconfig.default_translate_y);
    } else {
     view.setontouchlistener(montouchlistener);
    }
   }
  }
 }
 
 private view.ontouchlistener montouchlistener = new view.ontouchlistener() {
 
  @override
  public boolean ontouch(view v, motionevent event) {
   recyclerview.viewholder childviewholder = mrecyclerview.getchildviewholder(v);
   if (motioneventcompat.getactionmasked(event) == motionevent.action_down) {
    mitemtouchhelper.startswipe(childviewholder);
   }
   return false;
  }
 };
 
}

状态回调接口:onswipelistener

/**
 * @author 状态回调接口
 */
 
public interface onswipelistener<t> {
 
 /**
  * 卡片还在滑动时回调
  *
  * @param viewholder 该滑动卡片的viewholder
  * @param ratio  滑动进度的比例
  * @param direction 卡片滑动的方向,cardconfig.swiping_left 为向左滑,cardconfig.swiping_right 为向右滑,
  *     cardconfig.swiping_none 为不偏左也不偏右
  */
 void onswiping(recyclerview.viewholder viewholder, float ratio, int direction);
 
 /**
  * 卡片完全滑出时回调
  *
  * @param viewholder 该滑出卡片的viewholder
  * @param t   该滑出卡片的数据
  * @param direction 卡片滑出的方向,cardconfig.swiped_left 为左边滑出;cardconfig.swiped_right 为右边滑出
  */
 void onswiped(recyclerview.viewholder viewholder, t t, int direction);
 
 /**
  * 所有的卡片全部滑出时回调
  */
 void onswipedclear();
 
}

自定义条目图片样式:roundimageview:

/**
 * 自定义图片样式,顶部圆角显示
 */
 
public class roundimageview extends imageview {
 
 private path mpath;
 private rectf mrectf;
 /*圆角的半径,依次为左上角xy半径,右上角,右下角,左下角*/
 private float[] rids = new float[8];
 private paintflagsdrawfilter paintflagsdrawfilter;
 
 public roundimageview(context context) {
  this(context, null);
 }
 
 public roundimageview(context context, attributeset attrs) {
  this(context, attrs, 0);
 }
 
 public roundimageview(context context, attributeset attrs, int defstyleattr) {
  super(context, attrs, defstyleattr);
  typedarray array = context.obtainstyledattributes(attrs, r.styleable.roundimageview);
  float mradius = array.getdimension(r.styleable.roundimageview_radius, 10);
  rids[0] = mradius;
  rids[1] = mradius;
  rids[2] = mradius;
  rids[3] = mradius;
  rids[4] = 0f;
  rids[5] = 0f;
  rids[6] = 0f;
  rids[7] = 0f;
  array.recycle();
  //用于绘制的类
  mpath = new path();
  //抗锯齿
  paintflagsdrawfilter = new paintflagsdrawfilter(0, paint.anti_alias_flag | paint.filter_bitmap_flag);
  //关闭硬件加速,同时其他地方依然享受硬件加速
  setlayertype(view.layer_type_hardware, null);
 }
 
 @override
 protected void ondraw(canvas canvas) {
  // log.d("mylog", "ondraw: ");
  //重置path
  mpath.reset();
  //p1:大小,p2:圆角,p3:cw:顺时针绘制path,ccw:逆时针
  mpath.addroundrect(mrectf, rids, path.direction.cw);
  //添加抗锯齿
  canvas.setdrawfilter(paintflagsdrawfilter);
  canvas.save();
  //该方法不支持硬件加速,如果开启会导致效果出不来,所以之前设置关闭硬件加速
  //clip(剪切)的时机:通常理解的clip(剪切),是对已经存在的图形进行clip的。
  // 但是,在android上是对canvas(画布)上进行clip的,要在画图之前对canvas进行clip,
  // 如果画图之后再对canvas进行clip不会影响到已经画好的图形。一定要记住clip是针对canvas而非图形
  //开始根据path裁剪
  canvas.clippath(mpath);
  super.ondraw(canvas);
  canvas.restore();
 }
 
 int a,b;
 //执行在ondraw()之前
 @override
 protected void onsizechanged(int w, int h, int oldw, int oldh) {
  super.onsizechanged(w, h, oldw, oldh);
  // log.d("mylog", "onsizechanged: ");
  a = w;
  b = h;
  mrectf = new rectf(0, 0, w, h);
  log.d("mylog", "onsizechanged: "+w+"-----"+h);
 }
 
}

mainactivity:

public class mainactivity extends appcompatactivity {
 private list<integer> list = new arraylist<>();
 @override
 protected void oncreate(bundle savedinstancestate) {
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.activity_main);
 
  initview();
  initdata();
 }
 private void initview() {
  final recyclerview recyclerview = findviewbyid(r.id.recyclerview);
  recyclerview.setitemanimator(new defaultitemanimator());
  recyclerview.setadapter(new myadapter());
  carditemtouchhelpercallback cardcallback = new carditemtouchhelpercallback(recyclerview.getadapter(), list);
  cardcallback.setonswipedlistener(new onswipelistener<integer>() {
 
   @override
   public void onswiping(recyclerview.viewholder viewholder, float ratio, int direction) {
    myadapter.myviewholder myholder = (myadapter.myviewholder) viewholder;
    viewholder.itemview.setalpha(1 - math.abs(ratio) * 0.2f);
    if (direction == cardconfig.swiping_left) {
     myholder.dislikeimageview.setalpha(math.abs(ratio));
    } else if (direction == cardconfig.swiping_right) {
     myholder.likeimageview.setalpha(math.abs(ratio));
    } else {
     myholder.dislikeimageview.setalpha(0f);
     myholder.likeimageview.setalpha(0f);
    }
   }
 
   @override
   public void onswiped(recyclerview.viewholder viewholder, integer o, int direction) {
    myadapter.myviewholder myholder = (myadapter.myviewholder) viewholder;
    viewholder.itemview.setalpha(1f);
    myholder.dislikeimageview.setalpha(0f);
    myholder.likeimageview.setalpha(0f);
    toast.maketext(mainactivity.this, direction == cardconfig.swiped_left ? "swiped left" : "swiped right", toast.length_short).show();
   }
 
   @override
   public void onswipedclear() {
    toast.maketext(mainactivity.this, "data clear", toast.length_short).show();
    recyclerview.postdelayed(new runnable() {
     @override
     public void run() {
      initdata();
      recyclerview.getadapter().notifydatasetchanged();
     }
    }, 3000l);
   }
 
  });
  final itemtouchhelper touchhelper = new itemtouchhelper(cardcallback);
  final cardlayoutmanager cardlayoutmanager = new cardlayoutmanager(recyclerview, touchhelper);
  recyclerview.setlayoutmanager(cardlayoutmanager);
  touchhelper.attachtorecyclerview(recyclerview);
 }
 
 
 private void initdata() {
  list.add(r.drawable.img_avatar_01);
  list.add(r.drawable.img_avatar_02);
  list.add(r.drawable.img_avatar_03);
  list.add(r.drawable.img_avatar_04);
  list.add(r.drawable.img_avatar_05);
  list.add(r.drawable.img_avatar_06);
  list.add(r.drawable.img_avatar_07);
 }
 
 private class myadapter extends recyclerview.adapter {
  @override
  public recyclerview.viewholder oncreateviewholder(viewgroup parent, int viewtype) {
   view view = layoutinflater.from(parent.getcontext()).inflate(r.layout.item, parent, false);
   return new myviewholder(view);
  }
 
  @override
  public void onbindviewholder(recyclerview.viewholder holder, int position) {
   imageview avatarimageview = ((myviewholder) holder).avatarimageview;
   avatarimageview.setimageresource(list.get(position));
  }
 
  @override
  public int getitemcount() {
   return list.size();
  }
 
  class myviewholder extends recyclerview.viewholder {
 
   imageview avatarimageview;
   imageview likeimageview;
   imageview dislikeimageview;
 
   myviewholder(view itemview) {
    super(itemview);
    avatarimageview = (imageview) itemview.findviewbyid(r.id.iv_avatar);
    likeimageview = (imageview) itemview.findviewbyid(r.id.iv_like);
    dislikeimageview = (imageview) itemview.findviewbyid(r.id.iv_dislike);
   }
 
  }
 }
}

attrs:

<resources>
 <declare-styleable name="roundimageview">
  <attr name="radius" format="reference|dimension" />
 </declare-styleable>
</resources>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。