RecyclerView实现探探卡片滑动效果
程序员文章站
2022-03-23 13:47:13
这里是一个通过自定义view和自定义recyclerview的:layoutmanager,再结合itemtouchhelper实现的一个仿探探的卡片滑动的效果:
效...
这里是一个通过自定义view和自定义recyclerview的:layoutmanager,再结合itemtouchhelper实现的一个仿探探的卡片滑动的效果:
效果图已经奉上,接下来是代码:
首先是每张图片的布局: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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
js实现做通讯录的索引滑动显示效果和滑动显示锚点效果
-
Android Recyclerview实现水平分页GridView效果示例
-
Android使用TabLayou+fragment+viewpager实现滑动切换页面效果
-
Android实现手势滑动和简单动画效果
-
Android Studio使用ViewPager+Fragment实现滑动菜单Tab效果
-
Android中RecyclerView实现多级折叠列表效果(二)
-
Android中RecyclerView实现多级折叠列表效果(TreeRecyclerView)
-
js移动端滑动事件(js实现左右滑动页面效果)
-
html5 touch事件实现页面上下滑动效果【附代码】
-
高仿网易新闻顶部滑动条效果实现代码