效果图:

分析:

从效果上看图片的展示具有层次感,在数据结构上更像是stack,所以通过继承FrameLayout来实现(不清楚FrameLayout布局特点的可以先百度下哈),外面是通过继承FrameLayout自定义的TinderStackLayout,它主要作为一个容器,里面的卡片效果也是继承FrameLayout自定义TinderCardView,需要对其onTouch()事件进行监听,设置阀值来判断TinderCardView的移动距离,然后通过设置动画即可实现。

具体实现:

TinderCardView实现:

1、在布局上最外层使用CardView,里面包含两部分内容,上面ImageView,下面TextView.通过inflate设置内容视图。在最顶层还需要一个ImageView来作为Tips

2、通过变量downX,downY来记录按下时的坐标位置。

3、在移动过程中dX,dY来记录视图移动的坐标,然后重新设置移动后的视图位置,同时还要计算和设置视图的旋转角度以及tips的透明度等。

4、当手指抬起的时候需要判断视图向左或者向右移动的距离是否超过设定的阀值,如果超过了就从父容器TinderCardLayout中移除,否则重置视图位置。

5、在移除视图之后需要判断父容器内子视图的数量,如果仅剩下一个子视图,就通知TinderCardLayout加载更多。


public class TinderCardView extends FrameLayout implements View.OnTouchListener {private static final int PADDINGVALUE=16;private static final float CARD_ROTATION_DEGREES = 40.0f;public static final int DURATIONTIME=300;private ImageView iv;private TextView tv_name;private ImageView iv_tips;private int padding;private float downX;private float downY;private float newX;private float newY;private float dX;private float dY;private float rightBoundary;private float leftBoundary;private int screenWidth;private OnLoadMoreListener listener;public TinderCardView(Context context) {this(context,null);}public TinderCardView(Context context, AttributeSet attrs) {this(context, attrs,0);}public TinderCardView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}public void init(Context context){if(!isInEditMode()){inflate(context,R.layout.cardview,this);screenWidth=DensityUtil.getScreenWidth(context);leftBoundary =  screenWidth * (1.0f/6.0f);rightBoundary = screenWidth * (5.0f/6.0f);iv=(ImageView) findViewById(R.id.iv);tv_name=(TextView) findViewById(R.id.tv_name);iv_tips=(ImageView)findViewById(R.id.iv_tips);padding = DensityUtil.dip2px(context, PADDINGVALUE);setOnTouchListener(this);}}@Overridepublic boolean onTouch(final View view, MotionEvent motionEvent) {TinderStackLayout tinderStackLayout = ((TinderStackLayout) view.getParent());TinderCardView topCard = (TinderCardView) tinderStackLayout.getChildAt(tinderStackLayout.getChildCount() - 1);if (topCard.equals(view)) {switch (motionEvent.getAction()) {case MotionEvent.ACTION_DOWN:downX = motionEvent.getX();downY = motionEvent.getY();view.clearAnimation();return true;case MotionEvent.ACTION_MOVE:newX = motionEvent.getX();newY = motionEvent.getY();dX = newX - downX;dY = newY - downY;float posX = view.getX() + dX;view.setX(view.getX() + dX);view.setY(view.getY() + dY);float rotation = (CARD_ROTATION_DEGREES * (posX)) / screenWidth;int halfCardHeight = (view.getHeight() / 2);if(downY < halfCardHeight - (2*padding)){view.setRotation(rotation);} else {view.setRotation(-rotation);}float alpha = (posX - padding) / (screenWidth * 0.3f);if(alpha>0){iv_tips.setAlpha(alpha);iv_tips.setImageResource(R.drawable.ic_like);}else{iv_tips.setAlpha(-alpha);iv_tips.setImageResource(R.drawable.ic_nope);}return true;case MotionEvent.ACTION_UP:if(isBeyondLeftBoundary(view)){removeCard(view, -(screenWidth * 2));}else if(isBeyondRightBoundary(view)){removeCard(view,(screenWidth * 2));}else{resetCard(view);}return true;default :return super.onTouchEvent(motionEvent);}}return super.onTouchEvent(motionEvent);}private boolean isBeyondLeftBoundary(View view){return (view.getX() + (view.getWidth() / 2) < leftBoundary);}private boolean isBeyondRightBoundary(View view){return (view.getX() + (view.getWidth() / 2) > rightBoundary);}private void removeCard(final View view, int xPos){view.animate().x(xPos).y(0).setInterpolator(new AccelerateInterpolator()).setDuration(DURATIONTIME).setListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animator) {}@Overridepublic void onAnimationEnd(Animator animator) {ViewGroup viewGroup = (ViewGroup) view.getParent();if(viewGroup != null) {viewGroup.removeView(view);}int count=viewGroup.getChildCount();if(count==1 && listener!=null){listener.onLoad();}}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic void onAnimationRepeat(Animator animator) {}});}private void resetCard(final View view){view.animate().x(0).y(0).rotation(0).setInterpolator(new OvershootInterpolator()).setDuration(DURATIONTIME);iv_tips.setAlpha(0f);}public void bind(User u){if(u==null){return;}if(!TextUtils.isEmpty(u.getAvatarUrl())){Glide.with(iv.getContext()).load(u.getAvatarUrl()).into(iv);}if(!TextUtils.isEmpty(u.getName())){tv_name.setText(u.getName());}}public interface OnLoadMoreListener{void onLoad();}public void setOnLoadMoreListener(OnLoadMoreListener listener){this.listener=listener;}
}

TinderStackLayout实现:

作为父容器,通过addView()方法添加子视图,设置变量STACK_SIZE来表示父容器内可以展示的子视图数量。

在添加子视图时需要计算和设置子视图的缩放以及显示位置(这样才能达到层级显示效果)

onLoad()方法作为数据的回调,当父容器里面只有一个子视图时,就会调用该方法去加载数据。

public class TinderStackLayout extends FrameLayout implements TinderCardView.OnLoadMoreListener {private ViewGroup.LayoutParams params = null;public static final float BASESCALE_X_VALUE = 50.0f;public static final int BASESCALE_Y_VALUE = 8;public static final int DURATIONTIME = 300;private static final int STACK_SIZE = 4;private int index = 0;private int scaleY;private TinderCardView tc;private List<User> mList;public TinderStackLayout(Context context) {this(context, null);}public TinderStackLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public TinderStackLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}public void init() {params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);scaleY = DensityUtil.dip2px(getContext(), BASESCALE_Y_VALUE);}private void addCard(TinderCardView view) {int count = getChildCount();addView(view, 0, params);float scaleX = 1 - (count / BASESCALE_X_VALUE);view.animate().x(0).y(count * scaleY).scaleX(scaleX).setInterpolator(new AnticipateOvershootInterpolator()).setDuration(DURATIONTIME);}public void setDatas(List<User> list) {this.mList = list;if (mList == null) {return;}for (int i = index; index < i + STACK_SIZE; index++) {tc = new TinderCardView(getContext());tc.bind(mList.get(index));tc.setOnLoadMoreListener(this);addCard(tc);}}@Overridepublic void onLoad() {for (int i = index; index < i + (STACK_SIZE - 1); index++) {if (index == mList.size()) {return;}tc = new TinderCardView(getContext());tc.bind(mList.get(index));tc.setOnLoadMoreListener(this);addCard(tc);}int childCount = getChildCount();for (int i = childCount - 1; i >= 0; i--) {TinderCardView tinderCardView = (TinderCardView) getChildAt(i);if (tinderCardView != null) {float scaleValue = 1 - ((childCount - 1 - i) / 50.0f);tinderCardView.animate().x(0).y((childCount - 1 - i) * scaleY).scaleX(scaleValue).rotation(0).setInterpolator(new AnticipateOvershootInterpolator()).setDuration(DURATIONTIME);}}}
}

DensityUtil工具类:

主要是获取设备的屏幕宽度以及dip与px之间的转化(计算偏移量和缩放比例需要)。

public class DensityUtil {public static int getScreenWidth(Context context){WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);return wm.getDefaultDisplay().getWidth();}public static int dip2px(Context context, float dpValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}public static int px2dip(Context context, float pxValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (pxValue / scale + 0.5f);}
}

User实体类:

具体的数据模型,封装需要展示的基本信息。

private String name;private String avatarUrl;public User(String name, String avatarUrl) {this.name = name;this.avatarUrl = avatarUrl;}public User() {}public String getAvatarUrl() {return avatarUrl;}public void setAvatarUrl(String avatarUrl) {this.avatarUrl = avatarUrl;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

cardview.xml

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="36dp"app:cardCornerRadius="5dp"android:layout_gravity="top"><FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/iv"android:layout_width="match_parent"android:layout_height="320dp"android:layout_marginBottom="5dp"android:scaleType="centerCrop"android:src="@mipmap/ic_launcher"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:gravity="center_vertical"android:padding="16dp"android:background="@android:color/white"android:layout_gravity="bottom"><TextViewandroid:id="@+id/tv_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:fontFamily="sans-serif"android:text="Mersens"android:textSize="22sp"/></LinearLayout></FrameLayout><FrameLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="top"android:clipChildren="true"><ImageViewandroid:id="@+id/iv_tips"android:layout_height="62dp"android:layout_width="62dp"android:src="@drawable/ic_like"android:layout_gravity="center"android:layout_marginBottom="50dp"android:alpha="0"/></FrameLayout></FrameLayout></android.support.v7.widget.CardView>

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="com.mersens.tinderstackview.activity.MainActivity"><com.mersens.tinderstackview.view.TinderStackLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/tinderStackLayout"/>
</LinearLayout>

MainActivity

在MainActivity中的数据来源来自于values目录下的arrays.xml(这里就不再具体描述,详见源码),然后把数据传递给TinderStackLayout进行管理

public class MainActivity extends AppCompatActivity {private TinderStackLayout tinderStackLayout;private List<User> mList;private String names[];private String avatarUrls[];@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);init();}public void init(){mList=new ArrayList<User>();names=getResources().getStringArray(R.array.names);avatarUrls=getResources().getStringArray(R.array.avatar_urls);tinderStackLayout=(TinderStackLayout)findViewById(R.id.tinderStackLayout);for(int i=0;i<names.length;i++){User user=new User(names[i],avatarUrls[i]);mList.add(user);}tinderStackLayout.setDatas(mList);}
}

源码地址:

https: //github.com/Mersens/TinderStackView

Android高仿陌陌应用点点滑动效果相关推荐

  1. Android 高仿华为手机Tab页滑动导航效果

    首先带大家看一下实现效果,用了两种实现方式: 1.基于LinearLayout实现,导航栏不可响应手指滑动 2.基于HorizontalScrollView实现,导航栏可响应手指滑动 实现方式虽然不一 ...

  2. Android高仿iOS Messages录音操作按钮

    Android高仿iOS Messages录音操作按钮 目录 一.目标 二.功能分析 三.实现效果 四.实现过程 五.开发过程回顾 六.接下来 七.Finally 前面的2次开发,分别完成了实现录音和 ...

  3. android+qq底部界面,Android 高仿QQ 界面滑动效果

    Android高仿QQ界面滑动效果 点击或者滑动切换画面,用ViewPager实现, 首先是布局文件: android:layout_width="match_parent" an ...

  4. android+高仿视频录制,android高仿微信视频编辑页

    android高仿微信视频编辑页-视频多张图片提取 上一篇中介绍了有关视频提取图片的知识点,如果对这个不太了解 建议看下android提取视频多张图片和视频信息之前这篇. 这里实现的是仿微信的视频编辑 ...

  5. android qq红点,Android高仿QQ小红点功能

    先给大家展示下效果图: 绘制贝塞尔曲线: 主要是当在一定范围内拖拽时算出固定圆和拖拽圆的外切直线以及对应的切点,就可以通过path.quadTo()来绘制二阶贝塞尔曲线了~ 整体思路: 1.当小红点静 ...

  6. Android 高仿QQ5.2双向側滑菜单DrawerLayout实现源代码

    Android 高仿QQ5.2双向側滑菜单DrawerLayout实现源代码 左右側滑效果图 1.主页的实现 直接将DrawerLayout作为根布局,然后其内部第一个View为内容区域,第二个Vie ...

  7. android高仿微信视频编辑页-视频多张图片提取

    android高仿微信视频编辑页-视频多张图片提取 上一篇中介绍了有关视频提取图片的知识点,如果对这个不太了解 建议看下android提取视频多张图片和视频信息之前这篇. 这里实现的是仿微信的视频编辑 ...

  8. android com.mylhyl,Android 高仿微信朋友圈拍照上传功能

    模仿微信朋友圈发布动态,输入文字支持文字多少高度自增,有一个最小输入框高度,输入文字有限制,不过这些都很easy! 1. photopicker的使用 这是一个支持选择多张图片,点击图片放大,图片之间 ...

  9. (android高仿系列)今日头条 --新闻阅读器 (一)

    在模仿中循序渐进,以程序员角度去看待每一个APP是如何实现的,它有什么优缺点,并从中提升自己. 之前发现很多人在群里面.论坛上求网易新闻客户端的源码,之后我就去下了个网易新闻客户端和今日头条新闻客户端 ...

  10. android 微信高仿,Android 高仿微信朋友圈拍照上传功能

    模仿微信朋友圈发布动态,输入文字支持文字多少高度自增,有一个最小输入框高度,输入文字有限制,不过这些都很easy! 1. PhotoPicker的使用 这是一个支持选择多张图片,点击图片放大,图片之间 ...

最新文章

  1. 初中数学503个必考知识点_初中数学无非就这146个必考知识点,全摸透,轻松应对考试!...
  2. 解决:Please specify a different SDK name--PyCharm报错
  3. 上海1.3万座玻璃幕墙建筑“一网统管”
  4. 从几个版本的memcpy的测速过程学习一点底层的东西
  5. mysql 5.6.26 编译安装
  6. python怎么去掉换行符_python去除字符串中的换行符
  7. Wannafly挑战赛14 F
  8. 第8.18节 Python类中内置析构方法__del__
  9. 苹果ll是什么版本_如何鉴别自己的iPhone手机,是什么版本呢?国行,美版,还是韩版?...
  10. 2-4 赋值运算符“=”
  11. 童鞋们,颜色采色器,实用工具
  12. win10计算机内存,win10多大内存够用 win10系统需要多大的运行内存
  13. Django—CRM项目练习
  14. JAVA实现简单“伪植物大战僵尸“游戏
  15. 川希:日引流100+宝妈粉蓝海方法,精准引流预产期宝妈思路
  16. 文本意图识别方案整理
  17. 读书-每天为自己打个勾-郭腾尹
  18. JavaEE - Linux基本使用和程序部署
  19. 【PHP基础】Cookie基础知识、应用案例代码及攻防
  20. 【经验分享】【沟通:人人会沟通】

热门文章

  1. 《数据库原理》实验报告DB3——数据完整性与安全性控制
  2. lpc1768ADC使用
  3. 得到当前dgv的CurrentRow
  4. Markdownpad2下载
  5. R12 Customer新建或更新时的工作过程 - DQM Serial Sync Index Program
  6. 保存富文本编辑器内容
  7. 百度编辑器(UEditor)自定义内容样式
  8. 无缘中兴(拒绝了offer)
  9. Drop tablespace
  10. PowerPivot的杀手锏是什么?